10 : REM >.Src.Invaders 20 : REM Taito Space Invaders (Circa 1978) Clone 30 : REM (c) DynaByte Software 2007-2010 40 : 50 : REM LibASH Extended heap manager (c)2008-2010 DynaByte Software. 60 : REM Derived from ASH by Steve Revill (c)2004 7th Software. 70 : 80 : REM Keys | Title | In-Game 90 : REM ======+===============+============= 100 : REM 1 | Select 1UP | 110 : REM 2 | Select 2UP | 120 : REM SPACE | Start Game | 130 : REM Z | | Left. 140 : REM X | | Right. 150 : REM Enter | | Fire. 160 : 170 : REM S | Screenshot | Screenshot 180 : REM M | Music On/Off | Music On/Off 190 : REM + | Volume Up | Volume Up 200 : REM - | Volume Down | Volume Down 210 : REM < | Speed Down | Speed Down 220 : REM > | Speed Up | Speed Up 230 : REM P | | Pause/Resume Game. 240 : REM Q | Quit Program | Suicide. 250 : 260 : REM Keys 1 & 2 set the number of players. It is still required 270 : REM to press SPACE to start a 2 player game. You can't change 280 : REM the number of players once the game has started, only during 290 : REM the title sequence. 300 : 310 : REM ************************************************************* 320 : REM Initialisation Part 1... (Files & Environment.) 330 : REM ************************************************************* 340 : 350 : REM Setup error trapping and announce ourselves... 360 : invaders_debug%=FALSE :REM Enable/Disable debug. 370 : ON ERROR PROCtrap_error:END 380 : MODE28:OFF :REM 800*600 (256 Colours) 390 : string$="Acorn Invaders. (c)2007-2010 DynaByte Software" 400 : PRINT string$:PRINT STRING$(LEN(string$),"-"):PRINT 410 : 420 : REM Check we have all our essential files... 430 : REM Our Hiscore table is a special case as it can be either 440 : REM generated if one doesn't exist, or we can carry on regardless 450 : REM if we can't create it. 460 : 470 : REM Set Directory pointers... 480 : app_dir$=".Resources." :REM Root resources. 490 : dat_dir$="." :REM Writable location base. 500 : log_dir$=dat_dir$ :REM Our RunLog goes here. (Debug set only.) 510 : sav_dir$=dat_dir$+"Data." :REM Location of saved data. 520 : scr_dir$=dat_dir$+"Screens." :REM Location of screenshots. 530 : 540 : REM dat_dir$ is now re-set to point at the read only data... 550 : gfx_dir$=app_dir$+"Graphics." :REM Graphics. 560 : dat_dir$=app_dir$+"Data." :REM Data, Lookups etc. 570 : snd_dir$=app_dir$+"Sound." :REM Sound. 580 : 590 : REM Locate files... (Mission critical...) 600 : bail%=FALSE 610 : PRINT"Checking files..." 620 : IF NOT FNfind(gfx_dir$+"Gfx0","Sprites") THEN bail%=TRUE 630 : IF NOT FNfind(gfx_dir$+"Gfx1","Charset") THEN bail%=TRUE 640 : IF NOT FNfind(gfx_dir$+"Gfx2","Numerics") THEN bail%=TRUE 650 : IF NOT FNfind(snd_dir$+"Intro","Music") THEN bail%=TRUE 660 : IF NOT FNfind(snd_dir$+"Teckno","Music") THEN bail%=TRUE 670 : IF NOT FNfind(dat_dir$+"Creds","Credits") THEN bail%=TRUE 680 : IF NOT FNfind(dat_dir$+"Msg","Messages") THEN bail%=TRUE 690 : IF NOT FNfind(dat_dir$+"ImgLUT","Lookups") THEN bail%=TRUE 700 : IF NOT FNfind(dat_dir$+"SWIs","Lookups") THEN bail%=TRUE 710 : IF NOT FNfind(dat_dir$+"AshLUT","Lookups") THEN bail%=TRUE 720 : IF bail% THEN ERROR 255,"File Missing!!" 730 : 740 : REM These two files are required before we can do anything else, so 750 : REM load them here... Other strings are further down with other file 760 : REM loading stuff. 770 : PRINT:PRINT"Loading LibASH Resources." :REM LibASH Resources. 780 : ashmsg_max%=FNstring_enumprocessed(dat_dir$+"AshLUT","ALUT") 790 : DIM ashmsg_num%(ashmsg_max%),ashmsg_msg$(ashmsg_max%) 800 : key%=FNstring_load(dat_dir$+"AshLUT","ALUT",ashmsg_num%(),ashmsg_msg$()) 810 : 820 : PROCash_init(key%) :REM Kick the Heap Manager. 830 : gamestate%=FNash_claimfilled(112,0) :REM Claim for state info. 840 : audiostate%=FNash_claimfilled(28,0) :REM Claim for audio info. 850 : 860 : REM Load Strings... 870 : PRINT"Loading Messages..."; :REM Text strings. 880 : string_max%=FNstring_enumprocessed(dat_dir$+"Msg","MSGS") 890 : DIM string_num%(string_max%),string_msg$(string_max%) 900 : key%=FNstring_load(dat_dir$+"Msg","MSGS",string_num%(),string_msg$()) 910 : gamestate%!68=key%:PRINTFNstring_get(14):PRINT 920 : 930 : REM ************************************************************* 940 : REM Initialisation Part 2... (Gamestate Initialisation.) 950 : REM ************************************************************* 960 : 970 : REM Setup our variables. (These are used all over...) 980 : REM A lot of misc values like timers and such are now held in the 990 : REM gamestate% object. Values held here are listed below. Music 1000 : REM stuff was previously stored in it's own object, but didn't 1010 : REM really warrant the extra overhead. A lot of the other values 1020 : REM were bundled into the player% object, but didn't really 1030 : REM belong there. This new object pulls all this stuff together 1040 : REM in one place. 1050 : 1060 : REM Changes to gamestate% object contents... 1070 : REM Offsets 4, 8 & 12 re-used for multi-player values. - 10/08/10 1080 : REM Certain values moved to player%() object array. - 30/07/10 1090 : REM Audio stuff moved to audiostate% - 28/05/10 1100 : 1110 : 1120 : REM Offs.| Usage Offs.| Usage 1130 : REM -----+-------------------------------+-----+----------------------- 1140 : REM 0 : Un-used | 60 : Credit screen XOR hash. 1150 : REM 4 : Is game over. (Multiplayer) | 64 : HS Table XOR hash. 1160 : REM 8 : Number of players. | 68 : Messages XOR hash. 1170 : REM 12 : Current player ID. | 72 : Credit screen size. 1180 : REM 16 : Extra life HUD counter. | 76 : Sprite area pointer. 1190 : REM 20 : Extra life frame toggle. | 80 : Charset area pointer. 1200 : REM 24 : Extra life frame timer. | 84 : Charset2 area pointer. 1210 : REM 28 : Pause Game toggle. | 88 : Current GFX area ptr. 1220 : REM 32 : HUD Pause game status. | 92 : Config changed flag. 1230 : REM 36 : HUD Pause game timer. | 96 : Un-used 1240 : REM 40 : Screenshot counter. | 100 : Has player achieved HiScore. 1250 : REM 44 : Media writeable flag. | 104 : Un-used (Was Bunkers Invaded flag) 1260 : REM 48 : Un-used | 108 : VSync skip info. 1270 : REM 52 : Number of inv. missiles | 112 : 1280 : REM 56 : Number of bunkers. | : 1290 : REM -----+-------------------------------+-----+----------------------- 1300 : 1310 : REM Multi-Player info (Offsets 4, 8 & 12)... 1320 : REM These store state & status data required to perform the switch 1330 : REM operation between players when in 2UP mode. 1340 : 1350 : REM Timers (Offsets 24 & 36)... 1360 : REM All timers are snapshots of OS_ReadMonotonicTime used for 1370 : REM delay or animation events. 1380 : 1390 : REM Number of invader missiles (Offset 52)... 1400 : REM This value represents the maximum number of missiles currently 1410 : REM allowed on screen. This increases up to a maximum of 10 as the 1420 : REM player gets further down the line. For ASH allocation and 1430 : REM freeing purposes, this number is ALWAYS 10 (The size of the 1440 : REM array.) Slots higher than the number in this offset are masked 1450 : REM off when it comes to the operation of the handler. 1460 : 1470 : REM Pointers (Offsets 76, 80, 84 & 88)... 1480 : REM These are all pointers to blocks of memory, allocated by 1490 : REM LibASH, that hang off this block. The only exception to this is 1500 : REM offset 88 which always points to one of the other three and 1510 : REM signifies the currently selected bank when bank-switching. When 1520 : REM switching banks, we just change the value stored here to the 1530 : REM desired bank. The graphics code uses the base address stored here 1540 : REM when looking up graphics data. 1550 : 1560 : REM HiScore Flag (Offset 100)... 1570 : REM Although this is strictly player related, it cannot go in the 1580 : REM player% block, as it is used during the title sequence AFTER a game 1590 : REM has finished, therefore player% has already been clobbered. 1600 : 1610 : REM Bunkers Invaded Flag (Offset 104)... 1620 : REM This is set if the remaining invaders have destroyed the bunkers on 1630 : REM their way down to the player's base. If so, scores for each hit are 1640 : REM halved until the end of the current rack. 1650 : 1660 : REM VSync skip info (Offset 108)... 1670 : REM This is used to slow down the game when being played on fast 1680 : REM hardware. The game plays at it's natural speed when played on an 1690 : REM A7000+ (circa 48MHz). Anything faster than this and we need to 1700 : REM throttle the hardware or we run waaay too fast. 1710 : PRINTFNstring_get(12):PRINTFNstring_get(8)+" "+FNstring_get(2) 1720 : PROCinit_memusage(gamestate%,206,0) :REM gamestate% alloc. 1730 : PROCinit_memusage(audiostate%,210,0) :REM audiostate% alloc. 1740 : gamestate%!0=1:gamestate%!4=-1 1750 : 1760 : REM Screenshot Init... 1770 : REM Set the initial value to -1 and then find the first free slot. If 1780 : REM this value is still -1 when we return, then we're out of slots. 1790 : gamestate%!40=FNinit_setdumpcounter(0) :REM Screendump counter. 1800 : IF gamestate%!40=-1 THEN ERROR255,FNstring_get(109) 1810 : PRINTFNstring_get(15)+STR$(gamestate%!40) 1820 : 1830 : REM Misc gamestate% setup... 1840 : gamestate%!4=TRUE :REM Game is over. (Multiplayer.) 1850 : gamestate%!8=1 :REM Number of players. (Multiplayer.) 1860 : gamestate%!12=1 :REM Currently active player. (Multiplayer.) 1870 : gamestate%!44=TRUE :REM TRUE to save HiTable. 1880 : gamestate%!52=5 :REM Invader missiles. 1890 : gamestate%!56=5 :REM Number of bunkers. 1900 : gamestate%!92=FALSE :REM Configs changed flag. 1910 : gamestate%!100=FALSE :REM Have player(s) achieved HiScore. 1920 : gamestate%!108=1 :REM VSync control (1:1). 1930 : 1940 : 1950 : REM Player State... 1960 : REM Some of this stuff was in gamestate% up until the addition of 1970 : REM multi-player ability. The rest of it was always in the player% 1980 : REM object. The offsets are as follows for reference purposes. We 1990 : REM setup all of this stuff inside the main game loop (PROCmain) 2000 : 2010 : REM Offs | Usage 2020 : REM -----+--------------------------------------- 2030 : REM 0 : Lives. 2040 : REM 4 : Score. 2050 : REM 8 : Rack number. 2060 : REM 12 : Suicide flag. 2070 : REM 16 : Score last Extra Life achieved (Not including saucers.) 2080 : REM 20 : Bunkers invaded flag. 2090 : REM -----+--------------------------------------- 2100 : 2110 : REM Audio State... 2120 : REM This has been split from gamestate% for the time being, because 2130 : REM it was becoming too fragmented. Offset 4 is used to store the 2140 : REM value of a timer. We initialise it here for completeness, but 2150 : REM written before it's read in any case. 2160 : 2170 : REM Offs | Usage 2180 : REM -----+--------------------------------------- 2190 : REM 0 : Sound Status. 2200 : REM 4 : Volume change delay. 2210 : REM 8 : Music Handle (Main). 2220 : REM 12 : Music Handle (HiScore Entry). 2230 : REM 16 : Current playing Handle (Bank Select). 2240 : REM 20 : Volume 2250 : REM 24 : Volume step. 2260 : REM 28 : 2270 : REM -----+--------------------------------------- 2280 : 2290 : audiostate%!0=1 :REM Audio Status. 2300 : audiostate%!4=0 :REM Volume change delay. 2310 : audiostate%!8=-1 :REM TimPlayer Handle (Main Theme) 2320 : audiostate%!12=-1 :REM TimPlayer Handle (HiScore Entry) 2330 : audiostate%!16=-1 :REM Currently playing Handle (Bank Select). 2340 : audiostate%!20=128 :REM Default music volume. 2350 : audiostate%!24=2 :REM Volume step. 2360 : 2370 : REM HiScore Table allocations... 2380 : DIM scores_name$(10),scores_value%(10) :REM HiScore table. 2390 : DIM scores_rack%(10) 2400 : 2410 : REM ************************************************************* 2420 : REM Initialisation Part 3... (Object Offsets.) 2430 : REM ************************************************************* 2440 : 2450 : REM Objects... Each object (Base, saucer, missile etc.) is 2460 : REM given a block of memory up to 68 bytes long to hold all 2470 : REM relevant data for that object. This, apart from being 2480 : REM quicker, is far easier to code and saves memory. The reason 2490 : REM for the memory saving is this... As long as we store like 2500 : REM properties in the same offsets in each object, we can use the 2510 : REM same tokens to access offsets in all like objects. 2520 : 2530 : REM For example... 2540 : REM We could use seperate variables for base_x%, base_y%, base_spd% 2550 : REM and base_dir%. We would then have to use another set of variables 2560 : REM the saucer. Let's call this saucer_x%, saucer_y%, saucer_spd% & 2570 : REM saucer_dir%. That makes a total of 8 variables. BBC BASIC allocates 2580 : REM a memory word (4 bytes) to each one of these variables to store 2590 : REM the data. For only a couple of objects, the memory overhead isn't 2600 : REM too bad, but as you add more objects to the mix, the memory 2610 : REM overhead becomes huge and the program becomes difficult to follow. 2620 : REM However... If we allocate base% a block of memory large enough to 2630 : REM hold all it's properties, likewise we do the same for saucer%. We 2640 : REM only have 4 extra variables to worry about. These variables are 2650 : REM just set to offsets that are common to both of the blocks of memory 2660 : REM we've just setup. Therefore, we can use the same variables to 2670 : REM read or write the properties of both of the objects. This is how 2680 : REM Invaders works. 2690 : 2700 : REM These are those tokens to address offsets in each of the blocks 2710 : REM of memory that represent sprites on the screen. These blocks 2720 : REM don't actually contain any graphical data, just instance related 2730 : REM information such as position, size and whether we've hit it or 2740 : REM not. (See tutorial for how this works...) Contents for offsets 2750 : REM 32-64 (flags1% - flags9%) will be different, depending on the 2760 : REM nature of the object. Not all objects will have space for all 9 2770 : REM option flags. I'll define LOCAL variables with more meaningful 2780 : REM names and point them to these in the object's handler routines. 2790 : 2800 : PRINTFNstring_get(8)+" "+FNstring_get(22) 2810 : 2820 : REM Object offsets... 2830 : xpos%=0:ypos%=4:xsize%=8:ysize%=12 :REM Position & Size. 2840 : dir%=16:speed%=20 :REM Movement. 2850 : active%=24:beenhit%=28 :REM Object Status. 2860 : flags1%=32:flags2%=36:flags3%=40 :REM Misc flags. 2870 : flags4%=44:flags5%=48:flags6%=52 2880 : flags7%=56:flags8%=60:flags9%=64 2890 : 2900 : REM Define offsets for screen and rail objects... 2910 : REM We start at offset 16 here as the first 4 offsets that describe 2920 : REM the position & size are common across ALL objects. 2930 : x_eig%=16:y_eig%=20:setmode%=24 :REM Offsets in screen obj. 2940 : xfont%=28:yfont%=32 2950 : toprail%=16:baserail%=20 :REM Offsets in rail obj. 2960 : 2970 : REM ************************************************************* 2980 : REM Initialisation Part 4... (Load everything else in.) 2990 : REM ************************************************************* 3000 : 3010 : PRINTFNstring_get(9)+" "+FNstring_get(3) :REM Sprite names. 3020 : ilut_max%=FNstring_enumprocessed(dat_dir$+"ImgLUT","ILUT") 3030 : DIM ilut_num%(ilut_max%),ilut_msg$(ilut_max%) 3040 : key%=FNstring_load(dat_dir$+"ImgLUT","ILUT",ilut_num%(),ilut_msg$()) 3050 : PRINTFNstring_get(26):PROCstring_bulkdecode(ilut_max%,key%,ilut_msg$()) 3060 : PRINTFNstring_get(14):PRINT 3070 : 3080 : PRINTFNstring_get(9)+" "+FNstring_get(13) :REM SWI names. 3090 : slut_max%=FNstring_enumprocessed(dat_dir$+"SWIs","SLUT") 3100 : DIM slut_num%(slut_max%),slut_msg$(slut_max%) 3110 : key%=FNstring_load(dat_dir$+"SWIs","SLUT",slut_num%(),slut_msg$()) 3120 : PRINTFNstring_get(26):PROCstring_bulkdecode(slut_max%,key%,slut_msg$()) 3130 : PRINTFNstring_get(14):PRINT 3140 : 3150 : REM Load configs if we can find them... 3160 : REM This bit is not critical to us running, so just display a 3170 : REM message if we can't and carry on regardless. They will be 3180 : REM created the next time the program exits cleanly. Set our 3190 : REM save configs flag (offset 92 in gamestate% object) TRUE 3200 : REM in the second case so the file will be saved. 3210 : PRINTFNstring_get(9)+" "+FNstring_get(24)+" "; 3220 : IF FNfs_fileop(sav_dir$+"Configs",1)=1 THEN 3230 : PROCgs_loadconfig:PRINTFNstring_get(18) 3240 : ELSE 3250 : PRINTFNstring_get(25):gamestate%!92=TRUE 3260 : ENDIF 3270 : 3280 : REM Initialise Sound Sub-system and load music... 3290 : PRINT:PRINTFNstring_get(8)+" "+FNstring_get(1)+" "; 3300 : ver%=0:SYS FNswi_get(30) TO ver% 3310 : PRINTFNstring_get(21)+" "+STR$(ver%/100) 3320 : audiostate%!8=FNtimplay_songload(snd_dir$+"Intro") 3330 : audiostate%!12=FNtimplay_songload(snd_dir$+"Teckno") 3340 : PRINTFNstring_get(14):PRINT 3350 : 3360 : REM Initialise Graphics Sub-system... 3370 : REM The variable we setup is gfx_spriteop% which is the base 3380 : REM address of the OS call we'll use to draw graphics. Other 3390 : REM values bunged into gamestate% here are the base addresses 3400 : REM of the three sprite areas that hold the GFX and the two 3410 : REM character sets. We set the bank pointer (offset 88 in 3420 : REM gamestate% object) to the sprite area containing the GFX. 3430 : PRINT:PRINTFNstring_get(17)+" "+FNstring_get(16) 3440 : SYS FNswi_get(1),, FNswi_get(2) TO gfx_spriteop% 3450 : PRINTFNstring_get(9)+" "+FNstring_get(4) 3460 : gamestate%!76=FNgfx_load(gfx_dir$+"Gfx0") 3470 : PROCinit_memusage(gamestate%!76,203,0) 3480 : PRINTFNstring_get(14):PRINT 3490 : PRINTFNstring_get(9)+" "+FNstring_get(5) 3500 : gamestate%!80=FNgfx_load(gfx_dir$+"Gfx1") 3510 : PROCinit_memusage(gamestate%!80,204,0) 3520 : PRINTFNstring_get(14):PRINT 3530 : PRINTFNstring_get(9)+" "+FNstring_get(23) 3540 : gamestate%!84=FNgfx_load(gfx_dir$+"Gfx2") 3550 : PROCinit_memusage(gamestate%!84,205,0) 3560 : PRINTFNstring_get(14):PRINT 3570 : gamestate%!88=gamestate%!76 3580 : 3590 : REM Load Credit screen data... 3600 : REM This is now done by a single call to PROCinit_credscreen. There 3610 : REM were too many globals being defined by this one routine. Doing 3620 : REM it this way allows me to define them as LOCAL. 3630 : PRINT:PRINTFNstring_get(9)+" "+FNstring_get(6); 3640 : PROCinit_credscreen:PRINTFNstring_get(14):PRINT 3650 : 3660 : REM HiScore Table... 3670 : REM Here, we look for and load, if it exists, our Hiscores file. 3680 : REM If we can't find one, we check whether we can write to the 3690 : REM location before we attempt to create one. If we can't 3700 : REM do that, then we can still run, but the user's scores will 3710 : REM not be retained on exit. 3720 : 3730 : REM (This bit is crap, because I still get errors on certain 3740 : REM configs. Is there a better way of doing this?) 3750 : PRINT:PRINTFNstring_get(7) 3760 : filespec$=sav_dir$+"HiTable" :REM Location of file. 3770 : IF FNfs_fileop(filespec$,1)<>1 THEN 3780 : IF NOT FNfs_writeable(0,sav_dir$) THEN 3790 : gamestate%!44=FALSE :REM HiTable save flag. 3800 : PRINTFNstring_get(19)+" "+FNstring_get(10)+" "+FNstring_get(20) 3810 : ELSE 3820 : PRINTFNstring_get(19)+" "+FNstring_get(11)+" "+FNstring_get(20) 3830 : ENDIF 3840 : PROChitable_create(gamestate%!44) :REM HiTable save flag. 3850 : ELSE 3860 : IF NOT FNfs_writeable(1,filespec$) THEN 3870 : gamestate%!44=FALSE :REM HiTable save flag. 3880 : PRINTFNstring_get(18)+" "+FNstring_get(10)+" "+FNstring_get(20) 3890 : ELSE 3900 : PRINTFNstring_get(18)+" "+FNstring_get(11)+" "+FNstring_get(20) 3910 : ENDIF 3920 : PROChitable_load 3930 : ENDIF 3940 : 3950 : PRINT:PRINTFNstring_get(12):PROCwait(150) 3960 : 3970 : REM ************************************************************* 3980 : REM Initialisation Part 5... (Screen, Rail & Box Objects.) 3990 : REM ************************************************************* 4000 : 4010 : REM Reserve memory for global object blocks... 4020 : screen%=FNash_claim(36):rail%=FNash_claim(24) :REM Global objects. 4030 : 4040 : REM Get the video display into a VGA 256 colour mode. This is 4050 : REM also the same MODE as our sprites. Call to PROCget_screeninfo 4060 : REM calls the OS to get some values and sets up our screen object 4070 : REM for us. We still have to claim this screen% object as done 4080 : REM above. 4090 : CLS:PROCget_screeninfo :REM Setup screen object. 4100 : screen%!xfont%=FNgfx_convunits(8,1) 4110 : screen%!yfont%=FNgfx_convunits(8,2)+7 4120 : 4130 : REM Define property values for other global objects. 4140 : PROCgfx_setupspritesize(FNsprite_get(108),rail%) 4150 : rail%!toprail%=screen%!ysize%-50 4160 : rail%!baserail%=30 4170 : 4180 : REM ************************************************************* 4190 : REM Main Loop... (Title>Game>Title.) 4200 : REM ************************************************************* 4210 : 4220 : REM Start music playing... 4230 : REM Our last known status has already been loaded from the config 4240 : REM file. If we don't have a config file, or the contents don't 4250 : REM make sense, then the default values are assumed. Either way, 4260 : REM they've already been bunged into the audiostate% object at 4270 : REM offsets 0 & 20. Offsets 8 & 12 were setup when we loaded the 4280 : REM music. Offset 4 is used for fade in/outs and volume control. 4290 : REM Offset 16 is a pointer to one of the other two handles and 4300 : REM is used for bankswitching in exactly the same way as we manage 4310 : REM the graphics. 4320 : 4330 : REM Offs. | Purpose | Value 4340 : REM ------+---------+------ 4350 : REM 0 | Status | 0=Mute, 1=Music & SFX, 2=SFX Only. 4360 : REM 4 | Delay | Timer... (n=0 - Do it now, n>0 Wait n counts.) 4370 : REM 8 | Handle | TimPlayer Handle (Main Theme) 4380 : REM 12 | Handle | TimPlayer Handle (HiScore Entry) 4390 : REM 16 | Handle | Pointer to handle currently playing. 4400 : REM 20 | Volume | Music volume. (Loaded from configs file.) 4410 : REM 24 | VStep | Fade in/out control. 4420 : audiostate%!16=audiostate%!8 :REM Set to main theme. 4430 : IF audiostate%!0=1 THEN PROCtimplay_fadein(0) 4440 : 4450 : REM Cycle attract mode and play game... 4460 : REM At this point we're up and running. We drop out of this loop 4470 : REM when the player quits the game. ("Q" From attract mode.) 4480 : REM This is actually the top level loop. When we exit this 4490 : REM loop, we prepare to leave the game. 4500 : WHILE NOT FNtitle_loop 4510 : CLS:PROCmain 4520 : ENDWHILE 4530 : 4540 : REM ************************************************************* 4550 : REM Shutdown & Clear up... (Player has quit the game.) 4560 : REM ************************************************************* 4570 : 4580 : REM Enclose our goodbyes in a border... 4590 : REM Setup our border size and position. Centre this on the screen 4600 : REM and then drop it down by 3px. (Raising the text by 3px would 4610 : REM break too many things!) Call PROCgfx_drawbox() to produce our 4620 : REM border and then display our text. Once we've done all that, we 4630 : REM start to fade our music and bail back to the desktop. 4640 : CLS:PROCgfx_centredtextbox(FNstring_get(31)+FNstring_get(83),10,0) 4650 : 4660 : REM Write back our current configuration... 4670 : REM We re-check for writeability here, just in case the location 4680 : REM IS writable, but the HiScore file wasn't. In that case, our 4690 : REM flag would have been set FALSE when we loaded it in. 4700 : 4710 : REM IF FNfs_writeable(0,sav_dir$) THEN PROCgs_saveconfig 4720 : IF gamestate%!44 THEN PROCgs_saveconfig 4730 : 4740 : REM Fade out music & free up audio resources... 4750 : PROCtimplay_fadeout(4) :REM Fade-out current track. 4760 : PROCtimplay_bail :REM Free TimPlayer resources. 4770 : 4780 : REM Release any other LibASH claims & bail to desktop... 4790 : REM We don't know if we're going to be bailing to BASIC or back to 4800 : REM the desktop. Flushing the KB buffer is essential if we're bailing 4810 : REM back to a command line. If we're bailing back to the desktop it's 4820 : REM not necessary, but it doesn't hurt. We're done here. 4830 : SYSFNswi_get(8),"FX 21,0" :REM Flush Keyboard buffer... 4840 : PROCash_free(rail%) :REM Release global objects... 4850 : PROCash_free(screen%) 4860 : FOR i%=76 TO 84 STEP 4 4870 : PROCash_free(gamestate%!i%) :REM Free up GFX areas. 4880 : NEXT i% 4890 : PROCash_free(gamestate%) :REM Release gamestate%. 4900 : PROCash_free(audiostate%) :REM Release audiostate%. 4910 : PROCash_destroy :REM Tear down ASH Subsystem... 4920 : END 4930 : 4940 : REM ************************************************************* 4950 : REM Title Loop... (Display our title sequence.) 4960 : REM ************************************************************* 4970 : 4980 : REM Animate in our initial screen layout and then perform a cycle 4990 : REM between Credits, Score Advance, Keyboard Controls & HiScore 5000 : REM Table screens. Do a keyboard scan for each screen and check 5010 : REM for hotkeys and return from function accordingly... Object 5020 : REM hwind% points to the area of the screen where where the 5030 : REM current screen in the sequence gets placed in the attract 5040 : REM mode. Although not a window in the true sense, in that it has 5050 : REM no focus, it's easier to think of it that way. This is the 5060 : REM area below "Invaders" and above "PRESS SPACE TO PLAY" text. 5070 : REM Objects inv1% - inv3% are identical copies of each other, 5080 : REM except for values of ypos%. Likewise, the key-% objects are 5090 : REM copies, apart from the co-ordinates. Our saucer% object here, 5100 : REM is only large enough to hold screen co-ordinates. It is made 5110 : REM LOCAL to this function so we can re-define it for the main 5120 : REM game loop. 5130 : 5140 : DEFFNtitle_loop 5150 : LOCAL space%,invt%,hwind%,str%,saucer%,inv1%,inv2%,inv3%,base% :REM Misc sprites. 5160 : LOCAL keyz%,keyx%,keye%,keyq%,keym%,keys%,keyu%,keyd%,keylt%,keygt% :REM Keytop sprites. 5170 : LOCAL scores_save%,gfx_area%,hstab% :REM Object offsets. 5180 : LOCAL i%,x%,y%,rc%,s$,string$ :REM Misc LOCALS. 5190 : scores_save%=44:gfx_area%=80:hstab%=100 :REM gamestate% object offsets. 5200 : 5210 : REM Allocate & initialise title sequence objects using ASH... 5220 : REM Space is allocated for keyx%, keye%, keyq%, keym%, keys%, 5230 : REM keyu%, keyd%, inv2% and inv3% by FNash_blockcopy() all we 5240 : REM need to do is remember to release it afterwards. 5250 : space% =FNash_claimfilled(16,0) 5260 : invt% =FNash_claimfilled(16,0) 5270 : hwind% =FNash_claimfilled(16,0) 5280 : saucer% =FNash_claimfilled(16,0) 5290 : inv1% =FNash_claimfilled(16,0) 5300 : keyz% =FNash_claimfilled(16,0) 5310 : base% =FNash_claimfilled(16,0) 5320 : 5330 : REM Initialise LOCAL (title sequence) variables. 5340 : PROCgfx_setupspritesize(FNsprite_get(100),space%) :REM "Space" text properties. 5350 : space%!xpos%=FNgfx_centresprite(space%,screen%) 5360 : space%!ypos%=800 5370 : 5380 : PROCgfx_setupspritesize(FNsprite_get(101),invt%) :REM "Invaders" text properties. 5390 : invt%!xpos%=FNgfx_centresprite(invt%,screen%) 5400 : invt%!ypos%=space%!ypos%-invt%!ysize% 5410 : 5420 : hwind%!xsize%=720:hwind%!ysize%=500 :REM Window properties. 5430 : hwind%!xpos%=FNgfx_centresprite(hwind%,screen%) 5440 : hwind%!ypos%=200 5450 : 5460 : PROCgfx_setupspritesize(FNsprite_get(11),saucer%) :REM Saucer properties. 5470 : saucer%!xpos%=(hwind%!xpos%+hwind%!xsize%/2)-150 5480 : saucer%!ypos%=(hwind%!ypos%+hwind%!ysize%)-125 5490 : 5500 : PROCgfx_setupspritesize(FNsprite_get(1),base%) :REM Base properties. 5510 : 5520 : PROCgfx_setupspritesize(FNsprite_get(20),inv1%) :REM Common invader props. 5530 : inv1%!xpos%=(hwind%!xpos%+hwind%!xsize%/2)-150 5540 : inv1%!xpos%+=FNgfx_centresprite(inv1%,saucer%) 5550 : 5560 : inv2%=FNash_blockcopy(inv1%) :REM Create two copies. 5570 : inv3%=FNash_blockcopy(inv1%) 5580 : 5590 : :REM Unique invader props. 5600 : inv3%!ypos%=saucer%!ypos%-40-inv3%!ysize% 5610 : inv2%!ypos%=inv3%!ypos%-40-inv2%!ysize% 5620 : inv1%!ypos%=inv2%!ypos%-40-inv1%!ysize% 5630 : 5640 : PROCgfx_setupspritesize(FNsprite_get(102),keyz%) :REM KB Controls "keys". 5650 : keyz%!xpos%=(hwind%!xpos%+hwind%!xsize%/2)-120 5660 : 5670 : keyx%=FNash_blockcopy(keyz%) :REM Clone three. 5680 : keye%=FNash_blockcopy(keyz%):keyq%=FNash_blockcopy(keyz%) 5690 : 5700 : keyq%!xpos%=hwind%!xpos% :REM Tweak & clone four. 5710 : keyp%=FNash_blockcopy(keyq%):keys%=FNash_blockcopy(keyq%) 5720 : keym%=FNash_blockcopy(keyz%):keylt%=FNash_blockcopy(keyq%) 5730 : 5740 : keym%!xpos%=hwind%!xpos% :REM Tweak & clone three. 5750 : keym%!xpos%+=(hwind%!xsize%/2)+10 5760 : keyu%=FNash_blockcopy(keym%):keyd%=FNash_blockcopy(keym%) 5770 : keygt%=FNash_blockcopy(keym%) 5780 : 5790 : keyz%!ypos%=(hwind%!ypos%+hwind%!ysize%)-90 :REM Unique "keys" props. 5800 : keyx%!ypos%=keyz%!ypos%-60:keye%!ypos%=keyx%!ypos%-60 5810 : keys%!ypos%=keye%!ypos%-90:keyp%!ypos%=keys%!ypos%-60 5820 : keyq%!ypos%=keyp%!ypos%-60:keym%!ypos%=keye%!ypos%-90 5830 : keyu%!ypos%=keym%!ypos%-60:keyd%!ypos%=keyu%!ypos%-60 5840 : keylt%!ypos%=keyq%!ypos%-60:keygt%!ypos%=keyd%!ypos%-60 5850 : 5860 : REM Animate the initial screen title "Space Invaders" & "PRESS 5870 : REM SPACE TO PLAY" messages. 5880 : CLS:PROCgfx_drawrail(rail%!toprail%):PROCgfx_drawrail(rail%!baserail%) 5890 : PROChud_soundstate:PROChud_playerstate 5900 : 5910 : FOR i%=0-space%!xsize% TO space%!xpos% STEP 5 5920 : WAIT:PROCgfx_plot(FNsprite_get(100),i%,space%!ypos%) 5930 : PROCwait(1) 5940 : NEXT i% 5950 : 5960 : FOR i%=screen%!xsize% TO invt%!xpos% STEP -5 5970 : WAIT:PROCgfx_plot(FNsprite_get(101),i%,invt%!ypos%) 5980 : PROCwait(1) 5990 : NEXT i% 6000 : 6010 : y%=170 6020 : PROCgfx_centredtext(FNstring_get(84),11,y%):y%-=screen%!yfont% 6030 : PROCgfx_centredtext(FNstring_get(32),11,y%):y%-=screen%!yfont% 6040 : PROCgfx_centredtext(FNstring_get(33),11,y%) 6050 : 6060 : REM Cycle the remainder of the attract mode. Display the 6070 : REM HiTable 1st if we've come from a game where the player 6080 : REM has achieved a hi score. 6090 : WHILE NOT INKEY(-99) AND NOT INKEY(-17) AND NOT INKEY(-82) 6100 : 6110 : REM Display HiScore table if we've just come from a game where the 6120 : REM player has achieved a high score. (1st time only.) 6130 : IF gamestate%!hstab% THEN 6140 : gamestate%!hstab%=FALSE 6150 : rc%=FNtitle_hstab 6160 : IF rc%=1 THEN =TRUE 6170 : IF rc%=2 THEN =FALSE 6180 : PROCgfx_wipesprite(hwind%) 6190 : ENDIF 6200 : 6210 : REM Display Credits Screen... 6220 : REM Text strings are held in cred_str$() in encrypted form. 6230 : REM Line spacing info is held in the matching element of 6240 : REM cred_spc%(). Negative values have special meanings... 6250 : 6260 : REM Value | Line spacing is... 6270 : REM --------+--------------------- 6280 : REM -1 | 1 * screen%!yfont% (Next line.) 6290 : REM <-n> | n-1 * screen%!yfont% (Leave n-1 blank lines.) 6300 : REM <+n> | Leave a gap of OS Units... 6310 : y%=hwind%!ypos%+hwind%!ysize% 6320 : FOR i%=1 TO gamestate%!72 6330 : string$=FNencode_string(cred_str$(i%),gamestate%!60) 6340 : CASE SGN(cred_spc%(i%)) OF 6350 : WHEN -1 : y%-=(ABS(cred_spc%(i%))*screen%!yfont%) 6360 : WHEN 1 : y%-=cred_spc%(i%) 6370 : OTHERWISE : ERROR255,FNstring_get(115)+" "+STR$(cred_spc%(i%)) 6380 : ENDCASE 6390 : PROCgfx_centredtext(string$,0,y%) 6400 : NEXT i% 6410 : 6420 : REM Wait for 750 counts or until SPACE or Q pressed... 6430 : rc%=FNtitle_wait 6440 : IF rc%=1 THEN =TRUE 6450 : IF rc%=2 THEN =FALSE 6460 : PROCgfx_wipesprite(hwind%) 6470 : 6480 : REM Display Score Advance table... 6490 : y%=hwind%!ypos%+hwind%!ysize%-20 6500 : PROCgfx_centredtext(FNstring_get(57)+" "+FNstring_get(34),0,y%) 6510 : y%-=screen%!yfont%:PROCgfx_centredtext(FNstring_get(35),0,y%) 6520 : 6530 : x%=saucer%!xpos%+saucer%!xsize%+10 6540 : PROCgfx_plot(FNsprite_get(11),saucer%!xpos%,saucer%!ypos%) 6550 : string$=FNstring_get(36)+" "+FNstring_get(40) 6560 : PROCgfx_print(x%,saucer%!ypos%+10,string$,0,gamestate%!gfx_area%) 6570 : PROCgfx_plot(FNsprite_get(24),inv3%!xpos%,inv3%!ypos%) 6580 : string$=FNstring_get(37)+" "+FNstring_get(40) 6590 : PROCgfx_print(x%,inv3%!ypos%+12,string$,0,gamestate%!gfx_area%) 6600 : PROCgfx_plot(FNsprite_get(22),inv2%!xpos%,inv2%!ypos%) 6610 : string$=FNstring_get(38)+" "+FNstring_get(40) 6620 : PROCgfx_print(x%,inv2%!ypos%+12,string$,0,gamestate%!gfx_area%) 6630 : PROCgfx_plot(FNsprite_get(20),inv1%!xpos%,inv1%!ypos%) 6640 : string$=FNstring_get(39)+" "+FNstring_get(40) 6650 : PROCgfx_print(x%,inv1%!ypos%+12,string$,0,gamestate%!gfx_area%) 6660 : 6670 : str%=base%!xsize%/screen%!xfont%+2 6680 : string$=FNstring_get(66)+STRING$(str%," ")+FNstring_get(67) 6690 : x%=FNgfx_centretext(LEN(string$)*screen%!xfont%,screen%!xsize%) 6700 : y%=inv1%!ypos%-(3*screen%!yfont%):PROCgfx_centredtext(string$,0,y%) 6710 : base%!xpos%=x%+7+(LEN(FNstring_get(66))*screen%!xfont%) 6720 : base%!ypos%=y%:PROCgfx_plot(FNsprite_get(1),base%!xpos%,base%!ypos%) 6730 : 6740 : REM Wait for 750 counts or until SPACE or Q pressed... 6750 : rc%=FNtitle_wait 6760 : IF rc%=1 THEN =TRUE 6770 : IF rc%=2 THEN =FALSE 6780 : PROCgfx_wipesprite(hwind%) 6790 : 6800 : REM Display Keyboard controls screen... 6810 : PROCgfx_centredtext(FNstring_get(41),0,hwind%!ypos%+hwind%!ysize%-20) 6820 : 6830 : REM Left, Right & Fire... 6840 : x%=keyz%!xpos%+keyz%!xsize%+10 6850 : PROCgfx_plot(FNsprite_get(102),keyz%!xpos%,keyz%!ypos%) :REM (Z) 6860 : string$=FNstring_get(42)+" "+FNstring_get(43) 6870 : PROCgfx_print(x%,keyz%!ypos%+12,string$,0,gamestate%!gfx_area%) 6880 : PROCgfx_plot(FNsprite_get(103),keyx%!xpos%,keyx%!ypos%) :REM (X) 6890 : string$=FNstring_get(42)+" "+FNstring_get(44) 6900 : PROCgfx_print(x%,keyx%!ypos%+12,string$,0,gamestate%!gfx_area%) 6910 : PROCgfx_plot(FNsprite_get(104),keye%!xpos%,keye%!ypos%) :REM (Enter) 6920 : PROCgfx_print(x%,keye%!ypos%+12,FNstring_get(45),0,gamestate%!gfx_area%) 6930 : 6940 : REM Extra control keys (Left column)... 6950 : x%=keyq%!xpos%+keyq%!xsize%+10 6960 : PROCgfx_plot(FNsprite_get(105),keyp%!xpos%,keyp%!ypos%) :REM (P) 6970 : string$=FNstring_get(46)+" "+FNstring_get(49) 6980 : PROCgfx_print(x%,keyp%!ypos%+12,string$,0,gamestate%!gfx_area%) 6990 : PROCgfx_plot(FNsprite_get(117),keys%!xpos%,keys%!ypos%) :REM (S) 7000 : PROCgfx_print(x%,keys%!ypos%+12,FNstring_get(76),0,gamestate%!gfx_area%) 7010 : PROCgfx_plot(FNsprite_get(107),keyq%!xpos%,keyq%!ypos%) :REM (Q) 7020 : string$=FNstring_get(48)+" "+FNstring_get(49) 7030 : PROCgfx_print(x%,keyq%!ypos%+12,string$,0,gamestate%!gfx_area%) 7040 : PROCgfx_plot(FNsprite_get(118),keylt%!xpos%,keylt%!ypos%) :REM (<) 7050 : string$=FNstring_get(78)+" "+FNstring_get(75) 7060 : PROCgfx_print(x%,keylt%!ypos%+12,string$,0,gamestate%!gfx_area%) 7070 : 7080 : REM Extra control keys (Right column)... 7090 : x%=keym%!xpos%+keym%!xsize%+10 7100 : PROCgfx_plot(FNsprite_get(106),keym%!xpos%,keym%!ypos%) :REM (M) 7110 : string$=FNstring_get(47)+" "+FNstring_get(73) 7120 : PROCgfx_print(x%,keym%!ypos%+12,string$,0,gamestate%!gfx_area%) 7130 : PROCgfx_plot(FNsprite_get(115),keyu%!xpos%,keyu%!ypos%) :REM (+) 7140 : string$=FNstring_get(47)+" "+FNstring_get(77)+" " 7150 : string$+=FNstring_get(74) 7160 : PROCgfx_print(x%,keyu%!ypos%+12,string$,0,gamestate%!gfx_area%) 7170 : PROCgfx_plot(FNsprite_get(116),keyd%!xpos%,keyd%!ypos%) :REM (-) 7180 : string$=FNstring_get(47)+" "+FNstring_get(77)+" " 7190 : string$+=FNstring_get(75) 7200 : PROCgfx_print(x%,keyd%!ypos%+12,string$,0,gamestate%!gfx_area%) 7210 : PROCgfx_plot(FNsprite_get(119),keygt%!xpos%,keygt%!ypos%) :REM (>) 7220 : string$=FNstring_get(78)+" "+FNstring_get(74) 7230 : PROCgfx_print(x%,keygt%!ypos%+12,string$,0,gamestate%!gfx_area%) 7240 : 7250 : REM Wait for 750 counts or until SPACE or Q pressed... 7260 : rc%=FNtitle_wait 7270 : IF rc%=1 THEN =TRUE 7280 : IF rc%=2 THEN =FALSE 7290 : PROCgfx_wipesprite(hwind%) 7300 : 7310 : REM Display HiScore table... 7320 : rc%=FNtitle_hstab 7330 : IF rc%=1 THEN =TRUE 7340 : IF rc%=2 THEN =FALSE 7350 : PROCgfx_wipesprite(hwind%) 7360 : ENDWHILE 7370 : 7380 : REM This should never get executed... 7390 : ERROR 255,FNstring_get(102) 7400 : =FALSE 7410 : 7420 : REM Display HiScore table... 7430 : DEFFNtitle_hstab 7440 : LOCAL rc%,x%,y%,i%,gfx_area%,string$ 7450 : gfx_area%=80 :REM gamestate% offsets... 7460 : 7470 : REM First line... (This is conditional on scores_save%) 7480 : y%=hwind%!ypos%+hwind%!ysize%-20 7490 : IF gamestate%!scores_save% THEN 7500 : PROCgfx_centredtext(FNstring_get(51),0,y%) 7510 : ELSE 7520 : PROCgfx_centredtext(FNstring_get(50),0,y%) 7530 : ENDIF 7540 : 7550 : REM Second line... 7560 : y%-=screen%!yfont% 7570 : PROCgfx_centredtext(FNstring_get(58)+" "+FNstring_get(52),0,y%) 7580 : 7590 : REM Table header... 7600 : y%=(hwind%!ypos%+hwind%!ysize%)-100 7610 : string$=" "+FNstring_get(68)+" "+FNstring_get(69)+"." 7620 : string$+=STRING$(12," ")+FNstring_get(57)+"." 7630 : string$+=STRING$(3," ")+FNstring_get(60)+"." 7640 : PROCgfx_centredtext(string$,0,y%) 7650 : y%-=2*screen%!yfont% 7660 : 7670 : REM Loop through the HiScore table arrays displaying names and 7680 : REM scores... 7690 : FOR i%=1 TO 10 7700 : IF i%<10 THEN 7710 : string$=" "+STR$(i%)+". " 7720 : ELSE 7730 : string$=" "+STR$(i%)+". " 7740 : ENDIF 7750 : s$=FNencode_string(scores_name$(i%),gamestate%!64) 7760 : string$+=FNstr_pad(s$,8," ",FALSE):string$+=" .... " 7770 : string$+=FNstr_rightalign(STR$(scores_value%(i%)),10) 7780 : string$+=" ... "+FNstr_rightalign(STR$(scores_rack%(i%)),3) 7790 : PROCgfx_centredtext(string$,0,y%-(i%*(screen%!yfont%+5))) 7800 : NEXT i% 7810 : 7820 : REM Wait for 750 counts or until SPACE or Q pressed... 7830 : =FNtitle_wait 7840 : 7850 : REM Wait for 750 counts or SPACE or Q to be pressed. Returns one 7860 : REM of three values in rc% 7870 : REM 0 -> Timeout period expired... (750 counts up.) 7880 : REM 1 -> "Q" to Quit... 7890 : REM 2 -> SPACE to Play... 7900 : DEFFNtitle_wait 7910 : LOCAL t%,rc% 7920 : t%=TIME+750:rc%=FALSE 7930 : REPEAT 7940 : IF INKEY(-82) THEN PROCevnt_kb_screendump 7950 : IF INKEY(-102) THEN PROCevnt_kb_togglemusic 7960 : IF INKEY(-59) THEN PROCevnt_kb_volume(4) 7970 : IF INKEY(-60) THEN PROCevnt_kb_volume(-4) 7980 : IF INKEY(-103) THEN PROCevnt_kb_vsync(0) 7990 : IF INKEY(-104) THEN PROCevnt_kb_vsync(1) 8000 : IF INKEY(-49) THEN PROCevnt_kb_toggleplayers(1) 8010 : IF INKEY(-50) THEN PROCevnt_kb_toggleplayers(2) 8020 : UNTIL TIME>=t% OR INKEY(-17) OR INKEY(-99) 8030 : IF INKEY(-17) THEN rc%=1 :REM Get outta here (Q)... 8040 : IF INKEY(-99) THEN rc%=2 :REM Play a game... 8050 : 8060 : REM If rc% is TRUE (non zero value) then we either start a game 8070 : REM or quit. Either way, we need to release the memory claimed 8080 : REM by our objects. 8090 : IF rc% THEN 8100 : PROCash_free(space%):PROCash_free(invt%):PROCash_free(hwind%) 8110 : PROCash_free(saucer%):PROCash_free(inv1%):PROCash_free(inv2%) 8120 : PROCash_free(inv3%):PROCash_free(keyz%):PROCash_free(keyx%) 8130 : PROCash_free(keye%):PROCash_free(keyq%):PROCash_free(keym%) 8140 : PROCash_free(keyp%):PROCash_free(keys%):PROCash_free(keyu%) 8150 : PROCash_free(keyd%):PROCash_free(base%):PROCash_free(keylt%) 8160 : PROCash_free(keygt%) 8170 : ENDIF 8180 : =rc% 8190 : 8200 : REM ************************************************************* 8210 : REM Game Loop... (Play one instance of the game.) 8220 : REM ************************************************************* 8230 : 8240 : REM Loop through one instance of the game. This basically means... 8250 : REM Setup in-game stuff. -> Play until out of bases. -> Manage 8260 : REM hiscore entry. -> Return. 8270 : DEFPROCmain 8280 : LOCAL base%,shell%,saucer%,rack%(),player%() :REM Objects. 8290 : LOCAL inv% :REM Single Invader. 8300 : LOCAL invstat%(),invfire%() :REM Invader status. 8310 : LOCAL bunker%(),bunknum%,bunk% :REM Bunker status. 8320 : LOCAL lives%,score%,racknum%,suicide% :REM Player properties. 8330 : LOCAL paused%,pstatus%,ptime% :REM Game state (Paused) 8340 : LOCAL elcount%,vsync%,cplyr%,plyrs%,over% :REM Game state (Misc) 8350 : LOCAL colgap%,xnum%,ynum%,rownum%,frcap% :REM Rack properties. 8360 : LOCAL str%,string$,x%,y%,i%,j% :REM Misc. 8370 : 8380 : REM Setup object pointers... 8390 : lives%=0:score%=4:racknum%=8:suicide%=12 :REM Object offsets. (player%) 8400 : elscore%=16:bunkinv%=20 8410 : over%=4:plyrs%=8:cplyr%=12:elcount%=16 :REM Object offsets. (gamestate%) 8420 : paused%=28:bunknum%=56 8430 : colgap%=flags2%:xnum%=flags3%:ynum%=flags4% :REM Object offsets. (rack%) 8440 : rownum%=flags7%:frcap%=flags8% 8450 : 8460 : REM Claim memory for local objects using ASH... 8470 : base%=FNash_claim(32):shell%=FNash_claim(28):saucer%=FNash_claim(48) 8480 : inv%=FNash_claim(16):bunk%=FNash_claim(16) 8490 : 8500 : REM Setup arrays... 8510 : REM Workaround for unknown bug in BASIC Make. Using gamestate%!bunknum% 8520 : REM directly "bunker%(2,gamestate%!bunknum%)", would produce the output 8530 : REM "d%(2,a% !Pa%) when crunched by MH's BASCrunch. The space before the 8540 : REM "!Pa%" barfed BASIC. What we do is to use i% as a temporary holder 8550 : REM for this value and use i% in the DIM. If you look at the output, the 8560 : REM space still exists, but because only the closing bracket follows, it 8570 : REM doesn't barf BASIC. Why this happens, I don't know. 8580 : i%=gamestate%!bunknum% 8590 : DIM invstat%(2,10,6),invfire%(10),bunker%(2,i%) 8600 : DIM player%(2),rack%(2) 8610 : 8620 : REM Initialise LOCAL (in-game) variables. 8630 : PROCgfx_setupspritesize(FNsprite_get(1),base%) :REM Base properties. 8640 : base%!xpos%=FNgfx_centresprite(base%,screen%) 8650 : base%!ypos%=50:base%!speed%=6:base%!dir%=base%!speed% 8660 : 8670 : PROCgfx_setupspritesize(FNsprite_get(20),inv%) :REM Invader properties. 8680 : 8690 : PROCgfx_setupspritesize(FNsprite_get(11),saucer%) :REM Saucer properties. 8700 : saucer%!ypos%=rail%!toprail%-(10+saucer%!ysize%) 8710 : saucer%!speed%=5:saucer%!active%=FALSE 8720 : saucer%!flags3%=1 8730 : 8740 : PROCgfx_setupspritesize(FNsprite_get(5),shell%) :REM Shell properties. 8750 : shell%!active%=FALSE:shell%!speed%=10 8760 : 8770 : gamestate%!elcount%=-1 :REM Initial game state. 8780 : gamestate%!over%=FALSE 8790 : 8800 : PROCinvfire_init :REM Invader missiles. 8810 : 8820 : REM This little lot needs to be done multiple times, one for each player. 8830 : REM LibASH claims for each of these arrays have been moved here too as it 8840 : REM made sense to have all the multiplayer logic and setup stuff together. 8850 : FOR i%=1 TO gamestate%!plyrs% 8860 : PROCplayer_setup(i%) :REM Setup Players. 8870 : PROCplayer_racksetup(i%) :REM Setup Racks. 8880 : PROCbunk_init(i%) :REM Setup Bunkers. 8890 : NEXT i% 8900 : 8910 : REM Initialise game screen... 8920 : REM Draw the top and bottom rails, prepare and display the HUD & 8930 : REM sort out the first rack of invaders. Finally... Display the 8940 : REM player's base. 8950 : PROCgfx_drawrail(rail%!toprail%):PROCgfx_drawrail(rail%!baserail%) 8960 : PROChud_init(0,scores_value%(1),player%(gamestate%!cplyr%)!lives%,player%(gamestate%!cplyr%)!racknum%) 8970 : 8980 : PROCrack_gameinit :REM Invaders. 8990 : PROCbunk_redrawall(gamestate%!cplyr%) :REM Bunkers. 9000 : PROCgfx_plot(FNsprite_get(1),base%!xpos%,base%!ypos%) :REM Base. 9010 : 9020 : REM Slow down fast hardware... 9030 : SYSFNswi_get(8),"VSkip "+STR$(gamestate%!vsync%) 9040 : 9050 : REM Our event generator... 9060 : REM This is the main polling loop. The program sits tight in this 9070 : REM loop firing off events until the player runs out of lives. 9080 : REM This has been hacked to co-operate with the two player code 9090 : REM changes and should work for a single player game. Player 9100 : REM switch-over code does not exist. Code to activate 2UP mode 9110 : REM has been written, but the trigger is missing from the title 9120 : REM loop. 9130 : 9140 : WHILE player%(gamestate%!cplyr%)!lives%>0 AND gamestate%!over%=FALSE 9150 : REM Keyboard poll... 9160 : REM We only check the top four if we haven't paused the game. 9170 : REM The rest we always need to check. 9180 : 9190 : IF gamestate%!paused%=0 THEN 9200 : IF INKEY(-74) THENPROCevnt_kb_basefire(base%!xpos%,base%!ypos%) 9210 : IF INKEY(-98) THENPROCevnt_kb_baseleft :REM(Z)Base left... 9220 : IF INKEY(-67) THENPROCevnt_kb_baseright :REM(X)Base right... 9230 : IF INKEY(-17) THENPROCevnt_kb_suicide :REM(Q)Suicide... 9240 : REMIF INKEY(-36) THENPROCevnt_kb_saucertest :REM(T)Test only... 9250 : REMIF INKEY(-87) THENPROCevnt_kb_eltest :REM(L)Test only... 9260 : REMIF INKEY(-35) THENPROCrack_endgame :REM(E)Test only... 9270 : ENDIF 9280 : 9290 : IF INKEY(-102)THENPROCevnt_kb_togglemusic :REM(M)Music On/Off 9300 : IF INKEY(-82) THENPROCevnt_kb_screendump :REM(S)Screenshot... 9310 : IF INKEY(-56) THENPROCevnt_kb_togglepause :REM(P)Pause Game... 9320 : IF INKEY(-59) THENPROCevnt_kb_volume(4) :REM(+)Volume Up... 9330 : IF INKEY(-60) THENPROCevnt_kb_volume(-4) :REM(-)Volume Down... 9340 : IF INKEY(-103)THENPROCevnt_kb_vsync(0) :REM(<)VSync faster... 9350 : IF INKEY(-104)THENPROCevnt_kb_vsync(1) :REM(>)VSync slower... 9360 : 9370 : REM Update screen... 9380 : REM This is our equivalent of an arcade game's VBlank interrupt. 9390 : REM We've polled the input device (our keyboard) and triggered 9400 : REM anything we need to do this time through. Now we need to deal 9410 : REM with events that have either already been triggered previously 9420 : REM or those that happen all the time. This means calling all the 9430 : REM handlers that update positions of objects on the screen, so 9440 : REM we can update the screen. If the game is currently paused 9450 : REM (gamestate%!paused% =1) then we only need to trigger the game 9460 : REM paused handler. We need to do this each time, so that the 9470 : REM text flashing effect works. The delay for this is managed by 9480 : REM code in the handler. 9490 : 9500 : REM Process evnt_fr events. (Frame Refresh.) 9510 : IF gamestate%!paused%=0 THEN 9520 : PROCevnt_fr_basefire :REM Animate player's shots. 9530 : PROCevnt_fr_saucer :REM Saucer handling & AI. 9540 : PROCevnt_fr_rackmove :REM Move rack & Collision detect. 9550 : PROCevnt_fr_rackfire :REM Rack missile fire control. 9560 : PROCevnt_fr_bunkers :REM Defense bunker collision stuff. 9570 : PROCevnt_fr_extralife :REM "Extra Life" text control. 9580 : ELSE 9590 : PROCevnt_fr_pausegame :REM "Game Paused" text control. 9600 : ENDIF 9610 : 9620 : REM Wait for next VBlank 9630 : WAIT 9640 : ENDWHILE 9650 : 9660 : REM Display GAME OVER!!! in a bordered rectangle in the middle 9670 : REM of the screen. 9680 : string$=FNstring_get(49)+" "+FNstring_get(53)+" "+FNstring_get(69)+" "+STR$(gamestate%!cplyr%) 9690 : PROCgfx_centredtextbox(string$,11,750) 9700 : 9710 : REM Check & Handle HiTable entry... 9720 : FOR i%=1 TO gamestate%!plyrs% 9730 : IF NOT player%(gamestate%!cplyr%)!suicide% THEN 9740 : IF player%(gamestate%!cplyr%)!score%>=scores_value%(10) THEN 9750 : PROChitable_entry(player%(gamestate%!cplyr%),gamestate%!cplyr%) 9760 : ENDIF 9770 : ENDIF 9780 : NEXT i% 9790 : 9800 : REM Free memory used for objects... 9810 : PROCash_free(base%):PROCash_free(saucer%):PROCash_free(inv%) 9820 : PROCash_free(shell%):PROCash_free(bunk%) 9830 : FOR i%=1 TO 10:PROCash_free(invfire%(i%)):NEXT i% 9840 : FOR i%=1 TO gamestate%!plyrs% 9850 : PROCash_free(player%(i%)):PROCash_free(rack%(i%)) 9860 : FOR j%=1 TO gamestate%!bunknum% 9870 : PROCash_free(bunker%(i%,j%)) 9880 : NEXT j% 9890 : NEXT i% 9900 : SYSFNswi_get(8),"VSkip 1" 9910 : ENDPROC 9920 : 9930 : REM ************************************************************* 9940 : REM Proceedures (Keyboard events.) 9950 : REM ************************************************************* 9960 : 9970 : REM Move base left. If we're already at left edge then bail... 9980 : DEFPROCevnt_kb_baseleft 9990 : REM Collision detection. (Base & Playfield.) 10000 : IF base%!xpos%<=0 THEN 10010 : base%!xpos%=0 :REM Don't move. 10020 : ELSE 10030 : base%!dir%=0-base%!speed% :REM Change direction. 10040 : base%!xpos%+=base%!dir% 10050 : PROCgfx_plot(FNsprite_get(1),base%!xpos%,base%!ypos%) 10060 : ENDIF 10070 : ENDPROC 10080 : 10090 : REM Move base right. If we're already at the right edge, bail... 10100 : DEFPROCevnt_kb_baseright 10110 : REM Collision detection. (Base & Playfield.) 10120 : IF base%!xpos%>=screen%!xsize%-base%!xsize% THEN 10130 : base%!xpos%=screen%!xsize%-base%!xsize% :REM Don't move. 10140 : ELSE 10150 : base%!dir%=0+base%!speed% :REM Change direction. 10160 : base%!xpos%+=base%!dir% 10170 : PROCgfx_plot(FNsprite_get(1),base%!xpos%,base%!ypos%) 10180 : ENDIF 10190 : ENDPROC 10200 : 10210 : REM Fire a shell. If we're already firing a shell bail. (We can 10220 : REM only fire one shell at a time or else the animation gets 10230 : REM confused.) shell%!active% is a property of the shell% object 10240 : REM that is set boolean TRUE when player fires a shell and is 10250 : REM cleared when either the shell scrolls off the top of the 10260 : REM screen or hits something. 10270 : DEFPROCevnt_kb_basefire(x%,y%) 10280 : REM Bail if shell already active. 10290 : IF NOT shell%!active% THEN 10300 : shell%!active%=TRUE :REM Set active. 10310 : shell%!xpos%=x%+(base%!xsize%/2) :REM Initial position. 10320 : shell%!ypos%=y%+base%!ysize% 10330 : PROCsfx_play(4) 10340 : ENDIF 10350 : ENDPROC 10360 : 10370 : REM Dump a screen to disc as ".Screens.Dump" 10380 : REM Where is a number from 0 to 999. FNinit_setdumpcounter() 10390 : REM when given the current value, returns the next free slot. 10400 : REM When the "Screens" directory is empty, we start from 0. 10410 : DEFPROCevnt_kb_screendump 10420 : LOCAL num%,mflag% 10430 : num%=40:mflag%=44 :REM Object offsets. 10440 : IF gamestate%!mflag% THEN 10450 : REPEAT:UNTIL NOT INKEY(-82) :REM De-bounce key. 10460 : gamestate%!num%=FNinit_setdumpcounter(gamestate%!num%) 10470 : IF gamestate%!num%=-1 THEN ERROR255,FNstring_get(109) 10480 : SYSFNswi_get(8),"ScreenSave "+scr_dir$+"Dump"+STR$(gamestate%!num%) 10490 : ENDIF 10500 : ENDPROC 10510 : 10520 : REM Player has chickened out. (Committed suicide. 'Q' in-game.) 10530 : REM Set lives to zero and flag it up as a suicide event in the 10540 : REM player object. (Skip Hiscore entry if suicide set.) 10550 : DEFPROCevnt_kb_suicide 10560 : LOCAL i%,plyrs%,cplyr%:plyrs%=8:cplyr%=12 10570 : PROCinvfire_clearall :REM Clear all invader missiles. 10580 : player%(gamestate%!cplyr%)!lives%=0 :REM Zero current player's lives. 10590 : player%(gamestate%!cplyr%)!suicide%=TRUE :REM Set suicide flag. 10600 : PROCplayer_dead :REM Handle loss of life event. 10610 : ENDPROC 10620 : 10630 : REM Toggle Music on & off... 10640 : REM Split music object data to new audiostate% object. This stuff 10650 : REM has resided is several different locations during the process 10660 : REM of development. First off, they resided in the player% object 10670 : REM and were moved to gamestate% when that was created to split 10680 : REM generic game state data from player specific stuff. Now it has 10690 : REM it's own audiostate% object as it was getting too fragmented 10700 : REM and hard to follow in the gamestate% object. 10710 : 10720 : REM Offs. | Purpose | Value 10730 : REM -------+----------+------ 10740 : REM 0 | Status | 0=Mute, 1=Music & SFX, 2=SFX Only. 10750 : REM 4 | Delay | Snapshot of OS_ReadMonotonicTime last done. 10760 : REM 8 | Handle | Main track handle. (TimPlayer) 10770 : REM 12 | Handle | HiScore entry track handle. (TimPlayer) 10780 : REM 16 | Handle | Currently playing Handle. (TimPlayer) 10790 : DEFPROCevnt_kb_togglemusic 10800 : LOCAL mstatus%,mhandle%,cfgflag% 10810 : mstatus%=0:mhandle%=16:cfgflag%=92 10820 : REPEAT:UNTIL NOT INKEY(-102) :REM De-bounce key. 10830 : audiostate%!mstatus%+=1 10840 : IF audiostate%!mstatus%>2 THEN audiostate%!mstatus%=0 10850 : IF audiostate%!mstatus%=1 THEN 10860 : SYSFNswi_get(33),audiostate%!mhandle% 10870 : ELSE 10880 : SYSFNswi_get(34),audiostate%!mhandle% 10890 : ENDIF 10900 : gamestate%!cfgflag%=TRUE :REM Config has changed. 10910 : PROChud_soundstate :REM Update HUD. 10920 : ENDPROC 10930 : 10940 : REM Turn Music volume up/down... 10950 : REM We only need to do this if music is actually playing... Don't 10960 : REM change anything otherwise. We don't de-bounce the keys here 10970 : REM as we don't exactly want a one-shot event. We do however, need 10980 : REM to introduce a delay, otherwise we would get an on/off switch 10990 : REM situation on fast hardware. During runtime, the value stored 11000 : REM in audiostate%!gsvol% (Offset 20) is not actually used. It is 11010 : REM there so we can write it and the current value of 11020 : REM audiostate%!mstatus% (Offset 0) to the configs file and read 11030 : REM it back next time the game is loaded. 11040 : DEFPROCevnt_kb_volume(vol%) 11050 : LOCAL mstatus%,mhandle%,mdelay%,gsvol%,mt%,cvol%,nvol%,cfgflag% 11060 : mstatus%=0:mdelay%=4:gsvol%=20:mhandle%=16 :REM Object offsets. (audiostate%) 11070 : cfgflag%=92 :REM Object offsets. (gamestate%) 11080 : IF audiostate%!mstatus%=1 THEN 11090 : SYSFNswi_get(6) TO mt% :REM OS_ReadMonotonicTime 11100 : IF mt%-audiostate%!mdelay%>=10 THEN 11110 : SYSFNswi_get(36),audiostate%!mhandle%,-1 TO ,cvol% 11120 : nvol%=cvol%+vol% 11130 : IF nvol%>=0 AND nvol%<=128 THEN 11140 : SYSFNswi_get(36),audiostate%!mhandle%,nvol% 11150 : audiostate%!gsvol%=nvol%:audiostate%!mdelay%=mt% 11160 : gamestate%!cfgflag%=TRUE 11170 : ENDIF 11180 : ENDIF 11190 : ENDIF 11200 : ENDPROC 11210 : 11220 : REM Adjust the VSync skip ratio and apply... 11230 : DEFPROCevnt_kb_vsync(by%) 11240 : LOCAL vsync%,cfgflag% 11250 : vsync%=108:cfgflag%=92 :REM Object offsets. 11260 : IF by%=0 THEN 11270 : REPEAT:UNTILNOTINKEY(-103) 11280 : IF gamestate%!vsync%<64 THEN gamestate%!vsync%+=1 11290 : ELSE 11300 : REPEAT:UNTILNOTINKEY(-104) 11310 : IF gamestate%!vsync%>1 THEN gamestate%!vsync%-=1 11320 : ENDIF 11330 : SYSFNswi_get(8),"VSkip "+STR$(gamestate%!vsync%) 11340 : gamestate%!cfgflag%=TRUE 11350 : ENDPROC 11360 : 11370 : REM Pause & Resume game... 11380 : REM Toggle between pause and resume. The actual logic is done 11390 : REM elsewhere. 11400 : DEFPROCevnt_kb_togglepause 11410 : LOCAL paused%,pstatus%,string$ 11420 : paused%=28:pstatus%=32 11430 : REPEAT UNTIL NOT INKEY(-56) :REM De-bounce. 11440 : string$=FNstring_get(49)+" "+FNstring_get(61) 11450 : PROCtoggle(gamestate%!paused%) :REM Toggle. 11460 : gamestate%!pstatus%=gamestate%!paused% :REM Set visible. 11470 : 11480 : IF gamestate%!paused%=0 THEN 11490 : string$=STRING$(LEN(string$)," ") 11500 : PROChud_bannertext(string$) 11510 : ENDIF 11520 : ENDPROC 11530 : 11540 : REM Toggle number of players... 11550 : REM This is the master control and is triggerable from within 11560 : REM attract mode. This sets the number of players as held in 11570 : REM the gamestate% object and referenced by the whole of the rest 11580 : REM of the game. This routine is passed either 1 or 2 as the value 11590 : REM of key%. Var. plyrs% points to the offset in gamestate% object. 11600 : DEFPROCevnt_kb_toggleplayers(key%) 11610 : LOCAL plyrs%,num%:plyrs%=8 11620 : num%=-48-key% :REM Calc. neg. keycode. 11630 : REPEAT UNTIL NOT INKEY(num%) :REM De-Bounce key. 11640 : IF gamestate%!plyrs%<>key% THEN 11650 : gamestate%!plyrs%=key% :REM Change value. 11660 : PROChud_playerstate :REM Update HUD. 11670 : ENDIF 11680 : ENDPROC 11690 : 11700 : REM ************************************************************* 11710 : REM Proceedures (VBlank/Frame Events) 11720 : REM ************************************************************* 11730 : 11740 : REM Continue animation of a player's shell... If one hasn't been 11750 : REM created then we bail back to event loop. 11760 : DEFPROCevnt_fr_basefire 11770 : IF shell%!active% THEN 11780 : 11790 : REM Have we hit the top?? 11800 : IF shell%!ypos%>=rail%!toprail%-shell%!ysize% THEN 11810 : shell%!active%=FALSE :REM Clear event. 11820 : shell%!ypos%-=2:PROCgfx_wipesprite(shell%) :REM Remove shell. 11830 : ELSE 11840 : 11850 : REM Display shell at current position. 11860 : PROCgfx_plot(FNsprite_get(5),shell%!xpos%,shell%!ypos%) 11870 : ENDIF 11880 : 11890 : shell%!ypos%+=shell%!speed% :REM Advance frame. 11900 : ENDIF 11910 : ENDPROC 11920 : 11930 : REM Process saucer animation & generation events. A saucer can be 11940 : REM randomely activated n% of the time, but not if one is 11950 : REM currently on the screen. When activated, there is a 50% chance 11960 : REM of it starting on the left hand side of the screen, likewise 11970 : REM we have a 50% chance of it starting on the right. 11980 : DEFPROCevnt_fr_saucer 11990 : LOCAL hit%,toggle%,spr%,mt%,hittime%,racknum%,bunkinv%,fr$ 12000 : LOCAL elcount%,score%,lives%,cplyr% 12010 : hit%=flags1%:toggle%=flags2%:spr%=flags3% :REM Object tokens. 12020 : lives%=0:score%=4:racknum%=8:bunkinv%=20 12030 : cplyr%=12:elcount%=16 12040 : IF NOT saucer%!active% THEN 12050 : IF FNpct(4) AND FNpct(5) THEN 12060 : saucer%!active%=TRUE:saucer%!beenhit%=FALSE 12070 : saucer%!toggle%=0 12080 : 12090 : REM Decide which side of the screen to start from. 12100 : IF FNpct(50) THEN 12110 : saucer%!dir%=0-saucer%!speed% :REM Right edge. (50%) 12120 : saucer%!xpos%=screen%!xsize% 12130 : ELSE 12140 : saucer%!dir%=0+saucer%!speed% :REM Left edge. (50%) 12150 : saucer%!xpos%=0-saucer%!xsize% 12160 : ENDIF 12170 : ENDIF 12180 : ELSE 12190 : 12200 : REM Has saucer gone off screen?? 12210 : IF saucer%!xpos%<=0-saucer%!xsize%ANDSGN(saucer%!dir%)=-1 THEN 12220 : saucer%!hit%=FALSE:saucer%!active%=FALSE 12230 : ENDPROC 12240 : ELSE 12250 : IF saucer%!xpos%>screen%!xsize%ANDSGN(saucer%!dir%)=1 THEN 12260 : saucer%!hit%=FALSE:saucer%!active%=FALSE 12270 : ENDPROC 12280 : ENDIF 12290 : ENDIF 12300 : 12310 : REM Has saucer been hit by player shot?? 12320 : REM Modified 15/01/10 to add penalty for bunkers being invaded. After 12330 : REM bunkers have been invaded, player no longer has the oportunity of 12340 : REM gaining an extra life on the current rack and the score for each 12350 : REM saucer is halved. 12360 : IF NOT saucer%!beenhit% THEN 12370 : IF FNhit(shell%,saucer%) THEN 12380 : PROCgfx_wipesprite(shell%) 12390 : PROCgfx_plot(FNsprite_get(12),saucer%!xpos%,saucer%!ypos%) 12400 : SYS FNswi_get(6) TO saucer%!hit%:saucer%!beenhit%=TRUE 12410 : shell%!active%=FALSE 12420 : IF player%(gamestate%!cplyr%)!bunkinv% THEN 12430 : PROChud_scorehit(gamestate%!cplyr%,250) 12440 : ELSE 12450 : IF player%(gamestate%!cplyr%)!racknum%>=5 AND FNpct(10) THEN 12460 : player%(gamestate%!cplyr%)!lives%+=1 12470 : PROChud_lives(player%(gamestate%!cplyr%)!lives%) 12480 : gamestate%!elcount%=0 12490 : ELSE 12500 : PROChud_scorehit(gamestate%!cplyr%,500) 12510 : ENDIF 12520 : ENDIF 12530 : ENDIF 12540 : ELSE 12550 : IF FNhit(shell%,saucer%) THEN PROCgfx_wipesprite(shell%):shell%!active%=FALSE 12560 : ENDIF 12570 : 12580 : REM Animate saucer... 12590 : IF NOT saucer%!beenhit% THEN 12600 : 12610 : REM Change image every 40 centi-seconds. 12620 : SYS FNswi_get(6) TO mt% :REM OS_ReadMonotonicTime 12630 : IF mt%-saucer%!spr%>=40 THEN 12640 : saucer%!spr%=mt%:PROCtoggle(saucer%!toggle%) 12650 : ENDIF 12660 : 12670 : REM Display one of two images. 12680 : fr$=FNsprite_get(10)+STR$(saucer%!toggle%) 12690 : PROCgfx_plot(fr$,saucer%!xpos%,saucer%!ypos%) 12700 : saucer%!xpos%+=saucer%!dir% 12710 : ELSE 12720 : 12730 : REM Animate/Remove explosion... 12740 : SYS FNswi_get(6) TO mt% 12750 : hittime%=mt%-saucer%!hit% 12760 : IF hittime%>=300 THEN 12770 : saucer%!active%=FALSE:saucer%!toggle%=1 12780 : PROCgfx_wipesprite(saucer%) 12790 : ELSE 12800 : IF hittime%>=250 THEN 12810 : PROCgfx_plot(FNsprite_get(15),saucer%!xpos%,saucer%!ypos%) 12820 : ELSE 12830 : IF hittime%>=200 THEN 12840 : PROCgfx_plot(FNsprite_get(14),saucer%!xpos%,saucer%!ypos%) 12850 : ELSE 12860 : IF hittime%>=100 THEN 12870 : PROCgfx_plot(FNsprite_get(13),saucer%!xpos%,saucer%!ypos%) 12880 : ENDIF 12890 : ENDIF 12900 : ENDIF 12910 : ENDIF 12920 : ENDIF 12930 : ENDIF 12940 : ENDPROC 12950 : 12960 : REM Check out the rack of invaders. Move left, right & down. Deal 12970 : REM with collision detection between player missiles and invaders. 12980 : DEFPROCevnt_fr_rackmove 12990 : LOCAL rack_ext%,z%,col%,row%,eclr%,sclr%,rclr%,offset%,div% 13000 : LOCAL frame%,scol%,ecol%,mt%,timer%,pcsinv%,pcsbnk% 13010 : LOCAL rownum%,bunkinv%,cplyr%,plyrs% 13020 : 13030 : REM Setup meaningful pointers to our object flags... 13040 : REM Vars colgap%, xnum%, ynum%, rownum% & frcap% have already 13050 : REM been tied to flags2%, flags3%, flags4%, flags7% & flags8% 13060 : REM respectively. 13070 : frame%=flags1%:scol%=flags5%:ecol%=flags6% :REM Object offsets (rack%) 13080 : rownum%=flags7%:timer%=flags9% 13090 : plyrs%=8:cplyr%=12:pcsinv%=52:pcsbnk%=56 :REM Object offsets (gamestate%) 13100 : bunkinv%=20 :REM Object offsets (player%) 13110 : 13120 : REM Alternate our animation frame... 13130 : SYS FNswi_get(6) TO mt% :REM OS_ReadMonotonicTime 13140 : IF mt%-rack%(gamestate%!cplyr%)!timer%>=50 THEN 13150 : rack%(gamestate%!cplyr%)!timer%=mt% 13160 : PROCtoggle(rack%(gamestate%!cplyr%)!frame%) 13170 : ENDIF 13180 : 13190 : REM Deal with rack movement... 13200 : REM Move rack left, right & down. Check each time we move down 13210 : REM to make sure that the rack hasn't invaded the base. If it has 13220 : REM then end the game. 13230 : rack_ext%=rack%(gamestate%!cplyr%)!xpos%+rack%(gamestate%!cplyr%)!xsize% 13240 : offset%=0-((rack%(gamestate%!cplyr%)!scol%-1)*(inv%!xsize%+rack%(gamestate%!cplyr%)!colgap%)) 13250 : 13260 : IF rack_ext%>=screen%!xsize% THEN 13270 : rack%(gamestate%!cplyr%)!dir%=0-rack%(gamestate%!cplyr%)!speed% 13280 : PROCrack_advance(gamestate%!cplyr%) 13290 : ELSE 13300 : IF rack%(gamestate%!cplyr%)!xpos%<=offset% THEN 13310 : rack%(gamestate%!cplyr%)!dir%=0+rack%(gamestate%!cplyr%)!speed% 13320 : PROCrack_advance(gamestate%!cplyr%) 13330 : ENDIF 13340 : ENDIF 13350 : 13360 : REM Have we extended below the bottom of the bunkers? 13370 : REM If so, then remove the bunkers... rack%!rownum% factored 13380 : REM in to prevent bunkers being removed by destroyed invader 13390 : REM rows. (15/01/10) 13400 : IF rack%(gamestate%!cplyr%)!ypos%+((rack%(gamestate%!cplyr%)!rownum%-1)*inv%!ysize%)<=bunk%!ypos%+bunk%!ysize% THEN 13410 : PROCbunk_clearall(gamestate%!cplyr%) 13420 : player%(gamestate%!cplyr%)!bunkinv%=TRUE 13430 : ENDIF 13440 : 13450 : REM Have we reached the base? 13460 : offset%=rack%(gamestate%!cplyr%)!ypos%+((rack%(gamestate%!cplyr%)!rownum%-1)*inv%!ysize%) 13470 : IF offset%<=base%!ypos%+base%!ysize% THEN 13480 : PROCrack_endgame(gamestate%!cplyr%):PROCplayer_dead:ENDPROC 13490 : ENDIF 13500 : 13510 : REM Deal with collision detection... 13520 : REM Firstly we check to see if the player's shell lays somewhere 13530 : REM inside the rack of invaders. If this is not the case then we 13540 : REM bail and take no further action this time. However, if we 13550 : REM are inside the rack (FNhit(shell%,rack%)=TRUE) then we need 13560 : REM to see what, if anything, we've actually hit. 13570 : IF FNhit(shell%,rack%(gamestate%!cplyr%)) THEN 13580 : z%=inv%!xsize%+rack%(gamestate%!cplyr%)!colgap% 13590 : IF (shell%!xpos%-rack%(gamestate%!cplyr%)!xpos%) MOD z% <= inv%!xsize% THEN 13600 : 13610 : REM We've hit something... Find out what. 13620 : col%=((shell%!ypos%-rack%(gamestate%!cplyr%)!ypos%) DIV inv%!ysize%)+1 13630 : row%=((shell%!xpos%-rack%(gamestate%!cplyr%)!xpos%) DIV z%)+1 13640 : 13650 : REM Act on it... 13660 : REM Vars col% & row% now contain the index of the invader that 13670 : REM the player hit. We now check the status of that invader 13680 : REM and act accordingly. A value of zero indicates that it's 13690 : REM already dead and buried, whilst a negative value is used 13700 : REM as a countdown timer for the explosion sprite. We are only 13710 : REM interested in positive non-zero values. Anything else, we 13720 : REM take no action. 13730 : IF SGN(invstat%(gamestate%!cplyr%,row%,col%))=1 THEN 13740 : invstat%(gamestate%!cplyr%,row%,col%)=-5 :REM Set explosion timer. 13750 : PROCgfx_wipesprite(shell%) :REM Remove shell. 13760 : shell%!active%=FALSE :REM De-activate shell. 13770 : rack%(gamestate%!cplyr%)!beenhit%+=1 :REM Another one down. 13780 : PROCsfx_play(1) 13790 : 13800 : REM Scoring... 13810 : REM Modified 15/01/10 to allow for invaded bunkers. When scoring 13820 : REM normally, div%=1 therefore 100/1=100 etc. If the bunkers 13830 : REM have been invaded, then we set div%=2 and the scoring is halved. 13840 : div%=1 :REM Normal scoring. 13850 : IF player%(gamestate%!cplyr%)!bunkinv% THEN div%=2 13860 : CASE col% OF 13870 : WHEN 1,2 : PROChud_scorehit(gamestate%!cplyr%,INT(50/div%)) :REM Bottom two rows. 13880 : WHEN 3,4 : PROChud_scorehit(gamestate%!cplyr%,INT(75/div%)) :REM Middle two rows. 13890 : WHEN 5,6 : PROChud_scorehit(gamestate%!cplyr%,INT(100/div%)) :REM Top two rows. 13900 : ENDCASE 13910 : ENDIF 13920 : ENDIF 13930 : ENDIF 13940 : 13950 : REM Have we destroyed the last invader in the current rack? 13960 : REM If so, clear the play area and create and display the next 13970 : REM wave. In the process of clearing the play area, we seem to 13980 : REM wipe part of the HUD too. This happens when the cleared rows 13990 : REM drop below the position of the rail marking the bottom of the 14000 : REM play area. For the moment, I am just re-drawing this as and 14010 : REM when it happens, but really it needs fixing. 14020 : 14030 : IF rack%(gamestate%!cplyr%)!beenhit%>=rack%(gamestate%!cplyr%)!xnum%*rack%(gamestate%!cplyr%)!ynum% THEN 14040 : player%(gamestate%!cplyr%)!racknum%+=1 :REM Advance rack counter. 14050 : PROCgfx_wipesprite(rack%(gamestate%!cplyr%)) :REM Clear the last invader. 14060 : PROCgfx_drawrail(rail%!baserail%) :REM Re-draw bottom rail. 14070 : PROChud_lives(player%(gamestate%!cplyr%)!lives%) :REM Update HUD. 14080 : PROChud_rack(player%(gamestate%!cplyr%)!racknum%) 14090 : PROChud_soundstate 14100 : PROCgfx_plot(FNsprite_get(1),base%!xpos%,base%!ypos%) :REM Redraw base. 14110 : PROCbunk_reset(gamestate%!cplyr%) :REM Reset bunkers. 14120 : PROCbunk_redrawall(gamestate%!cplyr%) :REM Redraw bunkers. 14130 : PROCrack_init(gamestate%!cplyr%) :REM Create new rack. 14140 : 14150 : REM Increase difficulty by allowing more invader missiles as 14160 : REM number of racks destroyed increases. 14170 : IF player%(gamestate%!cplyr%)!racknum%>=5 THEN gamestate%!pcsinv%=7 14180 : IF player%(gamestate%!cplyr%)!racknum%>=10 THEN gamestate%!pcsinv%=10 14190 : ENDIF 14200 : 14210 : REM Do we need to resize the rack? The rack is defined as a block 14220 : REM of a fixed size. We display the invaders in this block. Each 14230 : REM time we clear the left or rightmost column or the bottom row, 14240 : REM we need to update the size of this block to keep the display 14250 : REM accurate. 14260 : eclr%=TRUE:sclr%=TRUE 14270 : FOR col%=1 TO rack%(gamestate%!cplyr%)!ynum% 14280 : IF invstat%(gamestate%!cplyr%,rack%(gamestate%!cplyr%)!ecol%,col%)<>0 THEN eclr%=FALSE 14290 : IF invstat%(gamestate%!cplyr%,rack%(gamestate%!cplyr%)!scol%,col%)<>0 THEN sclr%=FALSE 14300 : NEXT col% 14310 : 14320 : rclr%=TRUE 14330 : FOR row%=1 TO rack%(gamestate%!cplyr%)!xnum% 14340 : IF invstat%(gamestate%!cplyr%,row%,rack%(gamestate%!cplyr%)!rownum%)<>0 THEN rclr%=FALSE 14350 : NEXT row% 14360 : 14370 : IF sclr% THEN rack%(gamestate%!cplyr%)!scol%+=1 14380 : IF eclr% THEN 14390 : rack%(gamestate%!cplyr%)!xsize%-=(inv%!xsize%+rack%(gamestate%!cplyr%)!colgap%) 14400 : rack%(gamestate%!cplyr%)!ecol%-=1 14410 : ENDIF 14420 : IF rclr% THEN rack%(gamestate%!cplyr%)!rownum%+=1 14430 : 14440 : REM Finally... Redraw the rack... 14450 : rack%(gamestate%!cplyr%)!xpos%+=rack%(gamestate%!cplyr%)!dir% 14460 : PROCrack_redraw(gamestate%!cplyr%) 14470 : ENDPROC 14480 : 14490 : REM Invader missile fire logic... 14500 : REM This... Like all the rest of these animation routines is 14510 : REM timer based. We call this event handler once every 14520 : REM repetition of the game loop. All invader missile information 14530 : REM is held in our invfire%() array. This array is just an array 14540 : REM of object pointers. Data can be accessed using offsets just 14550 : REM like all the others. Every call to this event advances the 14560 : REM animation by one frame and then handles display & collision 14570 : REM detection for that frame before returning. 14580 : DEFPROCevnt_fr_rackfire 14590 : LOCAL i%,j%,mt%,pcsinv%,pcsbnk%,cplyr% 14600 : 14610 : REM Assign meaningful names to object specific flags. 14620 : cplyr%=12:pcsinv%=52:pcsbnk%=56 14630 : 14640 : REM Loop through all invader missile slots... 14650 : FOR i%=1 TO gamestate%!pcsinv% 14660 : IF NOT invfire%(i%)!active% THEN 14670 : PROCinvfire_create(i%,gamestate%!cplyr%) :REM Create a missile. 14680 : ELSE 14690 : PROCinvfire_animate(i%) :REM Animate and move down. 14700 : 14710 : REM Collision Detection... 14720 : REM One of three events can happen here. Either we hit the 14730 : REM bottom of the playfield, the player's base, or a shell 14740 : REM hits us on the way up. 14750 : 14760 : REM Have we reached the bottom?? 14770 : IF invfire%(i%)!ypos%<=rail%!baserail%+(rail%!ysize%+5) THEN 14780 : invfire%(i%)!active%=FALSE:PROCgfx_wipesprite(invfire%(i%)) 14790 : ENDIF 14800 : 14810 : REM Have we hit the player's base?? 14820 : IF FNhit(invfire%(i%),base%) AND invfire%(i%)!active% THEN 14830 : PROCinvfire_hitbase(i%,gamestate%!cplyr%) 14840 : ENDIF 14850 : 14860 : REM Have we hit a bunker?? 14870 : REM Currently just remove the missile. Replace this bit when 14880 : REM we've worked out how to do a proper collision detection on 14890 : REM it. 14900 : FOR j%=1 TO gamestate%!pcsbnk% 14910 : IF FNhit(invfire%(i%),bunker%(gamestate%!cplyr%,j%)) THEN 14920 : PROCbunk_beenhit(j%,FALSE,gamestate%!cplyr%) 14930 : PROCgfx_wipesprite(invfire%(i%)):invfire%(i%)!active%=FALSE 14940 : ENDIF 14950 : NEXT j% 14960 : 14970 : REM Has a shell hit us?? 14980 : IF FNhit(shell%,invfire%(i%)) THEN 14990 : PROCinvfire_beenhit(i%) 15000 : ENDIF 15010 : 15020 : REM Explosion frame... 15030 : IF invfire%(i%)!beenhit% THEN 15040 : PROCinvfire_explode(i%) 15050 : ENDIF 15060 : ENDIF 15070 : NEXT i% 15080 : ENDPROC 15090 : 15100 : REM Handle the pause status and update the HUD... 15110 : REM Text display is carried out by a HUD routine. We need to check 15120 : REM and fire it here only... 15130 : DEFPROCevnt_fr_pausegame 15140 : LOCAL paused%,pstatus%,ptime%,mt%,string$ 15150 : paused%=28:pstatus%=32:ptime%=36 :REM Object offsets. 15160 : SYS FNswi_get(6) TO mt% 15170 : IF mt%-gamestate%!ptime%>=50 THEN 15180 : PROCtoggle(gamestate%!pstatus%) 15190 : gamestate%!ptime%=mt% 15200 : ENDIF 15210 : string$=FNstring_get(49)+" "+FNstring_get(61) :REM "GAME PAUSED" 15220 : IF gamestate%!pstatus%=0 OR gamestate%!paused%=0 THEN 15230 : string$=STRING$(LEN(string$)," ") 15240 : ENDIF 15250 : PROChud_bannertext(string$) 15260 : ENDPROC 15270 : 15280 : REM Handle the extra life text status and update the HUD... 15290 : REM This routine is fired if gamestate%!elcount% is zero or greater. 15300 : REM When our count (gamestate%!elcount%) gets above 5, we set it to 15310 : REM -1... It is then triggered by either the scoring routine or 15320 : REM the PROCevnt_fr_saucer when an extra life condition occurs by 15330 : REM setting our count to 0. Our current state (either blink on, or 15340 : REM blink off) is held as 1 or 0 in gamestate%!elframe%. 15350 : DEFPROCevnt_fr_extralife 15360 : LOCAL elcount%,elframe%,eltime%,mt%,string$ 15370 : elcount%=16:elframe%=20:eltime%=24 :REM Object offsets. 15380 : IF gamestate%!elcount%>=0 THEN 15390 : SYS FNswi_get(6) TO mt% 15400 : string$=FNstring_get(66)+" "+FNstring_get(72) :REM "EXTRA LIFE" 15410 : IF mt%-gamestate%!eltime%>=50 THEN 15420 : gamestate%!eltime%=mt%:PROCtoggle(gamestate%!elframe%) 15430 : IF gamestate%!elframe%=1 THEN gamestate%!elcount%+=1 15440 : ENDIF 15450 : 15460 : REM Are we in blink off state?? 15470 : IF gamestate%!elframe%=0 THEN 15480 : string$=STRING$(LEN(string$)," ") :REM Clear the line... 15490 : ENDIF 15500 : 15510 : REM Have we blinked 5 times?? 15520 : IF gamestate%!elcount%>5 THEN 15530 : string$=STRING$(LEN(string$)," ") :REM Clear the line... 15540 : gamestate%!elcount%=-1 :REM Disable event... 15550 : ENDIF 15560 : 15570 : PROChud_bannertext(string$) :REM Update the HUD... 15580 : ENDIF 15590 : ENDPROC 15600 : 15610 : REM Deal with the bunkers... 15620 : REM Currently we destroy the bunker after a combination of 10 15630 : REM hits by either the player or the invaders. Each time a bunker 15640 : REM is hit, we bump it up or down by an offset of 5px accordingly. 15650 : REM this is a temporary nudge like effect which returns the 15660 : REM bunker to it's rightful place shortly afterwards. 15670 : DEFPROCevnt_fr_bunkers 15680 : LOCAL i%,mt%,pcsbnk%,timer%,cplyr%,blk% 15690 : cplyr%=12:pcsbnk%=56:timer%=flags2% :REM Object offsets. 15700 : blk%=FNash_claim(16) :REM Temp bunker object. 15710 : FOR i%=1 TO gamestate%!pcsbnk% :REM Loop through each bunker. 15720 : IF FNhit(shell%,bunker%(gamestate%!cplyr%,i%)) THEN 15730 : PROCgfx_wipesprite(shell%):shell%!active%=FALSE 15740 : PROCbunk_beenhit(i%,TRUE,gamestate%!cplyr%) 15750 : ENDIF 15760 : 15770 : IF bunker%(gamestate%!cplyr%,i%)!beenhit% THEN 15780 : SYSFNswi_get(6) TO mt% 15790 : IF mt%-bunker%(gamestate%!cplyr%,i%)!timer%>=5 THEN 15800 : blk%!xpos%=bunker%(gamestate%!cplyr%,i%)!xpos% 15810 : blk%!xsize%=bunker%(gamestate%!cplyr%,i%)!xsize% 15820 : blk%!ypos%=bunker%(gamestate%!cplyr%,i%)!ypos%-5 15830 : blk%!ysize%=bunker%(gamestate%!cplyr%,i%)!ysize%+10 15840 : PROCgfx_wipesprite(blk%):PROCbunk_redraw(bunker%(gamestate%!cplyr%,i%)) 15850 : bunker%(gamestate%!cplyr%,i%)!beenhit%=FALSE 15860 : ENDIF 15870 : ENDIF 15880 : NEXT i% 15890 : PROCash_free(blk%) 15900 : ENDPROC 15910 : 15920 : REM ************************************************************* 15930 : REM Functions & Proceedures (Rack & Invaders) 15940 : REM ************************************************************* 15950 : 15960 : REM Cold initialisation... 15970 : REM Called directly by PROCmain once at the beginning of each 15980 : REM game to initialise the first wave... 15990 : DEFPROCrack_gameinit 16000 : LOCAL i%,frame%,scol%,ecol%,rownum%,plyrs%,cplyr% 16010 : frame%=flags1%:scol%=flags5%:ecol%=flags6%:rownum%=flags7% 16020 : plyrs%=8:cplyr%=12 16030 : FOR i%=1 TO gamestate%!plyrs%:PROCrack_init(i%):NEXT i% 16040 : PROCrack_redraw(gamestate%!cplyr%) 16050 : ENDPROC 16060 : 16070 : REM Initialise a rack of invaders... 16080 : REM This is called at the beginning of the game and whenever 16090 : REM rack%!beenhit%=rack%!xnum%*rack%!ynum% (All invaders hit.) 16100 : REM to produce the next wave of the invasion force... We hold the 16110 : REM status of each invader in the invstat%() array... 16120 : 16130 : REM Value Significance 16140 : REM ======================== 16150 : REM -n Just been hit. 16160 : REM 0 Dead and buried. 16170 : REM >0 Current graphic. 16180 : DEFPROCrack_init(cplyr%) 16190 : LOCAL i%,col%,row%,bunkinv% 16200 : bunkinv%=20 :REM Object offsets (player%). 16210 : 16220 : REM Rack properties... 16230 : REM These are set here as they have to be reset at the beginning 16240 : REM of each wave. 16250 : rack%(cplyr%)!xpos%=50:rack%(cplyr%)!ypos%=550 16260 : rack%(cplyr%)!frame%=0:rack%(cplyr%)!beenhit%=0 16270 : rack%(cplyr%)!speed%=3:rack%(cplyr%)!dir%=rack%(cplyr%)!speed% 16280 : rack%(cplyr%)!scol%=1:rack%(cplyr%)!ecol%=rack%(cplyr%)!xnum% 16290 : rack%(cplyr%)!rownum%=1 16300 : rack%(cplyr%)!xsize%=(rack%(cplyr%)!xnum%*inv%!xsize%)+((rack%(cplyr%)!xnum%-1)*rack%(cplyr%)!colgap%) 16310 : rack%(cplyr%)!ysize%=rack%(cplyr%)!ynum%*inv%!ysize% 16320 : 16330 : REM Initialise invaders status. 16340 : FOR row%=1 TO rack%(cplyr%)!xnum% 16350 : FOR col%=1 TO rack%(cplyr%)!ynum% 16360 : invstat%(cplyr%,row%,col%)=((col%+1)/2)+(rack%(cplyr%)!frame%*10) 16370 : NEXT col% 16380 : NEXT row% 16390 : 16400 : REM Reset Bunkers Invaded flag. 16410 : player%(cplyr%)!bunkinv%=FALSE 16420 : ENDPROC 16430 : 16440 : REM Redraw the whole rack of invaders at once. 16450 : DEFPROCrack_redraw(cplyr%) 16460 : LOCAL col%,row%,x%,y%,blk%:blk%=FNash_claim(16) 16470 : FOR row%=rack%(cplyr%)!scol% TO rack%(cplyr%)!ecol% 16480 : x%=rack%(cplyr%)!xpos%+((row%-1)*(inv%!xsize%+rack%(cplyr%)!colgap%)) 16490 : FOR col%=rack%(cplyr%)!rownum% TO rack%(cplyr%)!ynum% 16500 : y%=rack%(cplyr%)!ypos%+((col%-1)*inv%!ysize%) 16510 : IF invaders_debug% THEN 16520 : PRINT"Value of cplyr% : "+STR$(cplyr%)+" | Value of row% : "+STR$(row%)+" | Value of col% : "+STR$(col%) 16530 : PRINT"Value of rack%(cplyr%)!scol% : "+STR$(rack%(cplyr%)!scol%) 16540 : PRINT"Value of rack%(cplyr%)!ecol% : "+STR$(rack%(cplyr%)!scol%) 16550 : ENDIF 16560 : IF invstat%(cplyr%,row%,col%)>0 THEN 16570 : invstat%(cplyr%,row%,col%)=((col%+1)/2)+(rack%(cplyr%)!frame%*10) 16580 : ENDIF 16590 : IF invstat%(cplyr%,row%,col%)<>0 THEN 16600 : CASE SGN(invstat%(cplyr%,row%,col%)) OF 16610 : WHEN -1 : PROCrack_checkinv(x%,y%,row%,col%,cplyr%) 16620 : WHEN 1 : PROCgfx_plot(FNsprite_get(27)+STR$(invstat%(cplyr%,row%,col%)),x%,y%) 16630 : ENDCASE 16640 : ELSE 16650 : blk%!xpos%=x%:blk%!ypos%=y%:blk%!xsize%=inv%!xsize% 16660 : blk%!ysize%=inv%!ysize%:PROCgfx_wipesprite(blk%) 16670 : ENDIF 16680 : NEXT col% 16690 : NEXT row% 16700 : PROCash_free(blk%) 16710 : ENDPROC 16720 : 16730 : REM We start our explosion at -5. Every time we redraw an invader 16740 : REM we add to the count until zero is reached. When this happens 16750 : REM we wipe the sprite by creating a temporary object and passing 16760 : REM it to PROCgfx_wipesprite() in the normal way. 16770 : REM +----+--------------------------+ 16780 : REM | x% | Physical X co-ordinate. | 16790 : REM | y% | Physical Y co-ordinate. | 16800 : REM | r% | Row index into array. | 16810 : REM | c% | Column index into array. | 16820 : REM +----+--------------------------+ 16830 : DEFPROCrack_checkinv(x%,y%,r%,c%,cplyr%) 16840 : LOCAL blk% 16850 : invstat%(cplyr%,r%,c%)+=1:PROCgfx_plot(FNsprite_get(26),x%,y%) 16860 : IF invstat%(cplyr%,r%,c%)=0 THEN 16870 : blk%=FNash_claim(16):blk%!xpos%=x%:blk%!ypos%=y% 16880 : blk%!xsize%=inv%!xsize%:blk%!ysize%=inv%!ysize% 16890 : PROCgfx_wipesprite(blk%):PROCash_free(blk%) 16900 : ENDIF 16910 : ENDPROC 16920 : 16930 : REM Advance rack down by one row... 16940 : REM Object blk% hovers above the top row of invaders to allow me a 16950 : REM quick use of PROCgfx_wipesprite() to remove the old top row. We 16960 : REM drop a row by dropping 1/3 of a row three times to lessen the 16970 : REM "jump". The original used to do something like this, but I'm 16980 : REM guessing this was down to the lack of CPU power rather than by 16990 : REM design. 17000 : DEFPROCrack_advance(cplyr%) 17010 : LOCAL blk%,i%:blk%=FNash_claim(16) 17020 : FOR i%=1 TO 3 17030 : rack%(cplyr%)!ypos%-=inv%!ysize%/3 17040 : PROCrack_redraw(cplyr%) 17050 : NEXT i% 17060 : blk%!xpos%=rack%(cplyr%)!xpos% 17070 : blk%!ypos%=rack%(cplyr%)!ypos%+rack%(cplyr%)!ysize% 17080 : blk%!xsize%=rack%(cplyr%)!xsize%:blk%!ysize%=inv%!ysize% 17090 : PROCgfx_wipesprite(blk%):PROCash_free(blk%) 17100 : ENDPROC 17110 : 17120 : REM End game... 17130 : REM We are called when the rack of invaders has reached the 17140 : REM player's base. Do a little animation before we return to the 17150 : REM Hiscore entry etc... 17160 : DEFPROCrack_endgame(cplyr%) 17170 : LOCAL inv_left%,inv_right%,mt%,frtime%,alt%,speed%,i%,img$ 17180 : speed%=1:inv_ext%=24 17190 : 17200 : REM Reserve space... 17210 : inv_left%=FNash_claim(28):inv_right%=FNash_claim(28) 17220 : 17230 : REM Clear any in-game debris from the screen. (Invaders, saucers, 17240 : REM missiles and other junk.) 17250 : PROCgfx_wipesprite(rack%(cplyr%)):PROCgfx_wipesprite(base%) 17260 : IF shell%!active% THEN PROCgfx_wipesprite(shell%):shell%!active%=FALSE 17270 : IF saucer%!active% THEN PROCgfx_wipesprite(saucer%):saucer%!active%=FALSE 17280 : PROCinvfire_clearall:PROCbunk_clearall 17290 : 17300 : REM Setup initial values. 17310 : base%!xpos%=FNgfx_centresprite(base%,screen%) 17320 : inv_left%!xpos%=0-inv%!xsize% :REM Left invader properties. 17330 : inv_left%!ypos%=base%!ypos% 17340 : inv_left%!dir%=speed% 17350 : 17360 : inv_right%!xpos%=screen%!xsize% :REM Right invader properties. 17370 : inv_right%!ypos%=base%!ypos% 17380 : inv_right%!dir%=0-speed% 17390 : 17400 : REM Zero any remaining lives & update bottom of HUD... 17410 : player%(cplyr%)!lives%=0 17420 : PROCgfx_drawrail(rail%!baserail%) 17430 : PROChud_lives(player%(cplyr%)!lives%) 17440 : PROChud_rack(player%(cplyr%)!racknum%) 17450 : 17460 : REM Hit it!! 17470 : REM Two invaders, one from each side, approach the base and blow 17480 : REM it up... 17490 : PROCgfx_plot(FNsprite_get(1),base%!xpos%,base%!ypos%) 17500 : 17510 : REM The approach... 17520 : frame%=0:alt%=FALSE 17530 : REPEAT 17540 : inv_left%!xpos%+=inv_left%!dir% 17550 : inv_left%!inv_ext%=inv_left%!xpos%+inv%!xsize% 17560 : inv_right%!xpos%+=inv_right%!dir% 17570 : SYS FNswi_get(6) TO mt% :REM OS_ReadMonotonicTime 17580 : IF mt%-frtime%>=50 THEN PROCtoggle(alt%):frtime%=mt% 17590 : IF alt% THEN img$=FNsprite_get(21) ELSE img$=FNsprite_get(20) 17600 : PROCgfx_plot(img$,inv_left%!xpos%,inv_left%!ypos%) 17610 : PROCgfx_plot(img$,inv_right%!xpos%,inv_right%!ypos%) 17620 : PROCwait(2):WAIT 17630 : UNTIL inv_left%!inv_ext%>=base%!xpos% 17640 : 17650 : REM The cheer... 17660 : FOR i%=1 TO 10 17670 : PROCgfx_plot(FNsprite_get(20),inv_left%!xpos%,inv_left%!ypos%) 17680 : PROCgfx_plot(FNsprite_get(20),inv_right%!xpos%,inv_right%!ypos%) 17690 : PROCwait(20) 17700 : PROCgfx_plot(FNsprite_get(21),inv_left%!xpos%,inv_left%!ypos%) 17710 : PROCgfx_plot(FNsprite_get(21),inv_right%!xpos%,inv_right%!ypos%) 17720 : PROCwait(20):WAIT 17730 : NEXT i% 17740 : 17750 : REM The end... 17760 : PROCsfx_play(2) 17770 : FOR i%=1 TO 3 17780 : PROCgfx_plot(FNsprite_get(2)+STR$(i%),base%!xpos%,base%!ypos%) 17790 : PROCwait(20):WAIT 17800 : NEXT i% 17810 : 17820 : PROCwait(20):PROCgfx_wipesprite(base%) 17830 : PROCash_free(inv_left%):PROCash_free(inv_right%) 17840 : ENDPROC 17850 : 17860 : REM ************************************************************* 17870 : REM Functions & Proceedures (Invader Fire) 17880 : REM ************************************************************* 17890 : 17900 : REM Setup our array of invader missile objects... 17910 : REM Like everything else, sizes are supplied in pixels. These are 17920 : REM passed to FNgfx_convunits() which converts them to OS Units. All 17930 : REM system calls expect these values as OS Units. 17940 : DEFPROCinvfire_init 17950 : LOCAL i%,hittime%,frtime% 17960 : hittime%=flags3%:frtime%=flags4% :REM Offsets. 17970 : FOR i%=1 TO 10 17980 : invfire%(i%)=FNash_claimfilled(48,0) :REM Claim memory. 17990 : PROCgfx_setupspritesize(FNsprite_get(31),invfire%(i%)) :REM Width & Height. 18000 : SYS FNswi_get(6) TO invfire%(i%)!hittime% :REM Timer stores. 18010 : SYS FNswi_get(6) TO invfire%(i%)!frtime% 18020 : invfire%(i%)!active%=FALSE :REM Set inactive. 18030 : NEXT i% 18040 : ENDPROC 18050 : 18060 : REM If we haven't already got an active missile in this slot 18070 : REM then we create one. The likelyhood of this happening is 18080 : REM a percentage chance based on the player's current level +5. 18090 : REM This is capped by a second call to FNpct(), the value of 18100 : REM which is initially 4. This value is incremented by 1 for 18110 : REM every 10 racks the player destroys. 18120 : 18130 : REM Prop.. | Value | Comments. 18140 : REM -------+---------+-------------- 18150 : REM type% | 1 or 2 | Missile type. (Type 1 can be hit.) 18160 : REM frame% | 0,1,2,3 | Current frame. (2 & 3 For type 2 only.) 18170 : 18180 : REM Missile types are defined 75/25. Having a 75% chance of 18190 : REM being type 1. Speed for type 1 is 4 OS Units per frame, for 18200 : REM type 2, we half that. 18210 : DEFPROCinvfire_create(slot%,cplyr%) 18220 : LOCAL fpos%,firepos%,frate%,spd%,x%,y% 18230 : LOCAL frame%,type% 18240 : 18250 : frame%=flags1%:type%=flags2% :REM Object properties. 18260 : fpos%=RND(10):frate%=player%(cplyr%)!racknum%+5 :REM Other constants. 18270 : 18280 : 18290 : IF frate%>100 THEN 18300 : frate%=100 18310 : IF player%(cplyr%)!racknum% MOD 10=0 THEN 18320 : rack%(cplyr%)!frcap%+=1 18330 : ENDIF 18340 : ENDIF 18350 : IF FNpct(frate%) AND FNpct(rack%(cplyr%)!frcap%) THEN 18360 : IF invstat%(cplyr%,fpos%,rack%(cplyr%)!rownum%)>0 THEN 18370 : x%=rack%(cplyr%)!xpos%+(fpos%-1)*(inv%!xsize%+rack%(cplyr%)!colgap%) 18380 : y%=rack%(cplyr%)!ypos%+((rack%(cplyr%)!rownum%-1)*inv%!ysize%)-inv%!ysize% 18390 : firepos%=(inv%!xsize%/2)-(invfire%(slot%)!xsize%/2) 18400 : invfire%(slot%)!xpos%=x%+firepos% 18410 : invfire%(slot%)!ypos%=y% 18420 : invfire%(slot%)!active%=TRUE:invfire%(slot%)!beenhit%=FALSE 18430 : invfire%(slot%)!type%=1:invfire%(slot%)!frame%=0:spd%=4 18440 : IF FNpct(25) THEN invfire%(slot%)!type%=2:spd%=spd%/2 18450 : invfire%(slot%)!speed%=spd% 18460 : invfire%(slot%)!dir%=invfire%(slot%)!speed% 18470 : PROCsfx_play(3) 18480 : ENDIF 18490 : ENDIF 18500 : ENDPROC 18510 : 18520 : REM Continue animating down the screen... 18530 : REM Handle the image swapping (for the animation) and the 18540 : REM position of the currently selected invader missile. 18550 : DEFPROCinvfire_animate(slot%) 18560 : LOCAL mt%,frame%,frtime%,type%,fr$,fr2$ 18570 : 18580 : REM Assign meaningful names to object specific flags. 18590 : frame%=flags1%:type%=flags2%:frtime%=flags4% 18600 : 18610 : SYS FNswi_get(6) TO mt% :REM Handle the timer... 18620 : IF mt%-invfire%(slot%)!frtime%>=40 THEN 18630 : invfire%(slot%)!frtime%=mt% 18640 : invfire%(slot%)!frame%+=1 18650 : ENDIF 18660 : :REM Limit frame counter... 18670 : IF invfire%(slot%)!type%=1 AND invfire%(slot%)!frame%>1 THEN 18680 : invfire%(slot%)!frame%=0 18690 : ENDIF 18700 : IF invfire%(slot%)!type%=2 AND invfire%(slot%)!frame%>3 THEN 18710 : invfire%(slot%)!frame%=0 18720 : ENDIF 18730 : 18740 : fr2$=STR$(invfire%(slot%)!frame%) :REM Prepare frame... 18750 : IF invfire%(slot%)!type%=2 AND invfire%(slot%)!frame%=3 THEN 18760 : fr2$="1" 18770 : ENDIF 18780 : fr$=FNsprite_get(30)+STR$(invfire%(slot%)!type%)+fr2$ 18790 : 18800 : REM Display frame... 18810 : REM Only advance and display prepared frame if we haven't 18820 : REM already been hit, otherwise we just display the explosion. 18830 : IF NOT invfire%(slot%)!beenhit% THEN 18840 : invfire%(slot%)!ypos%-=invfire%(slot%)!dir% 18850 : PROCgfx_plot(fr$,invfire%(slot%)!xpos%,invfire%(slot%)!ypos%) 18860 : ELSE 18870 : fr$=FNsprite_get(30)+"3" 18880 : PROCgfx_plot(fr$,invfire%(slot%)!xpos%,invfire%(slot%)!ypos%) 18890 : ENDIF 18900 : ENDPROC 18910 : 18920 : REM Missile has hit the player's base... 18930 : DEFPROCinvfire_hitbase(slot%,cplyr%) 18940 : LOCAL i%,plyrs%:plyrs%=8 18950 : PROCsfx_play(2) 18960 : FOR i%=1 TO 3 18970 : PROCgfx_plot(FNsprite_get(2)+STR$(i%),base%!xpos%,base%!ypos%) 18980 : PROCwait(20):WAIT 18990 : NEXT i% 19000 : PROCgfx_wipesprite(base%) 19010 : IF shell%!active% THEN 19020 : PROCgfx_wipesprite(shell%):shell%!active%=FALSE 19030 : ENDIF 19040 : base%!xpos%=FNgfx_centresprite(base%,screen%) 19050 : PROCplayer_loselife :REM Handle loss of life event. 19060 : PROChud_lives(player%(cplyr%)!lives%) 19070 : PROCgfx_wipesprite(invfire%(slot%)):invfire%(slot%)!active%=FALSE 19080 : PROCgfx_plot(FNsprite_get(1),base%!xpos%,base%!ypos%) 19090 : ENDPROC 19100 : 19110 : REM We've been hit by a player's shell... 19120 : DEFPROCinvfire_beenhit(slot%) 19130 : LOCAL type%,hittime% 19140 : type%=flags2%:hittime%=flags3% :REM Object offsets. 19150 : IF invfire%(slot%)!type%=1 THEN 19160 : IF NOT invfire%(slot%)!beenhit% THEN 19170 : invfire%(slot%)!beenhit%=TRUE:PROChud_scorehit(5) 19180 : fr$=FNsprite_get(30)+"3" 19190 : PROCgfx_plot(fr$,invfire%(slot%)!xpos%,invfire%(slot%)!ypos%) 19200 : SYS FNswi_get(6) TO invfire%(slot%)!hittime% 19210 : ENDIF 19220 : ENDIF 19230 : PROCgfx_wipesprite(shell%):shell%!active%=FALSE 19240 : ENDPROC 19250 : 19260 : REM We have been destroyed... 19270 : DEFPROCinvfire_explode(slot%) 19280 : LOCAL hittime%,mt% 19290 : hittime%=flags3% :REM Object offsets. 19300 : SYS FNswi_get(6) TO mt% 19310 : IF mt%-invfire%(slot%)!hittime%>=40 THEN 19320 : PROCgfx_wipesprite(invfire%(slot%)) 19330 : invfire%(slot%)!active%=FALSE 19340 : ENDIF 19350 : ENDPROC 19360 : 19370 : REM Clear all invader missiles off the screen... 19380 : REM Used by PROCrack_endgame in preparation for the animation. 19390 : DEFPROCinvfire_clearall 19400 : LOCAL i%,pcsinv%:pcsinv%=52 19410 : FOR i%=1 TO gamestate%!pcsinv% 19420 : IF invfire%(i%)!active% THEN 19430 : PROCgfx_wipesprite(invfire%(i%)):invfire%(i%)!active%=FALSE 19440 : ENDIF 19450 : NEXT i% 19460 : ENDPROC 19470 : 19480 : REM ************************************************************* 19490 : REM Functions & Proceedures (Bunkers) 19500 : REM ************************************************************* 19510 : 19520 : REM Setup an arbitary number of bunkers... 19530 : REM This allocates memory and performs the calculations required 19540 : REM to convert from pixels to OS Units to give us the correct 19550 : REM sizes for all further display & collision detection etc... 19560 : REM Bunker positions are calculated using the following formula... 19570 : 19580 : REM x=(s/n)+((c-1)*(s/n)) -> Gives us the position of the current 19590 : REM bunker. Where x = X Co-ordinate. 19600 : REM s = Screen width. 19610 : REM n = Number of bunkers. 19620 : REM c = Current bunker. 19630 : DEFPROCbunk_init(cplyr%) 19640 : LOCAL i%,x%,gap%,hitcount%,pcsbnk% 19650 : hitcount%=flags1%:pcsbnk%=56 :REM Object offsets. 19660 : bunk%!xpos%=0:bunk%!ypos%=0 19670 : PROCgfx_setupspritesize(FNsprite_get(16),bunk%) :REM Width & Height. 19680 : gap%=screen%!xsize%/gamestate%!pcsbnk% :REM Gap between bunkers. 19690 : x%=gap%-150 :REM Left bunker pos. 19700 : FOR i%=1 TO gamestate%!pcsbnk% 19710 : bunker%(cplyr%,i%)=FNash_claimfilled(48,0) :REM Claim memory. 19720 : bunker%(cplyr%,i%)!xsize%=bunk%!xsize% :REM Width. 19730 : bunker%(cplyr%,i%)!ysize%=bunk%!ysize% :REM Height. 19740 : bunker%(cplyr%,i%)!xpos%=x%+((i%-1)*gap%) :REM Position (X) 19750 : bunker%(cplyr%,i%)!ypos%=base%!ypos%+100 :REM Position (Y) 19760 : bunker%(cplyr%,i%)!active%=TRUE :REM Set active. 19770 : bunker%(cplyr%,i%)!hitcount%=0 :REM Hit counter. 19780 : NEXT i% 19790 : bunk%!xpos%=bunker%(cplyr%,1)!xpos% :REM Not used. 19800 : bunk%!ypos%=bunker%(cplyr%,1)!ypos% :REM Common Y co-ord. 19810 : ENDPROC 19820 : 19830 : REM Reset all bunkers to active state... 19840 : REM Perform a quick init. We already know where everything goes, 19850 : REM we just need to get them all in a known condition. 19860 : DEFPROCbunk_reset(cplyr%) 19870 : LOCAL i%,hitcount%,pcsbnk% 19880 : hitcount%=flags1%:pcsbnk%=56 :REM Object offsets. 19890 : FOR i%=1 TO gamestate%!pcsbnk% 19900 : bunker%(cplyr%,i%)!active%=TRUE :REM Set active. 19910 : bunker%(cplyr%,i%)!hitcount%=0 :REM Reset hit counter. 19920 : NEXT i% 19930 : ENDPROC 19940 : 19950 : REM Redraw all active bunkers... 19960 : REM Does much the same as PROCbunk_init, but without all the setup 19970 : REM as we already know where everything is. If the selected bunker 19980 : REM is not active then remove it. 19990 : DEFPROCbunk_redrawall(cplyr%) 20000 : LOCAL i%,pcsbnk% 20010 : pcsbnk%=56 :REM Object offsets (gamestate%) 20020 : FOR i%=1 TO gamestate%!pcsbnk% 20030 : IF bunker%(cplyr%,i%)!active% THEN 20040 : PROCbunk_redraw(bunker%(cplyr%,i%)) 20050 : ENDIF 20060 : NEXT i% 20070 : ENDPROC 20080 : 20090 : REM Redraw a single bunker... 20100 : REM Using the old redraw routine, now defunct, did a blanket 20110 : REM redraw whether the particular bunker needed updating or not. 20120 : REM This cuts down the work by targetting the bunker that actually 20130 : REM needs updating. 20140 : DEFPROCbunk_redraw(slot%) 20150 : LOCAL tx%,ty%,hits%,str%,hitcount%,gfx_area%,string$ 20160 : hitcount%=flags1%:gfx_area%=84 :REM Properties 20170 : hits%=10-slot%!hitcount% :REM Hits left. 20180 : string$=STR$(hits%):str%=LEN(string$) :REM String 20190 : tx%=slot%!xpos%+FNgfx_centretext(str%,slot%!xsize%) :REM Co-ords. 20200 : ty%=slot%!ypos%+(slot%!ysize%/2)-(screen%!yfont%/2) 20210 : ty%+=7:IF str%=1 THEN tx%-=7 ELSE tx%-=15 :REM Fine tune 20220 : PROCgfx_wipesprite(slot%) :REM Wipe old. 20230 : PROCgfx_plot(FNsprite_get(16),slot%!xpos%,slot%!ypos%):REM Display. 20240 : PROCgfx_print(tx%,ty%,string$,0,gamestate%!gfx_area%) 20250 : ENDPROC 20260 : 20270 : REM Bunker collision detection... 20280 : REM Both invader fire and player fire handlers call this routine 20290 : REM if they detect a collision with a bunker. Bunkers can be hit 20300 : REM 10 times by either invader fire or player's shots before they 20310 : REM disappear until the beginning of the next rack. Var by% is 20320 : REM TRUE if hit by player or FALSE otherwise. 20330 : DEFPROCbunk_beenhit(slot%,by%,cplyr%) 20340 : LOCAL y%,hitcount% 20350 : hitcount%=flags1%:timer%=flags2% :REM Object offsets (bunker%) 20360 : bunker%(cplyr%,slot%)!hitcount%+=1 :REM Increment hit counter. 20370 : IF bunker%(cplyr%,slot%)!hitcount%>=10 THEN 20380 : PROCgfx_wipesprite(bunker%(cplyr%,slot%)) 20390 : bunker%(cplyr%,slot%)!active%=FALSE 20400 : ELSE 20410 : PROCgfx_wipesprite(bunker%(cplyr%,slot%)) 20420 : y%=bunker%(cplyr%,slot%)!ypos% 20430 : IF by% THEN bunker%(cplyr%,slot%)!ypos%+=5 ELSE bunker%(cplyr%,slot%)!ypos%-=5 20440 : PROCbunk_redraw(bunker%(cplyr%,slot%)) 20450 : bunker%(cplyr%,slot%)!beenhit%=TRUE:bunker%(cplyr%,slot%)!ypos%=y% 20460 : SYSFNswi_get(6) TO bunker%(cplyr%,slot%)!timer% 20470 : ENDIF 20480 : ENDPROC 20490 : 20500 : REM Clear all bunkers off the screen... 20510 : REM Used by PROCrack_endgame in preparation for the animation. 20520 : DEFPROCbunk_clearall(cplyr%) 20530 : LOCAL i%,blk%,pcsbnk%:pcsbnk%=56 20540 : blk%=FNash_claim(16) 20550 : FOR i%=1 TO gamestate%!pcsbnk% 20560 : IF bunker%(cplyr%,i%)!active% THEN 20570 : bunker%(cplyr%,i%)!active%=FALSE 20580 : blk%!xpos%=bunker%(cplyr%,i%)!xpos%:blk%!ypos%=bunker%(cplyr%,i%)!ypos% 20590 : blk%!xsize%=bunk%!xsize%:blk%!ysize%=bunk%!ysize% 20600 : PROCgfx_wipesprite(blk%) 20610 : ENDIF 20620 : NEXT i% 20630 : PROCash_free(blk%) 20640 : ENDPROC 20650 : 20660 : REM ************************************************************* 20670 : REM Functions & Proceedures. (HiScore Table/HiScore Entry.) 20680 : REM ************************************************************* 20690 : 20700 : REM Create a clean HiScore table. The value passed to us in save% 20710 : REM determines whether we can save the table to disc or not. If 20720 : REM we can (save%=TRUE) then we call PROChitable_save to write the 20730 : REM file back. 20740 : DEFPROChitable_create(save%) 20750 : LOCAL i%,n$,key% 20760 : key%=64 :REM Offset into gamestate% obj. 20770 : gamestate%!key%=RND(128)+127 20780 : FOR i%=1 TO 10 20790 : IF i% MOD 2=0 THEN n$=FNstring_get(71) ELSE n$=FNstring_get(70) 20800 : scores_name$(i%)=FNencode_string(n$,gamestate%!key%) 20810 : scores_value%(i%)=(11-i%)*1000:scores_rack%(i%)=0 20820 : NEXT i% 20830 : IF save% THEN PROChitable_save 20840 : ENDPROC 20850 : 20860 : REM Load our HiTable from disc. The names are stored in RAM in 20870 : REM encoded form and are only decoded for display. Checksum added 20880 : REM 02/06/09. 20890 : DEFPROChitable_load 20900 : LOCAL i%,hdl%,cplgot%,chfgot%,cpl%,chf%,key%,maggot$ 20910 : hdl%=OPENIN(sav_dir$+"HiTable") 20920 : INPUT#hdl%,maggot$ 20930 : IF maggot$<>"xBATSH" THEN CLOSE#hdl%:ERROR255,FNstring_get(115) 20940 : INPUT#hdl%,key%,chfgot%,cplgot% 20950 : chfgot%=chfgot% EOR key%:cplgot%=cplgot% EOR key% 20960 : FOR i%=1 TO 10 20970 : INPUT#hdl%,scores_name$(i%),scores_value%(i%),scores_rack%(i%) 20980 : NEXT i% 20990 : CLOSE#hdl% 21000 : FOR i%=1 TO 10:chf%+=LEN(scores_name$(i%)):NEXT i% 21010 : cpl%=chf%/10 21020 : IF chfgot%<>chf% OR cplgot%<>cpl% THEN ERROR255:FNstring_get(113) 21030 : gamestate%!64=key% 21040 : ENDPROC 21050 : 21060 : REM Save our HiTable back to disc. The names are stored in RAM in 21070 : REM encoded form and are only decoded for display. Checksum added 21080 : REM 02/06/09. 21090 : DEFPROChitable_save 21100 : LOCAL i%,hdl%,chf%,cpl%,key%,newhash%,name$ 21110 : key%=64 :REM Offset into gamestate% obj. 21120 : newhash%=RND(128)+127 :REM Calculate new hash value. 21130 : 21140 : REM Re-Hash HiScore Table... 21150 : FOR i%=1 TO 10 21160 : name$=FNencode_string(scores_name$(i%),gamestate%!key%) 21170 : scores_name$(i%)=FNencode_string(name$,newhash%) 21180 : NEXT i% 21190 : gamestate%!key%=newhash% :REM Update copy in gamestate% obj. 21200 : 21210 : REM Work out the checksums and write out the file... 21220 : FOR i%=1 TO 10:chf%+=LEN(scores_name$(i%)):NEXT i% 21230 : cpl%=chf%/10 21240 : chf%=chf% EOR gamestate%!key%:cpl%=cpl% EOR gamestate%!key% 21250 : hdl%=OPENOUT(sav_dir$+"HiTable") 21260 : PRINT#hdl%,"xBATSH",gamestate%!key%,chf%,cpl% 21270 : FOR i%=1 TO 10 21280 : PRINT#hdl%,scores_name$(i%),scores_value%(i%),scores_rack%(i%) 21290 : NEXT i% 21300 : CLOSE#hdl% 21310 : ENDPROC 21320 : 21330 : REM HiScore name entry... 21340 : REM Sort through our hiscore array and find out where the player 21350 : REM is. (We already know he is worthy.) Allow to enter their name 21360 : REM and save the HiTable back to disk (Only if scores_save%=TRUE). 21370 : REM then return... 21380 : DEFPROChitable_entry(player%,cplyr%) 21390 : LOCAL invt%,space%,done%,g%,i%,pos%,str%,x%,y%,string$ 21400 : LOCAL scores_save%,gfx_area%,key%,hstab% 21410 : 21420 : space%=FNash_claim(16):invt%=FNash_claim(16) :REM Space for objects. 21430 : 21440 : scores_save%=44:gfx_area%=80 :REM gamestate% object offs. 21450 : key%=64:hstab%=100 21460 : 21470 : REM Setup graphical objects for later on... 21480 : PROCgfx_setupspritesize(FNsprite_get(100),space%) :REM Space text properties. 21490 : space%!xpos%=FNgfx_centresprite(space%,screen%) 21500 : space%!ypos%=800 21510 : 21520 : PROCgfx_setupspritesize(FNsprite_get(101),invt%) :REM Invaders text properties. 21530 : invt%!xpos%=FNgfx_centresprite(invt%,screen%) 21540 : invt%!ypos%=space%!ypos%-invt%!ysize% 21550 : 21560 : REM Find out where we are and stick it in pos% 21570 : pos%=0 21580 : FOR i%=10 TO 1 STEP -1 21590 : IF player%!score%>=scores_value%(i%) THEN pos%=i% 21600 : NEXT i% 21610 : 21620 : REM Insert a new entry into the table and drop everything below 21630 : REM us by one place... (We are 5th, so old 5th becomes 6th... 9th 21640 : REM becomes 10th and 10th gets dropped.) We need to do this to 21650 : REM all three arrays... 21660 : FOR i%=9 TO pos% STEP -1 21670 : scores_name$(i%+1)=scores_name$(i%) 21680 : scores_value%(i%+1)=scores_value%(i%) 21690 : scores_rack%(i%+1)=scores_rack%(i%) 21700 : NEXT i% 21710 : 21720 : REM Insert Player's achieved score & rack into the table... 21730 : scores_value%(pos%)=player%!score% 21740 : scores_rack%(pos%)=player%!racknum% 21750 : 21760 : PROCtimplay_crossfade(4) :REM Switch music track. 21770 : 21780 : REM Now deal with the graphics... 21790 : REM Produce our basic Space Invaders layout a la title sequence. 21800 : CLS:PROCgfx_drawrail(rail%!toprail%):PROCgfx_drawrail(rail%!baserail%) 21810 : PROCgfx_plot(FNsprite_get(100),space%!xpos%,space%!ypos%) 21820 : PROCgfx_plot(FNsprite_get(101),invt%!xpos%,invt%!ypos%) 21830 : 21840 : REM Build and display the text. 21850 : string$=FNstring_get(80)+" "+STR$(cplyr%)+" "+FNstring_get(83)+" " 21860 : string$+=FNstring_get(81)+" "+STR$(player%!score%)+" "+FNstring_get(82) 21870 : string$+=" "+STR$(pos%)+FNsuffix(pos%):y%=invt%!ypos%-170 21880 : PROCgfx_centredtext(string$,0,y%) 21890 : 21900 : REM Free memory used by objects. 21910 : PROCash_free(space%):PROCash_free(invt%) 21920 : 21930 : REM Create and display a box... 21940 : box%!xsize%=165:box%!ysize%=40 21950 : PROCgfx_centresprite_xy(box%,screen%):PROCgfx_drawbox(box%) 21960 : 21970 : REM Handle player input... 21980 : REM Code 13 is Return/Enter. Code 8 is BackSpace. All other codes 21990 : REM are filtered with FNhitable_valid() before being added to string$ 22000 : REM to prevent the program breaking when we can't find a graphic. 22010 : x%=box%!xpos%+20:y%=box%!ypos%+12:string$="":i%=1:done%=FALSE 22020 : SYSFNswi_get(8),"FX 21,0" :REM Flush Keyboard buffer... 22030 : REPEAT 22040 : g%=GET :REM Get a char from Keyboard. 22050 : 22060 : REM Validate input... 22070 : REM Only allow character input if our string length is less 22080 : REM than 8 characters. Only accept Enter or Backspace for the 22090 : REM 9th character. 22100 : CASE g% OF 22110 : WHEN 13 :done%=TRUE 22120 : WHEN 8 :i%-=1:string$=LEFT$(string$,i%-1) 22130 : OTHERWISE :IF i%<9 AND FNhitable_valid(g%) THEN i%+=1:string$+=CHR$(g%) 22140 : ENDCASE 22150 : 22160 : REM Update Display... 22170 : REM Var i% will be less than 1 if the first character pressed 22180 : REM was backspace. We need to trap that and make it 1. We also 22190 : REM clear the input field before we re-display string$... This 22200 : REM is so that when Backspace is pressed, the character is 22210 : REM actually removed from the screen as well as from the input. 22220 : REM (string$) 22230 : REM We also need to check if we are trying to display an empty 22240 : REM string, as our PROCgfx_print() will fall over. This occurs if 22250 : REM the first key typed was BackSpace or a character we can't 22260 : REM display. (In the case of the latter, it is not added to 22270 : REM string$, but we still come down here to update the display.) 22280 : 22290 : IF i%<1 THEN i%=1 22300 : PROCgfx_print(x%,y%,STRING$(8," "),0,gamestate%!gfx_area%) 22310 : IF string$<>"" THEN PROCgfx_print(x%,y%,string$,0,gamestate%!gfx_area%) 22320 : UNTIL done% 22330 : 22340 : REM Add player's name to entry already prepared earlier and save 22350 : REM if we can... (scores_save%=TRUE) 22360 : IF string$="" OR string$=" " THEN string$=FNstring_get(56) 22370 : scores_name$(pos%)=FNencode_string(string$,gamestate%!key%) 22380 : gamestate%!hstab%=TRUE 22390 : 22400 : IF gamestate%!scores_save% THEN PROChitable_save 22410 : PROCtimplay_crossfade(4) :REM Switch music back. 22420 : ENDPROC 22430 : 22440 : REM Have we got the ability to display this character? 22450 : REM Return TRUE if we can or FALSE if we can't. Evaluate this 22460 : REM as an expression. 22470 : DEFFNhitable_valid(chr%) 22480 : =chr%>=32 AND chr%<=126 22490 : 22500 : REM ************************************************************* 22510 : REM Functions & Proceedures (HUD & Scoring) 22520 : REM ************************************************************* 22530 : 22540 : REM Initialise the HUD display... 22550 : DEFPROChud_init(sc%,hi%,l%,r%) 22560 : LOCAL x%,gfx_area%,string$:gfx_area%=80 22570 : string$=FNstring_get(57)+" "+FNstring_get(59) 22580 : string$+=FNstr_pad(STR$(sc%),10," ",TRUE) 22590 : PROCgfx_print(0,screen%!ysize%-screen%!yfont%,string$,0,gamestate%!gfx_area%) 22600 : string$=FNstring_get(58)+" "+FNstring_get(57) 22610 : string$+=" "+FNstring_get(59)+FNstr_pad(STR$(hi%),10," ",TRUE) 22620 : x%=screen%!xsize%-LEN(string$)*screen%!xfont% 22630 : PROCgfx_print(x%,screen%!ysize%-screen%!yfont%,string$,0,gamestate%!gfx_area%) 22640 : PROChud_lives(l%):PROChud_rack(r%) 22650 : PROChud_soundstate:PROChud_playerstate 22660 : ENDPROC 22670 : 22680 : REM Update HUD lives display... 22690 : DEFPROChud_lives(num%) 22700 : LOCAL gfx_area%,string$:gfx_area%=80 22710 : PROCgfx_plot(FNsprite_get(1),0,0):string$=" = "+STR$(num%) 22720 : PROCgfx_print(base%!xsize%,0,string$,0,gamestate%!gfx_area%) 22730 : ENDPROC 22740 : 22750 : REM Update HUD rack display... 22760 : DEFPROChud_rack(num%) 22770 : LOCAL x%,gfx_area%,string$:gfx_area%=80 22780 : string$=FNstring_get(60)+" "+FNstring_get(59) 22790 : string$+=FNstr_pad(STR$(num%),3,"0",TRUE) 22800 : x%=screen%!xsize%-LEN(string$)*screen%!xfont% 22810 : PROCgfx_print(x%,0,string$,0,gamestate%!gfx_area%) 22820 : ENDPROC 22830 : 22840 : REM HUD banner text display routine... 22850 : REM This is called by the Pause Game & Extra Life event refresh 22860 : REM handlers at the moment and possibly others in the future. 22870 : DEFPROChud_bannertext(string$) 22880 : LOCAL y%:y%=screen%!ysize%-screen%!yfont% 22890 : PROCgfx_centredtext(string$,0,y%) 22900 : ENDPROC 22910 : 22920 : REM Score a hit... 22930 : REM The first part updates the score display on the top left 22940 : REM corner of the HUD. If the player's current score is greater 22950 : REM than that of the top spot, this value is also reflected in the 22960 : REM Hi Score display in the top right. For every 5k points the 22970 : REM player scores, he/she gains an extra base. We check this and 22980 : REM increment here too. 22990 : DEFPROChud_scorehit(cplyr%,value%) 23000 : LOCAL elscore%,elcount%,gfx_area%,x%,string$ 23010 : elscore%=16:elcount%=16:gfx_area%=80 :REM Object offsets. 23020 : 23030 : REM Increment player score. 23040 : player%(cplyr%)!score%+=value% 23050 : string$=FNstring_get(57)+" "+FNstring_get(59) 23060 : string$+=FNstr_pad(STR$(player%(cplyr%)!score%),10," ",TRUE) 23070 : PROCgfx_print(0,screen%!ysize%-screen%!yfont%,string$,0,gamestate%!gfx_area%) 23080 : 23090 : REM Deal with extra bases. 23100 : IF player%(cplyr%)!score%-player%(cplyr%)!elscore%>=5000 THEN 23110 : player%(cplyr%)!lives%+=1:PROChud_lives(player%(cplyr%)!lives%) 23120 : player%(cplyr%)!elscore%=player%(cplyr%)!score%:gamestate%!elcount%=0 23130 : ENDIF 23140 : 23150 : REM Update Hi-Score (Right of display.) 23160 : IF player%(cplyr%)!score%>scores_value%(1) THEN 23170 : string$=FNstring_get(58)+" "+FNstring_get(57)+" " 23180 : string$+=FNstring_get(59) 23190 : string$+=FNstr_pad(STR$(player%(cplyr%)!score%),10," ",TRUE) 23200 : x%=screen%!xsize%-LEN(string$)*screen%!xfont% 23210 : PROCgfx_print(x%,screen%!ysize%-screen%!yfont%,string$,0,gamestate%!gfx_area%) 23220 : ENDIF 23230 : ENDPROC 23240 : 23250 : REM Display sound status... 23260 : REM Displays one of three icons in the HUD area depending on the 23270 : REM current sound status. The placement of this needs to make 23280 : REM sense during both game and attract modes. 23290 : DEFPROChud_soundstate 23300 : LOCAL mstatus%,blk%,shim%,fr$ 23310 : blk%=FNash_claim(16) :REM Claim space for image block. 23320 : mstatus%=0 :REM Object offsets. (audiostate%) 23330 : shim%=25 23340 : 23350 : PROCgfx_setupspritesize(FNsprite_get(120),blk%) 23360 : blk%!xpos%=FNgfx_centresprite(blk%,screen%)+shim%:blk%!ypos%=0 23370 : 23380 : REM Trap invalid sound state. 23390 : REM by the time this is called, our status flag should be setup 23400 : REM to something meaningful. Just in case it isn't, we sanity 23410 : REM check it here. 23420 : IF audiostate%!mstatus%<0 OR audiostate%!mstatus%>2 THEN 23430 : ERROR255,FNstring_get(111)+" "+STR$(audiostate%!mstatus%) 23440 : ENDIF 23450 : 23460 : REM Update display and free ASH block. 23470 : fr$=FNsprite_get(120+audiostate%!mstatus%) 23480 : PROCgfx_plot(fr$,blk%!xpos%,blk%!ypos%):PROCash_free(blk%) 23490 : ENDPROC 23500 : 23510 : REM Display player status... 23520 : REM Displays the current number of players. This can be changed 23530 : REM before the start of each game using the "1" or "2" number 23540 : REM keys. Object blk2% is setup for clearing previous screen 23550 : REM contents and allocated using FNash_blockcopy() after we've 23560 : REM setup blk% (Only xpos% & xsize% offsets differ.) 23570 : DEFPROChud_playerstate 23580 : LOCAL status%,blk%,blk2%,shim%,i% 23590 : blk%=FNash_claim(16) :REM Claim space for image block. 23600 : status%=8 :REM Object offsets. (gamestate%) 23610 : shim%=25 23620 : 23630 : REM Prepare. 23640 : PROCgfx_setupspritesize(FNsprite_get(123),blk%) 23650 : blk%!xpos%=FNgfx_centresprite(blk%,screen%)-shim%:blk%!ypos%=0 23660 : blk2%=FNash_blockcopy(blk%) :REM Make copy of blk% object. 23670 : blk2%!xpos%=blk%!xpos%-blk%!xsize%:blk2%!xsize%=2*blk%!xsize% 23680 : PROCgfx_wipesprite(blk2%):PROCash_free(blk2%) 23690 : IF gamestate%!status%<1 OR gamestate%!status%>2 THEN 23700 : ERROR255,FNstring_get(117)+" "+STR$(gamestate%!status%) 23710 : ENDIF 23720 : 23730 : REM Update display and free ASH block. 23740 : FOR i%=0 TO gamestate%!status%-1 23750 : PROCgfx_plot(FNsprite_get(123),blk%!xpos%-(i%*blk%!xsize%),blk%!ypos%) 23760 : NEXT i% 23770 : PROCash_free(blk%) 23780 : ENDPROC 23790 : 23800 : 23810 : REM ************************************************************* 23820 : REM Functions & Proceedures (Multiplayer Logic & Setup) 23830 : REM ************************************************************* 23840 : 23850 : REM Initial setup of player%() object. Called from PROCmain. 23860 : DEFPROCplayer_setup(cplyr%) 23870 : LOCAL lives%,score%,racknum%,suicide%,elscore%,bunkinv% 23880 : lives%=0:score%=4:racknum%=8:suicide%=12 :REM Object offsets. (player%) 23890 : elscore%=16:bunkinv%=20 23900 : 23910 : player%(cplyr%)=FNash_claimfilled(24,0) 23920 : player%(cplyr%)!racknum%=1:player%(cplyr%)!lives%=3 23930 : player%(cplyr%)!suicide%=FALSE:player%(cplyr%)!elscore%=0 23940 : player%(cplyr%)!bunkinv%=FALSE 23950 : ENDPROC 23960 : 23970 : REM Initial setup of rack%() object. Called from PROCmain. 23980 : DEFPROCplayer_racksetup(cplyr%) 23990 : LOCAL colgap%,xnum%,ynum%,frcap%,timer% 24000 : colgap%=flags2%:xnum%=flags3%:ynum%=flags4% :REM Object offsets. (rack%) 24010 : frcap%=flags8%:timer%=flags9% 24020 : 24030 : rack%(cplyr%)=FNash_claim(68) 24040 : rack%(cplyr%)!colgap%=FNgfx_convunits(20,1) 24050 : rack%(cplyr%)!xnum%=10:rack%(cplyr%)!ynum%=6 24060 : rack%(cplyr%)!active%=TRUE:rack%(cplyr%)!frcap%=4 24070 : SYSFNswi_get(6) TO rack%(cplyr%)!timer% 24080 : ENDPROC 24090 : 24100 : REM Handle Loss of life events... 24110 : REM These occur when we lose 1 life. We deduct a life from the current 24120 : REM player and call PROCplayer_dead to handle the switch-over logic. 24130 : DEFPROCplayer_loselife 24140 : LOCAL lives%,cplyr% 24150 : lives%=0 :REM Object offsets. (player%) 24160 : cplyr%=12 :REM Object offsets. (gamestate%) 24170 : 24180 : player%(gamestate%!cplyr%)!lives%-=1 24190 : PROCplayer_dead 24200 : ENDPROC 24210 : 24220 : REM Handle Player dead events... 24230 : REM This handles the gamestate%!over% flag logic and calls 24240 : REM PROCplayer_switch if required. We don't lump it together with 24250 : REM PROCplayer_loselife, so it can be called from the two other 24260 : REM events where a player's game is over. (Suicide & Rack hits base.) 24270 : DEFPROCplayer_dead 24280 : LOCAL lives%,plyrs%,cplyr%,over% 24290 : lives%=0 :REM Object offsets. (player%) 24300 : over%=4:plyrs%=8:cplyr%=12 :REM Object offsets. (gamestate%) 24310 : 24320 : IF gamestate%!plyrs%>1 THEN 24330 : IF player%(1)!lives%=0 AND player%(2)!lives%=0 THEN gamestate%!over%=TRUE 24340 : IF gamestate%!cplyr%=1 THEN 24350 : IF player%(2)!lives%>0 THEN PROCplayer_switch 24360 : ELSE 24370 : IF player%(1)!lives%>0 THEN PROCplayer_switch 24380 : ENDIF 24390 : ELSE 24400 : IF player%(1)!lives%=0 THEN gamestate%!over%=TRUE 24410 : ENDIF 24420 : ENDPROC 24430 : 24440 : REM Handle state switching between players... 24450 : REM This routine handles switching the current player and clearing 24460 : REM or updating any on screen detritus like saucers, invaders, missiles 24470 : REM and the like. We don't free blk% here as this will barf player%() 24480 : REM object. THIS IS UNTESTED CODE!! 24490 : DEFPROCplayer_switch 24500 : LOCAL plyrs%,cplyr%,elcount%,blk%,string$ 24510 : plyrs%=8:cplyr%=12:elcount%=16 24520 : 24530 : REM Clear current player's detritus... 24540 : PROCinvfire_clearall:PROCgfx_wipesprite(base%) 24550 : PROCgfx_wipesprite(rack%(gamestate%!cplyr%)) 24560 : IF saucer%!active% THEN PROCgfx_wipesprite(saucer%):saucer%!active%=FALSE 24570 : IF shell%!active% THEN PROCgfx_wipesprite(shell%):shell%!active%=FALSE 24580 : gamestate%!elcount%=-1:PROChud_bannertext(STRING$(11," ")) 24590 : 24600 : REM Perform switch... 24610 : REM Our program logic expects this value to be either 1 or 2. To 24620 : REM make it compatible with our PROCtoggle() routine, we need to 24630 : REM perform a bit of manipulation here. 24640 : gamestate%!cplyr%-=1:PROCtoggle(gamestate%!cplyr%):gamestate%!cplyr%+=1 24650 : 24660 : 24670 : REM Display "Ready..." text in box and count down... 24680 : REM 24690 : string$=FNstring_get(69)+" "+STR$(gamestate%!cplyr%)+" "+FNstring_get(79) 24700 : string$+=FNstring_get(83):PROCgfx_centredtextbox(string$,10,750) 24710 : 24720 : 24730 : REM Update screen contents for next player... 24740 : blk%=player%(gamestate%!cplyr%) 24750 : PROChud_init(blk%!score%,scores_value%(1),blk%!lives%,blk%!racknum%) 24760 : PROCbunk_redrawall(gamestate%!cplyr%):PROCrack_redraw(gamestate%!cplyr%) 24770 : base%!xpos%=FNgfx_centresprite(base%,screen%) 24780 : PROCgfx_plot(FNsprite_get(1),base%!xpos%,base%!ypos%) 24790 : ENDPROC 24800 : 24810 : REM ************************************************************* 24820 : REM Functions & Proceedures (Core Graphics Routines) 24830 : REM ************************************************************* 24840 : 24850 : REM Load and Decompress GFX data... 24860 : REM Doing it this way allows us to use three different buffers, one 24870 : REM for the sprites and the other two for the charsets. All Graphics 24880 : REM are compressed using the Squash module. The data is loaded 24890 : REM and decompressed to memory, with some munging and a bit of 24900 : REM magic here, it becomes our sprite area. This code can also 24910 : REM process raw sprite data as it did before. The code assumes 24920 : REM that if the Squash header isn't present ("SQSH") then we're 24930 : REM dealing with raw sprite data. No new checks are made as to 24940 : REM the validity of the sprite data at this stage. We do a 24950 : REM verify area at the end. The only disadvantage with this as it 24960 : REM stands it that the file will be loaded & discarded and loaded 24970 : REM again if it is already decompressed. Is there a better way of 24980 : REM doing this??? 24990 : REM Modified (11/06/10) to seperate decompression code from GFX 25000 : REM loader. We now call two routines from this code to handle 25010 : REM the decompression part that was embedded here. 25020 : DEFFNgfx_load(filespec$) 25030 : LOCAL size%,osize%,blk%,area% 25040 : size%=FNfs_fileop(filespec$,2) :REM Input file size. 25050 : blk% =FNash_claimfilled(size%,0) :REM Claim buffer. 25060 : PROCinit_memusage(blk%,208,0) :REM Display alloc. 25070 : SYSFNswi_get(3),16,filespec$,blk%,0 :REM Load file. 25080 : osize%=FNsquash_init(blk%) :REM Init Squash. 25090 : IF osize%>0 THEN 25100 : area%=FNgfx_createdefaultarea(osize%+4) :REM Create a sprite area. 25110 : PROCsquash_decompress(blk%,area%,4) :REM Decompress to memory. 25120 : ELSE 25130 : area%=FNgfx_createdefaultarea(size%+256) :REM Create a sprite area. 25140 : SYSgfx_spriteop%,256+10,area%,filespec$ :REM Load raw data. 25150 : ENDIF 25160 : 25170 : SYSgfx_spriteop%,256+17,area% :REM Verify data. 25180 : =area% 25190 : 25200 : REM Create a default sprite area... 25210 : REM This requires a few default parameters to be set up and is the same 25220 : REM for both cases. Only the values differ. 25230 : DEFFNgfx_createdefaultarea(size%) 25240 : LOCAL area% 25250 : area%=FNash_claimfilled(size%,0) 25260 : area%!0=size%:area%!4=0:area%!8=16:area%!12=16 25270 : =area% 25280 : 25290 : REM Return the address of a given sprite... 25300 : REM This is used by both PROCgfx_plot() & PROCgfx_setupspritesize() 25310 : REM to return the sprite's address offset when given it's name. 25320 : DEFFNgfx_getaddressfromname(sprite$) 25330 : LOCAL address%,gfx_area%:gfx_area%=88 25340 : SYS gfx_spriteop%,256+24,gamestate%!gfx_area%,sprite$ TO ,,address% 25350 : =address% 25360 : 25370 : REM Return width and height of an image... 25380 : REM This does the setup and conversion of the width and height 25390 : REM stuff previously done manually with values hard-wired into 25400 : REM the code. All we do now is use the values stored in the 25410 : REM sprite file. 25420 : DEFPROCgfx_setupspritesize(sprite$,blk%) 25430 : LOCAL address%,x%,y%,gfx_area%:gfx_area%=88 25440 : address%=FNgfx_getaddressfromname(sprite$) 25450 : SYS gfx_spriteop%,512+40,gamestate%!gfx_area%,address% TO ,,,x%,y% 25460 : blk%!xsize%=FNgfx_convunits(x%,1):blk%!ysize%=FNgfx_convunits(y%,2) 25470 : ENDPROC 25480 : 25490 : REM Convert between pixels & OS Units. Our screen object has been 25500 : REM setup already using PROCget_screeninfo. However, if the 25510 : REM current value of MODE is different to that in screen%!setmode% 25520 : REM then the screen mode has changed, so call PROCget_screeninfo 25530 : REM again. 25540 : REM size% = Value to convert. 25550 : REM op% = What to do. 1 = Width (Pixels > OS Units) 25560 : REM 2 = Height (Pixels > OS Units) 25570 : REM 3 = Width (OS Units > Pixels) 25580 : REM 4 = Height (OS Units > Pixels) 25590 : REM Returns 25600 : REM conv% = Converted value. 25610 : DEFFNgfx_convunits(size%,op%) 25620 : LOCAL conv%,mode% 25630 : 25640 : REM Check to see if the screen mode has changed. If so, then 25650 : REM call PROCget_screeninfo to update the values. This is 25660 : REM probably overkill and can be omitted, but still good practice 25670 : REM for debugging. 25680 : mode%=MODE 25690 : IF mode%<>screen%!setmode% THEN PROCget_screeninfo 25700 : 25710 : REM Do the conversion. 25720 : CASE op% OF 25730 : WHEN 1 : conv%=size%<>screen%!x_eig% 25760 : WHEN 4 : conv%=size%>>screen%!y_eig% 25770 : OTHERWISE 25780 : ERROR 255,FNstring_get(103)+" "+STR$(op%) 25790 : ENDCASE 25800 : =conv% 25810 : 25820 : REM Display a given image. Modified to use a local sprite area & 25830 : REM system calls to speed things up dramatically. (02/01/08) 25840 : REM sprite$ = Name of image to display. 25850 : REM x% = X Co-ordinate. 25860 : REM y% = Y Co-ordinate. 25870 : DEFPROCgfx_plot(sprite$,x%,y%) 25880 : LOCAL address%,gfx_area%:gfx_area%=88 25890 : address%=FNgfx_getaddressfromname(sprite$) 25900 : SYS gfx_spriteop%, 512+34, gamestate%!gfx_area%, address%, x%, y%, 0 25910 : ENDPROC 25920 : 25930 : REM Display a string character by character with an optional 25940 : REM delay between each one. Each character is indexed to the 25950 : REM 8*8 character set sprites using ASCII codes. 25960 : DEFPROCgfx_print(x%,y%,string$,delay%,bank%) 25970 : LOCAL i%,chsize%,gfx_area%,gfx_sprs% 25980 : gfx_area%=88:gfx_sprs%=76 :REM GFX bank ptrs. 25990 : 26000 : REM Check for bank garbage and switch... 26010 : IF bank%<>gamestate%!80 AND bank%<>gamestate%!84 THEN 26020 : ERROR 255,FNstring_get(112) 26030 : ENDIF 26040 : gamestate%!gfx_area%=bank% :REM Switch banks. 26050 : 26060 : REM Display our text... 26070 : chsize%=screen%!xfont% 26080 : FOR i%=1 TO LEN(string$) 26090 : PROCgfx_plot(STR$(ASC(MID$(string$,i%,1))),x%+((i%-1)*chsize%),y%) 26100 : IF delay%>0 THEN PROCwait(delay%) 26110 : NEXT i% 26120 : 26130 : gamestate%!gfx_area%=gamestate%!gfx_sprs% :REM Switch back... 26140 : ENDPROC 26150 : 26160 : REM Clear a sprite from the screen. (New method.) 26170 : REM Before we created an identical bitmap in the sprite file 26180 : REM and displayed this over our image. This should be faster, and 26190 : REM result in a smaller sprite file. 26200 : DEFPROCgfx_wipesprite(obj%) 26210 : GCOL0,0 TINT0 26220 : RECTANGLE FILLobj%!xpos%,obj%!ypos%,obj%!xsize%,obj%!ysize% 26230 : ENDPROC 26240 : 26250 : REM ************************************************************* 26260 : REM Functions & Proceedures (Graphics Effects & Alignment) 26270 : REM ************************************************************* 26280 : 26290 : REM Draw a box centred on the screen with the specified 26300 : REM text inside. Also centered in relation to the box. 26310 : DEFPROCgfx_centredtextbox(string$,delay%,wait%) 26320 : LOCAL box%:box%=FNash_claim(16) 26330 : box%!xsize%=(LEN(string$)+2)*screen%!xfont%:box%!ysize%=40 26340 : PROCgfx_centresprite_xy(box%,screen%):box%!ypos%-=3 26350 : PROCgfx_wipesprite(box%):PROCgfx_drawbox(box%) 26360 : PROCgfx_centretext_xy(string$,delay%):PROCash_free(box%) 26370 : PROCwait(wait%) 26380 : ENDPROC 26390 : 26400 : REM Centre text relative to the screen width and display at a 26410 : REM supplied y co-ordinate. 26420 : DEFPROCgfx_centredtext(string$,delay%,ypos%) 26430 : LOCAL str%,gfx_area%:gfx_area%=80:str%=LEN(string$)*screen%!xfont% 26440 : PROCgfx_print(FNgfx_centretext(str%,screen%!xsize%),ypos%,string$,delay%,gamestate%!gfx_area%) 26450 : ENDPROC 26460 : 26470 : 26480 : REM Centre text horizontally... 26490 : REM Where string_length% is the length of the string in multiples 26500 : REM of OS Units, where each multiple is the width of a character. 26510 : REM Var screen% is the screen width. 26520 : DEFFNgfx_centretext(string_length%,screen%) 26530 : =(screen%/2)-(string_length%/2) 26540 : 26550 : REM Centre text horizontally & vertically. 26560 : DEFPROCgfx_centretext_xy(string$,delay%) 26570 : LOCAL str%,x%,y%,gfx_area%:gfx_area%=80 26580 : str%=LEN(string$)*screen%!xfont%:x%=FNgfx_centretext(str%,screen%!xsize%) 26590 : y%=(screen%!ysize%/2)-(screen%!yfont%/2) 26600 : PROCgfx_print(x%,y%,string$,delay%,gamestate%!gfx_area%) 26610 : ENDPROC 26620 : 26630 : REM Centre sprite horizontally... 26640 : REM Takes the two "object" blocks as parameters and extracts the 26650 : REM required values from them. Then returns the centre position. 26660 : DEFFNgfx_centresprite(obj%,relative%) 26670 : =(relative%!xsize%/2)-(obj%!xsize%/2) 26680 : 26690 : REM Centre sprite horizontally & vertically. 26700 : REM Returns the new position at offsets xpos% & ypos% in obj%. 26710 : DEFPROCgfx_centresprite_xy(obj%,relative%) 26720 : obj%!xpos%=(relative%!xsize%/2)-(obj%!xsize%/2) 26730 : obj%!ypos%=(relative%!ysize%/2)-(obj%!ysize%/2) 26740 : ENDPROC 26750 : 26760 : REM Repeat a 6*6 image across the screen to produce a "rail" like 26770 : REM effect at position y%. 26780 : DEFPROCgfx_drawrail(ypos%) 26790 : LOCAL i%,reps% 26800 : reps%=screen%!xsize%/rail%!xsize% 26810 : FOR i%=1 TO reps%+1 26820 : PROCgfx_plot(FNsprite_get(108),(i%-1)*rail%!xsize%,ypos%) 26830 : NEXT i% 26840 : ENDPROC 26850 : 26860 : REM Produce a box to surround text in GAME OVER!!! & GOODBYE!!! 26870 : REM texts. (Among others.) 26880 : DEFPROCgfx_drawbox(obj%) 26890 : LOCAL x%,y%,cxsize%,cysize%,rxsize%,rysize%,i% 26900 : 26910 : REM Setup... 26920 : cxsize%=FNgfx_convunits(3,1):cysize%=FNgfx_convunits(3,2) 26930 : rxsize%=1:rysize%=3 26940 : 26950 : REM The top & bottom... 26960 : FOR i%=1 TO obj%!xsize% 26970 : x%=obj%!xpos%+((i%-1)*rxsize%) 26980 : PROCgfx_plot(FNsprite_get(113),x%,obj%!ypos%) 26990 : PROCgfx_plot(FNsprite_get(113),x%,obj%!ypos%+(obj%!ysize%-(2*rysize%))) 27000 : NEXT i% 27010 : 27020 : REM The two sides... 27030 : x%=obj%!xpos%+(obj%!xsize%-cxsize%) 27040 : FOR i%=1 TO obj%!ysize%-1 27050 : PROCgfx_plot(FNsprite_get(114),obj%!xpos%,obj%!ypos%+((i%-1)*rxsize%)) 27060 : PROCgfx_plot(FNsprite_get(114),x%,obj%!ypos%+((i%-1)*rxsize%)) 27070 : NEXT i% 27080 : 27090 : REM The four corners... 27100 : PROCgfx_plot(FNsprite_get(111),obj%!xpos%,obj%!ypos%) 27110 : PROCgfx_plot(FNsprite_get(109),obj%!xpos%,(obj%!ypos%+obj%!ysize%)-cysize%) 27120 : PROCgfx_plot(FNsprite_get(112),obj%!xpos%+(obj%!xsize%-cxsize%),obj%!ypos%) 27130 : x%=(obj%!xpos%+obj%!xsize%)-cxsize% 27140 : y%=(obj%!ypos%+obj%!ysize%)-cysize% 27150 : PROCgfx_plot(FNsprite_get(110),x%,y%) 27160 : ENDPROC 27170 : 27180 : REM ************************************************************* 27190 : REM Functions & Proceedures (String & Text De-coding) 27200 : REM ************************************************************* 27210 : 27220 : REM These routines deal with our garbled strings. This is done 27230 : REM this way for a couple of reasons... 27240 : 27250 : REM # BasCrunch output is smaller. 27260 : REM # BasCrunch ouptut is harder to reverse engineer. 27270 : 27280 : REM Get number of messages in a processed file... 27290 : DEFFNstring_enumprocessed(filespec$,magic$) 27300 : LOCAL hdl%,count%,junk%,key%,maggot$ 27310 : hdl%=OPENIN(filespec$) 27320 : INPUT#hdl%,maggot$:maggot$=FNstring_reverse(maggot$) 27330 : IF maggot$<>magic$ THEN CLOSE#hdl%:ERROR 255,"Bad Magic!" 27340 : INPUT#hdl%,key%,junk%,junk%,count% 27350 : CLOSE#hdl% 27360 : =count% EOR key% 27370 : 27380 : REM Load a file off disc and return messages setup in arrays. 27390 : DEFFNstring_load(filespec$,magic$,RETURN nums%(),RETURN strs$()) 27400 : LOCAL hdl%,i%,count%,key%,chfgot%,cplgot%,chf%,cpl%,maggot$:chf%=0 27410 : hdl%=OPENIN(filespec$) 27420 : INPUT#hdl%,maggot$:maggot$=FNstring_reverse(maggot$) 27430 : IF maggot$<>magic$ THEN CLOSE#hdl%:ERROR 255,"Bad Magic!" 27440 : INPUT#hdl%,key%,chfgot%,cplgot%,count% 27450 : chfgot%=chfgot% EOR key%:cplgot%=cplgot% EOR key% 27460 : count%=count% EOR key% 27470 : FOR i%=1 TO count% 27480 : INPUT#hdl%,nums%(i%),strs$(i%) 27490 : NEXT i% 27500 : CLOSE#hdl% 27510 : FOR i%=1 TO count%:chf%+=LEN(strs$(i%)):NEXT i% 27520 : cpl%=chf%/count% 27530 : IF chf%<>chfgot% OR cpl%<>cplgot% THEN 27540 : ERROR 255,"File Inconsistant with header!" 27550 : ENDIF 27560 : =key% 27570 : 27580 : REM Reverse a given string... 27590 : REM Reverse the order of all characters in the given string. "ABC" 27600 : REM becomes "CBA" etc. 27610 : DEFFNstring_reverse(string$) 27620 : LOCAL i%,out$ 27630 : FOR i%=LEN(string$) TO 1 STEP-1 27640 : out$+=MID$(string$,i%,1) 27650 : NEXT i% 27660 : =out$ 27670 : 27680 : REM Decode SWIs and Sprite at runtime to increase speed... 27690 : REM We don't store these strings as part of the listing, and we 27700 : REM don't want them as plaintext on disc, but the overheads 27710 : REM involved in decoding them on the fly are big. It doesn't 27720 : REM matter so much if these are as plaintext in memory. 27730 : DEFPROCstring_bulkdecode(max%,key%,RETURN str$()) 27740 : LOCAL i% 27750 : FOR i%=1 TO max% 27760 : str$(i%)=FNencode_string(str$(i%),key%) 27770 : NEXT i% 27780 : ENDPROC 27790 : 27800 : REM The following functions are all identical, other than what is 27810 : REM actually searched. (The arrays are different.) So... To avoid 27820 : REM duplicating code, I've written a generic search routine with 27830 : REM three wrappers passing the correct values to the generic code. 27840 : 27850 : REM Retrieve an encoded message for display... (Wrapper...) 27860 : DEFFNstring_get(msg%) 27870 : LOCAL key%:key%=gamestate%!68 27880 : =FNcore_get(msg%,string_max%,string_num%(),string_msg$(),key%,106) 27890 : 27900 : REM Retrieve an encoded Sprite name... (Wrapper...) 27910 : DEFFNsprite_get(msg%) 27920 : =FNcore_get(msg%,ilut_max%,ilut_num%(),ilut_msg$(),-1,107) 27930 : 27940 : REM Retrieve an encoded SWI name... (Wrapper...) 27950 : DEFFNswi_get(msg%) 27960 : =FNcore_get(msg%,slut_max%,slut_num%(),slut_msg$(),-1,108) 27970 : 27980 : REM Retrieve an encoded LibASH resource... (Wrapper...) 27990 : DEFFNash_get(msg%) 28000 : =FNcore_get(msg%,ashmsg_max%,ashmsg_num%(),ashmsg_msg$(),ash_key%,109) 28010 : 28020 : REM Generic core code for above get functions... 28030 : REM If our XOR key (key%) is -1 then we are dealing with raw 28040 : REM strings that have already been de-munged in memory. 28050 : DEFFNcore_get(msg%,max%,num%(),str$(),key%,msg_err%) 28060 : LOCAL i%,found%:found%=0 28070 : FOR i%=1 TO max% 28080 : IF num%(i%)=msg% THEN found%=i%:i%=max% 28090 : NEXT i% 28100 : IF found%=0 THEN 28110 : IF msg%>=106 AND msg%<=108 THEN 28120 : ERROR 255,"Missing message!!!" 28130 : ELSE 28140 : IF msg_err%=109 THEN 28150 : ERROR 255,"Can't find LibASH resource : "+STR$(msg%) 28160 : ELSE 28170 : ERROR 255,FNstring_get(msg_err%)+" "+STR$(msg%) 28180 : ENDIF 28190 : ENDIF 28200 : ENDIF 28210 : IF key%=-1 THEN =str$(found%) 28220 : =FNencode_string(str$(found%),key%) 28230 : 28240 : REM ************************************************************* 28250 : REM Functions & Proceedures (Sound Sub-system OS call wrappers.) 28260 : REM ************************************************************* 28270 : 28280 : REM Load a song file, decompress into memory and return 28290 : REM a handle... We use the generic decompression code extracted 28300 : REM from our old FNgfx_load() for this. We only need to free the 28310 : REM file buffer if osize% is zero. Likewise we only need to claim 28320 : REM an output buffer if we're actually going to decompress anything! 28330 : DEFFNtimplay_songload(filespec$) 28340 : LOCAL size%,osize%,blk%,area%,hdl% 28350 : size%=FNfs_fileop(filespec$,2) :REM Input file size. 28360 : blk% =FNash_claimfilled(size%,0) :REM Claim buffer. 28370 : IF invaders_debug% THEN PROCinit_memusage(blk%,208,0) :REM Disp. alloc. 28380 : SYSFNswi_get(3),16,filespec$,blk%,0 :REM Load file. 28390 : osize%=FNsquash_init(blk%) :REM Init Squash. 28400 : 28410 : REM Var. osize% is either a positive value, or zero. If zero then 28420 : REM the SQSH magic has not been found at the beginning of the 28430 : REM file, therefore it is assumed that the file contains raw data. 28440 : REM If positive, it contains the size of the output buffer we need 28450 : REM create. 28460 : IF osize%>0 THEN 28470 : area%=FNash_claimfilled(osize%,0) :REM Claim output buffer. 28480 : PROCsquash_decompress(blk%,area%,0) :REM Decompress to memory. 28490 : SYSFNswi_get(31),,0,area%,osize% TO hdl% :REM Copy to TimPlayer wksp. 28500 : PROCinit_memusage(area%,0,1) :REM Disp. free. 28510 : PROCash_free(area%) :REM Free output buffer. 28520 : ELSE 28530 : SYSFNswi_get(31),,0,blk%,size% TO hdl% :REM Copy to TimPlayer wksp. 28540 : PROCinit_memusage(blk%,0,1) :REM Disp. free. 28550 : PROCash_free(blk%) :REM Free file buffer. 28560 : ENDIF 28570 : =hdl% 28580 : 28590 : REM Fade out the current track whilst fading in the new one... 28600 : DEFPROCtimplay_crossfade(delay%) 28610 : LOCAL i%,j%,mstatus%,chandle%,mhandle%,ehandle%,gsvol%,mstep% 28620 : mstatus%=0:mhandle%=8:ehandle%=12 :REM Object offsets. 28630 : chandle%=16:gsvol%=20:mstep%=24 28640 : 28650 : REM Only do this if music is currently playing. If not, skip to 28660 : REM end and return to caller. We can't just bung an ENDPROC here 28670 : REM or we'll barf the cruncher. 28680 : IF audiostate%!mstatus%=1 THEN 28690 : 28700 : REM Check what we are currently playing and start playing the 28710 : REM other one at zero volume. 28720 : IF audiostate%!chandle%=audiostate%!mhandle% THEN 28730 : SYSFNswi_get(36),audiostate%!ehandle%,0 :REM Play HiScore Entry 28740 : SYSFNswi_get(33),audiostate%!ehandle% :REM track (fade-in) 28750 : ELSE 28760 : SYSFNswi_get(36),audiostate%!mhandle%,0 :REM Play Main Theme 28770 : SYSFNswi_get(33),audiostate%!mhandle% :REM track (fade-in) 28780 : ENDIF 28790 : 28800 : REM Start the cross-fade. 28810 : FOR i%=0 TO audiostate%!gsvol% STEP audiostate%!mstep% 28820 : j%=audiostate%!gsvol%-i% 28830 : IF audiostate%!chandle%=audiostate%!mhandle% THEN 28840 : SYSFNswi_get(36),audiostate%!ehandle%,i% 28850 : SYSFNswi_get(36),audiostate%!mhandle%,j% 28860 : ELSE 28870 : SYSFNswi_get(36),audiostate%!mhandle%,i% 28880 : SYSFNswi_get(36),audiostate%!ehandle%,j% 28890 : ENDIF 28900 : PROCwait(delay%) 28910 : NEXT i% 28920 : 28930 : REM Stop the track we've just faded out. 28940 : SYSFNswi_get(35),audiostate%!chandle% 28950 : 28960 : REM Update our current handle pointer. 28970 : IF audiostate%!chandle%=audiostate%!mhandle% THEN 28980 : audiostate%!chandle%=audiostate%!ehandle% 28990 : ELSE 29000 : audiostate%!chandle%=audiostate%!mhandle% 29010 : ENDIF 29020 : ENDIF 29030 : ENDPROC 29040 : 29050 : REM Fade-in the current track... 29060 : DEFPROCtimplay_fadein(delay%) 29070 : LOCAL i%,mstatus%,chandle%,gsvol%,mstep% 29080 : mstatus%=0:chandle%=16:gsvol%=20:mstep%=24 :REM Object offsets. 29090 : SYS FNswi_get(36),audiostate%!chandle%,0 :REM Set volume to zero. 29100 : SYS FNswi_get(33),audiostate%!chandle% :REM Start playing. 29110 : FOR i%=0 TO audiostate%!gsvol% STEP audiostate%!mstep% 29120 : SYS FNswi_get(36),audiostate%!chandle%,i% :REM Set volume. 29130 : IF delay%>0 THEN PROCwait(delay%) :REM Wait if delay%>0 29140 : NEXT i% 29150 : ENDPROC 29160 : 29170 : REM Fade-out the current track... 29180 : DEFPROCtimplay_fadeout(delay%) 29190 : LOCAL i%,mstatus%,chandle%,gsvol%,mstep% 29200 : mstatus%=0:chandle%=16:gsvol%=20:mstep%=24 :REM Object offsets. 29210 : IF audiostate%!mstatus%=1 THEN 29220 : FOR i%=audiostate%!gsvol% TO 0 STEP 0-audiostate%!mstep% 29230 : SYSFNswi_get(36),audiostate%!chandle%,i% 29240 : IF delay%>0 THEN PROCwait(delay%) :REM Wait if delay%>0 29250 : NEXT i% 29260 : SYSFNswi_get(34),audiostate%!chandle% 29270 : ENDIF 29280 : ENDPROC 29290 : 29300 : REM Release resources in preperation to bail... 29310 : REM We can't kill TimPlayer module until resources have been 29320 : REM released. First we try to release resources using 29330 : REM TimPlayer_SongUnload. We also issue a panic call 29340 : REM that frees everything so that TimPlayer can be RMKilled. 29350 : DEFPROCtimplay_bail 29360 : LOCAL mhandle%,ehandle%,chandle% 29370 : mhandle%=8:ehandle%=12:chandle%=16 :REM Object offsets. 29380 : 29390 : REM Manually unload each track... 29400 : IF audiostate%!mhandle%<>-1 THEN SYSFNswi_get(32),audiostate%!mhandle% 29410 : IF audiostate%!ehandle%<>-1 THEN SYSFNswi_get(32),audiostate%!ehandle% 29420 : 29430 : REM Clear handles and release and stuck TimPlayer resources. 29440 : audiostate%!mhandle%=-1:audiostate%!ehandle%=-1 29450 : audiostate%!chandle%=-1 29460 : SYSFNswi_get(38),-1 :REM Release TimPlayer resources. 29470 : ENDPROC 29480 : 29490 : REM Trigger one of a number of loaded sound effects... 29500 : REM The number passed in what% is the number of the sample to 29510 : REM play. We currently only have 4 sound effects numbered 1-4. 29520 : DEFPROCsfx_play(what%) 29530 : LOCAL mstatus%,err$ 29540 : mstatus%=0:err$=FNstring_get(110)+" "+STR$(what%) 29550 : IF audiostate%!mstatus%>0 THEN 29560 : IF what%<1 AND what%>4 THEN ERROR255,err$ 29570 : SYSFNswi_get(8),"SPlay_fire0"+STR$(what%)+"mhh" 29580 : ENDIF 29590 : ENDPROC 29600 : 29610 : REM ************************************************************* 29620 : REM Functions & Proceedures (Misc. Game Specific.) 29630 : REM ************************************************************* 29640 : 29650 : REM Get various information about screen resolution. This includes 29660 : REM viewing area and conversion factors. We cannot rely on fixed 29670 : REM values because they change from mode to mode. So grab the 29680 : REM current values from the OS and bung 'em into some variables. 29690 : DEFPROCget_screeninfo 29700 : LOCAL blk% 29710 : blk%=FNash_claim(20) :REM Reserve space for parameter block. 29720 : 29730 : REM Setup parameter block... 29740 : blk%!0=4 :REM XEigFactor (Convert.) 29750 : blk%!4=5 :REM YEigFactor (Convert.) 29760 : blk%!8=11 :REM XWindLimit (Width.) 29770 : blk%!12=12 :REM YWindLimit (Height.) 29780 : blk%!16=-1 :REM Termination byte. 29790 : 29800 : SYS FNswi_get(5), blk%, blk%:REM Call OS. 29810 : 29820 : REM Setup our screen% object... 29830 : screen%!x_eig%=blk%!0 :REM X & Y EigFactor. 29840 : screen%!y_eig%=blk%!4 29850 : screen%!xsize%=(blk%!8)+1<=targ%!xpos% AND obj%!xpos%=targ%!ypos% AND obj%!ypos%=0 AND value%<=2 THEN audiostate%!mstatus%=value% 30590 : IF vol%>=0 AND vol%<=128 THEN audiostate%!gsvol%=vol% 30600 : IF sval%>=1 AND sval%<=64 THEN gamestate%!vsync%=sval% 30610 : gamestate%!cfgflag%=FALSE 30620 : ENDPROC 30630 : 30640 : REM Save the configs file... 30650 : REM This is performed on clean exit of the program. Currently 30660 : REM contains the last known sound & vsync states. Only saves 30670 : REM if config data has changed. (Offset 92 in gamestate% 30680 : REM is set.) 30690 : DEFPROCgs_saveconfig 30700 : LOCAL hdl%,mstatus%,gsvol%,cfgflag%,vsync% 30710 : mstatus%=0:gsvol%=20 :REM Object offsets. 30720 : vsync%=108:cfgflag%=92 30730 : IF gamestate%!cfgflag% THEN 30740 : hdl%=OPENOUT(sav_dir$+"Configs") 30750 : PRINT#hdl%,audiostate%!mstatus%,audiostate%!gsvol% 30760 : PRINT#hdl%,gamestate%!vsync% 30770 : CLOSE#hdl% 30780 : ENDIF 30790 : ENDPROC 30800 : 30810 : REM ************************************************************* 30820 : REM Functions & Proceedures (Data Decompression.) 30830 : REM ************************************************************* 30840 : 30850 : REM Return our output buffer size requirements... 30860 : REM Returns either a positive value indicating the size of the 30870 : REM output buffer we need to create, or zero if the data is not 30880 : REM compressed. (We assume the data is compressed if we find 30890 : REM "SQSH" as the first 4 bytes of the input buffer. No further 30900 : REM checks as to the validity of the file are made by the this 30910 : REM code. This routine would pass a file containing only this 30920 : REM "SQSHed file NOT" string as valid when obviously it's not.) 30930 : DEFFNsquash_init(in%) 30940 : LOCAL size%,wsize%,osize%,i%,magic$ 30950 : size%=FNash_blocksize(in%) :REM Get size of input buffer. 30960 : PROCinit_memusage(in%,208,0) :REM Display alloc. 30970 : 30980 : REM Quick sanity check here. We return zero if data not SQSHed. 30990 : FOR i%=0 TO 3:magic$+=CHR$(in%?i%):NEXT i% 31000 : IF magic$<>"SQSH" THEN =0 31010 : 31020 : REM OK to continue... Buffer contains what appears to be squashed 31030 : REM data. 31040 : PRINTFNstring_get(27)+"..." 31050 : SYSFNswi_get(7),1<<3,size% TO wsize%,osize% :REM Init Squash. 31060 : IF osize%=-1 THEN osize%=in%!4 :REM Squash bugfix. 31070 : =osize% 31080 : 31090 : REM Decompress data... 31100 : REM We are supplied with a full input buffer and an empty output 31110 : REM buffer. It is assumed that we can obtain sizes for both using 31120 : REM LibASH calls. NOTE:- Input buffer is clobbered on exit!! 31130 : 31140 : REM Bug-fixed : Takes a third parameter (offs%) which specifies 31150 : REM an offset to apply to the base address of the output buffer. 31160 : REM This fixes a potential problem where the caller would give us 31170 : REM the base address + a pre-calculated offset. This routine 31180 : REM would assume it was given the base address as allocated. It 31190 : REM would then use this base address to get block size and possibly 31200 : REM trample over other stuff. Asking for the offset as the third 31210 : REM parameter prevents us from getting a tainted base address, yet 31220 : REM still gives the flexability to start decompression at an offset. 31230 : REM (30/07/10) 31240 : DEFPROCsquash_decompress(in%,out%,offs%) 31250 : LOCAL size%,wksp%,wsize%,osize% 31260 : size% =FNash_blocksize(in%) :REM Input buffer size. 31270 : osize%=FNash_blocksize(out%) :REM Output buffer size. 31280 : SYSFNswi_get(7),1<<3,size% TO wsize% :REM Init Squash. 31290 : wksp%=FNash_claimfilled(wsize%,0) :REM Claim workspace. 31300 : PROCinit_memusage(wksp%,207,0) :REM Display alloc. 31310 : SYSFNswi_get(7),1<<2,wksp%,in%+20,size%-20,out%+offs%,osize% TO err% 31320 : IF err%<>0 THEN ERROR255,FNstring_get(114)+" "+STR$(err%) 31330 : PROCinit_memusage(wksp%,0,1):PROCinit_memusage(in%,0,1) 31340 : PROCash_free(wksp%):PROCash_free(in%) :REM Free up memory. 31350 : ENDPROC 31360 : 31370 : REM ************************************************************* 31380 : REM Functions & Proceedures (Initialisation.) 31390 : REM ************************************************************* 31400 : 31410 : REM Run through a counter checking names of files in the "Screens" 31420 : REM directory inside the application until we have a file not 31430 : REM found condition. Set the counter to this. This routine starts 31440 : REM from count% and stops at the first gap in a chain. 31450 : REM Because we fire it from PROCevnt_kb_screendump we effectively 31460 : REM handle any possible breakage in a chain of existing numbers 31470 : REM because it'll always find the next free slot. We continue to 31480 : REM count up until "Dump" and the number of digits in our count 31490 : REM exceed the ADFS file name limit (10 Chars.), at that point we 31500 : REM flag up an error by returning -1. This gives us a max count 31510 : REM of 999999. Chances are we'd exceed the max number of files 31520 : REM allowed in a directory before we'd ever hit this limit!! 31530 : DEFFNinit_setdumpcounter(count%) 31540 : LOCAL dump_name$ 31550 : dump_name$="Dump"+STR$(count%) 31560 : IF FNfs_fileop(scr_dir$+dump_name$,1)<>0 THEN 31570 : REPEAT 31580 : count%+=1 31590 : dump_name$="Dump"+STR$(count%) 31600 : UNTIL FNfs_fileop(scr_dir$+dump_name$,1)=0 OR LEN(dump_name$)>10 31610 : IF LEN(dump_name$)>10 THEN count%=-1 31620 : ENDIF 31630 : =count% 31640 : 31650 : REM Displays helpful diagnostic information on startup. 31660 : REM Used to build the memory allocation strings during the 31670 : REM init sequence. Var blk% contains the base address 31680 : REM returned by a LibASH claim and msg% contains the token 31690 : REM for the unique part of the message. For displaying frees, 31700 : REM the unique part of the message passed in msg% is always 31710 : REM zero and is ignored. Allocs have an op% value of 0 and 31720 : REM frees have an op% value of 1 31730 : DEFPROCinit_memusage(blk%,msg%,op%) 31740 : LOCAL string$ 31750 : IF invaders_debug% THEN 31760 : IF op%=0 THEN string$=FNstring_get(200) ELSE string$=FNstring_get(209) 31770 : string$+=" &":PRINTstring$;~FNash_blocksize(blk%); 31780 : PRINT" "+FNstring_get(201)+" &";~blk%; 31790 : IF op%=0 THEN 31800 : string$=" "+FNstring_get(202)+" "+FNstring_get(msg%) 31810 : ELSE 31820 : string$="." 31830 : ENDIF 31840 : PRINTstring$ 31850 : ENDIF 31860 : ENDPROC 31870 : 31880 : REM This was originally set out as strings in the listing, but 31890 : REM implementing this in a loop reading data from an array 31900 : REM tidies up the listing somewhat. 31910 : DEFPROCinit_credscreen 31920 : LOCAL hdl%,chf%,cpl%,chfgot%,cplgot%,i%,gs%,magic$ :REM Misc LOCALs 31930 : LOCAL key%,size% :REM gamestate% obj. 31940 : key%=60:size%=72 :REM offsets. 31950 : hdl%=OPENIN(dat_dir$+"Creds") 31960 : INPUT#hdl%,magic$ :REM Read magic and verify. 31970 : IF magic$<>"DERC" THEN 31980 : CLOSE#hdl%:ERROR 255,FNstring_get(101):END 31990 : ENDIF 32000 : INPUT#hdl%,gamestate%!key%,gs%,chfgot%,cplgot% :REM File header. 32010 : gamestate%!size%=gs% EOR gamestate%!key% 32020 : chfgot%=chfgot% EOR gamestate%!key% 32030 : cplgot%=cplgot% EOR gamestate%!key% 32040 : DIM cred_str$(gamestate%!size%),cred_spc%(gamestate%!size%) 32050 : 32060 : FOR i%=1 TO gamestate%!size% 32070 : INPUT#hdl%,cred_spc%(i%),cred_str$(i%) 32080 : NEXT i% 32090 : CLOSE#hdl% 32100 : chf%=0 32110 : FOR i%=1 TO gamestate%!size%:chf%+=LEN(cred_str$(i%)):NEXT i% 32120 : cpl%=chf%/gamestate%!size% 32130 : IF cpl%<>cplgot% OR chf%<>chfgot% THEN ERROR 255,FNstring_get(113) 32140 : ENDPROC 32150 : 32160 : REM ************************************************************* 32170 : REM Functions & Proceedures (Generic & Portable Code) 32180 : REM ************************************************************* 32190 : 32200 : REM Returns TRUE n% percent of the time... 32210 : REM This is used all over the place, usually dictating the chances 32220 : REM of a particular event occuring, or as a bias for properties. 32230 : DEFFNpct(n%) 32240 : =RND(100)<=n% 32250 : 32260 : REM Encode or decode a string using a simple XOR algo... This 32270 : REM prevents hackers from peeking at and modifying strings in 32280 : REM files. Strings are in reverse order in BASIC datafiles, but 32290 : REM are still in plaintext format. 32300 : REM string$ = String to mash up. 32310 : REM Returns 32320 : REM mashed$ = Mashed up string. 32330 : DEFFNencode_string(string$,key%) 32340 : LOCAL i%,out$ 32350 : FOR i%=1 TO LEN(string$) 32360 : out$+=CHR$(ASC(MID$(string$,i%,1)) EOR key%) 32370 : NEXT i% 32380 : =out$ 32390 : 32400 : REM Introduce a delay of n% hundredths of a second. 32410 : DEFPROCwait(n%) 32420 : LOCAL t% 32430 : t%=TIME+n% 32440 : REPEAT:UNTIL TIME>=t% 32450 : ENDPROC 32460 : 32470 : REM Toggle a flag between 0 & 1. 32480 : DEFPROCtoggle(RETURN n%) 32490 : n%=n% EOR 1 32500 : ENDPROC 32510 : 32520 : REM Returns a suffix (st, nd, rd etc...) when given a number. This 32530 : REM number can be any number of digits long, we just deal with the 32540 : REM last digit. 32550 : DEFFNsuffix(n%) 32560 : LOCAL out$,u% 32570 : u%=VAL(RIGHT$(STR$(n%),1)) 32580 : out$=FNstring_get(65) 32590 : IF u%>=1 AND u%<=3 THEN out$=FNstring_get(u%+61) 32600 : =out$ 32610 : 32620 : REM Set a bit in a given bitmask. 32630 : DEFPROCbit_setbit(pos%,RETURN mask%) 32640 : mask%=mask% OR (1< File doesn't exist. Var filespec$ contains location. 33490 : REM op%=1 -> File exists. Can we write/replace it? 33500 : DEFFNfs_writeable(op%,filespec$) 33510 : LOCAL hdl%,flg%,flags%,result% 33520 : result%=FALSE 33530 : 33540 : REM Attempt to open or create a file depending on our opcode. 33550 : CASE op% OF 33560 : WHEN 0 : hdl%=OPENUP(filespec$+"chkfile") 33570 : WHEN 1 : hdl%=OPENUP(filespec$) 33580 : 33590 : REM Unknown op% code. 33600 : OTHERWISE : ERROR 255,FNstring_get(104)+" "+STR$(op%) 33610 : ENDCASE 33620 : 33630 : REM If we get this far and we still haven't got a valid file 33640 : REM handle then it's fairly safe to say that we're never gonna 33650 : REM get one, so skip the next bit and return FALSE. 33660 : IF hdl%<>0 THEN 33670 : SYS FNswi_get(4),254,hdl% TO flags%;flg% 33680 : IF (flg% AND 1) THEN CLOSE#hdl%:ERROR 255,FNstring_get(105) 33690 : IF ((flags% AND (1<<7))<>0) THEN result%=TRUE 33700 : CLOSE#hdl% 33710 : ENDIF 33720 : =result% 33730 : 33740 : REM ************************************************************ 33750 : REM Functions & Proceedures (Ash Heap manager for BASIC) 33760 : REM Copyright (c)2004 7th Software. 33770 : REM ************************************************************ 33780 : 33790 : REM Calls to FNash_get() aren't directly LibASH related, but call 33800 : REM code in the main program to decode strings from LUTs. 33810 : 33820 : REM Initialise ASH Subsystem... 33830 : DEFPROCash_init(key%) 33840 : LOCAL err% 33850 : ash_heap%=0:ash_total%=0:ash_key%=key% 33860 : SYS FNash_get(210), -1, -1 TO ash_slot% 33870 : SYS FNash_get(211) TO ash_page% ; err% 33880 : IF err% AND 1 THEN ash_page%=16384 33890 : PROCash_create(ash_page%) 33900 : ENDPROC 33910 : 33920 : REM Release the memory used for our heap 33930 : DEFPROCash_destroy 33940 : IF ash_heap% THEN 33950 : SYS FNash_get(210), ash_heap%-&8000,-1 TO ash_slot% 33960 : ash_heap%=0:ash_total%=0 33970 : ENDIF 33980 : ENDPROC 33990 : 34000 : REM Claim a block from the heap - generate an error if claim fails 34010 : REM Our block's size is now the requested size + 3 extra locations 34020 : REM @ 4 bytes per location + 3 extra bytes. 34030 : 34040 : REM Offs | Significance. 34050 : REM --------+--------------- 34060 : REM 0 | Low marker. 34070 : REM 4 | Block user area size in bytes. 34080 : REM 8 | User area base address. (This is what is returned.) 34090 : REM | High marker. 34100 : 34110 : DEFFNash_claim(bytes%) 34120 : LOCAL block%,size% 34130 : size%=(bytes%+15) AND &FFFFFFFC 34140 : block%=FNash_alloc(size%) 34150 : IF block% ELSE ERROR 254,FNash_get(110) 34160 : block%!0=&DEADDEAD :REM Store low marker... 34170 : block%!4=bytes% :REM Store user area size... 34180 : !(block%-8+(block%!-4))=&DEADDEAD :REM Store high marker... 34190 : =block%+8 34200 : 34210 : REM Claim a block of memory, as above, but fill it with a 34220 : REM specified value before returning. 34230 : DEFFNash_claimfilled(bytes%,value%) 34240 : LOCAL block% 34250 : block%=FNash_claim(bytes%) :REM Claim requested space... 34260 : PROCash_blockfill(block%,value%) :REM Fill with spec'd value... 34270 : =block% 34280 : 34290 : REM Free a block into the heap 34300 : DEFPROCash_free(RETURN block%) 34310 : CASE FNash_blockintegrity(block%) OF 34320 : WHEN 1 :ERROR 254,FNash_get(111) 34330 : WHEN 2 :ERROR 254,FNash_get(112) 34340 : WHEN 3 :ERROR 254,FNash_get(113) 34350 : ENDCASE 34360 : block%-=8:ash_total%-=block%!-4 34370 : SYS FNash_get(212), 3, ash_heap%, block% 34380 : block% = 0 34390 : ENDPROC 34400 : 34410 : REM Return the size (in bytes) of a specified block. 34420 : DEFFNash_blocksize(block%) 34430 : LOCAL blk% 34440 : blk%=block%-4 34450 : =blk%!0 34460 : 34470 : REM Fill a block of memory with a specified value... 34480 : DEFPROCash_blockfill(block%,value%) 34490 : LOCAL offset%,size% 34500 : size%=FNash_blocksize(block%) 34510 : FOR offset%=0 TO size%-4 STEP 4 34520 : block%!offset%=value% 34530 : NEXT offset% 34540 : ENDPROC 34550 : 34560 : REM Create a block with identical contents to the original. 34570 : DEFFNash_blockcopy(block%) 34580 : LOCAL blk%,offset%,size% 34590 : size%=FNash_blocksize(block%) 34600 : blk%=FNash_claim(size%) 34610 : FOR offset%=0 TO size%-4 STEP 4 34620 : blk%!offset%=block%!offset% 34630 : NEXT offset% 34640 : =blk% 34650 : 34660 : REM Returns integrity status of a given block... 34670 : REM 0 -> No Errors 34680 : REM 1 -> Low marker (Under-run) 34690 : REM 2 -> High marker (Over-run) 34700 : REM 3 -> Both markers!?! 34710 : DEFFNash_blockintegrity(block%) 34720 : LOCAL hi%,lo% 34730 : hi%=FALSE:lo%=FALSE :REM Default. (No Errors) 34740 : block%-=8 34750 : IF block%!0<>&DEADDEAD THEN lo%=TRUE 34760 : IF !(block%-8+(block%!-4))<>&DEADDEAD THEN hi%=TRUE 34770 : IF lo% AND hi% THEN =3 :REM Both markers trashed. 34780 : IF hi% THEN =2 :REM High marker trashed. 34790 : IF lo% THEN =1 :REM Low marker trashed. 34800 : =0 :REM Integrity OK. 34810 : 34820 : REM *** LibASH Internals *** 34830 : 34840 : REM Create a heap in the memory above HIMEM 34850 : DEFPROCash_create(size%) 34860 : IF ash_heap% THEN ERROR 254,FNash_get(114) 34870 : size%=(size%+ash_page%-1) AND NOT (ash_page%-1) 34880 : ash_heap%=HIMEM 34890 : SYS FNash_get(210), ash_slot%+size%, -1 TO ash_slot% 34900 : size%=ash_slot%+&8000-ash_heap% 34910 : IF size%<1 THEN 34920 : ash_heap%=0 34930 : ERROR 254,FNash_get(115) 34940 : ELSE 34950 : SYS FNash_get(212), 0, ash_heap%,, size% 34960 : ENDIF 34970 : ash_total%=0 34980 : ENDPROC 34990 : 35000 : REM Attempt to add some more memory into the end of our heap 35010 : DEFPROCash_grow(bytes%) 35020 : LOCAL prev% 35030 : prev%=ash_slot% 35040 : SYS FNash_get(210), ash_slot%+bytes%, -1 TO ash_slot% 35050 : bytes%=ash_slot%-prev% 35060 : IF bytes% THEN SYS FNash_get(212),5,ash_heap%,,bytes% TO ,,,bytes% 35070 : ENDPROC 35080 : 35090 : REM Claim a block from the heap (grow it if need be) - return 35100 : REM zero if claim fails. 35110 : DEFFNash_alloc(bytes%) 35120 : LOCAL block%, err% 35130 : SYS FNash_get(213), 2, ash_heap%,, bytes% TO ,, block% ; err% 35140 : IF err% AND 1 THEN 35150 : PROCash_grow((bytes%+ash_page%) AND NOT (ash_page%-1)) 35160 : SYS FNash_get(213), 2, ash_heap%,, bytes% TO ,, block% ; err% 35170 : IF err% AND 1 THEN =0 35180 : ENDIF 35190 : ash_total% += block%!-4 35200 : =block%