Skip to navigation

The Sentinel B source

Name: PanLandscapeView [Show more] Type: Subroutine Category: Drawing the landscape Summary: Pan the landscape and update the landscape view
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainGameLoop calls PanLandscapeView

Arguments: viewingObject The number of the object that is viewing the landscape (this is always the player object for this routine)
.PanLandscapeView LDY lastPanKeyPressed \ Set Y to the direction of the last pan key that was \ pressed (which may not still be held down) \ \ So this contains the direction of any scrolling that \ we need to apply to the landscape, as follows: \ \ * 0 = pan right \ \ * 1 = pan left \ \ * 2 = pan up \ \ * 3 = pan down \ \ We use this as an index into various tables, to look \ up the correct values for the direction in which we \ want to scroll LDX viewingObject \ Set X to the object number of the viewer, which we set \ to the player object in MainGameLoop CPY #2 \ If the panning direction in Y is up or down, jump to BCS lpan3 \ lpan3 to pan vertically \ If we get here then Y is 0 or 1 and the panning \ direction is right or left LDA objectYawAngle,X \ Rotate the player's gaze so that when we draw the CLC \ updated view, the new part of the landscape that we ADC panAngleToUpdate,Y \ need to scroll in from the side goes into the screen STA objectYawAngle,X \ buffer (as the screen buffer is mapped to the left \ portion of the view that we are drawing) LDA #25 \ Set A = 25 to pass to FillScreen, so we fill the \ screen buffer (as opposed to screen memory) LDY #24 \ Set Y = 24 to pass to FillScreen, so we fill 24 \ character rows of the screen buffer LDX #16 \ Set X = 16 to pass to FillScreen, so we fill 16 \ character columns in the screen buffer STX bufferColumns \ Set bufferColumns to 16 so we can refer to the buffer \ width during the drawing process JSR FillScreen \ Call FillScreen to fill the screen buffer with the \ background specified in screenBackground \ \ screenBackground variable is zeroed in DrawTitleView \ before the gameplay starts, so all calls to the \ FillScreen routine during gameplay fill the buffer \ with alternating colour 0/1 (blue/black) pixel rows, \ for the sky JSR UseColumnBuffer \ Configure the column buffer for use so we draw the \ updated part of the landscape view into the correct \ buffer type for a left or right pan JSR DrawLandscapeView \ Draw the landscape view into the screen buffer \ \ If the player held down the panning key throughout the \ drawing process and the whole landscape was drawn, \ then the C flag will be clear, otherwise the C flag \ will be set LDX viewingObject \ Set X to the object number of the viewer, which we set \ to the player object in MainGameLoop LDY lastPanKeyPressed \ Set Y to the direction of the last pan key that was \ pressed (which may not still be held down) BCS lpan2 \ If the call to DrawLandscapeView set the C flag then \ the landscape drawing process was aborted, so jump to \ lpan2 to revert the change to the player's yaw angle \ and return from the subroutine without updating the \ on-screen landscape view BNE lpan1 \ If Y <> 0 then Y must be 1, in which case the pan \ angle we fetched from the panAngleToUpdate table \ doesn't need correcting, so jump to lpan1 to skip the \ following \ If we get here then Y = 0, so we added 20 to the \ player's yaw angle so it would draw the new part of \ the view into the screen buffer, so we now need to \ subtract 12 from the player's yaw angle so they end up \ looking in the correct direction, i.e. a net rotation \ of +8 rather than +20 yaw angles LDA objectYawAngle,X \ Subtract 12 from the player's yaw angle SEC SBC #12 STA objectYawAngle,X .lpan1 LDA #16 \ We now need to scroll the contents of the screen JSR StartScrollingView \ buffer into the side of the on-screen landscape view, \ so call StartScrollingView to configure a background \ task to scroll 16 character columns from the screen \ buffer onto the screen, using the interrupt handler \ to do it in the background RTS \ Return from the subroutine .lpan2 \ If we get here then the panning process was aborted \ before the landscape was drawn LDA objectYawAngle,X \ Reverse the rotation that we applied to the player's SEC \ yaw angle for the pan, so this puts it back to the SBC panAngleToUpdate,Y \ value it had before we started the panning process STA objectYawAngle,X RTS \ Return from the subroutine .lpan3 \ If we get here then Y is 2 or 3 and the panning \ direction is up or down LDA objectPitchAngle,X \ If the player's pitch angle is equal to either CMP highestPitchAngle-2,Y \ highestPitchAngle (if we are panning up) or BEQ lpan6 \ lowestPitchAngle (if we are panning down) then the \ player is already pitched as far back or down as \ possible, so jump to lpan6 to return from the \ subroutine without panning the landscape view, as \ we can't pan any further CLC \ Rotate the player's gaze so that when we draw the ADC panAngleToUpdate,Y \ updated view, the new part of the landscape that we STA objectPitchAngle,X \ need to scroll in from above or below goes into the \ screen buffer (as the screen buffer is mapped to the \ top portion of the view that we are drawing) LDA #25 \ Set A = 25 to pass to FillScreen, so we fill the \ screen buffer (as opposed to screen memory) LDY #8 \ Set Y = 8 to pass to FillScreen, so we fill 8 \ character rows of the screen buffer LDX #40 \ Set X = 40 to pass to FillScreen, so we fill 40 \ character columns in the screen buffer STX bufferColumns \ Set bufferColumns to 40 so we can refer to the buffer \ width during the drawing process JSR FillScreen \ Call FillScreen to fill the screen buffer with the \ background specified in screenBackground \ \ screenBackground variable is zeroed in DrawTitleView \ before the gameplay starts, so all calls to the \ FillScreen routine during gameplay fill the buffer \ with alternating colour 0/1 (blue/black) pixel rows, \ for the sky JSR UseRowBuffer \ Configure the row buffer for use so we draw the \ updated part of the landscape view into the correct \ buffer type for an up or down pan JSR DrawLandscapeView \ Draw the landscape view into the screen buffer \ \ If the player held down the panning key throughout the \ drawing process and the whole landscape was drawn, \ then the C flag will be clear, otherwise the C flag \ will be set LDX playerObject \ Set X to the object number of the player \ \ This should really be an LDX viewingObject instruction \ to be consistent with the rest of the routine, but it \ doesn't make any difference as they are the same at \ this point \ \ Perhaps at some point this routine allowed panning of \ the view from viewing objects other than the player, \ which would have been interesting... LDY lastPanKeyPressed \ Set Y to the direction of the last pan key that was \ pressed (which may not still be held down) BCS lpan7 \ If the call to DrawLandscapeView set the C flag then \ the landscape drawing process was aborted, so jump to \ lpan7 to revert the change to the player's pitch angle \ and return from the subroutine without updating the \ on-screen landscape view CPY #3 \ If Y <> 3 then Y must be 2, in which case the pan BNE lpan4 \ angle we fetched from the panAngleToUpdate table \ doesn't need correcting, so jump to lpan4 to skip the \ following \ If we get here then Y = 3, so we subtracted 12 from \ the player's pitch angle so it would draw the new part \ of the view into the screen buffer, so we now need to \ add 8 to the player's pitch angle so they end up \ looking in the correct direction, i.e. a net rotation \ of -4 rather than -12 pitch angles LDA objectPitchAngle,X \ Add 8 to the player's pitch angle CLC ADC #8 STA objectPitchAngle,X .lpan4 LDA #8 \ We now need to scroll the contents of the screen JSR StartScrollingView \ buffer into the top or bottom of the on-screen \ landscape view, so call StartScrollingView to \ configure a background task to scroll 8 character rows \ from the screen buffer onto the screen, using the \ interrupt handler to do it in the background .lpan5 JSR SetColumnBufferMax \ Call SetColumnBufferMax to set the maximum and minimum \ pitch angles for the column buffer .lpan6 RTS \ Return from the subroutine .lpan7 \ If we get here then the panning process was aborted \ before the landscape was drawn LDA objectPitchAngle,X \ Reverse the rotation that we applied to the player's SEC \ pitch angle for the pan, so this puts it back to the SBC panAngleToUpdate,Y \ value it had before we started the panning process STA objectPitchAngle,X JMP lpan5 \ Jump to SetColumnBufferMax via lpan5 to set the \ maximum and minimum pitch angles for the column buffer \ and return from the subroutine via a tail call
Name: highestPitchAngle [Show more] Type: Variable Category: Sights Summary: The pitch angle of the highest point that the player can look at with the sights
Context: See this variable on its own page References: This variable is used as follows: * MoveSightsUpDown uses highestPitchAngle * PanLandscapeView uses highestPitchAngle
.highestPitchAngle EQUB 53
Name: lowestPitchAngle [Show more] Type: Variable Category: Sights Summary: The pitch angle of the lowest point that the player can look at with the sights
Context: See this variable on its own page References: This variable is used as follows: * MoveSightsUpDown uses lowestPitchAngle
.lowestPitchAngle EQUB -51
Name: ResetVariables [Show more] Type: Subroutine Category: Main title Loop Summary: Reset all the game's main variables
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainGameLoop calls ResetVariables * MainTitleLoop calls ResetVariables * SecretCodeError calls ResetVariables
.ResetVariables SEC \ Set bit 7 of gameInProgress to indicate that a game is ROR gameInProgress \ not currently in progress and that we are in the title \ and preview screens (so the interrupt handler doesn't \ update the game) \ We now zero the following variable blocks: \ \ * &0000 to &008F (Zero page workspace) \ \ * &0100 to &01BF (Stack variables workspace) \ \ * &0900 to &09EF (xObject) \ \ * &0A00 to &0AEF (yObjectLo) \ \ * &0C00 to &0CE3 (Main variable workspace) \ \ and set the following variable block to %10000000: \ \ * &0CE4 to &0CEF (Main variable workspace) LDX #0 \ Set X to use as a byte counter to run from 0 to &EF .rese1 LDA #0 \ Set A = 0 so we can zero the following variable blocks STA xObject,X \ Zero the X-th byte of xObject STA yObjectLo,X \ Zero the X-th byte of yObjectLo CPX #&90 \ If X >= &90 then skip the following instruction BCS rese2 STA &0000,X \ Zero the X-th byte of zero page .rese2 CPX #&C0 \ If X >= &C0 then skip the following instruction BCS rese3 STA &0100,X \ Zero the X-th byte of &0100 .rese3 CPX #&E4 \ If X < &E4 then skip the following instruction, BCC rese4 \ leaving A = 0, so we zero &0C00 to &0CE3 LDA #%10000000 \ If we get here then X >= &E4, so set A = %10000000 to \ set bit 7 of &0CE4 to &0CEF .rese4 STA &0C00,X \ Set the X-th byte of &0CE4 to A INX \ Increment the byte counter CPX #&F0 \ Loop back until we have processed X from &E4 to &EF BCC rese1 \ to set &0CE4 to &0CEF to &80 \ Fall through into ResetTilesObjects to reset the tile \ visibility table and deallocate all object numbers
Name: ResetTilesObjects [Show more] Type: Subroutine Category: Main title Loop Summary: Reset the tile visibility table and deallocate all object numbers
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainGameLoop calls ResetTilesObjects
.ResetTilesObjects \ We now set the following variable block to %11111111 \ to indicate that all tiles are visible: \ \ * tileVisibility to tileVisibility+127 \ \ and the following variable block to %10000000 to \ deallocate all the object numbers: \ \ * objectFlags to objectFlags+63 LDX #63 \ Set X to use as a byte counter to run from 63 to 0, \ so we can initialise the 64-byte objectFlags table and \ the 128-byte tileVisibility table .resv1 LDA #%11111111 \ Set the X-th and X+64-th bytes of tileVisibility to STA tileVisibility,X \ all set bits, to indicate visible tiles STA tileVisibility+64,X LDA #%10000000 \ Set bit 7 of the X-th byte of objectFlags, to denote STA objectFlags,X \ that object #X is not allocated to an object DEX \ Decrement the byte counter BPL resv1 \ Loop back until we have processed X from &3F to 0 INC seedNumberLFSR+2 \ Set bit 16 of the five-byte linear feedback shift \ register in seedNumberLFSR(4 3 2 1 0), as we need a \ non-zero element for the seed number generator to \ work (as otherwise the EOR feedback will not affect \ the contents of the shift register and it won't start \ generating non-zero numbers) JSR SetColumnBufferMax \ Call SetColumnBufferMax to set the maximum and minimum \ pitch angles for the column buffer RTS \ Return from the subroutine
Name: CheckForKeyPresses [Show more] Type: Subroutine Category: Keyboard Summary: Check for various game key presses and update the key logger and relevant variables (during the interrupt handler)
Context: See this subroutine on its own page References: This subroutine is called as follows: * IRQHandler calls CheckForKeyPresses
.CheckForKeyPresses LDA #%10000000 \ Set bit 7 of panKeyBeingPressed to indicate that no STA panKeyBeingPressed \ pan key is being pressed (we will update this below \ if a pan key is being pressed) LDX #&8E \ Scan the keyboard to see if function key f1 is being JSR ScanKeyboard \ pressed ("Quit game") BNE ckey1 \ If function key f1 is not being pressed, jump to ckey1 \ to skip the following SEC \ Function key f1 is not being pressed, which quits the ROR quitGame \ game, so set bit 7 of quitGame so that when we return \ to the start of the main game loop it jumps to the \ main title loop to restart the game .ckey1 LDX #&9D \ Scan the keyboard to see if SPACE is being pressed JSR ScanKeyboard \ ("Toggle sights on/off") BNE ckey4 \ If SPACE is not being pressed, jump to ckey4 to reset \ the value of spaceKeyDebounce to flag that SPACE is \ not being pressed \ If we get here then SPACE is being pressed LDA spaceKeyDebounce \ If spaceKeyDebounce is non-zero then we have already BNE ckey6 \ toggled the sights but the player is still holding \ down SPACE, so jump to ckey6 to avoid toggling the \ sights again LDA sightsAreVisible \ Flip bit 7 of sightsAreVisible to toggle the sights on EOR #%10000000 \ and off STA sightsAreVisible BPL ckey2 \ If bit 7 is now clear then we just turned the sights \ off, so jump to ckey2 to remove them from the screen \ Otherwise bit 7 is now set, so we need to show the \ sights JSR InitialiseSights \ Initialise the variables used to manage the sights, so \ the sights appear in the middle of the screen JSR DrawSights \ Draw the sights on the screen JMP ckey3 \ Jump to ckey3 to skip the following .ckey2 JSR RemoveSights \ Remove the sights from the screen .ckey3 LDA #%10000000 \ Set bit 7 of A to store in spaceKeyDebounce, to flag \ that we have toggled the sights (so we can make sure \ we don't keep toggling the sights if SPACE is being \ held down) BNE ckey5 \ Jump to ckey5 to set spaceKeyDebounce to the value of \ A in (this BNE is effectively a JMP as A is never \ zero) .ckey4 \ If we get here then SPACE is not being pressed LDA #0 \ Clear bit 7 of spaceKeyDebounce to record that SPACE \ is not being pressed .ckey5 STA spaceKeyDebounce \ Set spaceKeyDebounce to the value of A, so we record \ whether or not SPACE is being pressed to make sure \ we don't keep toggling the sights if SPACE is held \ down .ckey6 LDY #14 \ Scan the keyboard for all 14 game keys in the gameKeys JSR ScanForGameKeys \ table BPL ckey7 \ ScanForGameKeys will clear bit 7 of the result if at \ least one pan key is being pressed, in which case jump \ to ckey7 to skip the following, so pan keys take \ precedence over the other game keys (which are ignored \ while panning is taking place) \ If we get here then no pan keys are being pressed LDA #%01101011 \ Set a bit pattern in sightsInitialMoves to control the STA sightsInitialMoves \ initial movement of the sights when a pan key is \ pressed and held down \ \ Specifically, this value is shifted left once on each \ call to this routine, with a zero shifted into bit 0, \ and we only move the sights when a zero is shifted out \ of bit 7 \ \ This means that when we start moving the sights, they \ move like this, with each step happening on one call \ of the interrupt handler: \ \ 0 = Move \ 1 = Pause \ 1 = Pause \ 0 = Move \ 1 = Pause \ 0 = Move \ 1 = Pause \ 1 = Pause \ \ ...and then we move on every subsequent shift, as by \ now all bits of sightsInitialMoves are clear \ \ This means the sights move more slowly at the start, \ with a slight judder, before speeding up fully after \ eight steps (so this applies a bit of inertia to the \ movement of the sights) LDA keyLogger+1 \ Set A to the key logger entry for "A", "Q", "R", "T", \ "B", "H", or "U" (absorb, transfer, create robot, \ create tree, create boulder, hyperspace, U-turn) BPL FocusOnKeyAction \ If there is a key press in the key logger entry, jump \ to FocusOnKeyAction to start focusing effort on \ implementing the relevant action after returning from \ the subroutine using a tail call \ If we get here then the player is not pressing "A", \ "Q", "R", "T", "B", "H", or "U" (absorb, transfer, \ create robot, create tree, create boulder, hyperspace, \ U-turn) LDA #%01000000 \ Set bit 6 of uTurnStatus so that when the "U" key is STA uTurnStatus \ next pressed, this will trigger a U-turn in the \ ProcessActionKeys routine \ \ This implements debounce so that holding down "U" will \ not continuously perform U-turns, and instead the \ player has to release "U" before they can do a second \ U-turn \ \ A U-turn is only performed in ProcessActionKeys when \ bit 6 of uTurnStatus is set, at which point bit 6 is \ cleared, and this is the only place where bit 6 is \ set, so this ensures only one U-turn is performed \ until we get here again, when "U" has been released BNE focu1 \ Jump to focu1 to return from the subroutine (this BNE \ is effectively a JMP as A is never zero) .ckey7 \ If we get here then at least one pan key is being \ pressed LDX sightsAreVisible \ If bit 7 of sightsAreVisible is clear then the sights BPL ckey8 \ are not being shown, so jump to ckey8 to skip the \ following, as we don't need to move the sights when \ they aren't on-screen \ If we get here then the sights are visible, so the pan \ keys move the sights rather than panning the view ASL sightsInitialMoves \ Shift sightsInitialMoves to the left, so we pull the \ next bit from the pattern that determines the initial \ movement of the sights BCS focu1 \ If we shifted a 1 out of bit 7 of sightsInitialMoves, \ jump to focu1 to return from the subroutine without \ moving the sights, as a set bit indicates a pause in \ the initial movement of the sights JSR MoveSights \ Move the sights according to the pan key presses in \ the key logger \ \ If the player moves the sights off the edge of the \ screen, this routine will set panKeyBeingPressed to \ "press" the relevant pan key so that the screen \ scrolls in the same direction to bring the sights \ back into view JMP ckey10 \ Jump to ckey10 to process any pan "key press" from the \ call to MoveSights .ckey8 LDA keyLogger \ Set A to the key logger entry for "S" and "D" (pan \ left, pan right), which are used to move the sights BPL ckey9 \ If there is a key press in the key logger entry, jump \ to ckey9 to store this value in panKeyBeingPressed (so \ panning left or right takes precedence over panning up \ or down) LDA keyLogger+2 \ Set A to the key logger entry for "L" and "," (pan \ up, pan down), which are used to move the sights BMI focu1 \ If there is no key press in the key logger entry then \ no pan keys are being pressed, so jump to focu1 to \ return from the subroutine without recording a pan key \ press in panKeyBeingPressed .ckey9 STA panKeyBeingPressed \ Set panKeyBeingPressed to the key logger value of the \ pan key that's being pressed, as follows: \ \ * 0 = pan right \ \ * 1 = pan left \ \ * 2 = pan up \ \ * 3 = pan down .ckey10 LDA panKeyBeingPressed \ If bit 7 of panKeyBeingPressed is set then no pan keys BMI focu1 \ are being pressed, so jump to focu1 to return from the \ subroutine STA latestPanKeyPress \ Set latestPanKeyPress to the key logger value of the \ pan key that's being pressed, so it contains the most \ recent pan key press (i.e. the current one) \ Fall through into FocusOnKeyAction to start focusing \ effort on the pan that the player wants us to do
Name: FocusOnKeyAction [Show more] Type: Subroutine Category: Keyboard Summary: Tell the game to start focusing effort on the action that has been initiated, such as a pan of the landscape, absorb, transfer etc.
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckForKeyPresses calls FocusOnKeyAction * PerformHyperspace calls FocusOnKeyAction * ProcessActionKeys (Part 1 of 2) calls FocusOnKeyAction * ProcessGameplay calls FocusOnKeyAction * CheckForKeyPresses calls via focu1

Other entry points: focu1 Store the current setting of focusOnKeyAction in the previousFocus variable so we can detect (in the ProcessGameplay routine) whether the player is still holding down a pan key after we finish scrolling the screen for the previous pan... and then return from the subroutine
.FocusOnKeyAction LDA #%10000000 \ Set bit 7 of focusOnKeyAction to tell the game to STA focusOnKeyAction \ focus effort on implementing the key action that has \ just been initiated, such as a pan of the landscape \ view, absorb, transfer and so on STA doNotDitherObject \ Set bit 7 of doNotDitherObject to disable updating of \ objects on-screen using a dithered effect, so we don't \ dither any object changes onto the screen while we are \ focusing on processing the action, which itself might \ need to update the screen when it's done .focu1 LDA focusOnKeyAction \ Set previousFocus = focusOnKeyAction so we can detect STA previousFocus \ whether the value of focusOnKeyAction changes \ \ This is used by the ProcessGameplay routine to detect \ whether the player is still holding the same pan key \ down after we finish scrolling the screen for the pan RTS \ Return from the subroutine
Name: CheckForSamePanKey [Show more] Type: Subroutine Category: Keyboard Summary: Check to see whether the same pan key is being held down compared to the last time we checked
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawLandscapeView (Part 2 of 3) calls CheckForSamePanKey * ProcessGameplay calls CheckForSamePanKey

Returns: Z flag Determines whether the same pan key is being held down compared to the last time we checked: * Z flag will be set if the same pan key is being held down (so a BEQ branch will be taken) * Z flag will be clear otherwise (so a BNE branch will be taken)
.CheckForSamePanKey SEI \ Disable interrupts so the key logger doesn't get \ updated while we check it for pan key presses LDY #3 \ Scan the keyboard for the first four game keys ("S", JSR ScanForGameKeys \ "D", "L" and ",", for pan left, right up and down) LDA latestPanKeyPress \ Set A to the key logger value of the latest pan key \ press, which will either be a current key press or the \ value from the last pan key press to be made CMP keyLogger \ Compare with the key logger entry for "S" and "D" \ (pan left and right) BEQ cpan1 \ If the key logger entry is unchanged from the previous \ pan key press in latestPanKeyPress, then the same pan \ key is being held down, so jump to cpan1 with the \ Z flag set accordingly CMP keyLogger+2 \ Compare with the key logger entry for "L" and "," \ (pan up and down), so the Z flag will be set if the \ same pan key is being held down .cpan1 CLI \ Re-enable interrupts RTS \ Return from the subroutine
Name: spaceKeyDebounce [Show more] Type: Variable Category: Keyboard Summary: A variable to flag whether the SPACE key has been pressed, so we can implement debounce
Context: See this variable on its own page References: This variable is used as follows: * CheckForKeyPresses uses spaceKeyDebounce
.spaceKeyDebounce EQUB 0 EQUB &00 \ This byte appears to be unused
Name: PlaceObjectBelow [Show more] Type: Subroutine Category: 3D objects Summary: Attempt to place an object on a tile that is below the maximum altitude specified in A
Context: See this subroutine on its own page References: This subroutine is called as follows: * ExpendEnemyEnergy calls PlaceObjectBelow * PerformHyperspace calls PlaceObjectBelow * SpawnPlayer calls PlaceObjectBelow * SpawnTrees calls PlaceObjectBelow

Arguments: A The maximum desired altitude of the object (though we may end up placing the object higher than this) X The number of the object to add to the tile
Returns: C flag Status flag: * Clear if the object was successfully placed on a tile * Set if the object was not placed on a suitable tile
.PlaceObjectBelow STA tileAltitude \ Store the maximum altitude in tileAltitude LDA #0 \ We now loop through the landscape tiles, trying to STA loopCounter \ find a suitable location for the object, so set a \ loop counter to count 255 iterations for each loop .objb1 DEC loopCounter \ Decrement the loop counter BNE objb2 \ If we have not counted all 255 iterations yet, jump to \ objb2 to skip the following \ If we get here then we have tried 255 tiles at the \ altitude in tileAltitude, but without success, so we \ move to a higher altitude and try again INC tileAltitude \ Increment the altitude in tileAltitude to move up by \ one coordinate (where a tile-sized cube is one \ coordinate across) LDA tileAltitude \ If we just incremented tileAltitude to 12 then we have CMP #12 \ gone past the highest altitude possible, so jump to BCS objb3 \ objb3 to return from the subroutine with the C flag \ set to indicate failure \ Otherwise keep going to look for a suitable tile at \ the new, higher altitude .objb2 \ We now try to pick a tile in the landscape that might \ be suitable for placing our object JSR GetNextSeed0To30 \ Set A to the next number from the landscape's sequence \ of seed numbers, converted to the range 0 to 30 STA xTile \ Set xTile to this seed number, so it points to a \ tile corner that anchors a tile (so the tile corner \ isn't along the right edge of the landscape) JSR GetNextSeed0To30 \ Set A to the next number from the landscape's sequence \ of seed numbers, converted to the range 0 to 30 STA zTile \ Set zTile to this seed number, so it points to a \ tile corner that anchors a tile (so the tile corner \ isn't along the far edge of the landscape) JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile), setting the C flag if the tile \ contains an object BCS objb1 \ If the tile already contains an object, jump to objb1 \ to try another tile from the landscape's sequence of \ seed numbers AND #%00001111 \ If the tile shape in the low nibble of the tile data BNE objb1 \ is non-zero, then the tile is not flat, so jump to \ objb1 to try another tile from the landscape's \ sequence of seed numbers LDA (tileDataPage),Y \ Set A to the tile data for the tile anchored at \ (xTile, zTile) LSR A \ Set A to the tile altitude, which is in the top nibble LSR A \ of the tile data LSR A LSR A CMP tileAltitude \ If the altitude of the chosen tile is equal to or BCS objb1 \ higher than the minimum altitude in tileAltitude, then \ this tile is too high, so jump to objb1 to try another \ tile from the landscape's sequence of seed numbers \ If we get here then we have found a tile that is below \ the altitude in tileAltitude and which doesn't already \ contain an object, so we can use this for placing our \ object JSR PlaceObjectOnTile \ Place object #X on the tile anchored at (xTile, zTile) CLC \ Clear the C flag to indicate that we have successfully \ placed the object on a tile RTS \ Return from the subroutine .objb3 SEC \ Set the C flag to indicate that we have failed to \ place the object on a suitable tile RTS \ Return from the subroutine
Name: GetNextSeed0To30 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Set A to the next number from the landscape's sequence of seed numbers, converted to the range 0 to 30
Context: See this subroutine on its own page References: This subroutine is called as follows: * PlaceObjectBelow calls GetNextSeed0To30
.GetNextSeed0To30 JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers AND #31 \ Convert A into a number into the range 0 to 31 CMP #31 \ If A >= 31 or greater, repeat the process until we BCS GetNextSeed0To30 \ get a number in the range 0 to 30 RTS \ Return from the subroutine
Name: ProcessGameplay [Show more] Type: Subroutine Category: Gameplay Summary: A gameplay loop that processes all game key presses, returning to the main game loop when the player moves, quits, loses or pans
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainGameLoop calls ProcessGameplay

Returns: C flag Status flag on exit: * Clear if we have just finished processing a pan of the landscape view, and the player is still holding down a pan key * Set if any of the following are true: * The Sentinel has won * The player has moved to a new tile * The player has pressed the quit game key
.ProcessGameplay LDA #0 \ Clear bit 7 of focusOnKeyAction to tell the game to STA focusOnKeyAction \ stop focusing effort on implementing any key actions \ such as landscape pans, as we are now looking at the \ gameplay STA uTurnStatus \ Clear bits 6 and 7 of uTurnStatus to clear any record \ of a U-turn being in progress, and to prevent the "U" \ key from performing a U-turn, so that the "U" key has \ to be released (if applicable) before a second U-turn \ can be performed .play1 LDA focusOnKeyAction \ If bit 7 of focusOnKeyAction is set then the game is BMI play5 \ focusing effort on a key action such as a landscape \ pan, so jump to play5 to go straight to processing any \ action key presses, skipping all the key press logic \ below \ We now check whether we have just finished processing \ a landscape pan, and if so whether the player is still \ holding down the same pan key LSR samePanKeyPress \ Clear bit 7 of samePanKeyPress to record that the same \ pan key is not being held down, which we will change \ below if this is not the case LDA previousFocus \ If bit 7 of previousFocus is clear then it matches BPL play2 \ the current value of focusOnKeyAction, so the value \ of focusOnKeyAction has not changed since the last \ time we were in the FocusOnKeyAction routine, and at \ that point we were not focusing effort on implementing \ a key action such as a landscape pan \ \ So jump to play2 to skip the following check for pan \ keys, as it isn't relevant \ If we get here then bit 7 of focusOnKeyAction is \ clear and bit 7 of previousFocus is set, so we must \ have called the ProcessGameplay routine just after \ implementing a key action such as a landscape pan \ \ So let's check to see whether the same pan key is \ still being held down from the pan we just finished JSR CheckForSamePanKey \ Check to see whether the same pan key is being held \ down compared to the last time we checked BNE play2 \ If the same pan key is not being held down, jump to \ play2 to skip the following SEC \ The same pan key is still being held down, so set bit ROR samePanKeyPress \ 7 of samePanKeyPress to record this fact for use in \ the CheckObjVisibility routine, so we can work out \ whether it is safe to update objects without \ corrupting any ongoing pans in the landscape view .play2 JSR ApplyEnemyTactics \ Apply tactics to the enemy object in enemyObject \ \ This call also moves enemyObject on to the next enemy \ object (in the range 0 to 7), so the next time we \ reach this point in the gameplay loop, this call will \ apply tactics to the next enemy number \ \ If there is no enemy object allocated to the object \ number (which may be the case if the landscape has \ fewer than eight enemies, or if an enemy has been \ absorbed), then no tactics are applied and the object \ number in enemyObject moves on by one, ready for the \ next time we reach this point in the gameplay loop \ \ So we process each enemy once every eight iterations \ of the gameplay loop, irrespective of the number of \ enemies in the landscape LDA sentinelHasWon \ If bit 7 of sentinelHasWon is clear then the player BEQ play4 \ has not been absorbed by the Sentinel, so jump to \ play4 to progress the game LDA #30 \ The Sentinel has won, so display the game over screen JSR ShowGameOverScreen \ with A = 30, so we decay the screen to black with a \ mass of 30 * 2400 = 72,000 randomly placed black dots .play3 JSR FocusOnKeyAction \ Tell the game to start focusing effort on the key \ action that has been initiated (be it a pan of the \ landscape view or an action like absorb or transfer) SEC \ Set the C flag to indicate that one of the following \ is true: \ \ * The Sentinel has won \ \ * The player has moved to a new tile \ \ * The player has pressed the quit game key \ \ so the main game loop can process this action RTS \ Return from the subroutine .play4 ASL playerHasMovedTile \ Shift bit 7 of playerHasMovedTile into the C flag so \ we can check it in the next instruction, and clear bit \ 7 to clear the flag as we are about to process any \ tile move that has occurred BCS play3 \ If the C flag is set then bit 7 of playerHasMovedTile \ was set before we cleared it, which indicates that the \ player has moved to a new tile, so jump to play3 to \ return from the subroutine with the C flag set BIT quitGame \ If bit 7 of quitGame is set then the player has BMI play3 \ pressed function key f1 to quit the game, so jump to \ play3 to return from the subroutine with the C flag \ set JSR GetPlayerDrain \ Calculate whether the player is being scanned by an \ enemy and whether the enemy can see the player's tile JSR ProcessPauseKeys \ Pause or unpause the game when COPY or DELETE are \ pressed JSR ProcessSound \ Process any sounds or music that are being made in the \ background JSR ProcessVolumeKeys \ Adjust the volume of the sound envelopes when the \ volume keys are pressed JMP play1 \ Jump back to play1 to repeat the main game loop .play5 LDA panKeyBeingPressed \ If no pan key is currently being pressed, jump to BMI play6 \ play6 to process any action key presses CLC \ Clear the C flag to indicate that we just finished a \ landscape pan and the player is still holding down a \ pan key, so the main game loop can process a new pan RTS \ Return from the subroutine .play6 LDA keyLogger+1 \ Set A to the key logger entry for "A", "Q", "R", "T", \ "B", "H", or "U" (absorb, transfer, create robot, \ create tree, create boulder, hyperspace, U-turn) BMI play9 \ If there is no key press in the key logger entry, jump \ back to the start of the routine via play9 to keep \ checking for key presses \ If we get here then the player is pressing "A", "Q", \ "R", "T", "B", "H" or "U" (absorb, transfer, create \ robot, create tree, create boulder, hyperspace, \ U-turn), so the possible values for A are: \ \ * 0 for key press "R" (Create robot) \ * 2 for key press "T" (Create tree) \ * 3 for key press "B" (Create boulder) \ * 32 for key press "A" (Absorb) \ * 33 for key press "Q" (Transfer) \ * 34 for key press "H" (Hyperspace) \ * 35 for key press "U" (U-turn) CMP #34 \ If A >= 34 then "H" (hyperspace) or "U" (U-turn) is BCS play7 \ being pressed, so jump to play7 to skip the following \ check, as we can hyperspace and U-turn at any point, \ irrespective of whether the sights are being shown BIT sightsAreVisible \ If bit 7 of sightsAreVisible is clear then the sights BPL play9 \ are not being shown, so jump back to the start of the \ routine via play9 to keep checking for key presses, as \ we can only create, absorb and transfer when the \ sights are visible \ If we get here then the sights are being shown, so we \ can process the key press .play7 STA keyPress \ Record the value from the key logger in keyPress, so \ we can refer to it in the call to ProcessActionKeys LSR activateSentinel \ The player has pressed a key that expends or absorbs \ energy, which activates the Sentinel at the very start \ of each level (the Sentinel and Sentries are inactive \ until this point, giving the player time to get their \ bearings when they first start a landscape) \ \ So clear bit 7 of activateSentinel to indicate that \ the Sentinel is activated and the game has started JSR ProcessActionKeys \ Process any key presses in key logger entry 1, which \ is where action key presses are stored (absorb, \ transfer, create, hyperspace, U-turn) BCS play8 \ If the call to ProcessActionKeys returned with the C \ flag set, then it didn't add or remove any objects, so \ jump to play8 to skip the following \ If we get here then the call to ProcessActionKeys \ added or removed an object, so we now need to make the \ appropriate sound and draw the updated object \ on-screen JSR FlushSoundBuffer0 \ Flush the sound channel 0 buffer LDA #2 \ Make sound #2 (create/absorb object white noise) JSR MakeSound LDA #%11000000 \ Set bits 6 and 7 of ditherObjectSights so we dither STA ditherObjectSights \ the updated object onto the screen (bit 6) and remove \ the sights before updating (bit 7) in the call to the \ DrawUpdatedObject routine LSR doNotDitherObject \ Clear bit 7 of doNotDitherObject to enable objects to \ be updated on the screen with a dithered effect JSR DrawUpdatedObject \ Draw the updated object (or the landscape where the \ object used to be) into the screen buffer and dither \ it onto the screen, pixel by pixel and randomly JSR FlushSoundBuffer0 \ Flush the sound channel 0 buffer JSR UpdateIconsScanner \ Update the icons in the top-left corner of the screen \ to show the player's current energy level and redraw \ the scanner box .play8 ASL playerHasMovedTile \ Shift bit 7 of playerHasMovedTile into the C flag so \ we can check it in the next instruction, and clear bit \ 7 to clear the flag as we are about to process any \ tile move that has occurred BCC play9 \ If bit 7 of playerHasMovedTile was clear before we \ cleared it, then the player has not moved to a new \ tile, so jump back to the start of the routine via \ play9 to continue processing the gameplay \ If we get here then bit 7 of playerHasMovedTile was \ set before we cleared it, which indicates that the \ player has moved to a new tile, so return from the \ subroutine with the C flag set RTS \ Return from the subroutine .play9 JMP ProcessGameplay \ Jump back to the start of the ProcessGameplay routine \ to keep processing the gameplay
Name: UpdateEnemyTimers [Show more] Type: Subroutine Category: Gameplay Summary: Update the timers that control the enemy tactics
Context: See this subroutine on its own page References: This subroutine is called as follows: * IRQHandler calls UpdateEnemyTimers
.UpdateEnemyTimers LDA updateTimer \ If updateTimer is non-zero, jump to time3 to skip BNE time3 \ updating the enemy timers and run down the update \ timer \ If we get here then updateTimer is zero, so we \ update the enemy timers \ \ The update timer loops through 2, 1, 0, so this means \ we decrement the timers on one out of every three \ calls to the UpdateEnemyTimers \ \ UpdateEnemyTimers is called from the interrupt handler \ at IRQHandler, but only once the game has started and \ the Sentinel is active \ \ IRQHandler performs its actions 50 times a second, so \ this means the counters tick down at a rate of 16.7 \ times a second, or once every 0.06 seconds \ \ So setting a timer to n means it will count down in \ 3 * n / 50 = 3 * 0.06 seconds LDX #23 \ There are 24 bytes of enemy timers, split into three \ timers keeping track of up to eight enemies each: \ \ * enemyDrainTimer \ \ * enemyTacticTimer \ \ * enemyTimer3 \ \ So set a byte counter in X to update all the timers \ in one loop .time1 LDA enemyDrainTimer,X \ If the X-th timer is less than 2 then it has already CMP #2 \ counted down to 1, so jump to time2 to leave the timer BCC time2 \ alone as it is inactive \ So the timers count down until they reach 1, at which \ point they stay put, inactive until they are reset DEC enemyDrainTimer,X \ The X-th timer is actively counting down, so decrement \ the timer .time2 DEX \ Decrement the byte counter BPL time1 \ Loop back until we have decremented all the active \ enemy timers LDA #2 \ Set updateTimer = 2 so it loops back to the start to STA updateTimer \ count through 2, 1, 0 again RTS \ Return from the subroutine .time3 DEC updateTimer \ Decrement the update timer RTS \ Return from the subroutine
Name: ResetScreenAddress [Show more] Type: Subroutine Category: Graphics Summary: Reset the address of the start of screen memory
Context: See this subroutine on its own page References: This subroutine is called as follows: * ClearScreen calls ResetScreenAddress

This routine sets the screen addresses as follows: * Address of the start of screen memory in the 6845 CRTC registers = &7F80 * Address of the icon and scanner row at the top of the screen in iconRowAddr(1 0) = &7F80 * Address of the player's scrolling landscape view in viewScreenAddr(1 0) = &60C0 Note that &7F80 + 320 = &60C0, when the wrapping of screen memory is taken into consideration, so the player's scrolling landscape view in memory is one character line after the icon and scanner row at the top of the screen.
.ResetScreenAddress SEI \ Disable interrupts so we can update the 6845 registers LDA #&C0 \ Set viewScreenAddr(1 0) = &60C0 STA viewScreenAddr LDA #&60 STA viewScreenAddr+1 JSR GetIconRowAddress \ Set iconRowAddr(1 0) to the address in screen memory \ of the icon and scanner row at the top of the screen \ We now set the address of screen memory to &7F80 LDA #&0F \ Store &0F on the stack to use as the value of R12 in PHA \ the following LDA #&F0 \ Set 6845 register R13 = &F0, for the low byte LDX #13 \ STX SHEILA+&00 \ We do this by writing the register number (13) to STA SHEILA+&01 \ SHEILA &00, and then the value (&F0) to SHEILA &01 DEX \ Set 6845 register R12 = &0F, for the high byte STX SHEILA+&00 \ PLA \ We do this by writing the register number (12) to STA SHEILA+&01 \ SHEILA &00, and then the value (&0F) to SHEILA &01 \ This sets 6845 registers (R12 R13) = &0FF0 to point \ to the start of screen memory in terms of character \ rows. There are 8 pixel lines in each character row, \ so to get the actual address of the start of screen \ memory, we multiply by 8: \ \ &0FF0 * 8 = &7F80 \ \ So this sets the start of screen memory to &7F80 CLI \ Re-enable interrupts RTS \ Return from the subroutine
Name: InitialiseSights [Show more] Type: Subroutine Category: Sights Summary: Initialise the variables used to manage the sights, so the sights appear in the middle of the screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckForKeyPresses calls InitialiseSights
.InitialiseSights \ We start by calculating the screen address for the \ sights when they are in the middle of the screen, \ which is where we initialise them \ \ The base screen address has already been set up in \ viewScreenAddr(1 0), and we want to place the sights \ halfway down and halfway across the screen \ \ The custom screen mode 5 used by the game contains 25 \ character rows, each of which is eight pixels high \ \ The top character row is used for the energy icon and \ scanner, and viewScreenAddr(1 0) points to the start \ of screen memory just below this top row, so the \ player's view is 24 character rows high and row 12 is \ halfway down the screen \ \ Each character row in screen mode 5 takes up 320 bytes \ (40 character blocks of eight bytes each), so the \ offset within screen memory of the start of row 12 is \ 12 * 320, and we can move halfway along that row by \ adding a further 160, so that's an offset of: \ \ 12 * 320 + 160 = 4000 \ \ So the address of the sights in screen memory is: \ \ viewScreenAddr(1 0) + 4000 \ \ which is what we calculate now LDA viewScreenAddr \ Calculate the following: CLC \ ADC #&A0 \ (A sightsScreenAddr) = viewScreenAddr(1 0) + &0FA0 STA sightsScreenAddr \ = viewScreenAddr(1 0) + 4000 \ \ starting with the low bytes LDA viewScreenAddr+1 \ And then the high bytes ADC #&0F CMP #&80 \ If the high byte in A >= &80 then the new address is BCC sesi1 \ past the end of screen memory, so subtract &20 from SBC #&20 \ the high byte so the address wraps around within the \ range of screen memory between &6000 and &8000 .sesi1 STA sightsScreenAddr+1 \ Store the high byte of the result, so we now have: \ \ sightsScreenAddr(1 0) = viewScreenAddr(1 0) + 4000 LDA #80 \ Set the on-screen x-coordinate of the sights to 80, STA xSights \ which is in the middle of the 160-pixel wide screen LDA #95 \ Set the on-screen y-coordinate of the sights to 95, STA ySights \ which is in the middle of the 192-pixel high player's \ view (which is the whole screen apart from the energy \ icon and scanner row at the top) RTS \ Return from the subroutine
Name: ScanForGameKeys [Show more] Type: Subroutine Category: Keyboard Summary: Scan for game key presses and update the key logger
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckForKeyPresses calls ScanForGameKeys * CheckForSamePanKey calls ScanForGameKeys * IRQHandler calls ScanForGameKeys

Arguments: Y The offset within gameKeys where we start the scan, with the scan working towards the start of gameKeys
Returns: N flag Determines whether a pan key is being pressed: * Bit 7 of A will be set if no pan keys are being pressed (so a BMI branch will be taken) * Bit 7 of A will be clear if at least one pan key is being pressed (so a BPL branch will be taken)
.ScanForGameKeys LDX #3 \ We start by resetting the key logger, so set a loop \ counter in X for resetting all four entries LDA #%10000000 \ Set A = %10000000 to reset all four entries, as the \ set bit 7 indicates an empty entry in the logger .gkey1 STA keyLogger,X \ Reset the X-th entry in the key logger DEX \ Decrement the loop counter BPL gkey1 \ Loop back until we have reset all four entries \ We now work our way backwards through the gameKey \ table, starting at offset Y, and checking to see if \ each key is being pressed and logging the results in \ the key logger .gkey2 LDX gameKeys,Y \ Set X to the internal key number for the Y-th key in \ the gameKey table JSR ScanKeyboard \ Scan the keyboard to see if this key is being pressed BNE gkey3 \ If the key in X is not being pressed, jump to gkey3 to \ move on to the next key in the table LDA keyLoggerConfig,Y \ Set X to the key logger entry where we should store AND #%00000011 \ this key press, which is in bits 0 and 1 of the TAX \ corresponding entry in the keyLoggerConfig table LDA keyLoggerConfig,Y \ Set A to the value to store in the key logger for this LSR A \ key, which is in bits 2 to 7 of the corresponding LSR A \ entry in the keyLoggerConfig table STA keyLogger,X \ Store the configured value in the configured entry \ for this key press .gkey3 DEY \ Decrement the index in Y to move on to the next key \ in the gameKey table BPL gkey2 \ Loop back until we have checked all the keys up to the \ start of the gameKey table LDA keyLogger \ Combine the key logger entry for "S" and "D" (pan left AND keyLogger+2 \ and right) with the key logger entry for "L" and "," \ (pan left and right and set the status flags according \ to the result \ \ Specifically, if bit 7 is set in both entries, then no \ pan keys are being pressed, so a BMI following the \ call to ScanForGameKeys will be taken \ \ If, however, bit 7 is clear in either entry, then at \ least one pan key is being pressed, so a BPL following \ the call to ScanForGameKeys will be taken RTS \ Return from the subroutine
Name: gameKeys [Show more] Type: Variable Category: Keyboard Summary: Negative inkey values for the game keys
Context: See this variable on its own page References: This variable is used as follows: * ScanForGameKeys uses gameKeys

For a full list of negative inkey values, see Appendix C of the "Advanced User Guide for the BBC Micro" by Bray, Dickens and Holmes.
.gameKeys EQUB &AE \ Negative inkey value for "S" (pan left) EQUB &CD \ Negative inkey value for "D" (pan right) EQUB &A9 \ Negative inkey value for "L" (pan up) EQUB &99 \ Negative inkey value for "," (pan down) EQUB &BE \ Negative inkey value for "A" (absorb) EQUB &EF \ Negative inkey value for "Q" (transfer) EQUB &CC \ Negative inkey value for "R" (create robot) EQUB &DC \ Negative inkey value for "T" (create tree) EQUB &9B \ Negative inkey value for "B" (create boulder) EQUB &AB \ Negative inkey value for "H" (hyperspace) EQUB &DB \ Negative inkey value for "7" (volume down) EQUB &EA \ Negative inkey value for "8" (volume up) EQUB &96 \ Negative inkey value for COPY (pause) EQUB &A6 \ Negative inkey value for DELETE (unpause) EQUB &CA \ Negative inkey value for "U" (U-turn)
Name: keyLoggerConfig [Show more] Type: Variable Category: Keyboard Summary: The configuration table for storing keys the key logger
Context: See this variable on its own page References: This variable is used as follows: * ScanForGameKeys uses keyLoggerConfig

Each game key has an entry in the keyLoggerConfig table that corresponds with the internal key number in the gameKeys table. Bits 0 and 1 determine the entry in the four-byte key logger where we should record each key press (entry numbers are 0 to 3). Bits 2 to 7 contain the value to store in the key logger at that entry. The key logger entries fall into four categories: * Entry 0 is for sideways movement keys (pan left, pan right) * Entry 1 is for action keys (absorb, transfer, create, hyperspace, U-turn) * Entry 2 is for vertical movement keys (pan up, pan down) * Entry 3 is for utility keys (volume control, pause)
.keyLoggerConfig EQUB 0 + 1 << 2 \ Put 1 in logger entry 0 for "S" (pan left) EQUB 0 + 0 << 2 \ Put 0 in logger entry 0 for "D" (pan right) EQUB 2 + 2 << 2 \ Put 2 in logger entry 2 for "L" (pan up) EQUB 2 + 3 << 2 \ Put 3 in logger entry 2 for "," (pan down) EQUB 1 + 32 << 2 \ Put 32 in logger entry 1 for "A" (absorb) EQUB 1 + 33 << 2 \ Put 33 in logger entry 1 for "Q" (transfer) EQUB 1 + 0 << 2 \ Put 0 in logger entry 1 for "R" (create robot) EQUB 1 + 2 << 2 \ Put 2 in logger entry 1 for "T" (create tree) EQUB 1 + 3 << 2 \ Put 3 in logger entry 1 for "B" (create boulder) EQUB 1 + 34 << 2 \ Put 34 in logger entry 1 for "H" (hyperspace) EQUB 3 + 0 << 2 \ Put 0 in logger entry 3 for "7" (volume down) EQUB 3 + 1 << 2 \ Put 1 in logger entry 3 for "8" (volume up) EQUB 3 + 2 << 2 \ Put 2 in logger entry 3 for COPY (pause) EQUB 3 + 3 << 2 \ Put 3 in logger entry 3 for DELETE (unpause) EQUB 1 + 35 << 2 \ Put 35 in logger entry 1 for "U" (U-turn)
Name: DrawTitleView [Show more] Type: Subroutine Category: Title screen Summary: Draw the main title screen, the secret code screen or the landscape preview
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTitleScreen calls DrawTitleView * PreviewLandscape calls DrawTitleView

Arguments: A The object to draw on the title screen: * 0 = draw a robot on the right of the screen for the secret code screen * 5 = draw the Sentinel on the right of the screen for the main title screen * &80 = draw the landscape preview X The screen background: * 1 = fill with solid colour 0 (blue) for the main title screen * 3 = fill with solid colour 1 (black) and draw 240 randomly positioned stars in colour 2 (white, yellow, cyan or red), for the secret code screen and landscape preview Y The view type: * 0 = landscape preview * 1 = main title screen or secret code screen
.DrawTitleView STA titleObjectToDraw \ Set titleObjectToDraw to the object that we are \ drawing so we can refer to it throughout the title \ routines STX screenBackground \ Set screenBackground to the type of background to draw \ for the title screen or landscape preview \ We start by setting the correct colour for any drop \ shadow text on the title screen (i.e. text that uses \ the standard font and which is printed twice with an \ offset to give a drop-shadow effect, as opposed to the \ large 3D text which is drawn as 3D objects) TXA \ Set A to the background type LDX #2 \ Set X = 2 to use as the colour for the rear character \ of a drop shadow (this is red on the title screens or \ white/yellow/cyan/red in the landscape preview) EOR #3 \ Update the VDU codes at vduShadowFront so the front STA vduShadowFront+1 \ character of the dropdown text is printed in colour \ A EOR 3, so that's: \ \ * Colour 2 (red) for the solid blue background \ \ * Colour 0 (blue) for the star background BEQ tvew1 \ If we just set the colour of the front character to \ blue, skip the following instruction INX \ Set X = 3 to use as the colour for the rear character \ of a drop shadow (this is yellow on the title screens \ green/red/yellow/cyan in the landscape preview) .tvew1 STX vduShadowRear+1 \ Update the VDU codes at vduShadowRear so the rear \ character of the dropdown text is printed in colour X, \ so that's: \ \ * Colour 3 (yellow) for the solid blue background \ \ * Colour 2 for the star background, which is red for \ the secret code screen, or white/yellow/cyan/red \ in the landscape preview) STY viewType \ Set viewType to the view type so we can use it as an \ index into the screen configuration tables to set up \ the correct perspective depending on whether this is \ a landscape preview (Y = 0) or a title screen (Y = 1) \ We now set up object #16 to use as the viewing object \ for the large 3D text blocks, using the values from \ the configuration tables LDA yTextViewer,Y \ Set the y-coordinate of the viewer in object #16 STA yObjectHi+16 LDA textViewerPitch,Y \ Set the pitch angle of the viewer in object #16 STA objectPitchAngle+16 LDA xTextViewer,Y \ Set the x-coordinate of the viewer in object #16 STA xObject+16 LDA zTextViewer,Y \ Set the z-coordinate of the viewer in object #16 STA zObject+16 LDA textViewerYaw,Y \ Set the yaw angle of the viewer in object #16 STA objectYawAngle+16 LDA titleOffset,Y \ Store the title offset on the stack so we can retrieve PHA \ it below JSR ClearScreen \ Clear the screen to the screen background specified in \ screenBackground (so that's blue for the main title \ screen or black with stars for the secret code screen \ or landscape preview) LDA titleObjectToDraw \ If bit 7 of titleObjectToDraw is set then we are BMI tvew2 \ drawing the landscape preview, so skip the following \ instruction as there is no 3D object on the right of \ the screen JSR DrawTitleObjects \ Otherwise call DrawTitleObjects to draw an object of \ type A (so that's 0 for a robot or 5 for the Sentinel) \ standing on top of a tower on the right side of the \ screen .tvew2 PLA \ Set xTitleOffset to the title offset, which will only STA xTitleOffset \ be non-zero when we are drawing the large 3D text on \ the main title screen or secret code screen LDX #16 \ Set the viewing object to object #16 STX viewingObject JSR DrawLandscapeView \ Draw the landscape view to display the large 3D text \ (for the title screens) or draw the landscape preview LDA viewType \ If viewType is non-zero then this is the main title BNE tvew4 \ screen or secret code screen, so jump to tvew4 to skip \ the following LDX #127 \ We just drew the landscape preview, so we now reset \ the tile visibilities so we're ready to play the game, \ so set a byte counter in X to work through the 128 \ bytes in the tileVisibility table STX viewType \ Set viewType to a non-zero value (it doesn't matter \ what) so the GetObjectAngles routine will process \ angles for the game's landscape from now on .tvew3 STA tileVisibility,X \ Zero the X-th byte in the tileVisibility table DEX \ Decrement the byte counter BPL tvew3 \ Loop back until we have zeroed the whole \ tileVisibility table .tvew4 LDA #0 \ Set xTitleOffset = 0 to remove the x-coordinate offset STA xTitleOffset \ that we used for the text, as is it not needed during \ gameplay STA screenBackground \ Set screenBackground = 0 so if we start the game after \ showing this title screen, the FillScreen routine will \ clear the screen buffers to the blue and black sky, \ which is the only screen background we need during \ gameplay RTS \ Return from the subroutine
Name: yTextViewer [Show more] Type: Variable Category: Title screen Summary: The y-coordinate of the viewer for the large 3D text on the title screen
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleView uses yTextViewer
.yTextViewer EQUB 33 \ Landscape preview EQUB 75 \ Title screen or secret code screen
Name: textViewerPitch [Show more] Type: Variable Category: Title screen Summary: The pitch angle of the viewer for the large 3D text on the title screen
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleView uses textViewerPitch
.textViewerPitch EQUB -22 \ Landscape preview EQUB -39 \ Title screen or secret code screen
Name: xTextViewer [Show more] Type: Variable Category: Title screen Summary: The x-coordinate of the viewer for the large 3D text on the title screen
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleView uses xTextViewer
.xTextViewer EQUB 15 \ Landscape preview EQUB 0 \ Title screen or secret code screen
Name: titleOffset [Show more] Type: Variable Category: Title screen Summary: An offset to apply to the large 3D text for the title screen
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleView uses titleOffset
.titleOffset EQUB 0 \ Landscape preview EQUB -17 \ Title screen or secret code screen
Name: zTextViewer [Show more] Type: Variable Category: Title screen Summary: The z-coordinate of the viewer for the large 3D text on the title screen
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleView uses zTextViewer
.zTextViewer EQUB 194 \ Landscape preview EQUB 191 \ Title screen or secret code screen
Name: textViewerYaw [Show more] Type: Variable Category: Title screen Summary: The yaw angle of the viewer for the large 3D text on the title screen
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleView uses textViewerYaw
.textViewerYaw EQUB 0 \ Landscape preview EQUB 18 \ Title screen or secret code screen
Name: viewType [Show more] Type: Variable Category: Title screen Summary: Storage for the type of title view we are drawing in DrawTitleView (title screen or landscape preview)
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleView uses viewType * GetObjectAngles uses viewType
.viewType EQUB 1
Name: SpawnEnemies [Show more] Type: Subroutine Category: Landscape Summary: Calculate the number of enemies for this landscape, add them to the landscape and set the palette accordingly
Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishLandscape calls SpawnEnemies * PreviewLandscape calls SpawnEnemies
.SpawnEnemies LDA landscapeZero \ If this is not landscape 0000, jump to popu1 to BNE popu1 \ calculate the number of enemies to spawn LDA #1 \ This is landscape 0000, so set A = 1 to use for the \ total number of enemies BNE popu2 \ Jump to popu2 to set numberOfEnemies to the value of A .popu1 JSR GetEnemyCount \ Set A to the enemy count for this landscape, which is \ derived from the top digit of the landscape number and \ the next number in the landscape's sequence of seed \ numbers, so it is always the same value for the same \ landscape number \ \ At this point A is in the range 1 to 8, with higher \ values for higher landscape numbers CMP maxNumberOfEnemies \ If A < maxNumberOfEnemies then skip the following BCC popu2 \ instruction LDA maxNumberOfEnemies \ Set A = maxNumberOfEnemies, so the number of enemies \ does not exceed the value of maxNumberOfEnemies that \ we set in the InitialiseSeeds routine \ \ So landscapes 0000 to 0009 have a maximum enemy count \ of 1, landscapes 0010 to 0019 have a maximum enemy \ count of 2, and so on up to landscapes 0070 and up, \ which have a maximum enemy count of 8 .popu2 STA numberOfEnemies \ Store the number of enemies for this landscape in \ numberOfEnemies JSR AddEnemiesToTiles \ Add the required number of enemies to the landscape, \ starting from the highest altitude and working down, \ with no more than one enemy on each contour \ We now update colours 2 and 3 in the first palette in \ colourPalettes according to the number of enemies LDA numberOfEnemies \ Set X = (numberOfEnemies - 1) mod 8 SEC \ SBC #1 \ The mod 8 is not strictly necessary as numberOfEnemies AND #7 \ is in the range 1 to 8, but doing this ensures we can TAX \ safely use X as an index into the landscapeColour \ tables LDA landscapeColour3,X \ Set colour 3 in the game palette to the X-th entry STA colourPalettes+3 \ from landscapeColour3 LDA landscapeColour2,X \ Set colour 2 in the game palette to the X-th entry STA colourPalettes+2 \ from landscapeColour2 RTS \ Return from the subroutine
Name: SpawnPlayer [Show more] Type: Subroutine Category: Landscape Summary: Add the player object to the landscape, ideally placing it below all the enemies and in the bottom half of the landscape
Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishLandscape calls SpawnPlayer * PreviewLandscape calls SpawnPlayer
.SpawnPlayer LDA #0 \ Spawn the player's robot (an object of type 0), JSR SpawnObject \ returning the object number of the new object in X \ and currentObject STX playerObject \ Set playerObject to the object number of the newly \ spawned object LDA #10 \ Set the player's energy level to 10 STA playerEnergy LDA landscapeZero \ If the landscape number is not 0000, jump to sply1 BNE sply1 LDA #8 \ Set (xTile, zTile) = (8, 17) STA xTile \ LDA #17 \ So the player always starts on this tile in the first STA zTile \ landscape JSR PlaceObjectOnTile \ Place object #X on the tile anchored at (xTile, zTile) JMP SpawnTrees \ Jump to SpawnTrees to add trees to the landscape and \ move towards playing the game .sply1 \ If we get here then this is not landscape 0000 LDA minEnemyAltitude \ Set A to the altitude of the lowest enemy on the \ landscape CMP #6 \ If A >= 6 then set A = 6 BCC sply2 \ LDA #6 \ So A = min(6, minEnemyAltitude) .sply2 \ By this point A contains an altitude that is no higher \ than any enemies and is no greater than 6 \ \ We can use this as a cap on the player's starting \ altitude to ensure that the player starts below all \ the enemies, and in the bottom half of the landscape \ (which ranges from altitude 1 to 11) JSR PlaceObjectBelow \ Attempt to place the player object on a tile that is \ below the maximum altitude specified in A (though we \ may end up placing the object higher than this) BCS sply1 \ If the call to PlaceObjectBelow sets the C flag then \ the object has not been successfully placed, so loop \ back to sply1 to keep trying, working through the \ landscape's sequence of seed numbers until we do \ manage to place the player on a tile \ Otherwise we have placed the player object on a tile, \ so now we fall through into SpawnTrees to add trees to \ the landscape
Name: SpawnTrees [Show more] Type: Subroutine Category: Landscape Summary: Add trees to the landscape, ideally placing them below all the enemies in the landscape
Context: See this subroutine on its own page References: This subroutine is called as follows: * SpawnPlayer calls SpawnTrees
.SpawnTrees LDA #48 \ Set U = 48 - 3 * numberOfEnemies SEC \ SBC numberOfEnemies \ We use this to cap the number of trees we add to the SBC numberOfEnemies \ landscape (though it only affects higher levels) SBC numberOfEnemies STA U JSR GetNextSeed0To22 \ Set A to the next number from the landscape's sequence \ of seed numbers, converted to the range 0 to 22 CLC \ Set A to this number, converted to the range 10 to 32 ADC #10 CMP U \ If A >= U then set A = U BCC tree1 \ LDA U \ So A = min(U, A) .tree1 STA treeCounter \ By this point A contains a value in the range 10 to 32 \ that's no greater than 48 - 3 * numberOfEnemies \ \ So when numberOfEnemies is six or more, this reduces \ the value of A as follows: \ \ * When numberOfEnemies = 6, range is 10 to 30 \ * When numberOfEnemies = 7, range is 10 to 27 \ * When numberOfEnemies = 8, range is 10 to 24 \ \ As the number of trees determines the total amount of \ energy in the landscape, this makes the levels get \ even more difficult when there are higher enemy counts \ \ We now try to add this number of trees to the \ landscape, so store the result in treeCounter to use \ as a counter in the following loop .tree2 LDA #2 \ Spawn a tree (an object of type 2), returning the JSR SpawnObject \ object number of the new object in X and currentObject LDA minEnemyAltitude \ Set A to the altitude of the lowest enemy on the \ landscape, so we try to spawn all the trees at a lower \ altitude to the enemies JSR PlaceObjectBelow \ Attempt to place the player object on a tile that is \ below the maximum altitude specified in A (though we \ may end up placing the object higher than this) BCS tree3 \ If the call to PlaceObjectBelow sets the C flag then \ the object has not been successfully placed, so jump \ to tree3 to stop adding trees to the landscape DEC treeCounter \ Decrement the tree counter BNE tree2 \ Loop back until we have spawned the number of trees \ in treeCounter .tree3 \ We have now placed all the objects on the landscape, \ so now we fall through into CheckSecretCode to check \ that the player entered the correct secret code for \ this landscape
Name: CheckSecretCode (Part 1 of 2) [Show more] Type: Subroutine Category: Landscape Summary: Generate the secret code for this landscape and optionally check it against the entered code in the keyboard input buffer
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

At this point we have generated the landscape and populated it with enemies, the player and trees, all using the landscape's sequence of seed numbers. This sequence will be different for each individual level, but will be exactly the same sequence every time we generate a specific level. We now keep generating the landscape's sequence of seed numbers to get the landscape's secret code, as follows: * Generate another 38 numbers from the sequence * The next four numbers in the sequence form the secret code To get a secret code of the form 12345678, we take the last four numbers and convert them into binary coded decimal (BCD) by using the GetNextSeedAsBCD routine. These four two-digit pairs then form the secret code, with each of the four numbers producing a pair of digits, building up the secret code from left to right (so in the order that they are written down). If we are displaying the landscape number on-screen at the end of a level, then the last four numbers are generated in the SpawnSecretCode3D routine, but if we are checking the secret code entered by the player, then we generate the last four numbers in this routine (plus one extra number that is ignored). This behaviour is controlled by the doNotPlayLandscape variable.
Arguments: doNotPlayLandscape Controls how we generate the secret code and how we return from the subroutine: * If bit 7 is set, return from part 1 of the routine after generating the secret code sequence up to, but not including, the last four BCD numbers (i.e. the secret code itself), so these can be generated and drawn by the SpawnSecretCode3D routine * If bit 7 is clear, jump to part 2 after generating the whole secret code sequence, plus one more code, checking the generated code against the code entered into the input buffer as we go The first one is used when displaying a landscape number for a completed level, while the second is used to check an entered secret code before playing the landscape
.CheckSecretCode LDX #170 \ Set X = 170 to use as a loop counter for when we \ calculate the secret code, so the following loop \ counts down from 170 to 128 (inclusive, so that's a \ total of 38 + 4 + 1 iterations) \ \ This is the value for when bit 7 of doNotPlayLandscape \ is clear, so that's when we are about to play the game \ and we need to generate the secret code in the \ following loop so we can check it against the code \ entered by the player (which is still in the keyboard \ input buffer from when they typed it in) LDY stashOffset-170,X \ Set Y = stashOffset \ \ The value of stashOffset is set in the SetSecretStash \ routine during the landscape drawing process, where it \ is set to a value that is unique and consistent for \ each individual landscape \ \ We use it as an offset into the secretCodeStash list \ below, so the stash moves around in memory depending \ on the landscape number, making this whole process \ harder to follow (and therefore harder to crack) BIT doNotPlayLandscape \ If bit 7 of doNotPlayLandscape is clear then the next BPL srct1 \ step is to play the landscape, so skip the following \ to leave X set to 170 LDX #165 \ Set X = 165 to use as a loop counter for when we \ calculate the secret code, so the following loop \ counts down from 165 to 128 (inclusive, so that's a \ total of 38 iterations) \ \ This is the value for when bit 7 of doNotPlayLandscape \ is set, so that's when we are not going to play the \ game but are just generating the secret code, in which \ case we stop iterating just before the secret code is \ generated so the SpawnSecretCode3D can finish the job \ We now loop through a number of iterations, counting \ X down towards 128 \ \ On each iteration we do three things: \ \ * We generate the next number from the landscape's \ sequence of seed numbers, converted to BCD \ \ * We test this against the contents of memory from \ either &0D19 or &0D14 down to &0CEF and rotate the \ result into bit 0 of secretCodeChecks (the result \ is only relevant if we are checking the secret \ code against an entered code) \ \ * We add the objectFlags for the Sentinel (which is \ simply a way of incorporating a known value, in \ this case %01111111) and store the results in the \ table at secretCodeStash, from the offset in Y \ onwards, i.e. from offset stashOffset onwards \ \ If we are checking the code against an entered code, \ then the second step is the important part, and this \ is how it works \ \ The inputBuffer is at &0CF0, and it still holds the \ code that the player entered in &0CF0 to &0CF3, with \ the first two digits of the code in &0CF3 and the last \ two digits in &0CF0 \ \ This means that when X = 170, the last five checks in \ each iteration test against the four BCD numbers in \ the entered code, in the correct order, with one extra \ generation and check that is ignored (presumably to \ make this whole process harder to follow) \ \ So if bits 1 to 4 of secretCodeChecks are set by the \ end of the process, the secret code in the keyboard \ input buffer matches the secret code that we just \ generated for this level \ \ The third step is only relevant if we are going on to \ play the game, as this feeds into a second secret code \ check that is performed in the GetRowVisibility \ routine, which is only run during gameplay .srct1 JSR GetNextSeedAsBCD \ Set A to the next number from the landscape's sequence \ of seed numbers, converted to a binary coded decimal \ (BCD) number \ We now compare this generated number with the contents \ of memory, working our way down towards the keyboard \ input buffer (towards the end of the iterations) \ \ X counts down to 128, and for each iteration we check \ the generated number against a location in memory \ \ For the checks to work, we need the last five bytes \ to be the four secret code numbers in inputBuffer, \ plus one more, so: \ \ * When X > 132, we are checking against memory that \ comes after the inputBuffer, and we can safely \ ignore the results \ \ * When X = 132, 131, 130 and 129 we need to be \ checking against the four numbers in inputBuffer \ \ * When X = 128, we are doing the very last check, \ which we can also ignore \ \ The comparison is done by subtracting the contents of \ the memory location we are checking from the BCD \ number we just generated \ \ This is done with a SBC byteToCheck,X instruction \ \ To work out what byteToCheck should be, consider that: \ \ * When X = 129, we check the byte at inputBuffer \ (i.e. the last 2 digits of the code \ when written down or typed in) \ \ * When X = 130, we check the byte at inputBuffer+1 \ \ * When X = 131, we check the byte at inputBuffer+2 \ \ * When X = 132, we check the byte at inputBuffer+3 \ (i.e. the first 2 digits of the code \ when written down or typed in) \ \ To make this work, then, we need this instruction: \ \ SBC inputBuffer-129,X \ \ Note that for BeebAsm to parse this properly, we need \ to wrap the inputBuffer-129 part in brackets, and we \ have to use square brackets so it doesn't look like an \ indirect address instruction SEC \ Subtract the byte from memory that we are checking SBC [inputBuffer-129],X \ from the generated number BEQ srct2 \ If A = 0 then we have a match between the number in \ memory and the generated number, so jump to srct2 to \ keep the C flag set, so we can rotate this into \ secretCodeChecks to indicate a success CLC \ Otherwise we do not have a match, so clear the C flag \ and rotate this into secretCodeChecks to indicate a \ failure .srct2 ROL secretCodeChecks \ Rotate the C flag into bit 0 of secretCodeChecks, so \ secretCodeChecks contains a record of the last eight \ matches between memory and the generated sequence of \ numbers \ \ We only care about the last five comparisons, of which \ we ignore the very last, as the preceding four results \ are for the four BCD numbers in the keyboard input \ buffer (i.e. the entered number) \ We now move on to populate the secret code stash, \ which contains the result of each of the comparisons \ with %01111111 added to them \ \ The stash is checked in the GetRowVisibility routine \ and will abort the game if the values aren't correct, \ so this enables a second secret code check once the \ game has started \ \ The secret stash adds a known value into the mix, by \ fetching the value of objectFlags, which contains the \ object flags for object #0 \ \ Object #0 is always the Sentinel, and the Sentinel \ is always placed on top of the Sentinel's tower, so \ the object flags for the Sentinel are constructed as \ follows: \ \ * Bits 0-5 = the number of the object beneath this \ one \ \ * Bit 6 = set to indicate that this object is on top \ of another object \ \ * Bit 7 = clear to indicate that this object number \ is allocated to an object \ \ The Sentinel's tower is always the first object to be \ spawned, and object numbers are allocated from 63 and \ down, so this means the tower is always object #63, or \ %111111 \ \ The Sentinel's object flags are therefore %01111111 \ \ See the PlaceObjectOnTile routine for details of how \ the Sentinel's object flags are constructed CLC \ Set A = A + %01111111 ADC objectFlags STA secretCodeStash,Y \ Store A in the Y-th entry in the secretCodeStash list \ \ The addition above means that an entry of %01111111 in \ that stash indicates that A was zero before the \ addition, which also indicates a match \ \ If the entered code matches the generated sequence of \ numbers (i.e. it matches the landscape's secret code) \ then the four corresponding entries in secretCodeStash \ will be %01111111 \ \ See the GetRowVisibility routine to see this in action INY \ Increment the index in Y so we build the stash upwards \ in memory DEX \ Decrement the loop counter so the comparisons move \ down in memory, towards inputBuffer BMI srct1 \ Look back to compare the next byte until we have \ compared the bytes all the way down to X = 128 ASL doNotPlayLandscape \ Set the C flag to bit 7 of doNotPlayLandscape and \ clear bit 7 of doNotPlayLandscape, so from this point \ on any calls to GenerateLandscape will preview and \ play the game BCC srct4 \ If bit 7 of doNotPlayLandscape was clear then jump to \ part 2 to check the secret code and either show the \ "WRONG SECRET CODE" error or play the game \ Otherwise bit 7 of doNotPlayLandscape was set, so we \ return from the subroutine normally without playing \ the game .srct3 RTS \ Return from the subroutine \ \ We get to this point by calling the SpawnPlayer \ routine from one of two places: \ \ * PreviewLandscape \ \ * FinishLandscape \ \ and then either failing the secret code checks or \ finishing the current landscape \ \ If we got here from PreviewLandscape, then the next \ instruction jumps to SecretCodeError to display the \ "WRONG SECRET CODE" error, wait for a key press and \ rejoin the main title loop \ \ If we got here from FinishLandscape, then the next \ instructions display the landscape's secret code on \ completion of the level
Name: landscapeColour3 [Show more] Type: Variable Category: Landscape Summary: Physical colours for colour 3 in the game palette for the different numbers of enemies
Context: See this variable on its own page References: This variable is used as follows: * SpawnEnemies uses landscapeColour3
.landscapeColour3 EQUB 2 \ Enemy count = 1: blue, black, white, green EQUB 1 \ Enemy count = 2: blue, black, yellow, red EQUB 3 \ Enemy count = 3: blue, black, cyan, yellow EQUB 6 \ Enemy count = 4: blue, black, red, cyan EQUB 1 \ Enemy count = 5: blue, black, white, red EQUB 6 \ Enemy count = 6: blue, black, yellow, cyan EQUB 1 \ Enemy count = 7: blue, black, cyan, red EQUB 3 \ Enemy count = 8: blue, black, red, yellow
Name: CheckSecretCode (Part 2 of 2) [Show more] Type: Subroutine Category: Landscape Summary: Check the results of the secret code matching process, and if the secret codes match, jump to PlayGame to play the game
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.srct4 LDA secretCodeChecks \ If bits 1 to 4 of secretCodeChecks are not all set, AND #%00011110 \ then one or more of the four BCD bytes in the secret CMP #%00011110 \ code do not match, so jump to srct3 to return from the BNE srct3 \ subroutine normally, to display the "WRONG SECRET \ CODE" error page \ If get here then bits 1 to 4 of secretCodeChecks are \ all set, so the entered secret entry code matches the \ generated code, so we can now proceed to playing the \ landscape \ \ The following code simply jumps to the PlayGame \ routine, but in an obfuscated way that changes the \ return address on the stack to PlayGame-1, so the RTS \ instruction will jump to PlayGame (as that's how the \ RTS instruction works) PLA \ Remove the return address from the stack and discard PLA \ it \ In the following we use the value in objectFlags, \ which contains the object flags for the object #0 \ \ Object #0 is always the Sentinel, and the Sentinel \ is always placed on top of the Sentinel's tower, so \ the object flags for the Sentinel are constructed as \ follows: \ \ * Bits 0-5 = the number of the object beneath this \ one \ \ * Bit 6 = set to indicate that this object is on top \ of another object \ \ * Bit 7 = clear to indicate that this object number \ is allocated to an object \ \ The Sentinel's tower is always the first object to be \ spawned, and object numbers are allocated from 63 and \ down, so this means the tower is always object #63, or \ %111111 \ \ The Sentinel's object flags are therefore %01111111 \ \ The following code uses this fact to push the address \ of PlayGame-1 onto the stack, but in a totally \ obfuscated manner \ \ It calculates the high byte as follows: \ \ objectFlags + HI(PlayGame-1) - %01111111 \ = %01111111 + HI(PlayGame-1) - %01111111 \ = HI(PlayGame-1) \ \ and the low byte as follows: \ \ high byte + LO(PlayGame-1) - HI(PlayGame-1) \ = HI(PlayGame-1) + LO(PlayGame-1) - HI(PlayGame-1) \ = LO(PlayGame-1) \ \ For the first calculation, we need to apply a little \ hack to the code to get around a limitation in BeebAsm \ that rejects negative constants \ \ HI(PlayGame-1) - %01111111 in the first calculation is \ negative, so to persuade BeebAsm to accept it as a \ constant, we can wrap it in LO() to convert it into \ a two's complement value that BeebAsm will accept \ \ The LO() part is effectively applying MOD 256 in a way \ that works with negative arguments CLC \ Push PlayGame-1 onto the stack, high byte first LDA objectFlags ADC #LO(HI(PlayGame-1) - %01111111) PHA CLC ADC #LO(PlayGame-1) - HI(PlayGame-1) PHA RTS \ Return from the subroutine, which will take the \ address off the stack, increment it and jump to that \ address \ \ So this jumps to PlayGame to play the actual game
Name: landscapeColour2 [Show more] Type: Variable Category: Landscape Summary: Physical colours for colour 2 in the game palette for the different numbers of enemies
Context: See this variable on its own page References: This variable is used as follows: * SpawnEnemies uses landscapeColour2
.landscapeColour2 EQUB 7 \ Enemy count = 1: blue, black, white, green EQUB 3 \ Enemy count = 2: blue, black, yellow, red EQUB 6 \ Enemy count = 3: blue, black, cyan, yellow EQUB 1 \ Enemy count = 4: blue, black, red, cyan EQUB 7 \ Enemy count = 5: blue, black, white, red EQUB 3 \ Enemy count = 6: blue, black, yellow, cyan EQUB 6 \ Enemy count = 7: blue, black, cyan, red EQUB 1 \ Enemy count = 8: blue, black, red, yellow
Name: AddEnemiesToTiles [Show more] Type: Subroutine Category: Landscape Summary: Add the required number of enemies to the landscape, starting from the highest altitude and working down, with one enemy per contour
Context: See this subroutine on its own page References: This subroutine is called as follows: * SpawnEnemies calls AddEnemiesToTiles
.AddEnemiesToTiles JSR GetHighestTiles \ Calculate both the highest tiles in each 4x4 block of \ tiles in the landscape and the altitude of the highest \ tile, putting the results in the following variables: \ \ * maxAltitude contains the altitude of the highest \ tile in each 4x4 block in the landscape \ \ * xTileMaxAltitude contains the tile x-coordinate of \ the highest tile in each 4x4 block in the \ landscape \ \ * zTileMaxAltitude contains the tile z-coordinate of \ the highest tile in each 4x4 block in the \ landscape \ \ * tileAltitude contains the altitude of the highest \ tile in the entire landscape LDX #0 \ We now loop through the number of enemies, adding one \ enemy for each loop and iterating numberOfEnemies \ times, so set an enemy counter in X to count the \ enemies as we add them \ \ If this is a level with only one enemy, then that \ enemy must be the Sentinel, so when X = 0, we add the \ Sentinel to the landscape, otherwise we add a sentry \ \ Enemies are allocated object numbers from 0 to 7, with \ the Sentinel in object #0, and sentries from object #1 \ to object #7 (if there are any) .aden1 STX enemyCounter \ Set enemyCounter to the enemy counter in X, so we can \ retrieve it later in the loop LDA #1 \ Set the object type for object #X to type 1, which STA objectTypes,X \ denotes a sentry, so we spawn sentries in objects #1 \ to #numberOfEnemies (this also sets the type for \ object #0 to type 1, but this gets overridden when \ the Sentinel is spawned as object #0 below) \ We now work down the landscape, from the highest peaks \ down to lower altitudes, looking for suitable tile \ blocks to place an enemy \ \ To do this we start with tile blocks that are at an \ altitude of tileAltitude (which we set above to the \ altitude of the highest tile in the landscape), and we \ work down in steps of 16 .aden2 JSR GetTilesAtAltitude \ Set tilesAtAltitude to a list of tile block numbers \ whose highest tiles in the 4x4 block are at an \ altitude of tileAltitude, returning the length of the \ list in T and a bit mask in bitMask that has a \ matching number of leading zeroes as T BCC aden3 \ If the call to GetTilesAtAltitude returns at least one \ tile block at this altitude then the C flag will be \ clear, so jump to aden3 to add an enemy to one of the \ matched tile blocks LDA tileAltitude \ Otherwise we didn't find any tile blocks at this SEC \ altitude, so subtract 1 from the high nibble of SBC #%00010000 \ tileAltitude to move down one level (we subtract from STA tileAltitude \ the high nibble because tileAltitude contains tile \ data, which has the tile altitude in the high nibble \ and the tile shape in the low nibble) BNE aden2 \ Loop back to check for tile blocks at the lower \ altitude until we have reached an altitude of zero STX numberOfEnemies \ When the GetTilesAtAltitude routine returns with no \ matching tile blocks, it also returns a value of &FF \ in X, so this sets numberOfEnemies to -1 JMP aden6 \ Jump to aden6 to set the value of minEnemyAltitude \ and return from the subroutine .aden3 \ If we get here then we have found at least one tile \ block at the current altitude, so we now pick one of \ them, using the next seed number to choose which one, \ and we then add an enemy to the highest tile in the \ block \ \ We only pick one tile at this altitude so that the \ enemies are spread out over various altitudes JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers AND bitMask \ The call to GetTilesAtAltitude above sets bitMask to a \ bit mask that has a matching number of leading zeroes \ as the number of tile blocks at this altitude, so this \ instruction converts A into a number with the same \ range of non-zero bits as T CMP T \ If A >= T then jump back to fetch another seed number BCS aden3 \ When we get here, A is a seed number and A < T, so \ A can be used as an offset into the list of tile \ blocks in tilesAtAltitude (which contains T entries) TAY \ Set Y to the seed number in A so we can use it as an \ index in the following instruction LDX tilesAtAltitude,Y \ Set X to the Y-th tile block number in the list of \ tile blocks at an altitude of tilesAtAltitude \ We now zero the maximum tile altitudes for tile block \ X and the eight surrounding tile blocks, so that \ further calls to GetTilesAtAltitude won't match these \ tiles, so we therefore won't put any enemies on those \ blocks (this ensures we don't create enemies too close \ to each other) LDA #0 \ Set A = 0 so we can zero the maximum tile altitudes \ for tile block X and the eight surrounding blocks STA maxAltitude-9,X \ Zero the altitudes of the three tile blocks in the row STA maxAltitude-8,X \ in front of tile block X STA maxAltitude-7,X STA maxAltitude-1,X \ Zero the altitudes of tile block X and the two blocks STA maxAltitude,X \ to the left and right STA maxAltitude+1,X STA maxAltitude+7,X \ Zero the altitudes of the three tile blocks in the row STA maxAltitude+8,X \ behind tile block X STA maxAltitude+9,X LDA xTileMaxAltitude,X \ Set (xTile, zTile) to the tile coordinates of the STA xTile \ highest tile in the tile block, which is where we can LDA zTileMaxAltitude,X \ place an enemy STA zTile LDX enemyCounter \ Set X to the loop counter that we stored above BNE aden4 \ If the loop number is non-zero then we are adding a \ sentry, so jump to aden4 \ If we get here then the enemy counter is zero, so we \ are adding the Sentinel and the Sentinel's tower STA zTileSentinel \ Set (xTileSentinel, zTileSentinel) to the tile LDA xTile \ coordinates of the highest tile in the tile block, STA xTileSentinel \ which we put in (xTile, zTile) above, so this is the \ tile coordinate where we now spawn the Sentinel LDA #5 \ Set the object type for object #0 to type 5, which STA objectTypes \ denotes the Sentinel (so the Sentinel is always \ object #0, while other objects that are spawned are \ allocated to object #63 and work down the numbers) LDA #6 \ Spawn the Sentinel's tower (an object of type 6), JSR SpawnObject \ returning the object number of the new object in X \ and currentObject JSR PlaceObjectOnTile \ Place object #X on the tile anchored at (xTile, zTile) \ to place the tower on the landscape LDA #0 \ Set the tower object's objectYawAngle to 0, so it's STA objectYawAngle,X \ facing forwards and into the screen LDX enemyCounter \ Set X to the enemy counter, so X now contains the \ object number of the Sentinel (which is always zero \ as we only add the Sentinel on the first iteration of \ the loop) \ \ We now place the Sentinel object on the tile, which \ therefore places the Sentinel on top of the tower .aden4 JSR PlaceObjectOnTile \ Place object #X on the tile anchored at (xTile, zTile) \ to place the Sentinel or sentry in the correct place \ on the landscape JSR ResetMeanieScan \ Reset the data stored for any meanie scans that the \ enemy has tried in the past, so the enemy can start \ looking for potential meanies with a clean slate JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers LSR A \ Set the C flag to bit 7 of A (this also clears bit 7 \ of A but that doesn't matter as we are about to clear \ it in the next instruction anyway) AND #63 \ Set A to a number in the range 5 to 63 ORA #5 STA enemyTacticTimer,X \ Set the enemy's tactics timer to the number in A, so \ we wait for A * 0.06 seconds before applying tactics \ to this enemy in the ApplyTactics routine (so that's \ somewhere between 5 * 0.06 = 0.3 seconds and \ 63 * 0.06 = 3.8 seconds) LDA #20 \ Set A to either 20 or 236, depending on the value that BCC aden5 \ we gave to the C flag above LDA #236 .aden5 STA enemyYawStep,X \ Set enemyYawStep for the enemy to the value of A, \ so this sets the yaw step (i.e. the rate of rotation) \ to either 20 or 236 \ \ The degree system in the Sentinel looks like this: \ \ 0 \ -32 | +32 Overhead view of object \ \ | / \ \ | / 0 = looking straight ahead \ \|/ +64 = looking sharp right \ -64 -----+----- +64 -64 = looking sharp left \ /|\ \ / | \ \ / | \ \ -96 | +96 \ 128 \ \ The enemyYawStep speed is the angle through which the \ enemy rotates on each rotation, so this means we are \ setting the rotation speed to +20 degrees (a clockwise \ turn) or -20 degrees (an anticlockwise turn) INX \ Increment the enemy loop counter in X CPX numberOfEnemies \ If we have added a total of numberOfEnemies enemies, BCS aden6 \ jump to aden6 to finish off JMP aden1 \ Otherwise loop back to add another enemy .aden6 LDA tileAltitude \ Extract the altitude from tileAltitude, which is in LSR A \ the high nibble (as tileAltitude contains tile data, LSR A \ which has the tile altitude in the high nibble and LSR A \ the tile shape in the low nibble) LSR A STA minEnemyAltitude \ Store the result in minEnemyAltitude, so it contains \ the lowest altitude of the enemies we just added to \ the landscape CLC \ Clear the C flag (though this has no effect as the C \ flag is set as soon as we return to the SpawnEnemies \ routine) RTS \ Return from the subroutine
Name: GetTilesAtAltitude [Show more] Type: Subroutine Category: Landscape Summary: Return a list of tile blocks at a specified altitude
Context: See this subroutine on its own page References: This subroutine is called as follows: * AddEnemiesToTiles calls GetTilesAtAltitude

Argument: tileAltitude The altitude to search for
Returns: C flag Status flag: * Clear if at least one tile block is at an altitude of tileAltitude * Set if no tile blocks are at an altitude of tileAltitude, in which case X is set to &FF tilesAtAltitude A list of tile block numbers whose highest tiles match the altitude in tileAltitude T The number of entries in the list at tilesAtAltitude bitMask A bit mask with a matching number of leading zeroes as T
.GetTilesAtAltitude \ We start by fetching all the 4x4 tile blocks from the \ landscape whose altitude matches tileAltitude (so \ that's all the 4x4 blocks whose highest tile is at an \ altitude of tileAltitude) LDX #63 \ Set an index in X to work through all 4x4 tile blocks, \ of which there are 64 LDY #0 \ Set Y = 0 to count the number of tile blocks whose \ altitude matches tileAltitude .galt1 LDA maxAltitude,X \ If the highest tile in the X-th tile block does not CMP tileAltitude \ have an altitude of tileAltitude, jump to galt2 to BNE galt2 \ move on to the next tile block TXA \ Store the number of the tile block in the Y-th byte of STA tilesAtAltitude,Y \ tilesAtAltitude, so we end up compiling a list of all \ the tile blocks that have an altitude of tileAltitude INY \ Increment the counter in Y as we have just added a \ block number to tilesAtAltitude .galt2 DEX \ Decrement the block counter in X BPL galt1 \ Loop back until we have checked the altitudes of all \ the tile blocks \ By this point we have all the tile blocks with an \ altitude of tileAltitude in a list of length Y at \ tilesAtAltitude TYA \ Set A to the length of the list at tilesAtAltitude BEQ galt4 \ If the list is empty then jump to galt4 return from \ the subroutine with the C flag clear STA T \ Set T to the length of the list at tilesAtAltitude \ We now set bitMask to a bit mask that covers all the \ non-zero bits in the list length in A, so if A is of \ the form %001xxxxx, for example, then bitMask will \ contain %00111111, while A being like %000001xx will \ give a bitMask of %00000111 \ \ To do this we count the number of continuous clear \ bits at the top of A, and then use this as an index \ into the leadingBitMask table \ \ So we count zeroes from bit 7 down until we hit a 1, \ and put the result into Y LDY #&FF \ Set Y = -1 so the following loop counts the number of \ zeroes correctly .galt3 ASL A \ Shift A to the left, moving the top bit into the C \ flag INY \ Increment the zero counter in Y BCC galt3 \ Loop back to keep shifting and counting zeroes until \ we shift a 1 out of bit 7, at which point Y contains \ the length of the run of zeroes in bits 7 to 0 of the \ length of the list at tilesAtAltitude LDA leadingBitMask,Y \ Set bitMask to the Y-th entry from the leadingBitMask STA bitMask \ table, which will give us a bit mask with a matching \ number of leading zeroes as A CLC \ Clear the C flag to indicate that we have successfully \ found at least one tile block that matches the \ altitude in tileAltitude RTS \ Return from the subroutine .galt4 SEC \ If we get here then we have checked all 64 tile blocks \ and none of them are at an altitude of tileAltitude, \ so set the C flag to indicate that the returned list \ is empty RTS \ Return from the subroutine
Name: leadingBitMask [Show more] Type: Variable Category: Landscape Summary: A table for converting the number of leading clear bits in a number into a bit mask with the same number of leading zeroes
Context: See this variable on its own page References: This variable is used as follows: * DitherScreenBuffer uses leadingBitMask * GetTilesAtAltitude uses leadingBitMask
.leadingBitMask EQUB %11111111 EQUB %01111111 EQUB %00111111 EQUB %00011111 EQUB %00001111 EQUB %00000111 EQUB %00000011 EQUB %00000001
Name: GetHighestTiles [Show more] Type: Subroutine Category: Landscape Summary: Calculate both the highest tiles in each 4x4 block of tiles in the landscape and the altitude of the highest tile in the landscape
Context: See this subroutine on its own page References: This subroutine is called as follows: * AddEnemiesToTiles calls GetHighestTiles

Returns: maxAltitude The altitude (i.e. y-coordinate) of the highest tile in each 4x4 block in the landscape xTileMaxAltitude The tile x-coordinate of the highest tile in each 4x4 block in the landscape zTileMaxAltitude The tile z-coordinate of the highest tile in each 4x4 block in the landscape tileAltitude The altitude of the highest tile in the landscape
.GetHighestTiles \ This routine works through the tile corners in the \ landscape in 4x4 blocks and finds the highest flat \ tile within each block, so we can consider putting an \ enemy there \ \ To do this we split the 32x32-corner landscape up into \ 8x8 blocks of 4x4 tile corners each, iterating along \ each row of 4x4 blocks from left to right, and then \ moving back four rows to the next row of 4x4 blocks \ behind \ \ Because the tile corners along the right and back \ edges of the landscape don't have tile altitudes \ associated with them, we ignore those corners LDX #0 \ Set X to loop from 0 to 63, to use as a block counter \ while we work through the landscape in blocks of 4x4 \ tiles, of which there are 64 in total STX tileAltitude \ Set tileAltitude = 0 so we can use it to store the \ maximum tile altitude as we work through the landscape \ (so that's the altitude of the landscape's highest \ tile) .high1 TXA \ Set A = X mod 8 AND #7 \ \ The 32x32-tile landscape splits up into 8x8 blocks of \ 4x4 tiles each, so this sets A to the number of the \ block along the left-to-right x-axis row that we are \ working along (so A goes from 0 to 7 and around again) ASL A \ Set xBlock = A * 4 ASL A \ STA xBlock \ So xBlock is the tile x-coordinate of the tile in the \ front-left corner of the 4x4 block we are analysing \ (so xBlock goes 0, 4, 8 ... 24, 28) TXA \ Set A = X div 8 AND #%00111000 \ \ X is in the range 0 to 64, so this instruction has the \ same effect as AND #%11111000, which is equivalent to \ a div 8 operation \ \ The 32x32-tile landscape splits up into 8x8 blocks of \ 4x4 tiles each, so this sets A to the number of the \ row of 4x4 blocks along the front-to-back z-axis row \ that we are working along (so A goes 0, 8, 16 ... 56) LSR A \ Set zBlock = A / 2 STA zBlock \ \ So zBlock is the tile z-coordinate of the tile in the \ front-left corner of each of the 4x4 blocks in the row \ that we are analysing (so zBlock goes 0, 4, 8 ... 24, \ 28) \ Essentially, by this point we have converted the loop \ counter in X from the sequence 0 to 63 into an inner \ loop of xBlock and an outer loop of zBlock, with both \ variables counting 0, 4, 8 ... 24, 28 \ \ We can now use (xBlock, zBlock) as a tile coordinate \ and we can store the highest tile altitude within each \ 4x4 block using the index in X LDA #0 \ Zero the X-th entry in the maxAltitude table, which STA maxAltitude,X \ is where we will store the highest tile altitude \ within block X LDA #4 \ Set zCounter = 4 to iterate along the z-axis through STA zCounter \ each tile in the 4x4 block we are analysing, so \ zCounter iterates from 4 down to 1 LDA zBlock \ Set zTile = zBlock STA zTile \ \ So we can use zTile as the tile z-coordinate of the \ tile to analyse within the 4x4 block CMP #28 \ If zBlock < 28 then then we are not on the tile row at BCC high2 \ the back of the landscape, so jump to high2 to skip \ the following instruction DEC zCounter \ We are on the tile row at the back of the landscape, \ so set zCounter = 3 so it iterates from 3 down to 1 \ for this block, because the blocks along the back row \ are only three tiles deep (as the landscape is 31 \ tiles deep) .high2 LDA #4 \ Set xCounter = 4 to iterate along the x-axis through STA xCounter \ each tile in the 4x4 block we are analysing, so \ xCounter iterates from 4 down to 1 LDA xBlock \ Set xTile = xBlock STA xTile \ \ So we can use xTile as the tile x-coordinate of the \ tile to analyse within the 4x4 block CMP #28 \ If xBlock < 28 then we are not at the right end of the BCC high3 \ tile row, so jump to high3 to skip the following \ instruction DEC xCounter \ We are at the right end of the tile row, so set \ xCounter = 3 so it iterates from 3 down to 1 for this \ block, because the last block on the row is only three \ tiles across (as the landscape is 31 tiles across) .high3 JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile) \ \ This also sets the tile page in tileDataPage and the \ tile number in Y, so tileDataPage+Y now points to the \ tile data entry in the tileData table AND #%00001111 \ Set A to the tile shape for the tile, which is in the \ bottom nibble of the tile data BNE high5 \ If the shape is non-zero then the tile is not flat, so \ jump to high5 to move on to the next tile in the LDA (tileDataPage),Y \ Set A to the tile data for the tile anchored at \ (xTile, zTile) AND #%11110000 \ Set A to the tile altitude, which is in the top nibble \ of the tile data CMP maxAltitude,X \ If the altitude of the tile we are analysing is lower BCC high5 \ than the altitude we have currently stored in the \ maxAltitude table for this 4x4 tile block, jump to \ high5 to move on to the next tile, as this one isn't \ the highest in either this block or the landscape STA maxAltitude,X \ If we get here then the tile we are analysing is the \ highest in the 4x4 block so far, so store the altitude \ in the maxAltitude table for this 4x4 tile block so \ the table ends up recording the highest tile altitude \ in each 4x4 block CMP tileAltitude \ Set tileAltitude = max(tileAltitude, A) BCC high4 \ STA tileAltitude \ So tileAltitude contains the altitude of the highest \ tile that we've analysed so far, which means that \ tileAltitude ends up being set to the highest value \ in the entire landscape, which is the altitude of the \ highest tile of all .high4 LDA xTile \ Store the x-coordinate of the highest tile corner in STA xTileMaxAltitude,X \ this block (so far) in the xTileMaxAltitude table \ entry for this 4x4 block LDA zTile \ Store the z-coordinate of the highest tile corner in STA zTileMaxAltitude,X \ this block (so far) in the zTileMaxAltitude table \ entry for this 4x4 block .high5 INC xTile \ Increment xTile to move on to the next tile to the \ right, for the inner loop DEC xCounter \ Decrement the x-axis counter within this 4x4 block BNE high3 \ Loop back until we have processed all the tiles in the \ 4x4 block, working from left to right INC zTile \ Increment zTile to move on to the next tile towards \ the back, for the outer loop DEC zCounter \ Decrement the z-axis counter within this 4x4 block BNE high2 \ Loop back until we have processed all the tile rows in \ the 4x4 block, working from front to back INX \ Increment the block counter in X CPX #64 \ Loop back until we have processed all 63 4x4 blocks BCC high1 RTS \ Return from the subroutine
Name: UpdateScanner [Show more] Type: Subroutine Category: Scanner/energy row Summary: Update the scanner, if required
Context: See this subroutine on its own page References: This subroutine is called as follows: * IRQHandler calls UpdateScanner
.UpdateScanner LDA scannerUpdate \ If scannerUpdate is non-zero then the scanner is BNE UpdateScannerNow \ configured to update, so jump to UpdateScannerNow to \ do just that, returning from the subroutine using a \ tail call CMP lastScannerState \ If the scanner state has not changed since the last BEQ scan6 \ time we updated the scanner, jump to scan6 to return \ from the subroutine without updating the scanner \ Otherwise fall through into UpdateScannerNow to update \ the scanner
Name: UpdateScannerNow [Show more] Type: Subroutine Category: Scanner/energy row Summary: Update the scanner to a new state
Context: See this subroutine on its own page References: This subroutine is called as follows: * ProcessPauseKeys calls UpdateScannerNow * UpdateScanner calls UpdateScannerNow * UpdateScanner calls via scan6

Arguments: A The new state of the scanner: * 0 = fill scanner with black * 4 = fill the scanner with static in colour 3 * 8 = fill scanner with green
Other entry points: scan6 Contains an RTS
.UpdateScannerNow STA lastScannerState \ Store the new state of the scanner in lastScannerState \ so we can use this to check for when the scanner state \ changes in the future STA scannerState \ Store the new state of the scanner in scannerState so \ we can refer to it (and possibly change it) during the \ routine \ We start by calculating the screen address of the \ scanner, which is 79 bytes before the start of the \ player's scrolling landscape view (79 bytes represents \ ten character blocks of eight bytes each, less one \ byte, so the scanner address is the second pixel row \ in the character block that's ten blocks from the \ right end of the energy icon and scanner row at the \ top of the screen (the top pixel row contains the \ scanner box border) \ \ viewScreenAddr(1 0) contains the address of the \ latter, so we can simply subtract 79 LDA viewScreenAddr \ Set (A screenAddr) = viewScreenAddr(1 0) - &004F SEC \ = viewScreenAddr(1 0) - 79 SBC #&4F STA screenAddr LDA viewScreenAddr+1 SBC #&00 CMP #&60 \ If the high byte in A < &60 then the new address is BCS scan1 \ before the start of screen memory, so add &20 to the ADC #&20 \ high byte so the address wraps around within the range \ of screen memory between &6000 and &8000 .scan1 STA screenAddr+1 \ Store the high byte of the result, so we now have: \ \ screenAddr(1 0) = viewScreenAddr(1 0) - 79 \ \ with the address wrapped around as required LDA #8 \ The inside portion of the scanner consists of eight STA scannerBlock \ character blocks (the box edges on either side are \ outside of these eight blocks), so set scannerBlock \ to 8 so we can use it to count through the character \ blocks as we draw the scanner .scan2 \ Each character block contains one pixel row at the top \ for the top scanner box edge, then four pixel rows of \ scanner content, followed by another pixel row for the \ bottom scanner box edge \ \ We now draw the four pixel rows of scanner content, \ avoiding drawing over the top and bottom edges LDY #3 \ Set Y = 3 to use as a counter for drawing four pixel \ rows in the drawing loop JSR GetRandomNumber \ Set A to a random number to seed the drawing loop, of \ which we use two bits for each of the four pixel bytes \ we draw JMP scan4 \ Jump to scan4 to join the drawing loop .scan3 LDA scannerStatic \ Shift the random number in scannerStatic to the right LSR A \ by two places LSR A .scan4 STA scannerStatic \ Store the random number in scannerStatic (on the first \ iteration this is the full random number, while on \ subsequent iterations the number is shifted right by \ two places on each loop) AND #%00000011 \ Extract bits 0 and 1 from the random number, so we \ get two different random bits on each iteration and \ a value of A in the range 0 to 3 ORA scannerState \ Add the scanner state to the two random bits to get \ the following: \ \ * If scannerState = 0 then this leaves A in the \ range 0 to 3 \ \ * If scannerState = 4 then this sets A into the \ range 4 to 7 \ \ * If scannerState = 8 then this sets A into the \ range 8 to 11 TAX \ Copy A into X so we can use it as an index into the \ scannerPixelByte table LDA scannerPixelByte,X \ Set A to the X-th pixel byte from the relevant part of \ the scannerPixelByte lookup table, which will return a \ pixel byte containing black, random static or green, \ according to the value of scannerState STA (screenAddr),Y \ Draw the Y-th pixel row for this character block in \ the scanner DEY \ Decrement the pixel row counter in Y BPL scan3 \ Loop back to scan3 to draw the next pixel row until we \ have drawn all four pixel rows \ We now move along the scanner to draw the next \ character block to the right LDA screenAddr \ Set (A screenAddr) = screenAddr(1 0) + 8 CLC ADC #&08 STA screenAddr LDA screenAddr+1 ADC #&00 CMP #&80 \ If the high byte in A >= &80 then the new address is BCC scan5 \ past the end of screen memory, so subtract &20 from SBC #&20 \ the high byte so the address wraps around within the \ range of screen memory between &6000 and &8000 .scan5 STA screenAddr+1 \ Store the high byte of the result, so we now have: \ \ screenAddr(1 0) = screenAddr(1 0) + 8 \ \ with the address wrapped around as required \ \ This updates screenAddr(1 0) to point to the next \ character block to the right, so we move along the \ scanner DEC scannerBlock \ Decrement scannerBlock to move on to the next block \ in the scanner BEQ scan6 \ If we have drawn all eight columns, jump to scan6 to \ return LDA scannerBlock \ If scannerBlock <> 4 then loop back to scan2 to keep CMP #4 \ drawing the scanner BNE scan2 \ If we get here then scannerBlock = 4, so we have drawn \ the left half of the scanner LDA playerTileIsHidden \ If playerTileIsHidden <> 64 then the player's tile is CMP #64 \ not hidden from the Sentinel or sentry doing the scan, BNE scan2 \ so loop back to scan2 to keep drawing the scanner LDA #0 \ If we get here then playerTileIsHidden = 64, so the STA scannerState \ player's tile is hidden from the Sentinel or sentry \ doing the scan \ \ We represent this by only filling the left half of the \ scanner and leaving the right half blank, so set \ scannerState to zero so the rest of the fill routine \ fills the right half with black BEQ scan2 \ Jump back to scan2 to keep drawing the scanner (this \ BEQ is effectively a JMP as A is always zero) .scan6 RTS \ Return from the subroutine
Name: scannerStatic [Show more] Type: Variable Category: Scanner/energy row Summary: Storage for a random number that's used to generate static in the scanner
Context: See this variable on its own page References: This variable is used as follows: * UpdateScannerNow uses scannerStatic
.scannerStatic EQUB 0
Name: scannerBlock [Show more] Type: Variable Category: Scanner/energy row Summary: A counter for the eight character blocks that make up the scanner
Context: See this variable on its own page References: This variable is used as follows: * UpdateScannerNow uses scannerBlock
.scannerBlock EQUB 0
Name: scannerState [Show more] Type: Variable Category: Scanner/energy row Summary: The current state of the scanner (black, static or green)
Context: See this variable on its own page References: This variable is used as follows: * UpdateScannerNow uses scannerState
.scannerState EQUB 0 \ The current state of the scanner: \ \ * 0 = fill the scanner with black \ \ * 4 = fill the scanner with static in colour 3 \ \ * 8 = fill the scanner with green
Name: scannerPixelByte [Show more] Type: Variable Category: Scanner/energy row Summary: Pixel bytes for the three states of the scanner (black, static and green)
Context: See this variable on its own page References: This variable is used as follows: * UpdateScannerNow uses scannerPixelByte
.scannerPixelByte EQUB %00001111 \ Four bytes of colour 1 (black) for scanner state 0 EQUB %00001111 EQUB %00001111 EQUB %00001111 EQUB %10001111 \ Four bytes of static in colour 3 on a background of EQUB %01001111 \ colour 1 (black) EQUB %00101111 EQUB %00011111 EQUB %11111111 \ Four bytes of colour 3 (green) for scanner state 8 EQUB %11111111 EQUB %11111111 EQUB %11111111
Name: ApplyEnemyTactics [Show more] Type: Subroutine Category: Gameplay Summary: Apply tactics to an enemy object, setting things up so the next call applies tactics to the next enemy object
Context: See this subroutine on its own page References: This subroutine is called as follows: * ProcessGameplay calls ApplyEnemyTactics

Arguments: enemyObject The object number of the enemy to which we apply tactics (0 to 7)
.ApplyEnemyTactics TSX \ Store the stack pointer in gameplayStack, so we can STX gameplayStack \ return back to the ProcessGameplay routine from deep \ within the tactics routines if required LDX enemyObject \ Set X to the object number of the enemy to which we \ are thinking of applying tactics in this iteration of \ the gameplay loop (so object #X is the object that we \ are considering) LDA objectTypes,X \ Set A to the type of object #X CMP #1 \ If we are considering applying tactics to a sentry (an BEQ etac1 \ object of type 1) then jump to etac1 to apply tactics CMP #5 \ If we are not applying tactics to the Sentinel (an BNE MoveOnToNextEnemy \ object of type 5), then enemyObject is neither a \ sentry nor the Sentinel \ \ We only apply tactics to the Sentinel and sentries, \ so we don't need to apply tactics to this object \ \ So jump to MoveOnToNextEnemy to stop applying tactics \ to this enemy and set things up so we move on to the \ next enemy in the next iteration of the gameplay loop .etac1 STA titleObjectToDraw \ If we get here then we are applying tactics to the \ Sentinel or a sentry, so set titleObjectToDraw to the \ corresponding object type so if the enemy causes the \ player to lose, then the game over screen will show \ that enemy type to indicate that it was responsible \ for the player's demise LDA objectFlags,X \ Set A to the object flags for object #X (which is the \ object that we are applying tactics to) BPL ApplyTactics \ If bit 7 of the object flags is clear then this object \ number is allocated to a valid object, so jump to \ ApplyTactics to apply tactics to the object and return \ from the subroutine using a tail call \ If we get here then bit 7 of the enemy object's flags \ is clear, so the enemy has been removed from the \ landscape at some point JSR ExpendEnemyEnergy \ Drain one unit of energy from the enemy and expend it \ onto the landscape by spawning a tree, if possible \ \ This ensures that if an enemy has absorbed energy from \ somewhere, it always dissipates back into the \ landscape it when it gets the chance \ \ If we can't spawn a tree because we are about to pan \ the screen and the tree would spawn in a position that \ would be visible on-screen, then the routine does not \ expend energy or spawn a tree, and instead it gives up \ and aborts applying tactics for this gameplay loop \ \ If we successfully spawned a tree then the object \ number of the tree is in X BCS MoveOnToNextEnemy \ If the call to ExpendEnemyEnergy returned with the C \ flag set, then either the enemy didn't have any energy \ to expend or we couldn't spawn a tree, so jump to \ MoveOnToNextEnemy to stop applying tactics to this \ enemy and set things up so we move on to the next \ enemy in the next iteration of the gameplay loop JMP tact25 \ Otherwise jump to tact25 with X set to the object \ number of the tree to update it on-screen with a \ dithered effect and return from the subroutine using \ a tail call
Name: MoveOnToNextEnemy [Show more] Type: Subroutine Category: Gameplay Summary: Update enemyObject so the next time we consider applying enemy tactics, we apply them to the next enemy, looping from 7 to 0
.MoveOnToNextEnemy JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers DEC enemyObject \ Decrement the number of the enemy object that we are \ considering for tactics, so the next iteration of the \ gameplay loop will apply tactics to the next enemy BPL next1 \ If enemyObject is positive then it is still in the \ range 0 to 7, so jump to next1 to skip the following LDA #7 \ Set enemyObject = 7 to wrap the enemy number back up STA enemyObject \ to 7, so it iterates from 7 down to 0 and then back \ to 7 again .next1 LDA playerObject \ Set viewingObject to the object number of the player, STA viewingObject \ so any future view calculations are done from the \ aspect of the player rather than the enemy object \ (until we return to apply tactics to the next enemy) RTS \ Return from the subroutine
Name: ApplyTactics (Part 1 of 8) [Show more] Type: Subroutine Category: Gameplay Summary: Apply tactics to the Sentinel or a sentry
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyEnemyTactics calls ApplyTactics

Arguments: X The object number of the Sentinel or sentry to which we apply tactics (0 to 7) enemyObject Contains the same as X
.ApplyTactics LDA enemyTacticTimer,X \ If enemyTacticTimer >= 2 for this enemy, then jump to CMP #2 \ MoveOnToNextEnemy to jump to MoveOnToNextEnemy to BCS MoveOnToNextEnemy \ stop applying tactics to this enemy and set things up \ so we move on to the next enemy in the next iteration \ of the gameplay loop \ If we get here then enemyTacticTimer for this enemy \ has counted down to be less than 2, so it's time to \ apply tactics to the enemy LDA #4 \ Set enemyTacticTimer for the enemy to 4 so by default STA enemyTacticTimer,X \ we wait for 4 * 0.06 = 0.6 seconds before applying \ tactics to the enemy again (though we may change this) LDA #20 \ Set enemyViewingArc = 20, so the enemy's gaze has a STA enemyViewingArc \ viewing arc that's the same width as the screen (as \ the screen is 20 yaw angles across) LDA enemyMeanieTree,X \ Fetch the value of enemyMeanieTree for the enemy we \ are processing, so A contains tells us whether the \ enemy has turned a tree into a meanie BPL tact1 \ If bit 7 of enemyMeanieTree is clear then the enemy \ has turned a tree into a meanie, so jump to part 2 to \ apply tactics to the meanie JMP tact6 \ Bit 7 of enemyMeanieTree is set so the enemy has not \ turned a tree into a meanie, so jump to part 3 to skip \ the meanie tactics routine
Name: ApplyTactics (Part 2 of 8) [Show more] Type: Subroutine Category: Gameplay Summary: Process the tactics for a meanie
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tact1 STA viewingObject \ If we get here then the enemy has turned a tree into a \ meanie and A contains the meanie's object number \ (which we fetched from enemyMeanieTree in part 1), so \ this sets the viewing object to the meanie so the gaze \ checks below are performed from the point of view of \ the meanie LDY enemyTarget,X \ Set Y to the object number in enemyTarget, so when we \ refer to object #Y, it's the meanie's target object LDA objectFlags,Y \ If bit 7 of the object flags for the meanie's target BMI tact4 \ is set then the target doesn't have an associated \ object, so the player must have transferred and \ reabsorbed the original robot that the meanie was \ targeting, so jump to tact4 to restart the enemy's \ drain counter and turn the meanie back into a tree LDA #0 \ Set A = 0 to pass to CheckEnemyGaze as the object type \ of the target (object type 0 being a robot), so we \ only run the gaze calculations when object #Y is a \ robot JSR CheckEnemyGaze \ Call CheckEnemyGaze to check the gaze of the meanie \ towards the robot, returning the following if the \ the object is a robot (an object of type 0): \ \ * objectViewYaw(Hi Lo) = the yaw angle of the robot \ relative to the view (i.e. relative to the \ meanie's view of the world) \ \ * targetVisibility = bit 7 set if the robot's tile \ is visible, bit 6 set if the robot is visible \ \ * treeVisibility = bit 7 set if there is a tree in \ the way of the robot's tile, bit 6 set if there is \ a tree in the way of the robot LDA objectViewYawHi \ If objectViewYawHi >= 20 then the robot is outside of CMP #20 \ the meanie's viewing arc of 20 yaw angles, so jump to BCS tact2 \ tact2 to rotate the meanie in the direction of the \ robot CPY playerObject \ If the robot is not the player object, then the player BNE tact4 \ must have transferred to a different robot, so jump to \ tact4 to restart the enemy's drain counter and turn \ the meanie back into a tree LDA targetVisibility \ If the meanie can't see the robot or the robot's tile BEQ tact5 \ then targetVisibility will be zero, so jump to tact5 \ to turn the meanie back into a tree \ If we get here then the meanie is looking at a robot, \ the robot is within the meanie's viewing arc, the \ robot is the player object and the meanie can either \ see the robot's tile or the robot itself \ \ This means the meanie can see the player, so it can \ force the player into hyperspace JSR PerformHyperspace \ Hyperspace the player to a brand new tile LDA #4 \ Set titleObjectToDraw to the object type for a meanie, STA titleObjectToDraw \ so if the hyperspace fails because the player doesn't \ have enough energy, then the game over screen will \ show a meanie to indicate that it was responsible for \ the player's demise JMP MoveOnToNextEnemy \ Jump to MoveOnToNextEnemy to stop applying tactics to \ this enemy and set things up so we move on to the next \ enemy in the next iteration of the gameplay loop .tact2 LDA #8 \ Set A = 8 to use as the value of meanieYawStep when \ the meanie needs to rotate clockwise towards the \ player BIT objectViewYawHi \ If objectViewYawHi is positive then the player is to BPL tact3 \ the right of the meanie's viewing arc, so jump to \ tact3 to set the yaw step to +8 so the meanie rotates \ right (clockwise( towards the player to the right LDA #&F8 \ Otherwise objectViewYawHi is negative and the player \ is to the left of the meanie's viewing arc, so set A \ to -8 for the yaw step so the meanie rotates left \ (anticlockwise) towards the player to the left .tact3 STA meanieYawStep \ Store the value of A in meanieYawStep, so we can add \ it to the meanie's yaw angle below to make it rotate \ towards the player LDY enemyObject \ Set X to the object number of the meanie that we are LDX enemyMeanieTree,Y \ processing TXA \ Check to see whether it is safe to redraw the meanie JSR AbortWhenVisible \ without risk of corrupting a screen pan (if there is \ a risk and it can't be updated, then the call to \ AbortWhenVisible will not return here and will instead \ abort tactics for this iteration of the gameplay loop) LDA objectYawAngle,X \ Add meanieYawStep to the yaw angle for the meanie so CLC \ it rotates towards the player ADC meanieYawStep STA objectYawAngle,X LDA #10 \ Set enemyTacticTimer for the meanie to 10 so we wait STA enemyTacticTimer,Y \ for 10 * 0.06 = 0.6 seconds before applying tactics to \ the meanie again TXA \ Store the enemy object number on the stack so we can PHA \ retrieve it below LDX #3 \ Set X = 3 to pass to MakeSound-6 as the pitch of the \ first part of the rotating meanie sound LDY #70 \ Set Y = 70 to pass to MakeSound-6 as the pitch of the \ second part of the rotating meanie sound LDA #1 \ Make sound #1 (rotating meanie) with the pitches in X JSR MakeSound-6 \ and Y PLA \ Retrieve the enemy object number from the stack into X TAX JMP tact26 \ Jump to tact26 to draw the updated object #X without a \ dithered effect, so the meanie rotates instantly if it \ is on the screen .tact4 LDA #0 \ Set enemyDrainTimer = 0 to restart the drain counter STA enemyDrainTimer,X \ for the enemy in part 5 of ApplyTactics, so it doesn't \ drain energy for another 120 timer ticks (120 * 0.06 = \ 7.2 seconds) .tact5 \ If we get here then we need to turn the meanie back \ into a tree LDY enemyObject \ Set X to the object number of the meanie that we are LDX enemyMeanieTree,Y \ processing TXA \ Check to see whether it is safe to redraw the meanie JSR AbortWhenVisible \ without risk of corrupting a screen pan (if there is \ a risk and it can't be updated, then the call to \ AbortWhenVisible will not return here and will instead \ abort tactics for this iteration of the gameplay loop) LDA #%10000000 \ Set bit 7 of enemyMeanieTree so the enemy that turned STA enemyMeanieTree,Y \ a tree into the meanie is no longer flagged as such LDA #2 \ Turn the meanie object back into a tree (an object of STA objectTypes,X \ type 2) JMP tact25 \ Jump to tact25 with X set to the object number of \ the tree to update it on-screen with a dithered effect \ and return from the subroutine using a tail call
Name: ApplyTactics (Part 3 of 8) [Show more] Type: Subroutine Category: Gameplay Summary: If the enemy has any residual energy, try expending it onto the landscape in the form of a tree (and end tactics if successful)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tact6 STX viewingObject \ Set the viewing object to the enemy we are processing, \ so everything is done from their viewpoint from now on JSR ExpendEnemyEnergy \ Drain one unit of energy from the enemy and expend it \ onto the landscape by spawning a tree, if possible \ \ This ensures that if an enemy has absorbed energy from \ somewhere, it always dissipates back into the \ landscape it when it gets the chance \ \ If we can't spawn a tree because we are about to pan \ the screen and the tree would spawn in a position that \ would be visible on-screen, then the routine does not \ expend energy or spawn a tree, and instead it gives up \ and aborts applying tactics for this gameplay loop \ \ If we successfully spawned a tree then the object \ number of the tree is in X BCS tact7 \ If the call to ExpendEnemyEnergy returned with the C \ flag set, then either the enemy didn't have any energy \ to expend or we couldn't spawn a tree, so jump to part \ 4 to keep applying tactics JMP tact25 \ Otherwise that's enough tactics for this iteration, so \ jump to tact25 with X set to the object number of \ the tree to update it on-screen with a dithered effect \ and return from the subroutine using a tail call
Name: ApplyTactics (Part 4 of 8) [Show more] Type: Subroutine Category: Gameplay Summary: If configured, search the landscape for a suitable target for the enemy to drain of energy
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tact7 LDX enemyObject \ Set X to the object number of the enemy to which we \ are applying tactics (so this is now object #X) LDA enemyDrainScan,X \ If bit 7 of enemyDrainScan is clear, jump to part 5 to BPL tact9 \ skip the search for a drainable object JSR FindObjectToDrain \ Find a suitable target object for the enemy to drain \ (i.e. a tree that is stacked on top of another object, \ or a boulder, which is exposed to the enemy and on a \ tile that can be seen by the enemy) \ \ If successful, the object number is in targetObject \ and the C flag is clear, otherwise the C flag is set LDX enemyObject \ Set X to the object number of the enemy to which we \ are applying tactics (so this is now object #X) BCS tact8 \ If FindObjectToDrain set the C flag, then no suitable \ object was found for the enemy to drain, so jump to \ tact8 to stop the enemy from searching again and keep \ applying tactics LDA #64 \ Set enemyMeanieCheck = 64 for the enemy so if we start STA enemyMeanieScan,X \ scanning objects for trees to potentially turn into a \ meanie, we start from the last object and work down BNE tact15 \ Jump to tact15 to drain energy from the target object \ (this BNE is effectively a JMP as A is never zero) .tact8 LSR enemyDrainScan,X \ Clear bit 7 of enemyDrainScan for this enemy to stop \ it searching for a drainable target for now \ Fall through into part 5 to keep applying tactics to \ this enemy
Name: ApplyTactics (Part 5 of 8) [Show more] Type: Subroutine Category: Gameplay Summary: Look for a suitable robot to drain of energy, or look for a drainable tree or boulder if there are no suitable robots
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tact9 LDA enemyDrainTimer,X \ If enemyDrainTimer for the enemy is zero then jump to BEQ tact11 \ tact11 to skip the following, as this timer is not in \ use LDY enemyTarget,X \ Set Y to the object number in enemyTarget, so when we \ refer to object #Y, it's the enemy's target object \ \ If this hasn't been set to a valid object number yet \ then the call to CheckEnemyGaze will simply say the \ object is not visible LDA #0 \ Call CheckEnemyGaze to check the gaze of the enemy JSR CheckEnemyGaze \ towards object #Y, returning the following if the \ the object is a robot (an object of type 0): \ \ * targetVisibility = bit 7 set if the object's tile \ is visible, bit 6 set if the object is visible LDA targetVisibility \ If the target is not a robot, or the target is a robot BEQ tact10 \ but the enemy can't see it, then jump to tact10 to \ keep applying tactics JMP tact19 \ Otherwise the target is a robot and the enemy can see \ at least some of it, so jump to part 7 with the target \ object number in Y to try draining energy from the \ robot .tact10 STA enemyDrainTimer,X \ Set enemyDrainTimer = 0 to restart the drain counter \ for the enemy in part 5 of ApplyTactics, so it doesn't \ drain energy for another 120 timer ticks (120 * 0.06 = \ 7.2 seconds) .tact11 \ We now loop through every object on the landscape, \ looking for a robot that the enemy can drain of energy \ (in other words, a robot whose tile the enemy can see) \ \ If the robot turns out to be the player object, then \ we also check whether the object LDA #%10000000 \ Set bit 7 of playerTileObscured, so by default the STA playerTileObscured \ the player object can't be seen at all (though we will \ change this in the following loop if the enemy can see \ the partially obscured player object) LDY #63 \ Set a counter in Y to work through the object numbers .tact12 LDA #0 \ Call CheckEnemyGaze to check the gaze of the enemy JSR CheckEnemyGaze \ towards object #Y, returning the following if the \ the object is a robot (an object of type 0): \ \ * targetVisibility = bit 7 set if the object's tile \ is visible, bit 6 set if the object is visible \ \ * treeVisibility = bit 7 set if there is a tree in \ the way of the robot's tile, bit 6 set if there is \ a tree in the way of the robot LDA treeVisibility \ If bit 6 of treeVisibility is set then there is a tree AND #%01000000 \ in the way between the enemy and the robot, so jump to BNE tact13 \ tact13 to move on to the next object LDA targetVisibility \ If targetVisibility is zero then both bits 6 and 7 are BEQ tact13 \ clear, so the enemy can't see the robot or the tile \ it's on, so jump to tact13 to move on to the next \ object BMI tact19 \ If bit 7 of targetVisibility is set then the enemy can \ see the robot's tile, so jump to part 7 with the \ robot's object number in Y to try draining energy from \ the robot CPY playerObject \ If the robot is not the player object then jump to BNE tact13 \ tact13 to move on to the next object STY playerTileObscured \ The robot is the player object and we know that bit 6 \ of targetVisibility must be set (as targetVisibility \ is non-zero and bit 7 is clear), so this means the \ enemy can see the player but it can't see the tile \ that the player is on \ \ So set playerTileObscured to the object number of the \ player, so we can process this fact after we have \ finished looping through the object (assuming the \ enemy doesn't first get distracted by a different \ robot whose tile it can see, which will take \ precedence) .tact13 DEY \ Decrement the counter in Y to move on to the next \ object number BPL tact12 \ Loop back to tact12 to check the next object number \ If we get here then we have checked all 64 object \ numbers and none of them are suitable robots for the \ enemy to drain, so now we check whether any of them \ are the partially obscured player object LDY playerTileObscured \ If bit 7 of playerTileObscured is set then we didn't BMI tact14 \ change it to the number of the player object in the \ above loop, so we didn't find a partially obscured \ player object so jump to tact14 to look for trees \ and boulders for the enemy to drain instead \ If we get here then the enemy can see the player \ but it can't see the tile that the player is on, and \ the player object number is in Y TYA \ If the value of enemyFailTarget for this enemy matches CMP enemyFailTarget,X \ the player object number, then this enemy already BEQ tact14 \ tried scanning for a tree to turn into a meanie to \ attack the player and they failed to find one, so jump \ to tact14 to look for trees and boulders for the enemy \ to drain instead JSR ResetMeanieScan \ Reset the data stored for any meanie scans that the \ enemy has tried in the past, so the enemy can start \ looking for potential meanies with a clean slate LDA #%01000000 \ Set targetVisibility to record that the enemy can see STA targetVisibility \ the player object (bit 6 set) but it can't see the \ tile the player is on (bit 7 clear), so we store this \ in the enemy's data when we jump to part 7 BNE tact19 \ Jump to part 7 with the target object number in Y to \ try draining energy from the target object (this BNE \ is effectively a JMP as A is never zero) .tact14 LDA #0 \ Set enemyDrainTimer = 0 to restart the drain counter STA enemyDrainTimer,X \ for the enemy in part 5 of ApplyTactics, so it doesn't \ drain energy for another 120 timer ticks (120 * 0.06 = \ 7.2 seconds) JSR FindObjectToDrain \ Find a suitable target object for the enemy to drain \ (i.e. a tree that is stacked on top of another object, \ or a boulder, which is exposed to the enemy and on a \ tile that can be seen by the enemy) \ \ If successful, the object number is in targetObject \ and the C flag is clear, otherwise the C flag is set BCS tact16 \ If FindObjectToDrain set the C flag, then no suitable \ object was found for the enemy to drain, so jump to \ tact16 to keep applying tactics .tact15 \ If we get here then we drain energy from the target \ object in targetObject JSR DrainObjectEnergy \ Drain energy from the target object into the enemy, \ transforming it into an object with an energy level \ of one unit less (if applicable) \ \ This updates enemyEnergy with the drained energy, so \ it can be expended back onto the landscape later on \ \ It also sets X to the object number of the target that \ has been drained of energy (and which has therefore \ been transformed into a different object type) BCS tact17 \ If DrainObjectEnergy set the C flag then the enemy \ just drained the player object, so jump to tact17 to \ move on to the next enemy in the next iteration of \ the gameplay loop, as we don't need to update the \ player object on-screen (as we never see the player \ object) LDY enemyObject \ Set enemyTacticTimer for the enemy to 30 so we wait LDA #30 \ for 30 * 0.06 = 1.8 seconds before applying tactics STA enemyTacticTimer,Y \ to the enemy again JMP tact25 \ Jump to tact25 with X set to the object number of \ the enemy to update it on-screen with a dithered \ effect (as it has now been transformed into a \ different type of object) and return from the \ subroutine using a tail call .tact16 LDX enemyObject \ Set X to the object number of the enemy to which we \ are applying tactics (so this is now object #X) LDA enemyRotateTimer,X \ If the rotation timer for the enemy is less than 2 CMP #2 \ then it has run all the way down, so jump to part 6 BCC tact18 \ to rotate the enemy .tact17 JMP MoveOnToNextEnemy \ Otherwise jump to MoveOnToNextEnemy to stop applying \ tactics to this enemy and set things up so we move on \ to the next enemy in the next iteration of the \ gameplay loop
Name: ApplyTactics (Part 6 of 8) [Show more] Type: Subroutine Category: Gameplay Summary: Rotate the enemy and make a rotation sound
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tact18 \ If we get here then we need to rotate enemy #X TXA \ Check to see whether it is safe to redraw object #X JSR AbortWhenVisible \ without risk of corrupting a screen pan (if there is \ a risk and it can't be updated, then the call to \ AbortWhenVisible will not return here and will instead \ abort tactics for this iteration of the gameplay loop) LDA objectYawAngle,X \ Set objectYawAngle = objectYawAngle + enemyYawStep CLC \ ADC enemyYawStep,X \ So this rotates the enemy by the yaw angle in this STA objectYawAngle,X \ enemy's enemyYawStep variable LDA #200 \ Set enemyRotateTimer for the enemy to 200 so we wait STA enemyRotateTimer,X \ for 200 * 0.06 = 12 seconds before rotating the enemy \ again JSR ResetMeanieScan \ Reset the data stored for any meanie scans that the \ enemy has tried in the past, so the enemy can start \ looking for potential meanies with a clean slate LDX #7 \ Set X = 7 to pass to MakeSound-6 as the pitch of the \ first part of the rotating enemy sound LDY #120 \ Set Y = 70 to pass to MakeSound-6 as the pitch of the \ second part of the rotating enemy sound LDA #0 \ Make sound #0 (rotating enemy) with the pitches in X JSR MakeSound-6 \ and Y LDX enemyObject \ Set X to the object number of the enemy to which we \ are applying tactics JMP tact26 \ Jump to tact26 to draw the updated object #X without a \ dithered effect, so the enemy rotates instantly if it \ is on the screen
Name: ApplyTactics (Part 7 of 8) [Show more] Type: Subroutine Category: Gameplay Summary: Drain energy from the enemy's target object, or try scanning for a tree to turn into a meanie if the target's tile is obscured
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tact19 \ If we get here then there is a suitable target for the \ enemy to drain, and the target's object number is in Y TYA \ Store the target number in enemyTarget in the enemy's STA enemyTarget,X \ data, so it is set as the enemy's target going forward LDA targetVisibility \ Store the target's visibility in enemyVisibility for STA enemyVisibility,X \ this enemy LDA enemyDrainTimer,X \ If enemyDrainTimer for this enemy is non-zero then it CMP #1 \ is either counting down or has counted down, so jump BCS tact21 \ to tact21 to process this \ If we get here then enemyDrainTimer is zero, so we \ restart the timer from 120 LDA #120 \ Set enemyDrainTimer for this enemy to 120 so the enemy STA enemyDrainTimer,X \ doesn't drain energy for another 120 timer ticks \ (120 * 0.06 = 7.2 seconds) .tact20 JMP MoveOnToNextEnemy \ Jump to MoveOnToNextEnemy to stop applying tactics to \ this enemy and set things up so we move on to the next \ enemy in the next iteration of the gameplay loop .tact21 BNE tact20 \ We jump knowing that the enemyDrainTimer for the enemy \ is 1 or greater, so this jumps to tact20 to move on to \ the next enemy when enemyDrainTimer is greater than 1 \ and is still counting down \ If we get here then enemyDrainTimer for the enemy has \ counted down to 1, so we need to process that LDA targetVisibility \ If bit 7 of targetVisibility is clear then the enemy BPL tact22 \ can't see the tile containing the enemy's target (but \ we know it can see the enemy object as otherwise we \ wouldn't have reached this point), so jump to tact22 \ to consider turning a tree into a meanie \ If we get here then the enemy can see the tile \ containing the target, so it can drain its target of \ energy JSR DrainObjectEnergy \ Drain energy from the target object into the enemy, \ transforming it into an object with an energy level \ of one unit less (if applicable) \ \ This updates enemyEnergy with the drained energy, so \ it can be expended back onto the landscape later on \ \ It also sets X to the object number of the target that \ has been drained of energy (and which has therefore \ been transformed into a different object type) LDY enemyObject \ Set enemyTacticTimer for the enemy to 30 so we wait LDA #30 \ for 30 * 0.06 = 1.8 seconds before applying tactics STA enemyTacticTimer,Y \ to the enemy again BCS tact27 \ If DrainObjectEnergy set the C flag then the enemy \ just drained the player object, so jump to tact27 to \ stop applying tactics to this enemy and set things up \ so we move on to the next enemy in the next iteration \ of the gameplay loop JMP tact25 \ Otherwise it wasn't the player object being drained, \ so jump to tact25 with X set to the number of the \ object that was drained so we update it on-screen with \ a dithered effect (as it has now been transformed into \ a different type of object) and return from the \ subroutine using a tail call .tact22 \ If we get here then the enemy can't see the tile \ containing the enemy's target, but it can see the \ target object, so now we consider turning a tree into \ a meanie JSR ScanForMeanieTree \ Scan through the objects in the landscape to see if \ any of them are trees that are suitable for turning \ into a meanie to attack the target LDY enemyObject \ Set Y to the object number of the enemy to which we \ are applying tactics (so this is now object #Y) BCC tact24 \ If the call to ScanForMeanieTree cleared the C flag \ then we have successfully turned a tree into a meanie, \ so jump to tact24 to set up the enemy's tactics timer \ and redraw the transformed tree on-screen LDA enemyFailCounter,Y \ If enemyFailCounter for this enemy is two or more then CMP #2 \ the enemy has failed to find a suitable tree to turn BCS tact23 \ into a meanie on more than one occasion, so jump to \ tact23 to have a fairly long pause before applying \ tactics to this enemy again LDA #%10000000 \ Otherwise set bit 7 of enemyDrainScan so the next time STA enemyDrainScan,Y \ we apply tactics to the enemy it will immediately scan \ for an object to drain BNE tact27 \ Jump to tact27 to stop applying tactics to this enemy \ and set things up so we move on to the next enemy in \ the next iteration of the gameplay loop (this BNE is \ effectively a JMP as A is always non-zero) .tact23 LDA #0 \ Set enemyDrainTimer = 0 to restart the drain counter STA enemyDrainTimer,Y \ for the enemy in part 5 of ApplyTactics, so it doesn't \ drain energy for another 120 timer ticks (120 * 0.06 = \ 7.2 seconds) BEQ tact27 \ Jump to tact27 to stop applying tactics to this enemy \ and set things up so we move on to the next enemy in \ the next iteration of the gameplay loop (this BEQ is \ effectively a JMP as A is always zero) .tact24 \ If we get here then we have just turned a tree into a \ meanie and the enemy number is in Y LDA #50 \ Set enemyTacticTimer for the enemy to 50 so we wait STA enemyTacticTimer,Y \ for 50 * 0.06 = 3.0 seconds before applying tactics to \ the enemy again LDX enemyMeanieTree,Y \ Set X to the object number of the tree that we just \ turned into a meanie, and fall through into part 8 to \ redraw the transformed object on the screen with a \ dithered effect
Name: ApplyTactics (Part 8 of 8) [Show more] Type: Subroutine Category: Gameplay Summary: Redraw the object on the screen, optionally with a dithered effect
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyEnemyTactics calls via tact25 * ApplyTactics (Part 2 of 8) calls via tact25 * ApplyTactics (Part 3 of 8) calls via tact25 * ApplyTactics (Part 5 of 8) calls via tact25 * ApplyTactics (Part 7 of 8) calls via tact25

Other entry points: tact25 Dither the updated object #X onto the screen, with bit 7 of drawLandscape determining whether the object is drawn on its own (bit 7 set) or with the surrounding landscape (bit 7 clear)
.tact25 \ We jump here to update object #X on-screen by drawing \ the object (or the landscape without the object) onto \ the screen with a dithered effect \ \ This is used to show objects being absorbed, for \ example LDA #%01000000 \ Clear bit 7 and set bit 6 of ditherObjectSights so the STA ditherObjectSights \ updated object is dithered onto the screen in the call \ to DrawUpdatedObject below .tact26 \ If we jump here instead of tact25, then we update \ object #X on-screen, but instantly rather than with a \ dithered effect \ \ This is used to show objects rotating, for example STX currentObject \ Set the current object to X so the call LDA playerObject \ Set viewingObject to the object number of the player STA viewingObject JSR DrawUpdatedObject \ Draw the updated object (or the landscape where the \ object used to be) into the screen buffer and dither \ it onto the screen, pixel by pixel and randomly .tact27 JMP MoveOnToNextEnemy \ Jump to MoveOnToNextEnemy to stop applying tactics to \ this enemy and set things up so we move on to the next \ enemy in the next iteration of the gameplay loop