Processing key presses and enemy tactics with the gameplay loop
Here is a high-level look at the gameplay loop. This loop runs while the landscape view is on-screen and the player is actually playing the game, and it implements the game's key presses and applies tactics to the enemies.
The gameplay loop is run by calling the ProcessGameplay routine from part 2 of the main game loop. The gameplay loop returns control back to the main game loop if the player holds down a pan key long enough for the new part of the landscape to have been fully drawn into the screen buffer; it also returns if the Sentinel wins, or the player moves to a new tile, performs a U-turn or presses the quit key.
You can read all about the main game loop in the deep dive on program flow of the main game loop, but for now let's look at the program flow of the gameplay loop itself.
The gameplay loop
-----------------
The gameplay loop processes all game key presses, only returning to the main game loop when the player moves, quits, loses or completes a full pan.
- Clear bit 7 of focusOnKeyAction to tell the game to apply tactics and check the keyboard in the following step.
- If bit 7 of focusOnKeyAction is clear then the game is not currently focusing on a key action such as a landscape pan, so do the following:
- Check whether we have just finished processing a landscape pan, and if so whether the player is still holding down the same pan key. If they are, set bit 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.
- Call ApplyEnemyTactics to apply tactics to the next enemy object. This applies tactics to one enemy on each iteration of the gameplay loop, working through the enemies at a rate of one per iteration (see the deep dive on enemy tactics for details).
- If the tactics routine has set bit 7 of sentinelHasWon then the Sentinel has won, so call ShowGameOverScreen with A = 30 to display the game over screen, decaying the screen to black with a mass of 30 * 2400 = 72,000 randomly placed black dots. When the effect has finished, return to the main game loop (see program flow of the main game loop for details).
- If the player has moved to a new tile or performed a U-turn, return from the gameplay loop back to the main game loop (see program flow of the main game loop for details).
- If the player has pressed the quit key, return from the gameplay loop back to the main game loop (see program flow of the main game loop for details).
- Call GetPlayerDrain to calculate whether the player is being scanned by an enemy and whether the enemy can see the player's tile.
- Call ProcessPauseKeys to pause or unpause the game when COPY or DELETE are pressed.
- Call ProcessSound to process any sounds or music that are being made in the background.
- Call ProcessVolumeKeys to adjust the volume of the sound envelopes when the volume keys are pressed.
- Loop back to repeat the check of bit 7 of focusOnKeyAction above, as this may have been altered in the meantime, and either repeat the actions above (if it's still clear) or move on to the next step (if it's now set).
- If bit 7 of focusOnKeyAction is set then the game is currently focusing on a key action such as a landscape pan, so check whether the player is still holding down the same pan key. If they are, return to the main game loop to display the pan on-screen.
- If the key logger is empty, jump back to ProcessGameplay to repeat the gameplay loop.
- If the sights are not visible and the player has pressed the key for absorb, transfer, create robot, create tree or create boulder, then we can't process the key press as you can't do any of those things without the sights, so jump back to ProcessGameplay to repeat the gameplay loop.
- 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.
- Call ProcessActionKeys to process the action key press (see below).
- If the call to ProcessActionKeys added or removed an object, do the following:
- Call FlushSoundBuffer0 to flush the sound channel 0 buffer
- Call MakeSound with A = 2 to make sound #2 (create/absorb object white noise)
- Call DrawUpdatedObject to 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.
- Call FlushSoundBuffer0 to flush the sound channel 0 buffer again.
- Call UpdateIconsScanner to update the icons in the top-left corner of the screen to show the player's new energy level and redraw the scanner box.
- If the player has moved to a new tile or performed a U-turn, return from the gameplay loop back to the main game loop (see program flow of the main game loop for details).
- Jump back to ProcessGameplay to repeat the gameplay loop.
Processing action keys
----------------------
The ProcessActionKeys routine processes any action key presses that are captured in the gameplay loop. The action keys are:
- "A" (absorb)
- "Q" (transfer)
- "R" (create robot)
- "T" (create tree)
- "B" (create boulder)
- "H" (hyperspace)
- "U" (U-turn)
Note that the ProcessActionKeys routine contains a separate anti-cracker routine embedded between parts 1 and 2. This is documented in the deep dive on anti-cracker checks, but I have left it out of the following to make things easier to follow.
ProcessActionKeys (Part 1 of 2)
- If "H" (hyperspace) is being pressed, call PerformHyperspace to hyperspace the player to a brand new tile, ideally at the same altitude as the current tile, and set bit 7 of playerHasMovedTile so the landscape gets redrawn. Then return to the gameplay loop.
- If "U" (U-turn) is being pressed, check that we haven't just done a U-turn. If this is the case then rotate the player's yaw angle through 180 degrees to turn them around, call PlayMusic to play the U-turn music, and set bit 7 of playerHasMovedTile so the landscape gets redrawn. Then return to the gameplay loop.
- If we get here then the key press is either create, absorb or transfer, so do the following:
- Call GetSightsVector to get the vector from the player's eyes to the sights.
- Call FollowGazeVector to follow the gaze vector from the player to determine whether they can see a flat tile or a platform (i.e. boulder or tower).
- If the player can't see a tile or platform through the sights, make an error sound and return from the subroutine, as we can't see a suitable surface for the create, absorb or transfer action.
- If the key press is a create key, jump to part 2 to create the relevant object.
- If we get here then the key press is either absorb or transfer, so do the following:
- If the tile in the player's sights does not contain an object, make an error sound and return from the subroutine, as we can't absorb or transfer to an empty tile.
- If the key press is "A" (absorb), jump to part 2 to absorb the object.
- If we get here then the key press must be "Q" (transfer), so do the following:
- If the tile in the player's sights does not contain a robot, make an error sound and return from the subroutine, as the player can only transfer into other robots.
- Call FocusOnKeyAction to tell the game to start focusing effort on the action that has been initiated, i.e. the transfer.
- Set the player's object to the robot, which effectively performs the transfer.
- Fall into part 2 to call PlayMusic to play the transfer music and set bit 7 of playerHasMovedTile so the landscape gets redrawn. Then return to the gameplay loop.
ProcessActionKeys (Part 2 of 2)
- If the key press is "A" (absorb), do the following:
- Check whether the Sentinel object still exists. If it doesn't then it has been absorbed by the player, at which point the player is no longer allowed to absorb other objects, so make an error sound and return from the subroutine.
- If the player is trying to absorb a meanie, do the following:
- Loop through all the enemy objects, of which there are up to eight, looking for the object whose enemyMeanieTree entry matches the meanie that the player is trying to absorb (so we are looking for the enemy that turned a tree into this meanie).
- For each possible parent enemy, confirm that the enemy is either the Sentinel or a sentry, and that the enemy still exists and has not been absorbed, and that the enemy is indeed the creator of the meanie being absorbed.
- If we have managed to find the enemy that created this meanie, unset the enemyMeanieTree entry to remove the enemy's status as a meanie parent.
- Delete the meanie object that the player is absorbing.
- Call UpdatePlayerEnergy to update the player's energy levels by adding the energy from the meanie.
- Return from the subroutine.
- If the player is trying to absorb the Sentinel's tower, make an error sound and return from the subroutine.
- Otherwise the player is absorbing a tree, boulder, robot, sentry or the Sentinel, so do the following:
- Call DeleteObject to delete the object and remove it from the landscape.
- Call UpdatePlayerEnergy to update the player's energy levels by adding the amount of energy from the absorbed object.
- Return from the subroutine.
- If the key press is a create key ("T" for tree, "B" for boulder or "R" for robot), do the following:
- Call SpawnObject to add a new object of the correct type to the objectTypes table (so that's a tree, boulder or robot).
- If the object could not be spawned, make an error sound and return from the subroutine.
- Call UpdatePlayerEnergy to update the player's energy levels by subtracting the energy required to create the object from the player.
- If the creation of the object reduces the player's energy below zero, make an error sound and return from the subroutine.
- Call PlaceObjectOnTile to place the object on the target tile, putting it on top of any existing boulders or towers.
- If the object could not be placed on the tile, call UpdatePlayerEnergy to refund the energy that the player just spent, make an error sound and return from the subroutine.
- If we just created a robot, set the yaw angle for the newly created robot to the player's yaw angle rotated through 180 degrees, so the new robot faces the player.
- Return from the subroutine.