' INFILTRATOR 2000 ' ' A SHOOT 'EM UP GAME INSPIRED BY CLASSICS ' LIKE XEVIOUS & URIDIUM ' ' WRITTEN ENTIRELY ON AN IPHONE! ' ' CODE & GRAPHICS COPYRIGHT (C) 2019 NAT PRYCE ' MUSIC (C) 2019 DESBYC ' LICENSE: CC BY-NC-SA 4.0 ' DOING: ' * SOUND FX. OUTSTANDING: ' - PLAYER EXPLOSION ' - FINAL BOSS SFX ' ' TODO: ' * MULTIPLE SMART BOMBS ' * LASER BARRIERS IN THE ASSIMILATED ZONE ' - GFX DONE, NEED LOGIC TO CHANGE BG TILES ' AND CHECK COLLISIONS ' * NO CLOUDS IN BOSS BATTLE? ' * DARK CLOUDS IN ASSIMILATED ZONE? ' * MORE CLOUDS ON SCREEN AT ONCE? '========================================== ' INITIALISE THE HARDWARE GAMEPAD 1 DISPLAY(,1) ' AUDIO VOICES ' 0: PLAYER SFX (SHIELDS, SHOTS, EXPLODING) ' 1: ENEMIES EXPLODING ' 2: ENEMY SHOTS ' 3: AUDIO CUES ABOUT ENEMY BEHAVIOUR ' SOUNDS ' 0: PLAYER SHOOT ' 1: ENEMY EXPLODE ' 2: SHIELDS ON ' 3: SHIELDS OFF ' 4: ENEMY LAY MINE ' 5: ENEMY SHOOT ' 6: ENEMY TURN ROUND FOR REAR ATTACK ' 7: BULLET HITS BARRIER '========================================== ' CONSTANTS & UTILITIES GLOBAL DIAGONAL, TRUE, FALSE, NONE DIAGONAL = 1/SQR(2) FALSE = 0 TRUE = NOT FALSE ' FOR EMPTY LISTS OR MISSING ELEMENTS OF ' ARRAYS & LISTS NONE = -1 SUB FAIL(FNAME$, MSG$) ATTR (,0,0,1) SCROLL 0, 0, 0 TEXT 0,1, "ERROR!" TEXT 1,2, FNAME$ TEXT 1,3, MSG$ END END SUB SUB INSORT(A(), N) ' INSERTION SORT I = I WHILE I < N J = I DO ' NO SHORTCUT LOGICAL OPERATORS :-( IF J <= 0 THEN GOTO ENDINSERT IF A(J-1) <= A(J) THEN GOTO ENDINSERT SWAP A(J), A(J-1) J = J - 1 LOOP ENDINSERT: I = I + 1 WEND END SUB '========================================== ' DOUBLY-LINKED LISTS OF NODES STORED IN ' MULTIDIMENSIONAL ARRAYS. ' ' MULTIPLE LISTS CAN USE NODES FROM IN THE ' SAME ARRAY ' ' AN EMPTY LIST IS REPRESENTED BY NONE (-1) ' LIST ELEMENT FIELD INDICES GLOBAL _NXT, _PRV _NXT = 0 _PRV = 1 SUB L_INIT(NODES(), MAX_INDEX) ' INITIALISE AN ARRAY AS A LINKED LIST ' POST: 0 IS THE HEAD OF THE LIST FOR I=0 TO MAX_INDEX NODES(I,_NXT) = I+1 NODES(I,_PRV) = I-1 NEXT I NODES(MAX_INDEX,0) = NONE END SUB SUB L_ADD(NODES(), HEAD, N) ' ADD A NODE TO THE FRONT OF A LIST ' PRE: N IS NOT IN A LIST ' POST: HEAD = N NODES(N,_NXT) = HEAD NODES(N,_PRV) = NONE IF HEAD >= 0 THEN NODES(HEAD,_PRV) = N END IF HEAD = N END SUB SUB L_UNLINK(NODES(), HEAD, N) ' REMOVE A NODE FROM A LINKED LIST ' PRE: HEAD CONTAINS E ' POST: HEAD DOES NOT CONTAIN E ' POST: NEXT & PREV POINTERS OF E ARE NONE IF HEAD < 0 THEN CALL FAIL("L_UNLINK", "LIST EMPTY") END IF NXT = NODES(N,_NXT) PRV = NODES(N,_PRV) IF NXT >= 0 THEN NODES(NXT,_PRV) = PRV END IF IF PRV >= 0 THEN NODES(PRV,_NXT) = NXT ELSE HEAD = NXT END IF NODES(N,_NXT) = NONE NODES(N,_PRV) = NONE END SUB SUB L_MV(NODES(),FROM_LIST, E, TO_LIST) ' MOVE NODE E FROM ONE LIST TO ANOTHER ' PRE: FROM_LIST <> NONE ' PRE; FROM_LIST CONTAINS E ' POST: TO_LIST = E CALL L_UNLINK(NODES(), FROM_LIST, E) CALL L_ADD(NODES(), TO_LIST, E) END SUB '========================================== ' SPRITES ' ' DYNAMICALLY ALLOCATED SPRITES WITH ' FLOATING POINT VIRTUAL COORDINATES THAT ' CAN BE OUTSIDE THE PHYSICAL SCREEN ' WITHOUT WRAPPING ROUND. DIM GLOBAL SPRITES(63,8) ' FIELDS: ' 0: NEXT POINTER ' 1: PREV POINTER ' 2: X COORD ' 3: Y COORD ' 4-8: DEPENDS ON CONTROL SUBROUTINE ' SPRITE FIELD INDICES GLOBAL _X, _Y _X = 2 _Y = 3 ' THE LIST OF UNUSED SPRITES GLOBAL SPRITE_FREE SPRITE_FREE = NONE SUB SPRITE_INIT ' INITIALISE THE SPRITE ALLOCATOR CALL L_INIT(SPRITES(),63) SPRITE_FREE = 0 SPRITE OFF END SUB SUB SPRITE_ALLOC(S_OUT) ' ALLOCATE A SPRITE FROM THE FREE LIST & ' RETURN ITS ID IN S_OUT S_OUT = SPRITE_FREE CALL L_UNLINK(SPRITES(), SPRITE_FREE, S_OUT) END SUB SUB SPRITE_ALLOC_TO(LIST) ' ALLOCATE A SPRITE AND ADD IT TO THE HEAD ' OF LIST S = SPRITE_FREE CALL L_MV(SPRITES(), SPRITE_FREE, S, LIST) END SUB SUB SPRITE_ADD(S, TO_L) ' ADD AN ALLOCATED SPRITE TO LIST TO_L ' ' PRE: S IS NOT ALREADY IN A LIST ' POST: TO_L = S CALL L_ADD(SPRITES(), S, TO_L) END SUB SUB SPRITE_REALLOC(FROM_L, S, TO_L) ' REALLOCATE A SPRITE THAT HAS BEEN ' ALLOCATED, MOVING IT FROM LIST FROM_L ' TO THE HEAD OF LIST TO_L ' ' POST: TO_L = S CALL L_MV(SPRITES(), FROM_L, S, TO_L) END SUB SUB SPRITE_RELEASE(SLIST, S) ' REMOVE SPRITE S FROM SLIST & ADD IT TO ' THE FREE LIST ' ' YOU CANNOT USE S AFTER THIS CALL. IT IS ' SET TO NONE TO CATCH USE-AFTER-RELEASE ' BUGS. ' ' POST: S = NONE SPRITE OFF S CALL SPRITE_REALLOC(SLIST, S, SPRITE_FREE) S = NONE END SUB SUB SPRITE_RELEASE1(S) ' ADD S TO THE FREE LIST ' ' YOU CANNOT USE S AFTER THIS CALL. IT IS ' SET TO NONE TO CATCH USE-AFTER-RELEASE ' BUGS. ' ' POST: S = NONE SPRITE OFF S CALL SPRITE_ADD(S, SPRITE_FREE) S = NONE END SUB SUB SPRITE_SETPOS(S, X, Y) ' MOVE A SPRITE TO AN ABSOLUTE POSITION SPRITES(S,_X) = X SPRITES(S,_Y) = Y IF X < -32 OR Y < -32 OR X>160 OR Y>128 THEN SPRITE OFF S ELSE SPRITE S,X,Y, END IF END SUB SUB SPRITE_MOVE_BY(S, DX, DY) ' MOVE A SPRITE BY A RELATIVE VECTOR NEWX = SPRITES(S,_X) + DX NEWY = SPRITES(S,_Y) + DY CALL SPRITE_SETPOS(S, NEWX, NEWY) END SUB SUB SPRITE_CLAMPXY(S, MINX,MINY,MAXX,MAXY) X = SPRITES(S,_X) Y = SPRITES(S,_Y) NEWX = MIN(MAX(MINX,X),MAXX) NEWY = MIN(MAX(MINY,Y),MAXY) CALL SPRITE_SETPOS(S, NEWX,NEWY) END SUB SUB SPRITE_FRAME(S, C) SPRITE S,,,C END SUB SUB SPRITE_MOVE_TO(S, OX, OY, SPEED, DONE) SX = OX - SPRITES(S,_X) SY = OY - SPRITES(S,_Y) D = SQR(SX^2 + SY^2) IF D <= SPEED THEN CALL SPRITE_SETPOS(S, OX, OY) DONE = TRUE ELSE DX = SPEED * (SX/D) DY = SPEED * (SY/D) CALL SPRITE_MOVE_BY(S, DX, DY) DONE = FALSE END IF END SUB SUB SPRITE_CHASE(E, OTHER, SPEED, DONE) OX = SPRITES(OTHER,_X) OY = SPRITES(OTHER,_Y) CALL SPRITE_MOVE_TO(E, OX, OY, SPEED, DONE) END SUB SUB SPRITE_SETRELPOS(S, RELTO, DX, DY) X = SPRITES(RELTO,_X) + DX Y = SPRITES(RELTO,_Y) + DY CALL SPRITE_SETPOS(S, X, Y) END SUB '========================================== ' BACKGROUND SUB BG_INIT ' INITIALISE THE GAME BACKGROUND BG 0 FOR Y = 0 TO 31 CALL BG_RND_ROW(Y) NEXT Y END SUB SUB BG_SCROLL BG 0 T = GTIMER Y = -T*2 MOD 256 SCROLL 0,0,Y IF Y MOD 8 = 0 THEN NEXT_ROW=Y\8 - 1 CALL BG_RND_ROW(NEXT_ROW) END IF END SUB SUB BG_RND_ROW(Y) FOR X = 0 TO 31 CALL BG_RND_CELL(X,Y) NEXT X END SUB SUB BG_RND_CELL(X,Y) IF RND*ZONE_LEN < (GTIMER - ZONE_LEN) THEN CALL BG_RND_METAL_CELL(X,Y) ELSE CALL BG_RND_GRASS_CELL(X,Y) END IF END SUB SUB BG_RND_GRASS_CELL(X,Y) N = RND IF N > 0.995 THEN C = 7 ELSE IF N > 0.99 THEN C=6 ELSE IF N > 0.85 THEN C = 4+INT(RND*2) ELSE IF N > 0.6 THEN C = 1+INT(RND*3) ELSE C=0 END IF ATTR (0,RND<0.5,0,0,) CELL X,Y,C+32 END SUB SUB BG_RND_METAL_CELL(X,Y) INVERSE = RND < 0.5 N = RND IF N > 0.995 THEN C = 15 ELSE IF N > 0.9 THEN C=10+INT(RND*5) ELSE IF N > 0.6 THEN C = 8+INT(RND*2) ELSE C=0 END IF ATTR (5,INVERSE,INVERSE,0,) CELL X,Y,C+32 END SUB '========================================== ' SKY (CLOUDS ETC) SUB SKY_GENERATE BG 1 BG FILL 0, 0 TO 20, 15 CHAR 0 ATTR (5,0,0,1,0) FLUFFINESS = 0.66 W = 6 + INT(RND*6) X = 1 + INT(RND*(18-W)) CALL SKY_TOP_ROWS(X, 7, W, FLUFFINESS) CALL SKY_BTM_ROWS(X, 8, W, FLUFFINESS) ATTR (,0,0,,) END SUB SUB SKY_TOP_ROWS(X, Y, W, F) CALL SKY_TOP_ROW(X, Y, W) LINEW = W LINEX = 0 LINEY = Y DO STARTX = 1+INT(RND*(F*(LINEW-2))) ENDX = 1+INT(RND*(F*(LINEW-2))) LINEW = LINEW - (STARTX+ENDX) IF LINEW < 2 THEN EXIT SUB LINEX = LINEX + STARTX LINEY = LINEY - 1 CALL SKY_TOP_ROW(X+LINEX, LINEY, LINEW) LOOP END SUB SUB SKY_TOP_ROW(X0, Y, W) ATTR (,0,0,,) FOR X = 1 TO W-2 CELL X0+X, Y, 17 NEXT X CELL X0, Y, 16 CELL X0+W-1, Y, 18 END SUB SUB SKY_BTM_ROWS(X, Y, W, F) CALL SKY_BTM_ROW(X, Y, W) LINEW = W LINEX = 0 LINEY = Y DO STARTX = 1+INT(RND*(F*(LINEW-2))) ENDX = 1+INT(RND*(F*(LINEW-2))) LINEW = LINEW - (STARTX+ENDX) IF LINEW < 2 THEN EXIT SUB LINEX = LINEX + STARTX LINEY = LINEY + 1 CALL SKY_BTM_ROW(X+LINEX, LINEY, LINEW) LOOP END SUB SUB SKY_BTM_ROW(X0, Y, W) ATTR (,0,0,,) FOR X = 1 TO W-2 CELL X0+X, Y, 17 NEXT X ATTR (,1,1,,) CELL X0, Y, 18 CELL X0+W-1, Y, 16 END SUB SUB SKY_SCROLL S = GTIMER*3 MOD 256 SCROLL 1,0,-S IF S >= 128 AND S < 128+3 THEN ' RENDER NEW CLOUDS OFFSCREEN CALL SKY_GENERATE END IF END SUB SUB SKY_INIT ' INITIALISE THE SKY LAYER (CLOUDS) BG 1 BG FILL 0,0 TO 32,32 CHAR 0 CALL SKY_GENERATE END SUB '========================================== ' HEAD UP DISPLAY GLOBAL HUD_LIVES GLOBAL HUD_ALERT SUB HUD_SHOW_LIVES X = (5-PLAYER_LIVES)*-8 CALL SPRITE_SETPOS(HUD_LIVES, X, -24) END SUB SUB HUD_SHOW_ALERT IF HUD_ALERT = NONE THEN CALL ENEMY_ALLOC(HUD_ALERT) END IF SPRITE HUD_ALERT, 72, 8, 110 SPRITE.A HUD_ALERT, (7,0,0,1,1) END SUB SUB HUD_CLEAR_ALERT CALL SPRITE_RELEASE(HUD_ALERT, HUD_ALERT) END SUB SUB HUD_ANIMATE_ALERT IF HUD_ALERT = NONE THEN EXIT SUB SHOWN = (GTIMER MOD 60)\30 IF SHOWN THEN SPRITE HUD_ALERT, 72, 8, ELSE SPRITE OFF HUD_ALERT END IF END SUB '========================================== ' PLAYER MOVEMENT AND SHOOTING ' SPRITES GLOBAL PLAYER GLOBAL PLAYER_SHADOW GLOBAL BULLETS_FREE, BULLETS GLOBAL BULLET_MIN GLOBAL BULLET_MAX GLOBAL PLAYER_IS_STARTING PLAYER_IS_STARTING = 1 GLOBAL PLAYER_IS_UNSHIELDED PLAYER_IS_UNSHIELDED = 2 GLOBAL PLAYER_IS_SHIELDED PLAYER_IS_SHIELDED = 3 GLOBAL PLAYER_IS_EXPLODING PLAYER_IS_EXPLODING = 4 GLOBAL PLAYER_STATE GLOBAL PLAYER_COOLDOWN GLOBAL PLAYER_BOMBS GLOBAL PLAYER_LIVES GLOBAL PLAYER_SHOTS GLOBAL PLAYER_MISSES GLOBAL PLAYER_SPEED SUB PLAYER_INIT ' INITIALISE THE PLAYER SPRITES & HUD ' ' PRE: SPRITE_INIT MUST HAVE BEEN CALLED ' NOTE: MUST BE CALLED BEFORE ENEMY_INIT CALL SPRITE_ALLOC(PLAYER) CALL SPRITE_ALLOC(PLAYER_SHADOW) SPRITE.A PLAYER_SHADOW,(2) CALL PLAYER_SHADOW ' ALLOCATE A CONTIGUOUS RANGE OF SPRITES ' FOR PLAYER BULLETS TO MAKE COLLISION ' DETECTION EASY BULLETS = NONE BULLETS_FREE = NONE BULLET_MIN = SPRITE_FREE BULLET_MAX = SPRITE_FREE + 3 FOR I = BULLET_MIN TO BULLET_MAX CALL SPRITE_ALLOC_TO(BULLETS_FREE) CALL SPRITE_FRAME(BULLETS_FREE,7) SPRITE.A BULLETS_FREE,(3) NEXT I ' A 32X32 PIXEL SPRITE WITH THE BOTTOM 8 ' PIXEL ROWS ON SCREEN, TO DISPLAY THE ' NUMBER OF EXTRA LIVES CALL SPRITE_ALLOC(HUD_LIVES) SPRITE.A HUD_LIVES,(7,0,0,1,3) CALL SPRITE_FRAME(HUD_LIVES,0) HUD_ALERT = NONE PLAYER_SPEED = 1 PLAYER_BOMBS = 1 PLAYER_LIVES = 5 PLAYER_SHOTS = 0 PLAYER_MISSES = 0 CALL PLAYER_START_LIFE END SUB SUB PLAYER_START_LIFE PLAYER_STATE = PLAYER_IS_STARTING PLAYER_COOLDOWN = 1 CALL HUD_SHOW_LIVES SPRITE.A PLAYER,(1, 0, 0, 0, 0) CALL SPRITE_SETPOS(PLAYER, 76, 132) CALL SPRITE_FRAME(PLAYER, 2) SPRITE OFF PLAYER_SHADOW END SUB SUB PLAYER_LOSE_LIFE PLAYER_LIVES = PLAYER_LIVES - 1 CALL PLAYER_START_LIFE END SUB SUB PLAYER_EXPLODE ' OFFSET BY (-4,-4) BECAUSE EXPLOSION ' SPRITE IS 16X16 AND PLAYER SPRITE IS 8X8 CALL SPRITE_MOVE_BY(PLAYER, -4, -4) CALL SPRITE_FRAME(PLAYER, 128) SPRITE.A PLAYER,(3,RND<0.5,RND<0.5,,1) CALL FX_BIGEXPLOSION(PLAYER) PLAYER_STATE = PLAYER_IS_EXPLODING PLAYER_COOLDOWN = 0 SPRITE OFF PLAYER_SHADOW END SUB SUB FX_BIGEXPLOSION(S) FOR I = 1 TO 4 X = SPRITES(S,_X) + RND*4 Y = SPRITES(S,_Y) + RND*4 A = RND*2*PI DX = RND*0.5*COS(A) DY = RND*0.5*SIN(A) CALL ENEMY_SPAWN_EXPLODE(X, Y, DX, DY) NEXT I END SUB SUB PLAYER_SHIELD_OFF IF PLAYER_STATE <> PLAYER_IS_UNSHIELDED THEN PLAYER_STATE = PLAYER_IS_UNSHIELDED PLAYER_COOLDOWN = 0 PLAY 0, 30, 30 SOUND 3 END IF END SUB SUB PLAYER_MOVE S = PLAYER_STATE IF S = PLAYER_IS_STARTING THEN CALL PLAYER_ACT_STARTING ELSE IF S = PLAYER_IS_EXPLODING THEN CALL PLAYER_ACT_EXPLODING ELSE CALL PLAYER_ACT_ACTIVE END IF END SUB SUB PLAYER_ACT_STARTING DX = LEFT(0) - RIGHT(0) CALL SPRITE_MOVE_BY(PLAYER, DX, -0.25) IF SPRITES(PLAYER,_Y) < 100 THEN CALL PLAYER_SHIELD_OFF ELSE IF TIMER MOD 2 THEN SPRITE OFF PLAYER END IF END SUB SUB PLAYER_ACT_ACTIVE P = PLAYER IF PLAYER_COOLDOWN > 0 THEN PLAYER_COOLDOWN = PLAYER_COOLDOWN - 1 IF PLAYER_COOLDOWN = 0 THEN CALL PLAYER_SHIELD_OFF END IF END IF DX = LEFT(0) - RIGHT(0) DY = UP(0) - DOWN(0) IF DX <> 0 OR DY <> 0 THEN S = PLAYER_SPEED / SQR(DX^2 + DY^2) CALL SPRITE_MOVE_BY(P, DX*S, DY*S) END IF CALL SPRITE_CLAMPXY(P, 0, 0, 152, 116) CALL SPRITE_FRAME(P, 2+DX) IF PLAYER_STATE = PLAYER_IS_UNSHIELDED THEN CALL PLAYER_SHADOW ELSE IF TIMER MOD 2 THEN SPRITE OFF P END IF SPRITE OFF PLAYER_SHADOW END IF END SUB SUB PLAYER_ACT_EXPLODING F = PLAYER_COOLDOWN\5 IF F < 8 THEN CALL SPRITE_FRAME(PLAYER, 128+F*2) PLAYER_COOLDOWN = PLAYER_COOLDOWN+1 IF PLAYER_LIVES = 1 AND F > 0 THEN ' LOSING LAST LIFE, SO EPIC SLO-MO WAIT F END IF ELSE CALL PLAYER_LOSE_LIFE END IF END SUB SUB PLAYER_COLLIDE(E) IF PLAYER_STATE = PLAYER_IS_UNSHIELDED THEN ETYPE = SPRITES(E,_TYPE) IF NOT ETYPE AND INVULNERABLE THEN CALL ENEMY_EXPLODE(E, 0, 0.25) END IF CALL PLAYER_EXPLODE END IF END SUB SUB PLAYER_SHADOW ' POSITION A SEMI-TRANSPARENT SHADOW ' UNDER THE PLAYER'S SHIP X = SPRITE.X(PLAYER)+3 Y = SPRITE.Y(PLAYER)+3 C = SPRITE.C(PLAYER)+3 IF TIMER MOD 2 THEN SPRITE PLAYER_SHADOW,X,Y,C ELSE SPRITE OFF PLAYER_SHADOW END IF END SUB SUB PLAYER_SHOOT IF BUTTON(0,0) THEN CALL PLAYER_SHOOT_BULLET END IF IF BUTTON TAP(0,1) THEN CALL PLAYER_SMART_BOMB END IF END SUB SUB PLAYER_SHOOT_BULLET IF PLAYER_COOLDOWN > 0 THEN EXIT SUB X = SPRITES(PLAYER,_X) Y = SPRITES(PLAYER,_Y) - 6 B = BULLETS_FREE CALL SPRITE_REALLOC(BULLETS_FREE,B,BULLETS) CALL SPRITE_SETPOS(B, X, Y) PLAYER_COOLDOWN = 16 PLAYER_SHOTS = PLAYER_SHOTS + 1 PLAY 0, 61, 10 SOUND 0 END SUB SUB BULLETS_MOVE B = BULLETS WHILE B <> NONE NEXT_B = SPRITES(B,_NXT) CALL BULLET_MOVE(B) B = NEXT_B WEND END SUB SUB BULLET_MOVE(B) Y = SPRITES(B,_Y) IF Y < -8 THEN CALL BULLET_RELEASE(B) PLAYER_MISSES = PLAYER_MISSES + 1 ELSE CALL SPRITE_MOVE_BY(B,0,-2) END IF END SUB SUB BULLET_RELEASE(B) SPRITE OFF B CALL SPRITE_REALLOC(BULLETS,B,BULLETS_FREE) END SUB SUB PLAYER_SMART_BOMB IF PLAYER_BOMBS <= 0 THEN EXIT SUB IF ENEMY_LIST = NONE THEN EXIT SUB E = ENEMY_LIST WHILE E <> NONE NXT = SPRITES(E,_NXT) INV = SPRITES(E,_TYPE) AND INVULNERABLE IF NOT INV THEN CALL ENEMY_EXPLODE(E, 0, 0) END IF E = NXT WEND PLAYER_BOMBS = PLAYER_BOMBS - 1 END SUB '========================================== ' ENEMY MOVEMENT AND SHOOTING GLOBAL INVULNERABLE INVULNERABLE = $8000 ' ENEMY ACTIVITY TYPES GLOBAL ACT_DISC ACT_DISC = 1 GLOBAL ACT_CROSS ACT_CROSS = 2 GLOBAL ACT_SEEKER ACT_SEEKER = 3 GLOBAL ACT_WAVY ACT_WAVY = 4 GLOBAL ACT_SHOOTER ACT_SHOOTER = 5 GLOBAL ACT_BARRIER ACT_BARRIER = 6 GLOBAL ACT_LAYER ACT_LAYER = 7 GLOBAL ACT_EXPLODE ACT_EXPLODE = 8 GLOBAL ACT_BULLET ACT_BULLET = 9 GLOBAL ACT_WITHDRAW ACT_WITHDRAW = 10 GLOBAL ACT_REVERSE ACT_REVERSE = 11 GLOBAL ACT_MINE ACT_MINE = 12 GLOBAL ACT_SNAKE_SLITHER ACT_SNAKE_SLITHER = 13 + INVULNERABLE GLOBAL ACT_SNAKE_CHARGE ACT_SNAKE_CHARGE = 14 + INVULNERABLE GLOBAL ACT_SNAKE_TAIL ACT_SNAKE_TAIL = 15 + INVULNERABLE GLOBAL ACT_SNAKE_FINAL ACT_SNAKE_FINAL = 16 + INVULNERABLE ' ENEMY SPRITE PROPERTIES ' 4: ENEMY TYPE ' 5: START TIME (GTIMER WHEN STATE STARTED) ' 6: PROPA (USE DEPENDS ON THE TYPE) ' 7: PROPB (USE DEPENDS ON THE TYPE) ' 8: PROPC (USE DEPENDS ON THE TYPE) GLOBAL _TYPE, _START, _PROPA, _PROPB _TYPE = 4 _START = 5 _PROPA = 6 _PROPB = 7 _PROPC = 8 ' COMMON USES OF PROPS A, B & C GLOBAL _DX, _DY, _FLASH _DX = _PROPA _DY = _PROPB _FLASH = _PROPC GLOBAL ENEMY_LIST GLOBAL ENEMY_MIN SUB ENEMY_INIT ' INITIALISE THE LIST OF ENEMY SPRITES ' ' PRE: SPRITE_INIT MUST HAVE BEEN CALLED ' PRE: PLAYER_INIT MUST HAVE BEEN CALLED ENEMY_LIST = NONE ENEMY_MIN = SPRITE_FREE END SUB SUB ENEMY_ALLOC(E) CALL SPRITE_ALLOC(E) CALL L_ADD(SPRITES(), ENEMY_LIST, E) END SUB SUB ENEMY_RELEASE(E) ' RELEASES AN ENEMY SPRITE BACK TO THE ' FREE SPRITE POOL ' ' POST: E = NONE CALL SPRITE_RELEASE(ENEMY_LIST, E) END SUB SUB ENEMY_START(E, STATE) SPRITES(E,_TYPE) = STATE CALL ENEMY_RESET(E) END SUB SUB ENEMY_RESET(E) SPRITES(E,_START) = GTIMER END SUB SUB ENEMY_ANIMATE_ALL E = ENEMY_LIST WHILE E >= 0 NEXT_E = SPRITES(E,0) CALL ENEMY_ANIMATE(E) E = NEXT_E WEND END SUB SUB ENEMY_MOVEP(E, DXPROP, DYPROP) ' MOVE ENEMY BY DX,DY STORED IN ITS ' SPRITE PROPERTIES IF E = NONE THEN EXIT SUB IF DXPROP <> NONE THEN DX = SPRITES(E,DXPROP) ELSE DX = 0 END IF IF DYPROP <> NONE THEN DY = SPRITES(E,DYPROP) ELSE DY = 0 END IF CALL SPRITE_MOVE_BY(E, DX, DY) END SUB SUB ENEMY_RM_OFFSCREEN(E) IF E = NONE THEN EXIT SUB X = SPRITES(E,_X) Y = SPRITES(E,_Y) IF X<=-8 OR X>160 OR Y<=-8 OR Y>128 THEN CALL ENEMY_RELEASE(E) END IF END SUB SUB ENEMY_DIE_IF_SHOT(E) IF E = NONE THEN EXIT SUB H = SPRITE HIT(E,BULLET_MIN TO BULLET_MAX) IF H THEN CALL BULLET_RELEASE(HIT) CALL ENEMY_EXPLODE(E, 0, -0.25) E = NONE END IF END SUB SUB ENEMY_FLASH_IF_SHOT(E, WAS_HIT) IF E = NONE THEN EXIT SUB H = SPRITE HIT(E,BULLET_MIN TO BULLET_MAX) IF H THEN CALL BULLET_RELEASE(HIT) SPRITE.A E,(7) SPRITES(E,_FLASH) = 6 PLAY 1, 40, 20 SOUND 7 ELSE IF SPRITES(E,_FLASH) > 0 THEN SPRITES(E,_FLASH) = SPRITES(E,_FLASH)-1 IF SPRITES(E,_FLASH) = 0 THEN SPRITE.A E,(2) END IF END IF WAS_HIT = H END SUB SUB ENEMY_SHOOT_AT(FROM, AT, SPEED) ' SHOOT A BULLET TRAVELLING AT THE GIVEN ' SPEED FROM SPRITE `FROM` TO THE CURRENT ' POSITION OF SPRITE `AT` X = SPRITES(FROM,_X) Y = SPRITES(FROM,_Y) XX = SPRITES(AT,_X) - X YY = SPRITES(AT,_Y) - Y D = SQR(XX^2 + YY^2) IF D <> 0 THEN DX = SPEED * XX/D DY = SPEED * YY/D CALL ENEMY_SHOOT(X, Y, DX, DY) END IF END SUB SUB ENEMY_SHOOT_RND(E, SPEED) ' SHOOT IN A RANDOM DIRECTION FROM E X = SPRITES(E,_X) Y = SPRITES(E,_Y) RX = RND*2 - 1 RY = RND*2 - 1 RM = SQR(RX^2 + RY^2) DX = SPEED * RX/RM DY = SPEED * RY/RM CALL ENEMY_SHOOT(X, Y, DX, DY) END SUB SUB ENEMY_SHOOT(X, Y, DX, DY) ' SHOOT A BULLET FROM (X,Y) WITH VELOCITY ' (DX,DY) PER FRAME ' AVOID RUNNING OUT OF MEMORY IF SPRITE_FREE = NONE THEN EXIT SUB E = NONE CALL ENEMY_ALLOC(E) CALL SPRITE_SETPOS(E, X, Y) CALL SPRITE_FRAME(E, 8) SPRITE.A E,(3,0,0,0,0) SPRITES(E,_DX) = DX SPRITES(E,_DY) = DY CALL ENEMY_START(E, ACT_BULLET) PLAY 2, 48, 8 SOUND 5 END SUB SUB ENEMY_ANIMATE(E) TYPE = SPRITES(E,_TYPE) T = GTIMER - SPRITES(E,5) IF TYPE = ACT_DISC THEN CALL ENEMY_ACT_DISC(E, T) ELSE IF TYPE = ACT_WAVY THEN CALL ENEMY_ACT_WAVY(E, T) ELSE IF TYPE = ACT_SHOOTER THEN CALL ENEMY_ACT_SHOOTER(E, T) ELSE IF TYPE = ACT_BARRIER THEN CALL ENEMY_ACT_BARRIER(E, T) ELSE IF TYPE = ACT_BULLET THEN CALL ENEMY_ACT_BULLET(E, T) ELSE IF TYPE = ACT_EXPLODE THEN CALL ENEMY_ACT_EXPLODE(E,T) ELSE IF TYPE = ACT_REVERSE THEN CALL ENEMY_ACT_REVERSE(E, T) ELSE IF TYPE = ACT_SEEKER THEN CALL ENEMY_ACT_SEEKER(E, T) ELSE IF TYPE = ACT_WITHDRAW THEN CALL ENEMY_ACT_WITHDRAW(E, T) ELSE IF TYPE = ACT_CROSS THEN CALL ENEMY_ACT_CROSS(E, T) ELSE IF TYPE = ACT_LAYER THEN CALL ENEMY_ACT_LAYER(E, T) ELSE IF TYPE = ACT_MINE THEN CALL ENEMY_ACT_MINE(E, T) ELSE IF TYPE = ACT_SNAKE_SLITHER THEN CALL SNAKE_ACT_SLITHER(E, T) ELSE IF TYPE = ACT_SNAKE_CHARGE THEN CALL SNAKE_ACT_CHARGE(E, T) ELSE IF TYPE = ACT_SNAKE_TAIL THEN CALL SNAKE_ACT_TAIL(E, T) ELSE IF TYPE = ACT_SNAKE_FINAL THEN CALL SNAKE_ACT_FINAL(E, T) ELSE ERR$ = "UNKNOWN TYPE " + STR$(TYPE) CALL FAIL("ENEMY_ANIMATE", ERR$) END IF IF E <> NONE AND TYPE <> ACT_EXPLODE THEN IF SPRITE HIT(E,PLAYER) THEN CALL PLAYER_COLLIDE(E) END IF END IF END SUB ' DISC FLOATS DOWN THE SCREEN SUB ENEMY_SPAWN_DISC(E) X = SPRITES(PLAYER,_X) + RND*16 - 8 CALL ENEMY_ALLOC(E) CALL SPRITE_SETPOS(E, X, -8) CALL SPRITE_FRAME(E, 64) SPRITE.A E,(2,0,0,0,0) ' FRAME DIRECTION IF RND < 0.5 THEN FD = 1 ELSE FD = -1 END IF SPRITES(E,_PROPB) = FD CALL ENEMY_START(E, ACT_DISC) END SUB SUB ENEMY_ACT_DISC(E, T) CALL SPRITE_MOVE_BY(E, 0, 1.25) CALL ENEMY_RM_OFFSCREEN(E) CALL ENEMY_DIE_IF_SHOT(E) IF E <> NONE THEN DF = SPRITES(E,_PROPB) F = (4 + (T/6 MOD 4)*DF) MOD 4 CALL SPRITE_FRAME(E,64+F) END IF END SUB ' WAVY FOLLOWS A SINUSOIDAL PATH UNTIL IT ' REACHES THE BOTTOM OF THE SCREEN, THEN ' IT SHOOTS UP AND OFF THE TOP SUB ENEMY_SPAWN_WAVY(E) X = 16 + RND*96 CALL ENEMY_ALLOC(E) CALL SPRITE_SETPOS(E, RND*128,-8) CALL SPRITE_FRAME(E, 76) SPRITE.A E,(2,0,0,0,0) SPRITES(E,_PROPA) = X CALL ENEMY_START(E, ACT_WAVY) END SUB SUB ENEMY_ACT_WAVY(E, T) Y = T-8 IF Y < 180 THEN P = 90 XOFFSET = COS(2*PI * (T MOD P)/P) X = SPRITES(E,_PROPA) + XOFFSET*16 F = 76 + (2+INT(XOFFSET*2)) CALL SPRITE_SETPOS(E, X, Y) CALL SPRITE_FRAME(E,F) CALL ENEMY_DIE_IF_SHOT(E) ELSE X = SPRITES(PLAYER,_X) CALL SPRITE_SETPOS(E, X, 180) CALL ENEMY_START(E, ACT_REVERSE) PLAY 3, 48, 20 SOUND 6 END IF END SUB SUB ENEMY_ACT_REVERSE(E, T) CALL SPRITE_MOVE_BY(E, 0, -2) CALL ENEMY_DIE_IF_SHOT(E) IF E <> NONE THEN ' PLAYER IS STARTING PS = PLAYER_STATE = PLAYER_IS_STARTING ' PLAYER IS EXPLODING PE = PLAYER_STATE = PLAYER_IS_EXPLODING ' OFF SCREEN OS = SPRITES(E,_Y) <= -8 IF OS OR PS OR PE THEN CALL ENEMY_RELEASE(E) ELSE CALL SPRITE_FRAME(E,68+(T/6 MOD 4)) END IF END IF END SUB ' SHOOTER FLOATS ACROSS THE SCREEN ' HORIZONTALLY FIRING BULLETS SUB ENEMY_SPAWN_SHOOTER(E) IF RND < 0.5 THEN X = -8 DX = 0.5 ELSE X = 160 DX = -0.5 END IF Y = RND*64 CALL ENEMY_ALLOC(E) CALL SPRITE_SETPOS(E, X, Y) CALL SPRITE_FRAME(E, 72) SPRITE.A E,(2,0,0,0,0) SPRITES(E,_DX) = DX CALL ENEMY_START(E, ACT_SHOOTER) END SUB SUB ENEMY_ACT_SHOOTER(E, T) CALL ENEMY_MOVEP(E, _DX, NONE) CALL ENEMY_RM_OFFSCREEN(E) CALL ENEMY_DIE_IF_SHOT(E) IF E <> NONE THEN F = T/6 MOD 4 CALL SPRITE_FRAME(E,72+F) IF T MOD 60 = 0 THEN U = PLAYER_STATE = PLAYER_IS_UNSHIELDED IF U THEN CALL ENEMY_SHOOT_AT(E, PLAYER, 1) END IF END IF END IF END SUB SUB ENEMY_ACT_BULLET(E, T) CALL ENEMY_MOVEP(E, _DX, _DY) CALL ENEMY_RM_OFFSCREEN(E) END SUB ' BARRIER - BULLETS DO NOT DESTROY IT BUT ' CAN PUSH IT BACK OFF THE TOP OF THE SCREEN SUB ENEMY_SPAWN_BARRIER(E) CALL ENEMY_ALLOC(E) CALL SPRITE_SETPOS(E, RND*128,-8) CALL SPRITE_FRAME(E, 84) SPRITE.A E,(2,0,0,0,0) CALL ENEMY_START(E, ACT_BARRIER) END SUB SUB ENEMY_ACT_BARRIER(E, T) CALL SPRITE_MOVE_BY(E, 0, 0.25) CALL ENEMY_RM_OFFSCREEN(E) IF E <> NONE THEN WAS_HIT = FALSE CALL ENEMY_FLASH_IF_SHOT(E, WAS_HIT) IF WAS_HIT THEN CALL SPRITE_MOVE_BY(E, 0, -8) END IF F = T/6 MOD 4 CALL SPRITE_FRAME(E,84+F) END IF END SUB ' SEEKER - HOMES IN ON THE PLAYER SHIP SUB ENEMY_SPAWN_SEEKER(E) CALL ENEMY_ALLOC(E) Y = RND*64 IF RND < 0.5 THEN X = -8 ELSE X = 160 END IF CALL SPRITE_SETPOS(E, X, Y) CALL SPRITE_FRAME(E, 84) SPRITE.A E,(2,0,0,0,0) CALL ENEMY_START(E, ACT_SEEKER) END SUB SUB ENEMY_ACT_SEEKER(E, T) CALL SPRITE_CHASE(E, PLAYER, 0.5, -1) CALL ENEMY_DIE_IF_SHOT(E) IF E <> NONE THEN CALL SPRITE_FRAME(E, 80 + T/6 MOD 4) END IF IF PLAYER_STATE = PLAYER_IS_EXPLODING THEN CALL ENEMY_START(E, ACT_WITHDRAW) END IF END SUB SUB ENEMY_ACT_WITHDRAW(E, T) PX = SPRITES(PLAYER,_X) DX = SGN(SPRITES(E,_X)-PX) OR 1 CALL SPRITE_MOVE_BY(E, DX*1.5, 0) CALL ENEMY_RM_OFFSCREEN(E) CALL ENEMY_DIE_IF_SHOT(E) IF E <> NONE THEN CALL SPRITE_FRAME(E, 80 + T/6 MOD 4) END IF END SUB ' CROSS - LAUNCHES TOWARDS THE PLAYER ' POSITION, SHOOTING DIAGONALLY. ' FIRES UP TO FOUR DIAGONAL BULLETS ' WHEN SHOT. SUB ENEMY_SPAWN_CROSS(E) CALL ENEMY_ALLOC(E) SPRITE.A E,(2,0,0,0,0) SPEED = 0.5 X = RND*152 Y = -8 SX = SPRITES(PLAYER,_X) - X SY = SPRITES(PLAYER,_Y) - Y D = SQR(SX^2 + SY^2) CALL SPRITE_SETPOS(E, X, Y) SPRITES(E,_DX) = (SPEED * SX)/D SPRITES(E,_DY) = (SPEED * SY)/D CALL ENEMY_START(E, ACT_CROSS) END SUB SUB ENEMY_ACT_CROSS(E, T) DX = SPRITES(E,_DX) DY = SPRITES(E,_DY) CALL SPRITE_MOVE_BY(E, DX, DY) CALL ENEMY_DIE_IF_SHOT(E) CALL ENEMY_RM_OFFSCREEN(E) IF E <> NONE THEN CALL SPRITE_FRAME(E, 88+((T/6)MOD 4)) P = 40 IF T MOD P = 0 THEN DIR = INT((T MOD (P*4))/P) IF DIR = 3 THEN BDX = -1 BDY = -1 ELSE IF DIR = 2 THEN BDX = 1 BDY = -1 ELSE IF DIR = 1 THEN BDX = 1 BDY = 1 ELSE IF DIR = 0 THEN BDX = -1 BDY = 1 END IF X = SPRITES(E,_X) + BDX*4 Y = SPRITES(E,_Y) + BDY*4 S = DIAGONAL CALL ENEMY_SHOOT(X,Y,DX+BDX*S,DY+BDY*S) END IF END IF END SUB ' LAYER - SPAWNS A LINE OF STATIC MINES SUB ENEMY_SPAWN_LAYER(E) CALL ENEMY_ALLOC(E) SPRITE.A E, (2,0,0,0,0) IF RND < 0.5 THEN X0 = -8 X1 = 160 ELSE X0 = 160 X1 = -8 END IF Y0 = RND*112 Y1 = RND*96 SPEED = 0.25 PATHDX = X1-X0 PATHDY = Y1-Y0 S = SQR(PATHDX^2 + PATHDY^2) CALL SPRITE_SETPOS(E, X0, Y0) SPRITES(E,_DX) = SPEED * PATHDX/S SPRITES(E,_DY) = SPEED * PATHDY/S CALL ENEMY_START(E, ACT_LAYER) END SUB SUB ENEMY_ACT_LAYER(E, T) CALL ENEMY_MOVEP(E, _DX, _DY) CALL ENEMY_RM_OFFSCREEN(E) CALL ENEMY_DIE_IF_SHOT(E) IF E <> NONE THEN CALL SPRITE_FRAME(E, 92+((T/60) MOD 2)) IF T MOD 150 = 0 THEN CALL ENEMY_LAY_MINE(E) END IF END IF END SUB SUB ENEMY_LAY_MINE(E) IF SPRITE_FREE = NONE THEN EXIT SUB X = SPRITES(E,_X) Y = SPRITES(E,_Y) IF X > 8 AND X < 152 THEN CALL ENEMY_SPAWN_MINE(X, Y) PLAY 2, 56, 20 SOUND 4 END IF END SUB SUB ENEMY_SPAWN_MINE(X, Y) E = NONE CALL ENEMY_ALLOC(E) CALL SPRITE_SETPOS(E, X, Y) SPRITE.A E, (2,0,0,0,0) CALL ENEMY_START(E, ACT_MINE) END SUB SUB ENEMY_ACT_MINE(E, T) CALL SPRITE_FRAME(E, 94+((T/60) MOD 2)) CALL ENEMY_DIE_IF_SHOT(E) IF E <> NONE AND T >= 300 THEN FOR I = 1 TO 3 CALL ENEMY_SHOOT_RND(E, 1) NEXT I CALL ENEMY_EXPLODE(E, 0, 0) END IF END SUB ' EXPLOSIONS SUB ENEMY_EXPLODE(E, DX, DY) ' OFFSET BY (-4,-4) BECAUSE EXPLOSION ' SPRITE IS 16X16 AND ENEMY SPRITE IS 8X8 CALL SPRITE_MOVE_BY(E, -4, -4) CALL SPRITE_FRAME(E, 128) SPRITE.A E,(3, RND<0.5, RND<0.5, 1, 1) SPRITES(E,_DX) = DX SPRITES(E,_DY) = DY CALL ENEMY_START(E, ACT_EXPLODE) PLAY 1, 12, 10 SOUND 1 END SUB SUB ENEMY_SPAWN_EXPLODE(X, Y, DX, DY) IF SPRITE_FREE = NONE THEN EXIT SUB E = NONE CALL ENEMY_ALLOC(E) CALL SPRITE_SETPOS(E, X, Y) CALL ENEMY_EXPLODE(E, DX, DY) END SUB SUB ENEMY_ACT_EXPLODE(E, T) F = T\5 IF F < 8 THEN CALL ENEMY_MOVEP(E, _DX, _DY) CALL SPRITE_FRAME(E, 128+F*2) ELSE CALL ENEMY_RELEASE(E) END IF END SUB '========================================== ' BOSS BATTLE ENEMY BEHAVIOUR ' SNAKE PROPERTIES (ALSO USE _FLASH) GLOBAL _HEAD, _ACTIVE, _CHARGEX, _CHARGEY _HEAD = _PROPA _ACTIVE = _PROPB _CHARGEX = _PROPA _CHARGEY = _PROPB SUB SPAWN_BOSS X = 76 Y = -8 TAIL_SIZE=12 DIM SPR(TAIL_SIZE) FOR I = 0 TO TAIL_SIZE CALL SPRITE_ALLOC(SPR(I)) SPRITE.A SPR(I), (2,0,0,0,0) CALL SPRITE_SETPOS(SPR(I), X, Y) NEXT I ' SORT INTO DISPLAY ORDER CALL INSORT(SPR(), TAIL_SIZE+1) ' HEAD IS HIGHEST IN DISPLAY ORDER HEAD = SPR(0) CALL SNAKE_START_SLITHER(HEAD) ' TAIL FOR I = 1 TO TAIL_SIZE CALL ENEMY_START(SPR(I), ACT_SNAKE_TAIL) SPRITES(SPR(I),_HEAD) = HEAD SPRITES(SPR(I),_ACTIVE) = 0 NEXT I ' ADD TO ENEMY LIST IN REVERSE ORDER ' SO THAT THE SPRITES THAT ARE HIGHER IN ' THE DISPLAY ORDER ARE CHECKED FOR HITS ' BEFORE THOSE LOWER, AND SO SHIELD ' SPRITES THAT ARE NOT VISIBLE TO THE ' PLAYER FOR I = TAIL_SIZE TO 0 STEP -1 CALL L_ADD(SPRITES(), ENEMY_LIST, SPR(I)) NEXT I END SUB SUB SNAKE_ACTIVATE(E, ACTIVE_TIME) SPRITES(E,_ACTIVE) = ACTIVE_TIME END SUB SUB SNAKE_COUNTDOWN(E, NXT, ACTIVE_TIME) IF SPRITES(E,_ACTIVE) > 0 THEN SPRITES(E,_ACTIVE) = SPRITES(E,_ACTIVE)-1 IF SPRITES(E,_ACTIVE) = 0 THEN CALL SNAKE_ACTIVATE(NXT, ACTIVE_TIME) END IF END IF END SUB SUB SNAKE_START_SLITHER(E) CALL ENEMY_START(E, ACT_SNAKE_SLITHER) CALL SNAKE_ACTIVATE(E, 20) END SUB SUB SNAKE_ACT_SLITHER(E, T) N = SPRITES(E,_NXT) IF SPRITES(N,_TYPE) = ACT_SNAKE_TAIL THEN CALL SNAKE_MOVE_SLITHER(E, T) CALL SNAKE_COUNTDOWN(E, N, 10) IF SPRITES(E,_ACTIVE) THEN CALL SPRITE_FRAME(E, 97) ELSE CALL SPRITE_FRAME(E, 96) END IF IS_HIT = FALSE CALL ENEMY_FLASH_IF_SHOT(E, IS_HIT) IF IS_HIT THEN CALL SPRITE_MOVE_BY(E, 0, -4) END IF ELSE CALL SNAKE_START_FINAL(E) END IF END SUB SUB SNAKE_MOVE_SLITHER(E, T) TX = 152/2 + SIN(T/420 * 2*PI)*152/2 TY = 32 + COS(T/90 * 2*PI)*32 CALL SPRITE_MOVE_TO(E, TX, TY, 1.5, -1) END SUB SUB SNAKE_START_CHARGE(E) CALL ENEMY_START(E, ACT_SNAKE_CHARGE) SPRITES(E,_CHARGEX) = SPRITES(PLAYER,_X) SPRITES(E,_CHARGEY) = SPRITES(PLAYER,_Y) END SUB SUB SNAKE_ACT_CHARGE(E, T) CALL SPRITE_FRAME(E, 96 + (T\10) MOD 2) PX = SPRITES(E,_CHARGEX) PY = SPRITES(E,_CHARGEY) FIN = FALSE CALL SPRITE_MOVE_TO(E, PX, PY, 2.5, FIN) IS_HIT = FALSE CALL ENEMY_FLASH_IF_SHOT(E, IS_HIT) IF IS_HIT THEN CALL SPRITE_MOVE_BY(E, 0, -4) END IF PSAFE = PLAYER_STATE<>PLAYER_IS_UNSHIELDED IF FIN OR PSAFE THEN CALL SNAKE_START_SLITHER(E) END IF END SUB SUB SNAKE_START_FINAL(E) HEALTH = 6 HUD = NONE CALL SPRITE_ALLOC(HUD) SPRITE.A HUD, (2,0,0,1,1) CALL SPRITE_FRAME(HUD, 160+2*HEALTH) SPRITES(E,_PROPA) = HEALTH SPRITES(E,_PROPB) = HUD CALL ENEMY_START(E, ACT_SNAKE_FINAL) END SUB SUB SNAKE_ACT_FINAL(E, T) HUD = SPRITES(E,_PROPB) PS = PLAYER_STATE<>PLAYER_IS_UNSHIELDED SHOOT = (T/60) MOD 3 = 0 IF SHOOT AND NOT PS THEN CALL SPRITE_FRAME(E, 97) CALL SPRITE_CHASE(E, PLAYER, 0.75, -1) IF T MOD 2 = 0 THEN ANGLE = ((T MOD 60) / 11)*PI SPEED = 1.5 DX = SPEED*COS(ANGLE) DY = SPEED*SIN(ANGLE) X = SPRITES(E,_X) Y = SPRITES(E,_Y) CALL ENEMY_SHOOT(X, Y, DX, DY) END IF ELSE CALL SNAKE_MOVE_SLITHER(E, T) CALL SPRITE_FRAME(E, 96 + (T\10) MOD 2) END IF IS_HIT = FALSE CALL ENEMY_FLASH_IF_SHOT(E, IS_HIT) IF IS_HIT THEN CALL SPRITE_MOVE_BY(E, 0, -4) EX = SPRITES(E,_X)-4 EY = SPRITES(E,_Y)-4 CALL ENEMY_SPAWN_EXPLODE(EX,EY,RND,RND) SPRITES(E,_PROPA) = SPRITES(E,_PROPA) - 1 END IF HEALTH = SPRITES(E,_PROPA) IF HEALTH = 0 THEN CALL SPRITE_RELEASE1(HUD) CALL ENEMY_EXPLODE(E, 0, 0) CALL FX_BIGEXPLOSION(E) ELSE CALL SPRITE_FRAME(HUD, 160+2*HEALTH) CALL SPRITE_SETRELPOS(HUD, E, -4,-20) END IF END SUB SUB SNAKE_ACT_TAIL(E, T) HEAD = SPRITES(E,_HEAD) MODE = SPRITES(HEAD,_TYPE) N = SPRITES(E,_NXT) ' NO SHORTCUT BOOLEAN OPERATORS! IF N = NONE THEN ISTIP = TRUE ELSE NTYPE = SPRITES(N,_TYPE) ISTIP = NTYPE <> ACT_SNAKE_TAIL END IF P = SPRITES(E,_PRV) PX = SPRITES(P,_X) PY = SPRITES(P,_Y) X = SPRITES(E,_X) Y = SPRITES(E,_Y) DX = PX - X DY = PY - Y FLEX = 6 CALL SPRITE_MOVE_BY(E, DX/FLEX, DY/FLEX) ' ONLY SHOOT IN SLITHER MODE IF MODE <> ACT_SNAKE_SLITHER THEN SPRITES(E,_ACTIVE) = 0 END IF IF SPRITES(E,_ACTIVE) THEN CALL SPRITE_FRAME(E, 99) ELSE CALL SPRITE_FRAME(E, 98) END IF IF ISTIP THEN ISLAST = SPRITES(E,_PRV) = HEAD CALL ENEMY_DIE_IF_SHOT(E) IF E = NONE THEN IF ISLAST THEN CALL SNAKE_START_FINAL(HEAD) ELSE CALL SNAKE_START_CHARGE(HEAD) END IF ELSE SHOOT = SPRITES(E,_ACTIVE) = 1 B = PLAYER_STATE=PLAYER_IS_UNSHIELDED IF B AND SHOOT THEN CALL ENEMY_SHOOT_AT(E, PLAYER, 1) END IF CALL SNAKE_COUNTDOWN(E, HEAD, 20) END IF ELSE CALL ENEMY_FLASH_IF_SHOT(E, 0) CALL SNAKE_COUNTDOWN(E, N, 10) END IF END SUB '========================================== ' TITLE & INFO SCREENS DIM GLOBAL TITLE_LINE_OFFSETS(16) SUB TITLE_INIT SPRITE OFF CLS FOR I = 0 TO 16 TITLE_LINE_OFFSETS(I) = 0 NEXT I ON RASTER CALL TITLE_CENTER_LINES END SUB SUB TITLE_CENTER_LINES L = RASTER/8 SCROLL 0, TITLE_LINE_OFFSETS(L), 0 END SUB SUB TITLE_END ON RASTER OFF END SUB SUB CTEXT(Y, L$) TEXT 0, Y, L$ TITLE_LINE_OFFSETS(Y) = -(20-LEN(L$))*4 END SUB SUB TITLE_SCREEN CALL TITLE_INIT SOUND SOURCE ROM(14) MUSIC BG 1 ATTR (6,0,0,0) BG FILL 0,0 TO 32,32 CHAR 40 BG 0 ATTR(3,0,0,0) CALL CTEXT(2, "INFILTRATOR 2000") TEXT 0, 13, " PRESS: A TO PLAY" TEXT 0, 14, " B NEXT PAGE" ATTR (0,0,0) RESTORE INSTRUCTIONS D = 1 T = 1 REPEAT IF T MOD D = 0 OR BUTTON TAP(0,1) THEN T = 1 READ D IF D = -1 THEN RESTORE INSTRUCTIONS READ D END IF BG FILL 0,5 TO 28,10 CHAR 0 FOR I = 0 TO 5 READ L$ CALL CTEXT(5+I, L$) NEXT I ELSE T = T + 1 END IF SCROLL 1, 0, -T/4 WAIT VBL UNTIL BUTTON TAP(0,0) CALL TITLE_END STOP END SUB SUB STAT(Y, NAME$, V, VMAX) IF VMAX = 0 THEN PCT = 0 ELSE PCT = MIN(100, (100*V)\VMAX) END IF CALL CTEXT(Y, NAME$+": "+STR$(PCT)+"%") END SUB SUB GAME_OVER CALL TITLE_INIT BG 1 ATTR (6,0,0,0,0) BG FILL 0,0 TO 32,32 CHAR 40 HITS = PLAYER_SHOTS - PLAYER_MISSES BG 0 ATTR (3) CALL CTEXT(2, "GAME OVER") ATTR (0) CALL STAT(6, "DISTANCE", GTIMER, GAME_LEN) ZONE = GTIMER\ZONE_LEN IF ZONE = 0 THEN ZONE$ = "(WILDERNESS ZONE)" ELSE IF ZONE = 1 THEN ZONE$ = "(CORRUPTED ZONE)" ELSE IF ZONE = 2 THEN ZONE$ = "(ASSIMILATED ZONE)" ELSE ZONE$ = "(ROBOT LEADER)" END IF CALL CTEXT(7, ZONE$) CALL STAT(9,"ACCURACY",HITS,PLAYER_SHOTS) ATTR (3) CALL CTEXT(13, "PRESS ANY BUTTON") CALL CTEXT(14, "TO PLAY AGAIN") REPEAT SCROLL 1, 0, -TIMER/4 WAIT VBL UNTIL BUTTON TAP(0) CALL TITLE_END END SUB SUB VICTORY CALL TITLE_INIT BG 1 ATTR (6,0,0,0,0) BG FILL 0,0 TO 32,32 CHAR 40 HITS = PLAYER_SHOTS - PLAYER_MISSES BG 0 ATTR (3) CALL CTEXT(2, "VICTORY!") ATTR (0) CALL CTEXT(5, "YOU HAVE DEFEATED") CALL CTEXT(6, "THE ROBOT HORDES") CALL CTEXT(7, "THE WORLD IS SAFE") CALL STAT(10,"ACCURACY",HITS,PLAYER_SHOTS) ATTR (3) CALL CTEXT(13, "PRESS ANY BUTTON") CALL CTEXT(14, "TO PLAY AGAIN") REPEAT SCROLL 1, 0, -TIMER/4 WAIT VBL UNTIL BUTTON TAP(0) CALL TITLE_END END SUB '========================================== ' SPAWNING INCREASINGLY DIFFICULT ENEMIES GLOBAL ENEMY_SPAWN_TYPES ENEMY_SPAWN_TYPES=7 SUB ENEMY_SPAWN T = GTIMER IF T MOD 48 = 0 THEN ' ENSURE REPEATABLE SEQUENCE OF ENEMIES ' EVEN IF OTHER RANDOM ACTIVITY HAS ' OCCURRED THIS FRAME RANDOMIZE T N = MIN(T\768, ENEMY_SPAWN_TYPES) TYPE = INT(RND*N) CALL ENEMY_SPAWN_TYPE(TYPE) END IF END SUB SUB ENEMY_SPAWN_TYPE(TYPE) E = NONE IF SPRITE_FREE = NONE THEN EXIT SUB ELSE IF TYPE = 0 THEN CALL ENEMY_SPAWN_DISC(E) ELSE IF TYPE = 1 THEN CALL ENEMY_SPAWN_CROSS(E) ELSE IF TYPE = 2 THEN CALL ENEMY_SPAWN_SEEKER(E) ELSE IF TYPE = 3 THEN CALL ENEMY_SPAWN_WAVY(E) ELSE IF TYPE = 4 THEN CALL ENEMY_SPAWN_SHOOTER(E) ELSE IF TYPE = 5 THEN CALL ENEMY_SPAWN_BARRIER(E) ELSE IF TYPE = 6 THEN CALL ENEMY_SPAWN_LAYER(E) ELSE ERR$ = "UNKNOWN TYPE" CALL FAIL("ENEMY_SPAWN_TYPE", ERR$) END IF END SUB '========================================== ' GAME GLOBAL ZONE_LEN, GAME_LEN ZONE_LEN = 256*16 GAME_LEN = ZONE_LEN*3 ' TIMER COUNTING FROM THE START OF THE GAME GLOBAL GTIMER GLOBAL GAME_OVER GLOBAL GAME_PHASE ' 0 = PROCEDURAL ENEMY WAVES ' 1 = ALERT BEFORE BOSS ' 2 = BOSS SUB GAME_FRAME RANDOMIZE GTIMER CALL BG_SCROLL CALL SKY_SCROLL CALL PLAYER_MOVE CALL BULLETS_MOVE CALL PLAYER_SHOOT IF GAME_PHASE = 0 THEN IF GTIMER >= GAME_LEN THEN GAME_PHASE = 1 CALL HUD_SHOW_ALERT ELSE IF GTIMER > 256 THEN CALL ENEMY_SPAWN END IF ELSE IF GAME_PHASE = 1 THEN BOSSTIME = GTIMER >= GAME_LEN + 512 IF BOSSTIME AND ENEMY_LIST = NONE THEN CALL HUD_CLEAR_ALERT GAME_PHASE = 2 CALL SPAWN_BOSS ELSE CALL HUD_ANIMATE_ALERT END IF END IF CALL ENEMY_ANIMATE_ALL GTIMER = GTIMER + 1 END SUB SUB GAME_INIT SOUND SOURCE ROM(15) GAME_OVER = FALSE GAME_PHASE = 0 IF UP(0) THEN ' SUPER SECRET CHEAT MODE! GTIMER = GAME_LEN - 256 ELSE GTIMER = 0 END IF CALL BG_INIT CALL SKY_INIT CALL SPRITE_INIT CALL PLAYER_INIT CALL ENEMY_INIT END SUB SUB GAME CALL GAME_INIT REPEAT CALL GAME_FRAME IF GAME_PHASE=2 AND ENEMY_LIST=NONE THEN GAME_OVER = TRUE ELSE IF PLAYER_LIVES <= 0 THEN GAME_OVER = TRUE ELSE GAME_OVER = FALSE END IF WAIT VBL UNTIL GAME_OVER END SUB '========================================== ' TESTING SUB ENEMY_TESTBED GTIMER = 0 CALL BG_INIT CALL SPRITE_INIT CALL PLAYER_INIT CALL ENEMY_INIT ' ENSURE BUTTON 1 DOES NOT ACTIVATE SHIELDS PLAYER_BOMBS = 0 DO IF BUTTON TAP(0,1) THEN E = NONE 'CALL SPAWN_BOSS CALL ENEMY_ALLOC(E) CALL SPRITE_SETPOS(E, 76, -8) SPRITE.A E,(2,0,0,0,0) CALL SNAKE_START_FINAL(E) END IF RANDOMIZE GTIMER CALL BG_SCROLL CALL PLAYER_MOVE CALL BULLETS_MOVE CALL PLAYER_SHOOT CALL ENEMY_ANIMATE_ALL GTIMER = GTIMER + 1 WAIT VBL LOOP END SUB '========================================== ' WHAT ARE WE RUNNING? SUB MAIN DO CALL TITLE_SCREEN CALL GAME IF PLAYER_LIVES > 0 THEN CALL VICTORY ELSE CALL GAME_OVER END IF LOOP END SUB ENTRY_POINT: 'CALL ENEMY_TESTBED CALL MAIN INSTRUCTIONS: DATA 240 DATA "ROBOTS ARE TAKING" DATA "OVER THE WORLD" DATA "" DATA "ONLY YOU" DATA "CAN SAVE US" DATA "" DATA 240 DATA "INFILTRATE THE" DATA "ROBOT DEFENSES" DATA "" DATA "DESTROY THE" DATA "ROBOT LEADER" DATA "" DATA 240 DATA "YOUR CRAFT IS ARMED" DATA "WITH TWIN BLASTERS" DATA "AND A SMART BOMB" DATA "THAT DESTROYS ALL" DATA "ENEMIES IN SIGHT" DATA "" DATA 240 DATA "YOU WILL HAVE TO" DATA "CROSS THREE ZONES" DATA "" DATA "WILDERNESS ZONE" DATA "CORRUPTED ZONE" DATA "ASSIMILATED ZONE" DATA 300 DATA "CONTROLS" DATA "" DATA "PAD = MANOEVER " DATA "A = SHOOT " DATA "B = SMART BOMB " DATA "" DATA 240 DATA "TIP" DATA "" DATA "" DATA "KEEP MOVING" DATA "" DATA "" DATA 240 DATA "TIP" DATA "" DATA "DON'T LOITER" DATA "NEAR THE EDGE" DATA "OF THE SCREEN" DATA "" DATA 240 DATA "TIP" DATA "" DATA "ROBOTS THAT GET" DATA "PAST YOU MIGHT" DATA "ATTACK FROM BEHIND" DATA "" DATA 240 DATA "TIP" DATA "" DATA "KEEP SHOOTING!" DATA "" DATA "" DATA "" DATA 240 DATA "" DATA "" DATA "GOOD LUCK, PILOT" DATA "" DATA "" DATA "" DATA 120 DATA "" DATA "" DATA "YOU'RE GONNA" DATA "NEED IT" DATA "" DATA "" DATA 300 DATA "" DATA "DESIGN, PROGRAMMING" DATA "NAT PRYCE" DATA "" DATA "MUSIC" DATA "DESBYC" DATA 300 DATA "" DATA "LICENSE" DATA "" DATA "CC BY-NC-SA 4.0" DATA "" DATA "SHARE AND ENJOY" DATA -1 #1:MAIN PALETTES 002D0804003F1B3400311601003C3824 002A1500003F2A15002B1601003A3020 #2:MAIN CHARACTERS 00000000000000000000000000000000 18180C6C4E7E7E5A00183C5C7E7E7E5A 243C6676E7FFFF99003C7E6EFFFFFF99 18183078727E7E5A00183C747E7E7E5A 18183C7C7E7E7E5A18183C7C7E7E7E5A 243C7E7EFFFFFF99243C7E7EFFFFFF99 18183C7C7E7E7E5A18183C7C7E7E7E5A 5A245A00240000007E5A7E2424240024 0018245A5A24180000183C66663C1800 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 010A152A552A55AA0420400000800000 55AA55AA55AA55AA0000000000000000 00A050A854AA54AA4008040200000100 0420400000800000052A552A55AA55AA 000000000000000055AA55AA55AA55AA 400804020000010040A854AA54AA55AA 0343434343430603FFBFBFBFBFBF7EFF 432643434343433FBF5EBFBFBFBFBFFF 00FC00000002FFFDFD03FFFFFFFFFFFD 00BE40000040FFBFBF41BFFFFFFFFFBF FFAA00FFFF0055FFFFFFFF0000FFFFFF FF0055FFFFAA00FFFFFFAA000055FFFF D99BD99BD99BD99BE7E7E7E7E7E7E7E7 9DB99DB99DB99DB9E3C7E3C7E3C7E3C7 9B9818FFFF1819D9E7E7E70000E7E7E7 B9187DFFFFBE389DC7E782000041C7E3 0000000000000000FFFFFFFFFFFFFFFF 21210A4A40121200DEFFF5BFFFEDFFFF 2024444809212404DFFBBFF7FEDFFBFF 0404105042829010FBFFEFBFFD7FEFFF 2020240545494808DFFFFBFEBFF7FFFF 084C4D55B1B4A404F7BBFEEF5FFBFFFF 0404104A6C3D9E80FBFFEFB5D3E77FFF 20268CD9DB7F3600DFF977AEACC9FFFF 888822228888222277FFDDFF77FFDDFF CC003300CC003300BBFFEEFFBBFFEEFF FF818181818181FF017F7F7F7F7F7FFF 007E7E66667E7E00FF81839F9F9FBFFF 0018187E76181800FFEFEF89FFEFEFFF 0018347A5E2C1800FFEFCF8FFFFFFFFF 18347AF5AF5E2C18EFCF8F0FFFFFFFFF 3C42BDA5A5BD423CC3BD475F5F7FBFFF 0008302041221408003E4F5F7F3E1C08 0008302041221408003E4F5F7F3E1C08 0008302041221408003E4F5F7F3E1C08 0008302041221408003E4F5F7F3E1C08 3844AA92AA443800387CD6EED67C3800 3844AA92AA443800387CD6EED67C3800 3844AA92AA443800387CD6EED67C3800 3844AA92AA443800387CD6EED67C3800 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 001C6D6347373E3C3C62939DB9C9463C 002C7747236F3E3C3C5289BBDD914A3C 00347707636F3E3C3C4A89F99F91523C 00381B67675B1E3C3C46E59999A7623C 001C1C04040404081824243C3C3C3C18 00041C1C04040408183C24243C3C3C18 000404041C1C0408183C3C3C24243C18 00040404041C1C08183C3C3C3C242418 04022141412102043C7EE7C3C3E77E3C 04022159592102043C7EE7C3C3E77E3C 0402397D7D3902043C7EE7C3C3E77E3C 04023965653902043C7EE7C3C3E77E3C 00000060617E000000007E9F9F7E0000 00000030317E000000007ECFCF7E0000 0000000C0D7E000000007EF3F37E0000 00000006077E000000007EF9F97E0000 000000160C18100010387CEE7C381000 0000102E1C18100010386CD66C381000 0000382E3C181000103844D644381000 003844464C381000100038BA38001000 007E7E7E7E7E7E00007E7E7E7E7E7E00 00FF00FF7E7E000000FFFFFF7E7E0000 000000FFFF000000000000FFFF000000 0000000000FFFF0000007E7EFFFFFF00 000102181802010242E77E24247EE742 00013C24243C010242E742181842E742 002466000066240042C3183C3C18C342 42C300040418C34200247E3C3C7E2400 0000031B13073E3C3C7EFFFFFFFF7E3C 0000031B1B073E3C3C7EFFE7E7FF7E3C 00000004041800000000183C3C180000 0000001C1C1800000000182424180000 00180343470F3E3C3C7EFFFFFFFF7E3C 0018014343011A3C3C66FFBDBDFF663C 00000003070E181818183CFFFF3E1818 0000001B1F0E181818183CE7E73E1818 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 01030307070F0F1F01030307070E0E1E 80C0C0E0E0F0F0F880C0C0E0E0707078 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 1F3F3F7F7FFFFF7F1E3E3E7F7EFEFF7F F8FCFCFEFEFFFFFE787C7CFE7E7FFFFE 000000000106050B000000000107060C 000000008060A0D00000000080E06030 0000030E181337270000030F1F1C3838 0000C07018C8ECE40000C0F0F8381C1C 030F1C306367CFCF030F1F3F7C78F0F0 C0F0380CC6E6F3F3C0F0F8FC3E1E0F0F 030F1C306367CFCF030F1F3F7C78F0F0 C0F0380CC6E6F3F3C0F0F8FC3E1E0F0F 030F18306061C3C6030F1F3F7F7EFCF9 C0F0380C068603F3C0F0F8FCFE7EFFFF 030F1F3C7070E1E3030F1F3F7F7FFFFF C0F0F83C0EFEFF0FC0F0F8FCFEFEFF0F 030F1F2C7177ECE8030F1F2F7F7FFCF8 C0D0F8BCFE0C0201C0D0F8FCFE0C0201 030D1A28604080C0030D1A28604080C0 80D038040200000080D0380402000000 0B050601000000000C06070100000000 D0A06080000000003060E08000000000 273713180E03000038381C1F0F030000 E4ECC81870C000001C1C38F8F0C00000 CFCF6763301C0F03F0F0787C3F1F0F03 F3D386C60C38F0C00F2F7E3EFCF8F0C0 CFCF6662301C0F03F0F0797D3F1F0F03 836396966C38F0C07FFF9E9EFCF8F0C0 C5C36262331D0F03FBFF7E7E3F1F0F03 9B0F06060C98F0C09F0F06060C98F0C0 E6E464743C1E0F03FEFC7C7C3C1E0F03 07010202000000400701020200000040 F8B0706030080C01F8B0706030080C01 01010000000000000101000000000000 80004040000000008000404000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000003F3F00000000007F7F7F7F 0000000000FCFC0000000000FEFEFEFE 00000000003F3F00000000007F4F4F7F 0000000000FCFC0000000000FEFEFEFE 00000000003F3F00000000007F43437F 0000000000FCFC0000000000FEFEFEFE 00000000003F3F00000000007F40407F 0000000000FCFC0000000000FEFEFEFE 00000000003F3F00000000007F40407F 0000000000FCFC0000000000FE3E3EFE 00000000003F3F00000000007F40407F 0000000000FCFC0000000000FE0E0EFE 00000000003F3F00000000007F40407F 0000000000FCFC0000000000FE0202FE 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 3C3C3C3C3C3C3C3C3C242424243C243C FEFEFEFE7E000000FE9292DA7E000000 7EFFFFFFFFFFFF7E7EDB81DBDB81DB7E 1C7F7F7F7F7F7F1C1C7741477141771C F7FFFFFE7FFFFFEFF79D9BF66FD9B9EF FCFCFCFFFFFFFEFEFC849487919B82FE 3C3C3C3C380000003C24242C38000000 1E3E7E7C7C7E3E1E1E32664C4C66321E 787C7E3E3E7E7C78784C663232664C78 007E7EFFFFFF7E7E007E5AE781E75A7E 003C3CFFFFFF3C3C003C24E781E7243C 0000003C3C3C3C380000003C24242C38 000000FFFFFF0000000000FF81FF0000 0000003C3C3C3C000000003C24243C00 0F1F3F7EFCF8F0E00F193366CC98B0E0 FFFFFFFFFFFFFFFFFF819991899981FF 7C7C7C3C3C7E7E7E7C4464242466427E FFFFFFFFFFFFFFFFFF81F9819F9F81FF FFFFFF3F3FFFFFFFFF81F92139F981FF FEFEFFFFFF0E0E0EFE929381F302020E FFFFFFFFFFFFFFFFFF819F81F9F981FF FFFFFFFFFFFFFFFFFF819F81999981FF FFFFFF3F3F3C3C3CFF81F9212724243C FFFFFFFFFFFFFFFFFF819981999981FF FFFFFFFFFF0F0F0FFF819981F909090F 3C3C3C3C3C3C3C003C24243C24243C00 3C3C3C3C3C3C3C383C24243C24242C38 001E3E7E7C7E3E1E001E32664C66321E 0000FFFFFFFFFF000000FF81FF81FF00 00787C7E3E7E7C7800784C6632664C78 FFFFFFFF3E3C3C3CFF8199F3263C243C FFFFFFFFFFFFFFFFFF819999919F81FF FFFFFFFFFFFFFFFFFF819999819999FF FFFFFFFFFFFFFFFFFF819983999981FF FFFFFFF0F0FFFFFFFF819F90909F81FF FFFFFF7F7FFFFFFFFF81C94949C981FF FFFFFFFCFCFFFFFFFF819F849C9F81FF FFFFFFFCFCF0F0F0FF819F849C9090F0 FFFFFFFFFFFFFFFFFF819F91999981FF FFFFFFFFFFFFFFFFFF999981999999FF 7E7E7E3C3C7E7E7E7E4266242466427E FFFFFF0EFEFEFEFEFF81F908F89880FE FFFFFFFEFEFFFFFFFF999386869399FF F0F0F0F0F0FFFFFFF0909090909F81FF E7FFFFFFFFFFFFFFE7BD9981819999FF FFFFFFFFFFFFFFFFFF998981919999FF FFFFFFFFFFFFFFFFFF819999999981FF FFFFFFFFFFF0F0F0FF8199819F9090F0 FFFFFFFFFFFFFFFFFF819999959B85FF FFFFFFFFFFFFFFFFFF819981879399FF FFFFFFFFFFFFFFFFFF819F81F9F981FF FFFFFF3C3C3C3C3CFF81E7242424243C FFFFFFFFFFFFFFFFFF999999999981FF FFFFFFFFFFFF7E3CFF99999999C3663C FFFFFFFFFFFFFFE7FF9999818199BDE7 FFFFFF7EFFFFFFFFFF99C366C39999FF FFFFFFFFFF3C3C3CFF999981E724243C FFFFFF7EFCFFFFFFFF81F366CC9F81FF 7E7E7E78787E7E7E7E424E48484E427E F0F8FC7E3F1F0F07F098CC6633190D07 7E7E7E1E1E7E7E7E7E4272121272427E 3C7EFFFFFF0000003C66C399FF000000 0000000000FFFFFF0000000000FF81FF #14:TITLE MUSIC 4207F00F1CDA0F007008F09F1B5F0300 08006060000000002800400019FE0000 38003020000000007804905001000000 6207015801028000680201F7113F3300 0800000F000000000800000F00000000 0800000F000000000800000F00000000 0800000F000000000800000F00000000 0800000F000000000800000F00000000 4040024040400240C040020140030201 40000201400002014004400140044001 40004001400301014000020140000201 40000201400002014004024040040240 40044040400440010504400105000201 05000201050002010500020140840201 40404040404040404040404040404040 40404040404040404040404040404040 40404040404040404040404040404040 40404040404040404040404040404040 40404040404040404040404040404040 40404040404040404040404040404040 40404040404040404040404040404040 40404040404040404040404040404040 40404040404040404040404040404040 40404040404040404040404040404040 1B3F001B3F000000000000003F5F0000 00001B3F001B3F001B3F000000000000 000000003F5F001B3F00000000000000 1E3F000000001D3F000000003F5F0000 00000000000000001C3F001B3F000000 001B3F003F5F000000001B3F00000000 0F6F001B6F000F6F001B6F000F6F001B 6F000F6F000F6F00226F000F6F001B6F 000F6F00226F000F6F000F6F00206F00 0F6F001B6F000F6F00226F000F6F0016 6F001B6F000F6F00166F001B6F00166F 001B6F000F6F00226F000F6F00216F00 277F00000000297F00000000277F0000 00002A7F00000000287F00000000277F 000000002E7F000000001B7F00000000 417F00000000277F00000000337F0000 0000367F000000003A7F00000000397F 00000000397F00000000337F00000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 000000001B3F001B3F001B3F001B3F00 0000000000000000000000003F5F0000 00000000000000000000000000000000 000000003F5F00000000000000000000 0000000000000000000000003F5F0000 00000000000000000000000000000000 000000003F5F00000000000000000000 00000000000000000000000000000000 0000000000330F00000000330F000000 00330F00000000000000480F00470F00 460F00450F00440F00430F00420F0041 0F00400F003F0F003E0F003D0F000000 00000000000000000000000000000000 #15:MAIN SOUND 1200600F1CDA0F007008808019480900 2800848F0D7762002800848F09776200 1800202008DC05002800F03514CA0800 2E0082980CA764002000408E198CFF00 2800000F000000000800000F00000000 0800000F000000000800000F00000000 0800000F000000000800000F00000000 0800000F000000000800000F00000000