.CheckEnemyGaze STA T \ Store the object type in T so we can refer to it later STX xStoreEnemyGaze \ Store X in xStoreEnemyGaze so it can be preserved \ across calls to the routine STY targetObject \ Store the target object in targetObject, so object #Y \ is the target object LDA #0 \ Clear all bits of targetVisibility so we can set them STA targetVisibility \ later with the results of the analysis LDA objectFlags,Y \ If bit 7 is set for object #Y then this object number BMI egaz3 \ is not allocated to an object, so jump to egaz3 to \ return from the subroutine with the C flag clear to \ indicate that the target object can't be seen by the \ enemy LDA objectTypes,Y \ If object #Y is not an object of type T (which we set CMP T \ to the argument A above), then jump to egaz3 to return BNE egaz3 \ from the subroutine with the C flag clear to indicate \ that the target object is of the wrong type JSR GetObjectAngles \ Calculate the angles and distances of the vector from \ the viewer to object #Y and put them into the \ following variables: \ \ * Set objTypeToAnalyse to the type of object #Y \ \ * Set objectViewYaw(Hi Lo) to the yaw angle of the \ viewer's gaze towards the object, relative to the \ screen \ \ * Set angle(Hi Lo) to the angle of the hypotenuse of \ the triangle formed by the x-axis and z-axis, \ which is the projection of the 3D vector from the \ viewer to the object down onto the ground plane \ (so imagine a light shining down from above, \ casting the vector's shadow onto the y = 0 plane, \ and that's the hypotenuse) \ \ * Set hypotenuse(Hi Lo) to the length of the 3D \ vector from the viewer to the object when \ projected down onto the ground plane \ \ * Set yDelta(Hi Lo) to the difference (the delta) in \ the y-axis between the viewer and the object we are \ analysing, so this gives us the altitude difference \ between the viewer and the object \ We now have a very short interlude to set up some of \ the anti-cracker code before continuing in part 2Name: CheckEnemyGaze (Part 1 of 2) [Show more] Type: Subroutine Category: Gameplay Summary: Check to see whether the current enemy can see a specific target object of a specific typeContext: See this subroutine on its own page References: This subroutine is called as follows: * ApplyTactics (Part 2 of 8) calls CheckEnemyGaze * ApplyTactics (Part 5 of 8) calls CheckEnemyGaze * FindObjectToDrain calls CheckEnemyGaze * ScanForMeanieTree calls CheckEnemyGaze
Arguments: A The type of the object to match against the target (this ensures that we only perform the gaze calculations when the target object is of this type; if it isn't, the routine aborts without doing the calculations) Y The number of the target object to analyse viewingObject The viewing object (i.e. the enemy doing the scanning)
Returns: targetVisibility The target visibility result: * If the target object is a robot, then bit 6 will be set if the robot object can be seen, even if the robot's tile can't be seen * Bit 7 will be set if the enemy can see the target object's tile treeVisibility The tree visibility result: * If the target object is a robot, then bit 6 will be set if there is a tree blocking the view of the robot object * Bit 7 will be set if there is a tree blocking the view of the target object's tile C flag Status flag (though note that this is ignored for every call to this routine): * Clear if the target object does not exist, or is not of the specified type, or the object is in the viewing arc * Set if the target object is not within the viewing arc of the enemy X X is preserved Y Y is preserved.AlterCrackerSeed LDX #7 \ Set X = 7, to add to the cracker seed when its last \ digit is 9, so we effectively add 700 to the \ landscape number LDA CrackerSeed+1-7,X \ Set T to the contents of CrackerSeed+1, which we set STA T \ in the SetCrackerSeed routine to the high byte of the \ binary coded decimal (BCD) landscape number \ \ So if the landscape number 0123, T contains 01 in BCD \ format AND #%00001111 \ Set A to the second digit in the BCD landscape number CMP #9 \ If the second digit in the BCD number is 9, skip the BEQ alts1 \ following instruction so that X stays set to 7 LDX #1 \ Otherwise set X = 1, for when the second digit is in \ the range 0 to 8, so we effectively add 100 to the \ landscape number .alts1 TXA \ Set alteredSeed = T + X CLC \ ADC T \ Note that this addition is not done in BCD mode, so we STA alteredSeed \ know that the following is true: \ \ alteredSeed > T \ \ so: \ \ alteredSeed > CrackerSeed+1 \ \ as the largest possible value of T is &99 when the \ value of X is the largest possible at 7, and: \ \ &99 + 7 = &A0 \ \ so the addition never overflows and the result in \ alteredSeed is greater than the original high byte in \ CrackerSeed+1 \ \ This fact is used in the CheckCrackerSeed routine to \ ensure that this part of the anti-cracker code has \ been run correctly \ Fall through into part 2 of CheckEnemyGaze to continue \ checking the enemy's gazeName: AlterCrackerSeed [Show more] Type: Subroutine Category: Cracker protection Summary: Create an altered version of the anti-cracker seed-related data, as part of the anti-cracker codeContext: See this subroutine on its own page References: No direct references to this subroutine in this source fileLDA enemyViewingArc \ Set T = enemyViewingArc / 2 LSR A \ STA T \ So T is the yaw width of half the enemy's viewing arc, \ which is always 10 as enemyViewingArc is only ever set \ to 20 LDA objectViewYawHi \ Set A = objectViewYawHi - 10 + T SEC \ SBC #10 \ This sets A to the yaw angle of the viewer's gaze CLC \ towards the object, but relative to the enemy's ADC T \ viewing arc rather than the screen CMP enemyViewingArc \ If A >= enemyViewingArc then the enemy's gaze towards BCS egaz4 \ the target object falls outside the viewing arc, so \ the enemy can't see the target object, so jump to \ egaz4 to return from the subroutine with the C flag \ set to indicate that the target object can't be seen LDA angleLo \ Set vectorYawAngle(Hi Lo) = angle(Hi Lo) STA vectorYawAngleLo \ LDA angleHi \ So vectorYawAngle(Hi Lo) is the yaw angle of the enemy STA vectorYawAngleHi \ gaze towards the object, as taken from the triangle on \ the ground LDA #2 \ Set gazeCheckCounter = 2 so we perform the gaze check STA gazeCheckCounter \ twice in the loop below, once for the gaze from the \ enemy to the target object, and once for the gaze from \ the enemy to the target object's tile LDA objTypeToAnalyse \ Set A to the type of object #Y, i.e. the object being \ looked at by the enemy, which was set by the call to \ GetObjectAngles above BNE egaz2 \ If the enemy is not looking at a robot (an object of \ type 0), then jump to egaz2 to skip the first check \ against the target object and move on to checking the \ target object's tile \ If we get here then the enemy is looking towards a \ robot, so we now check whether the enemy can actually \ see the robot object \ \ The results will end up in bit 6 of targetVisibility \ and treeVisibility, as the first pass will rotate the \ results into bit 7 of the result variables, and the \ second pass will then rotate this first result down \ into bit 6 SEC \ Set bit 7 of enemyCheckingRobot so the call to the ROR enemyCheckingRobot \ FollowGazeVector routine below will know that the \ enemy is looking towards a robot \ \ This tells FollowGazeVector to return a positive \ result for looking at the robot even when the viewer \ is lower than the robot, so even though the enemy \ can't see the tile that the robot is standing on, it \ will still record that the enemy can see the robot \ object (so the robot is partially visible and we can \ use this to fill in the scanner halfway) LDA yDeltaLo \ Set (A xDeltaLo) = yDelta(Hi Lo) STA xDeltaLo \ LDA yDeltaHi \ The call to GetObjectAngles set yDelta(Hi Lo) to the \ difference in altitude between the enemy and the \ object, so we put this into (A xDeltaLo) to pass to \ GetPitchAngleDelta to calculate the pitch angle of the \ gaze from the enemy to the object \ Because the enemy is looking towards a robot, we do \ the following loop twice, using the counter we set in \ gazeCheckCounter \ \ The first time we follow the gaze from the enemy to \ the target object, and the second time we follow the \ gaze from the enemy to the target object's tile .egaz1 JSR GetPitchAngleDelta \ Set angle(Hi Lo) to the pitch angle of the vector \ along the hypotenuse of the vertical pitch angle \ triangle, given a vertical opposite side of length \ (A xDeltaLo) and an adjacent side along the ground of \ length hypotenuse(Hi Lo) \ \ So we now have the pitch angle of the vector from the \ enemy to the object LDA angleLo \ Set vectorPitchAngle(Hi Lo) = angle(Hi Lo) STA vectorPitchAngleLo \ STA T \ So vectorYawAngle(Hi Lo) is the pitch angle of the LDA angleHi \ enemy towards the object, as taken from the vertical STA vectorPitchAngleHi \ triangle with the gaze vector as the hypotenuse \ \ This also sets T to the low byte in vectorPitchAngleLo JSR GetVectorForAngles \ Convert the pitch and yaw angles of the enemy's gaze \ vector towards the object: \ \ vectorPitchAngle(Hi Lo) \ \ vectorYawAngle(Hi Lo) \ \ into a cartesian vector: \ \ [ xVector(Lo Bot) ] \ [ yVector(Lo Bot) ] \ [ zVector(Lo Bot) ] \ \ So this is the gaze vector from the enemy to the \ object we are checking JSR FollowGazeVector \ Follow the gaze vector from the viewing object to \ determine whether the enemy can see a flat tile or \ platform (i.e. boulder or tower) or the target object \ \ This sets the C flag as follows: \ \ * Clear if the viewing object can see a tile along \ the gaze vector \ \ * Set if the viewing object can't see a tile along \ the gaze vector \ \ It also sets bit 7 of targetOnTile depending on \ whether the gaze vector can see a tile containing the \ object whose number is in targetObject: \ \ * Bit 7 clear = gaze vector cannot see the target \ object \ \ * Bit 7 set = gaze vector can see the target object \ \ and if the gaze cannot see the target, then bit 7 of \ gazeCanSeeTree is set depending on whether the gaze \ vector can see a tree: \ \ * Bit 7 clear = gaze vector cannot see a tree \ \ * Bit 7 set = gaze vector can see a tree \ \ If we call this with bit 7 of enemyCheckingRobot set, \ which we do on the first pass when the enemy is \ looking at a robot, then this will return a positive \ response even if the enemy is at a lower altitude than \ the robot and can't see the tile that the robot is on, \ so this lets us record partial visibility of robots \ The following rotations ensure that we record the \ results correctly in the targetVisibility and \ treeVisibility variables: \ \ * If the enemy is looking at a robot then they will \ ensure that bit 6 records the results when looking \ at the robot, and bit 7 records the results when \ looking at the robot's tile \ \ * If the enemy is not looking at a robot then they \ will ensure that bit 7 records the results of \ looking at the target object's tile ROL targetOnTile \ Rotate bit 7 of targetOnTile into bit 7 of ROR targetVisibility \ targetVisibility ROL gazeCanSeeTree \ Rotate bit 7 of gazeCanSeeTree into bit 7 of ROR treeVisibility \ treeVisibility .egaz2 LSR enemyCheckingRobot \ Clear bit 7 of enemyCheckingRobot to pass to the next \ call to FollowGazeVector, so it returns results for \ the tile visibility rather than the object itself (so \ this will be on the second call if we are looking at a \ robot, or the only call if we are not looking at a \ robot) LDA yDeltaLo \ Set (A xDeltaLo) = yDelta(Hi Lo) - &E0 SEC \ = yDelta(Hi Lo) - 224 SBC #&E0 \ STA xDeltaLo \ The object is spawned at a y-coordinate of 224 above LDA yDeltaHi \ the tile, so this sets (A xDeltaLo) to the altitude of SBC #&00 \ the target object's tile rather than the target object \ itself DEC gazeCheckCounter \ Decrement the loop counter in gazeCheckCounter BNE egaz1 \ Loop back to repeat the check, but this time using the \ gaze vector from the enemy to the target object's tile .egaz3 CLC \ Clear the C flag to return from the subroutine to \ indicate that we have performed the gaze checks .egaz4 LDX xStoreEnemyGaze \ Restore the value of X from xStoreEnemyGaze that we \ stored at the start of the routine, so that it's \ preserved LDY targetObject \ Set Y to targetObject so that Y is preserved RTS \ Return from the subroutineName: CheckEnemyGaze (Part 2 of 2) [Show more] Type: Subroutine Category: Gameplay Summary: Calculate whether the current enemy can see the specified objectContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.xStoreEnemyGaze EQUB 0Name: xStoreEnemyGaze [Show more] Type: Variable Category: Gameplay Summary: Temporary storage for X so it can be preserved through calls to CheckEnemyGazeContext: See this variable on its own page References: This variable is used as follows: * CheckEnemyGaze (Part 1 of 2) uses xStoreEnemyGaze * CheckEnemyGaze (Part 2 of 2) uses xStoreEnemyGaze.GetPlayerDrain LDY #0 \ Set Y = 0 to use as the default state of the scanner \ (fill scanner with black) STY T \ Set T = 0 to store in playerTileIsHidden after the \ loop below, so the default value is that the player \ is not exposed to an enemy scan (though we may change \ this) LDX #7 \ We now loop through all eight enemies, so set an enemy \ counter in X .pdra1 LDA objectFlags,X \ If bit 7 is set for object #Y then this object number BMI pdra3 \ is not allocated to an object, so jump to pdra3 to \ move on to the next enemy object LDA objectTypes,X \ Set A to the object type for the enemy in object #X CMP #1 \ If object #X is a sentry (an object of type 1) then BEQ pdra2 \ jump to pdra2 CMP #5 \ If object #X is not the Sentinel (an object of type BNE pdra3 \ 5), then jump to pdra3 to move on to the next enemy \ object .pdra2 \ If we get here then object #X is the Sentinel or a \ sentry LDA enemyTarget,X \ If the enemy's target is not the player, jump to pdra3 CMP playerObject \ to move on to the next enemy object BNE pdra3 LDA enemyDrainTimer,X \ If the drain timer for the enemy is zero, jump to BEQ pdra3 \ pdra3 to move on to the next enemy object \ If we get here then the enemy is targeting the player \ and has a non-zero drain timer, so the player is at \ least partially exposed to an enemy who can drain them LDY #4 \ Set Y = 4 to use as the state of the scanner (fill the \ scanner with static in colour 3) LDA enemyVisibility,X \ Set T to the visibility of the enemy's target (the STA T \ player) to store in playerTileIsHidden after the loop \ \ This will either have bit 7 set (target's tile is \ visible) or bit 6 set (target's tile is not visible \ but target object is) BMI pdra4 \ If bit 7 of enemyVisibility is set then the enemy can \ see the player's tile, so jump to pdra4 to record this \ fact \ \ If bit 7 of enemyVisibility is clear then the enemy \ can see the player object but it can't see the \ player's tile, so we keep looping in case there is \ another target whose tile the enemy can see, as this \ will take precedence over the partially exposed player .pdra3 DEX \ Decrement the enemy counter in X BPL pdra1 \ Loop back until we have checked all eight enemies .pdra4 STY scannerUpdate \ Set scannerUpdate to the value of Y, which will be: \ \ * Zero if the player is not exposed to the enemy (so \ the scanner will not be updated) \ \ * Non-zero (i.e. 4) if the player is exposed to the \ enemy (so the scanner will then be updated) LDA T \ Set playerTileIsHidden to the value of T, which will STA playerTileIsHidden \ be: \ \ * Zero if the player is not exposed to the enemy \ \ * 128 (i.e. bit 7 set) if the player's tile is \ exposed to the enemy \ \ * 64 (i.e. bit 6 set) if the player is exposed to \ the enemy but the enemy can't see the player's \ tile \ \ This value is used in the UpdateScannerNow routine \ to determine whether we show the scanner with a full \ display of static (not 64) or only half (64) LDA soundEffect \ Set A to soundEffect, which determines how the current \ sound is processed by the ProcessSound routine CPY soundEffect \ Set the flags on the comparison of the current setting \ of soundEffect and the value of Y STY soundEffect \ Set soundEffect to Y, which will be: \ \ * Zero if the player is not exposed to the enemy (so \ no sound processing is required) \ \ * Non-zero (i.e. 4) if the player is exposed to the \ enemy (so the scanner sound is processed in the \ ProcessSound routine) BEQ pdra5 \ If the value of soundEffect has not changed, jump to \ pdra5 to return from the subroutine LDY #&12 \ Set the first parameter of sound block #2 (channel) to STY soundData+16 \ &12, so we make the scanner sound on channel 2 and \ with a flush control of 1 to make the sound instantly CMP #3 \ We set A to soundEffect above, so if soundEffect = 3, BEQ pdra5 \ which denotes that we are currently applying the music \ sound effect, jump to pdra5 to return from the \ subroutine to let the music sound finish first LDX #6 \ Otherwise we are not currently applying the music JSR FlushBuffer \ sound effect, so call the FlushBuffer routine with \ X = 6 to flush the sound channel 2 buffer .pdra5 RTS \ Return from the subroutineName: GetPlayerDrain [Show more] Type: Subroutine Category: Gameplay Summary: Calculate whether the player is being scanned by an enemy and whether the enemy can see the player's tileContext: See this subroutine on its own page References: This subroutine is called as follows: * ProcessGameplay calls GetPlayerDrain.ResetMeanieScan LDA #%10000000 \ Set bit 7 of enemyMeanieTree for object #X to indicate STA enemyMeanieTree,X \ that the enemy has not turned a tree into a meanie STA enemyFailTarget,X \ Set bit 7 of enemyFailTarget for object #X to remove \ the details of any failed meanie scans for this enemy LDA #0 \ Zero enemyFailCounter for object #X to reset the STA enemyFailCounter,X \ recorded number of failed meanie scans for this enemy 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 RTS \ Return from the subroutineName: ResetMeanieScan [Show more] Type: Subroutine Category: Gameplay Summary: Reset the data stored for any meanie scans that the enemy has tried in the past, so we can start looking with a clean slateContext: See this subroutine on its own page References: This subroutine is called as follows: * AddEnemiesToTiles calls ResetMeanieScan * ApplyTactics (Part 5 of 8) calls ResetMeanieScan * ApplyTactics (Part 6 of 8) calls ResetMeanieScan
Arguments: X The object number of the enemy being added.ScanForMeanieTree LDA #40 \ Set enemyViewingArc = 40, so the enemy's gaze has a STA enemyViewingArc \ viewing arc that's twice the width of the screen (as \ the screen is 20 yaw angles across) LDX enemyObject \ Set the viewing object to the enemy so the gaze checks STX viewingObject \ below are performed from the point of view of the \ enemy object .mean1 LDX enemyObject \ Set X to the object number of the enemy to which we \ are applying tactics (so this is now object #X) LDY enemyMeanieScan,X \ Set Y to the object number that we have reached while \ scanning for a tree to turn into a meanie, which is \ stored in the enemyMeanieScan for this enemy BNE mean2 \ If enemyMeanieScan is non-zero then we have not yet \ scanned every object for a tree to turn into a meanie, \ so jump to mean2 to check the next object INC enemyFailCounter,X \ We just failed to find a suitable tree to attack the \ target, so increment the failure counter for the enemy \ in enemyFailCounter LDA enemyTarget,X \ We just failed to find a suitable tree to attack the STA enemyFailTarget,X \ target, so store the target number in enemyFailTarget \ for this enemy to record this fact, so the enemy \ doesn't try spawning a meanie for this target again \ (at least, until the enemy's data is reset) SEC \ Set the C flag to indicate that we have not been able \ to find a suitable tree to turn into a meanie RTS \ Return from the subroutine .mean2 DEC enemyMeanieScan,X \ Decrement the object number that we are checking for \ meanie potential, so we work our way down the object \ list DEY \ We already set Y to the object number before jumping \ here, so decrement Y as well, so object #Y is our \ potential meanie LDA objectFlags,Y \ If bit 7 of the object flags for the potential meanie BMI mean1 \ is set then the object number doesn't have an \ associated object, so jump to mean1 to move on to the \ next object to check that instead LDA objectTypes,Y \ If the potential meanie is not a tree (an object of CMP #2 \ type 2) then it can't be turned into a meanie, so jump BNE mean1 \ to mean1 to move on to the next object to check that \ instead LDA enemyTarget,X \ Set X to the enemy's current target object, so object TAX \ #X is the target object LDA xObject,X \ Set A to the difference in x-coordinate between object SEC \ #X and object #Y (i.e. between the target and SBC xObject,Y \ potential meanie) BPL mean3 \ If the result is positive, jump to mean3 as the result \ is already the correct sign EOR #&FF \ Negate A using two's complement, so that A is now CLC \ positive and contains the absolute value of the ADC #1 \ distance in the x-axis between the target and \ potential meanie .mean3 CMP #10 \ If the target and potential meanie are ten or more BCS mean1 \ tiles apart then this is too far away from the target \ to be turned into a meanie, so jump to mean1 to move \ on to the next object to check that instead LDA zObject,X \ Set A to the difference in z-coordinate between object SEC \ #X and object #Y (i.e. between the target and SBC zObject,Y \ potential meanie) BPL mean4 \ If the result is positive, jump to mean4 as the result \ is already the correct sign EOR #&FF \ Negate A using two's complement, so that A is now CLC \ positive and contains the absolute value of the ADC #1 \ distance in the z-axis between the target and \ potential meanie .mean4 CMP #10 \ If the target and potential meanie are ten or more BCS mean1 \ tiles apart then this is too far away from the target \ to be turned into a meanie, so jump to mean1 to move \ on to the next object to check that instead LDA #2 \ Call CheckEnemyGaze to check the gaze of the enemy JSR CheckEnemyGaze \ towards the potential meanie in object #Y, returning \ the following if object #Y is a tree (an object of \ type 2), which we have already confirmed: \ \ * targetVisibility = bit 7 set if the tree's tile \ is visible, bit 6 set if the tree is visible LDA targetVisibility \ If bit 7 of targetVisibility is clear then the enemy BPL mean1 \ can't see the tree's tile, so it can't turn it into \ a meanie, so jump to mean1 to move on to the next \ object to check that instead LDX enemyObject \ Set X to the object number of the enemy to which we \ are applying tactics (so this is now object #X) TYA \ Set A to the object number of the potential meanie JSR CheckObjVisibility \ Check whether the potential meanie in object A is \ visible and could therefore be seen on-screen by the \ player BCC mean5 \ If the C flag is clear then we are about to repeat the \ same screen pan (as the player is still holding down \ the same pan key) and the potential meanie would be \ visible in the landscape view if we drew it again \ \ This means that when we pan the screen, the new part \ of the screen that pans into view might show part of \ the meanie while the rest of the screen won't, and \ that won't look good \ \ So jump to mean5 to stop processing this potential \ meanie for now, but set things up so we can come back \ to it the next time we process tactics for this enemy, \ as the player might not be panning the screen by then \ If we get here then we have found a suitable tree to \ turn into a meanie, so let's do that TYA \ Set enemyMeanieTree for this enemy to Y, as object #Y STA enemyMeanieTree,X \ is the object number of the tree that we are going to \ turn into a meanie, and storing it in enemyMeanieTree \ records the fact that the enemy has changed this \ object from a tree into a meanie LDA #4 \ Set the type of object #Y to 4, so it turns into a STA objectTypes,Y \ meanie (an object of type 4) LDA #104 \ Set minObjWidth = 104 so we use the width of the wider STA minObjWidth \ object, the tree, when we calculate the object's \ visibility when updating the object on-screen CLC \ Clear the C flag to indicate that we have successfully \ turned a tree into a meanie RTS \ Return from the subroutine .mean5 INC enemyMeanieScan,X \ If we get here then we have found a suitable tree to \ turn into a meanie but updating it on-screen might \ corrupt the landscape view as a pan is in progress \ \ So increment the object number in enemyMeanieScan, so \ the next time we apply tactics to this enemy, we can \ pick up where we left off, by which time the player \ might no longer be panning the screen JMP FinishEnemyTactics \ Jump to FinishEnemyTactics to stop applying tactics to \ the current enemy and return to the ProcessGameplay \ routine to continue with the gameplay loopName: ScanForMeanieTree [Show more] Type: Subroutine Category: Gameplay Summary: Scan through the objects in the landscape to see if any of them are trees that are suitable for turning into a meanieContext: See this subroutine on its own page References: This subroutine is called as follows: * ApplyTactics (Part 7 of 8) calls ScanForMeanieTree
Returns: C flag The result of the tree scan: * Clear = we have successfully turned a tree into a meanie * Set = we have not been able to find a suitable tree to turn into a meanie.dobj1 \ If we jump here from below here then the player has \ run out of energy LDA #%10000000 \ Set bit 7 of sentinelHasWon to indicate that the STA sentinelHasWon \ player has run out of energy and the Sentinel has won JMP FinishEnemyTactics \ Jump to FinishEnemyTactics to stop applying tactics to \ the current enemy and return to the ProcessGameplay \ routine to continue with the gameplay loop .DrainObjectEnergy LDX targetObject \ Set X to the object number that is being drained of \ energy CPX playerObject \ If this is not the player object, jump to dobj2 to BNE dobj2 \ drain energy from object #X rather than the player \ If we get here then this is the player object, so we \ now drain energy from the player LDA playerEnergy \ If the player has no energy then draining more energy BEQ dobj1 \ will kill them, so jump to dobj1 to process this SEC \ Subtract 1 from the player's energy SBC #1 STA playerEnergy 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 LDA #5 \ Make sound #5 (ping) JSR MakeSound SEC \ Set the C flag to indicate that the player still has a \ non-zero energy level JMP dobj7 \ Jump to dobj7 to finish up and return from the \ subroutine .dobj2 \ If we get here then we need to drain energy from \ object #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 objectTypes,X \ Set A to the type of object #X BNE dobj3 \ If object #X is not a robot (i.e. not an object of \ type 0), jump to dobj3 \ If we get here then we are draining energy from a \ robot in object #X LDY enemyObject \ Set enemyDrainTimer = 0 to restart the drain counter LDA #0 \ for the enemy in part 5 of ApplyTactics, so it doesn't STA enemyDrainTimer,Y \ drain energy for another 120 timer ticks (120 * 0.06 = \ 7.2 seconds) LDA #3 \ Set A = 3 so the robot loses one energy unit and \ changes into a boulder (i.e. an object of type 3) BNE dobj5 \ Jump to dobj5 to set the new object type of object #X \ to the value in A, so the robot turns into a boulder .dobj3 CMP #2 \ If object #X is not a tree (i.e. not an object of BNE dobj4 \ type 2), jump to dobj4 \ If we get here then we are draining energy from a \ tree in object #X JSR DeleteObject \ Delete object #X and remove it from the landscape, \ as trees only have one energy unit, so draining one \ unit from a tree removes it altogether JMP dobj6 \ Jump to dobj6 to skip updating the energy for object \ #X, as we just deleted it .dobj4 \ If we get here then we must be draining energy from a \ boulder in object #X, so now we change it into a tree LDA #116 \ Set minObjWidth = 116 so we use the width of the wider STA minObjWidth \ object, the boulder, when we calculate the object's \ visibility when updating the object on-screen LDA #2 \ Set A = 2 so the boulder loses one energy unit and \ changes into a tree (i.e. an object of type 2) .dobj5 STA objectTypes,X \ Set the object type of object #X to the new type in A .dobj6 CLC \ Clear the C flag to return from the subroutine .dobj7 PHP \ Store the C flag on the stack so we can retrieve it \ below to return from the subroutine LDY enemyObject \ Increment the energy level for the enemy object by one LDA enemyEnergy,Y \ unit, as the enemy just absorbed one unit from the CLC \ target object ADC #1 STA enemyEnergy,Y PLP \ Retrieve the C flag from the stack to return from the \ subroutine RTS \ Return from the subroutineName: DrainObjectEnergy [Show more] Type: Subroutine Category: Gameplay Summary: Drain energy from an object into an enemy, transforming it into an object with an energy level of one unit less (if applicable)Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyTactics (Part 5 of 8) calls DrainObjectEnergy * ApplyTactics (Part 7 of 8) calls DrainObjectEnergy
Arguments: targetObject The number of the object being drained of energy enemyObject The number of the object doing the draining
Returns: C flag Result flag: * Set if the player object is being drained and they still have a positive energy level after the draining * Clear if one of the following is true: * The player object is being drained and they now have a negative energy level, so the Sentinel has won * The player object is not being drained X The object number of the target that has been drained of energy (and which has therefore been transformed into a different object type).ExpendEnemyEnergy LDX enemyObject \ Set X to the object number of the enemy we are \ draining (so this is now object #X) SEC \ If the enemy has zero energy, jump to dren1 to return LDA enemyEnergy,X \ from the subroutine with the C flag set BEQ dren1 \ If we get here then the enemy has got some energy \ left, so we spawn a tree and decrease the enemy's \ energy level by one (as a tree is worth one energy \ unit) 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 altitude of the lowest enemy on the landscape JSR PlaceObjectBelow \ Attempt to place the tree on a tile that is below the \ maximum altitude specified in A (though we may end up \ placing the tree higher than this) BCS dren1 \ If the call to PlaceObjectBelow sets the C flag then \ the tree has not been successfully placed, so jump to \ dren1 to return from the subroutine with the C flag \ set TXA \ Set A to the object number of the tree we added to the \ landscape so we can pass it to CheckObjVisibility JSR CheckObjVisibility \ Check whether the tree in object A is visible and \ could therefore be seen on-screen by the player BCC dren2 \ If the C flag is clear then we are about to repeat the \ same screen pan (as the player is still holding down \ the same pan key) and the tree we just added would be \ visible in the landscape view if we drew it again \ \ This means that when we pan the screen, the new part \ of the screen that pans into view might show part of \ the tree while the rest of the screen won't, and that \ won't look good \ \ So jump to dren2 to delete the tree and stop applying \ tactics to the enemy, thus aborting the whole process \ If we get here then we are either not repeating the \ same screen pan, or we are but the object is not \ visible on-screen, so in either case the tree won't \ only partially appear on-screen so we can go ahead \ with the energy drain LDX enemyObject \ Decrement enemyEnergy for the enemy object we are DEC enemyEnergy,X \ draining LDX currentObject \ Set X to currentObject to return from the subroutine, \ which the call to CheckObjVisibility set to the object \ number of the newly spawned tree CLC \ Clear the C flag to indicate success .dren1 RTS \ Return from the subroutine .dren2 JSR DeleteObject \ Delete the tree in object #X and remove it from the \ landscape JMP FinishEnemyTactics \ Jump to FinishEnemyTactics to stop applying tactics to \ the current enemy and return to the ProcessGameplay \ routine to continue with the gameplay loopName: ExpendEnemyEnergy [Show more] Type: Subroutine Category: Gameplay Summary: Drain one unit of energy from an enemy and expend it onto the landscape by spawning a tree, if possibleContext: See this subroutine on its own page References: This subroutine is called as follows: * ApplyEnemyTactics calls ExpendEnemyEnergy * ApplyTactics (Part 3 of 8) calls ExpendEnemyEnergy
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 drain energy or spawn a tree, and instead it gives up and aborts applying tactics for this gameplay loop.
Arguments: enemyObject The object number of the enemy to drain
Returns: C flag Status flag: * Clear if the energy is drained and a tree is spawned successfully * Set if: * The enemy had no energy to drain * We could not spawn a tree to take on the drained energy X The object number of the spawned tree, if one was spawned.FinishLandscape SED \ Set the D flag to switch arithmetic to binary coded \ decimal (BCD), so the call to GetPlayerEnergyBCD and \ the following addition are all done in BCD JSR GetPlayerEnergyBCD \ Set A to the player's energy in BCD CLC \ Set (Y X) = landscapeNumber(Hi Lo) + A ADC landscapeNumberLo \ TAX \ This addition is done in BCD so the result is a new LDA landscapeNumberHi \ landscape number that's also in BCD (which we need to ADC #0 \ do as landscape numbers are in BCD) TAY CLD \ Clear the D flag to switch arithmetic to normal JSR InitialiseSeeds \ Initialise the seed number generator to generate the \ sequence of seed numbers for the landscape number in \ (Y X) and set maxNumberOfEnemies and the landscapeZero \ flag accordingly \ We set bit 7 of doNotPlayLandscape when the landscape \ was completed in the PerformHyperspace routine, so the \ following calls to GenerateLandscape and SpawnPlayer \ return normally, without previewing the landscape \ (GenerateLandscape) or starting the game (SpawnPlayer) JSR GenerateLandscape \ Call GenerateLandscape to generate the landscape JSR SpawnEnemies \ Calculate the number of enemies for this landscape, \ add them to the landscape and set the palette \ accordingly JSR SpawnPlayer \ Add the player and trees to the landscape LDA #&80 \ Call DrawTitleScreen with A = &80 to draw the screen JSR DrawTitleScreen \ showing the landscape's secret code LDX #5 \ Print text token 5: Print "SECRET ENTRY CODE" at JSR PrintTextToken \ (64, 768), "LANDSCAPE" at (192, 704), move cursor \ right JMP PrintLandscapeNum \ Print the four-digit landscape (0000 to 9999) and \ return from the subroutine using a tail callName: FinishLandscape [Show more] Type: Subroutine Category: Main game loop Summary: Add the player's energy to the landscape number to get the number of the next landscape and display that landscape's secret codeContext: See this subroutine on its own page References: This subroutine is called as follows: * MainGameLoop calls FinishLandscape.FindObjectToDrain LDX #63 \ Set a counter in X to work through the object numbers \ until we find a suitable tree or boulder that can be \ seen by the viewing object \ \ Note that we only call this routine with the viewing \ object set to the enemy that we are processing in the \ ApplyTactics routine, so I'll refer to the viewing \ object as the enemy in the following to make it \ easier to follow .dran1 LDA objectFlags,X \ Set A to the object flags for object #X, which are \ stored in the X-th entry in the objectFlags table BMI dran4 \ If bit 7 of object flags for object #X is set then \ this object number is not yet allocated to an object, \ so jump to dran4 to move on to the next object number CMP #%01000000 \ If bit 6 of the object flags for object #X is set BCS dran2 \ then object #X is stacked on top of another object, \ so jump to dran2 to keep checking as this is a \ potential target LDA objectTypes,X \ If object #X is not a boulder (an object of type 3) CMP #3 \ then jump to dran4 to move on to the next object BNE dran4 \ number .dran2 \ If we get here then object #X is either a boulder or \ it is an object that is stacked on top of another \ object LDA xObject,X \ Set (xTile, zTile) to the tile coordinates of the STA xTile \ tile containing object #X LDA zObject,X STA zTile 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 BCC dran4 \ If the C flag is clear then this tile does not already \ have an object placed on it, so jump to dran4 to move \ on to the next object number AND #%00111111 \ Because the tile has an object on it, the tile data TAY \ contains the number of the top object on the tile in \ bits 0 to 5, so extract the object number into Y (so \ object #Y is either directly on the tile or is on the \ top of the stack, so in either case it is the exposed \ object in terms of potential targets) LDA objectTypes,Y \ Set A to the type of object that's exposed on the tile \ (i.e. the type of object #Y) CMP #2 \ If the exposed object is a tree (an object of type 2), BEQ dran3 \ jump to dran3 to keep checking as this is a potential \ target CMP #3 \ If the exposed object is a boulder (an object of BNE dran4 \ type 3), jump to dran4 to move on to the next object \ number .dran3 \ If we get here then object #Y is either a boulder or \ it is a tree that is stacked on top of another object, \ and it is exposed as a potential target, so now we \ check whether the enemy can see the object's tile (and \ therefore whether it can be drained of energy by the \ enemy) JSR CheckEnemyGaze \ Call CheckEnemyGaze to check the gaze of the enemy \ towards object #Y, returning the following if the \ the object matches the object type in A: \ \ * targetVisibility = bit 7 set if the object's tile \ is visible, bit 6 set if the object is visible LDA targetVisibility \ If bit 7 of targetVisibility is clear then the enemy BPL dran4 \ can't see the exposed object's tile, so jump to dran4 \ to move on to the next object number \ If we get here then object #Y's tile can be seen by \ the enemy, so it is a suitable target for draining STY targetObject \ Set targetObject to the object number in Y CLC \ Clear the C flag to indicate that the enemy can see a \ drainable tree or boulder RTS \ Return from the subroutine .dran4 DEX \ Decrement the counter in X to move on to the next \ object number BPL dran1 \ Loop back to dran1 to check the next object number SEC \ If we get here then we have checked all 64 object \ numbers and none of them are suitable targets for the \ enemy to drain, so set the C flag to indicate this RTS \ Return from the subroutineName: FindObjectToDrain [Show more] Type: Subroutine Category: Gameplay Summary: Find a suitable target object for an enemy to drainContext: See this subroutine on its own page References: This subroutine is called as follows: * ApplyTactics (Part 4 of 8) calls FindObjectToDrain * ApplyTactics (Part 5 of 8) calls FindObjectToDrain
Arguments: viewingObject The viewing object (i.e. the enemy doing the scanning)
Returns: C flag Status flag: * Clear if we have found an object that is a suitable target 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) * Set if no objects of them are suitable targets for the enemy to drain targetObject The number of the target object (if the C flag is set).AbortWhenVisible JSR CheckObjVisibility \ Check whether object A is visible and could therefore \ be seen on-screen by the player BCS cvis1 \ If the C flag is clear then the object is partially \ on-screen and we are about to repeat a screen pan, so \ the object must not be updated or it could corrupt the \ landscape view, so in this case fall through into \ FinishEnemyTactics to stop applying tactics for this \ gameplay loop \ \ Otherwise the C flag is set so we can safely update \ the object without fear of corrupting a screen pan, \ so we can jump to cvis1 to return from the subroutine \ without aborting the tactics routineName: AbortWhenVisible [Show more] Type: Subroutine Category: Gameplay Summary: Abort applying the tactics for this gameplay loop if updating the object on-screen will corrupt a screen panContext: See this subroutine on its own page References: This subroutine is called as follows: * ApplyTactics (Part 2 of 8) calls AbortWhenVisible * ApplyTactics (Part 6 of 8) calls AbortWhenVisible * DrainObjectEnergy calls AbortWhenVisible
Arguments: A The number of the object to check.FinishEnemyTactics LDX gameplayStack \ Restore the stack pointer to the position it was in TXS \ when we called ApplyEnemyTactics from ProcessGameplay, \ so that executing an RTS instruction will now take us \ back to the ProcessGameplay routine, just after the \ JSR ApplyEnemyTactics instruction at play2 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 loopName: FinishEnemyTactics [Show more] Type: Subroutine Category: Gameplay Summary: Stop applying tactics to the current enemy and return to the ProcessGameplay routine to continue with the gameplay loopContext: See this subroutine on its own page References: This subroutine is called as follows: * DrainObjectEnergy calls FinishEnemyTactics * ExpendEnemyEnergy calls FinishEnemyTactics * ScanForMeanieTree calls FinishEnemyTactics.CheckObjVisibility SEC \ If bit 6 of samePanKeyPress is clear then the player BIT samePanKeyPress \ if not holding down the same pan key following the BPL cvis1 \ completion of a landscape pan, so jump to cvis1 to \ return from the subroutine with the C flag set \ If we get here then we have just completed a pan of \ the landscape view and the player is still holding \ down the same pan key STA currentObject \ Set currentObject to the object in A to pass to the \ GetObjVisibility routine so we check whether the 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 TXA \ Store X and Y on the stack so they can be preserved PHA \ across the call to GetObjVisibility TYA PHA JSR GetObjVisibility \ Calculate whether any part of an object is visible \ on-screen and set the C flag as follows: \ \ * Clear = at least some of the object is visible \ on-screen \ \ * Set = the object is not visible on-screen \ \ If the object is visible, also set the following: \ \ * bufferColumns = the number of character columns \ in the screen buffer that the object spans \ \ * objYawOffset = the yaw offset of the left edge of \ the object from the left edge of the screen, in \ character columns PLA \ Retrieve X and Y from the stack so they can be TAY \ preserved PLA TAX .cvis1 RTS \ Return from the subroutineName: CheckObjVisibility [Show more] Type: Subroutine Category: Drawing objects Summary: Check whether an object is visible on-screen and should therefore not be changed if a pan operation is about to happenContext: See this subroutine on its own page References: This subroutine is called as follows: * AbortWhenVisible calls CheckObjVisibility * ExpendEnemyEnergy calls CheckObjVisibility * ScanForMeanieTree calls CheckObjVisibility * AbortWhenVisible calls via cvis1
Arguments: A The number of the object to check
Returns: C flag The object's visibility: * Clear = we are about to repeat a pan and at least some of the object is visible on-screen (so the object must not be updated or it could corrupt the landscape view) * Set = we are about to repeat a pan and the object is not visible on-screen, or we are not about to repeat a pan (so the object can be updated) bufferColumns If the object is visible, this is set to the number of character columns in the screen buffer that the object spans objYawOffset If the object is visible, this is set to the yaw offset of the left edge of the object from the left edge of the screen, in character columns currentObject If we are about to repeat a pan, this is set to the object number in A X X is preserved Y Y is preserved
Other entry points: cvis1 Contains an RTS.ProcessActionKeys LDA keyPress \ Set A to the value from the key logger for the key \ press we want to process, which we know contains a \ valid key press as we only call this routine when \ bit 7 of the key logger value is clear \ \ The possible values for key logger entry 1 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) \ \ So now we perform the correct action for the key press \ in A CMP #34 \ If A <> 34 then the "H" key (hyperspace) is not being BNE pkey2 \ pressed, so jump to pkey2 to move on to the next check \ If we get here then "H" (hyperspace) is being pressed JSR PerformHyperspace \ Hyperspace the player to a brand new tile LDA #0 \ Set titleObjectToDraw to the object type for a robot, STA titleObjectToDraw \ so if the hyperspace fails because the player doesn't \ have enough energy, then the game over screen will \ show a robot to indicate that the player was \ responsible for their own demise .pkey1 SEC \ Set the C flag to denote that no object has been added \ or removed by the routine RTS \ Return from the subroutine .pkey2 LDX viewingObject \ Set X to the object number of the viewer, which we set \ to the player object in MainGameLoop before getting \ here via the ProcessGameplay routine CMP #35 \ If A <> 35 then the "U" key (U-turn) is not being BNE pkey3 \ pressed, so jump to pkey3 to move on to the next check \ If we get here then "U" (U-turn) is being pressed \ If we get here with bit 6 of uTurnStatus set, then \ this is the first time we have reached this point \ since the key was pressed (as we are about to clear \ bit 6), so we can perform the requested U-turn \ \ If we get here with bit 6 of uTurnStatus clear, then \ this isn't the first time we've reached this point \ since the key was pressed, so in this case we don't \ do another U-turn, as we're already done one ASL uTurnStatus \ Clear bit 6 uTurnStatus to prevent the "U" key from \ performing a U-turn, so that the "U" key has to be \ released before a second U-turn can be performed BPL pkey1 \ If bit 7 of uTurnStatus is now clear, then that means \ that bit 6 was clear before the above shift, which \ means we are currently ignoring the "U" key to prevent \ doing a second U-turn, so jump to pkey1 to skip doing \ another U-turn and instead return from the subroutine \ with the C flag set \ If we get here then "U" is being pressed and bit 6 of \ uTurnStatus was set before being cleared above, so we \ now perform a U-turn LDA objectYawAngle,X \ Rotate the player's yaw angle through 180 degrees by EOR #%10000000 \ flipping bit 7, which turns the player around STA objectYawAngle,X LDA #40 \ Set A = 40 to pass to the PlayMusic routine after we \ jump to pkey4, so it plays the music for a U-turn BNE pkey4 \ Jump to pkey4 to play the U-turn music and return from \ the subroutine with the C flag set (this BNE is \ effectively a JMP as A is never zero) .pkey3 \ If we get here then the key press is either create, \ absorb or transfer LSR enemyCheckingRobot \ Clear bit 7 of enemyCheckingRobot to pass to the call \ to FollowGazeVector below, so it returns results for \ the tile visibility rather than the object itself JSR GetSightsVector \ Calculate the vector from the player's eyes to the \ sights, returning it in both angle format: \ \ vectorYawAngle(Hi Lo) \ \ vectorPitchAngle(Hi Lo) \ \ and as a cartesian vector: \ \ [ xVector(Lo Bot) ] \ [ yVector(Lo Bot) ] \ [ zVector(Lo Bot) ] \ \ This vector is the vector from the player's eyes to \ the sights, divided by 16 by the GetVectorForAngles \ routine that GetSightsVector uses \ \ The "divided by 16" part is important, as we want to \ divide the vector from the player to the sights into \ small steps, so we can move along the vector \ sequentially, checking on each step whether the \ vector is passing through a flat tile or platform JSR FollowGazeVector \ Follow the gaze vector from the player's eyes to the \ sights to determine whether the player can see a flat \ tile or platform (i.e. boulder or tower) or the \ target object \ \ If it does hit a tile or platform, it sets xCoordHi \ and zCoordHi to the tile coordinates of the tile, \ which we use below for creating or removing objects \ on the tile in the sights BCS pkey7 \ If the C flag is set then this means the player can't \ see a tile or platform through the sights, so jump to \ pkey7 to make an error sound and return from the \ subroutine as we can't see a suitable surface for the \ create, absorb or transfer action LDA keyPress \ If bit 5 of keypress is clear then A is not 32 or 33, AND #%00100000 \ so A must be 0, 2 or 3 (one of the create keys), so BEQ pkey8 \ jump to pkey8 to spawn a robot, tree or boulder \ If we get here then A must be 32 or 33, so the key \ press is either absorb or transfer 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 BCC pkey7 \ The tile does not contain an object, so jump to pkey7 \ to make an error sound and return from the subroutine \ as we can't absorb or transfer to an empty tile AND #%00111111 \ Because the tile has an object on it, the tile data TAX \ contains the number of the top object on the tile in \ bits 0 to 5, so extract the object number into X LDA keyPress \ If bit 0 of keypress is clear then A must be 32, which LSR A \ is absorb, so jump to pkey5 to absorb the object on BCC pkey5 \ the tile anchored at (xTile, zTile) \ If we get here then A must be 33, so the key press is \ transfer and the player is trying to transfer to the \ tile anchored at (xTile, zTile), which contains \ object #X LDY objectTypes,X \ Set Y to the type of object #X BNE pkey7 \ If object #X is not a robot (i.e. not an object of \ type 0), jump to pkey7 to make an error sound and \ return from the subroutine as the player can only \ transfer into other robots JSR FocusOnKeyAction \ Tell the game to start focusing effort on the key \ action that has been initiated (i.e. the transfer) STX playerObject \ Set the player's object number to that of the robot \ on the tile anchored at (xTile, zTile), so this \ effectively performs the transfer across to the new \ robot \ We now take a short interlude to set the value of \ playerIsOnTower, as part of the game's anti-cracker \ code, and we pick up the robot transfer code in part 2Name: ProcessActionKeys (Part 1 of 2) [Show more] Type: Subroutine Category: Keyboard Summary: Process an action key press from key logger entry 1 (absorb, transfer, create, hyperspace, U-turn)Context: See this subroutine on its own page References: This subroutine is called as follows: * ProcessGameplay calls ProcessActionKeys
Returns: C flag Status flag: * Clear if we have added or removed an object: * Player has absorbed (and therefore removed) an object * Player has created an object * Set if we have not added or removed any objects: * Player has hyperspaced * Player has done a U-turn * Player has transferred to a new tile * Player has tried to do something that results in an error sound and no action (such as trying to transfer to an empty tile or into an object that isn't a robot, for example).SetPlayerIsOnTower \ We now work out whether the player just transferred \ into a robot on the Sentinel's tower \ \ We do this by starting at object #X, which is the \ robot that the player just transferred into, and if \ it's on top of another object or stack of objects, we \ work our way down the stack, checking to see if the \ stack contains the Sentinel's tower \ \ 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, so that's what we look for while working \ through the object stack \ \ This forms part of the code to protect the game from \ crackers, as all we do with this information is to set \ playerIsOnTower to the object type of the tower (which \ is 6). This value is then used to generate the secret \ code for the completed landscape, so if crackers have \ jumped straight to the end of the level without \ setting playerIsOnTower to 6, then the wrong code will \ be generated in the SpawnSecretCode3D routine .ptow1 LDA objectFlags,X \ Set A to the object flags for object #X CMP #%01000000 \ If both bits 6 and 7 of the object flags for object #X BCC ptow2 \ are clear then object #X is not stacked on top of \ another object (so if we have looped back here from \ below, we have reached the bottom of the stack), so \ jump to ptow2 to stop looping through the object stack \ and go back to the ProcessActionKeys routine \ If we get here then object #X is stacked on top of \ another object, and the number of the object below \ object #X is in bits 0 to 5 of the object flags for \ object #X, which is currently in A AND #%00111111 \ Extract bits 0 to 5 of the object flags into A and X, TAX \ so X now contains the object number of the next object \ down in the stack EOR #%00111111 \ If the object number in A is not the Sentinel's tower BNE ptow1 \ (i.e. it is not 63, or %111111), then loop back to \ check the next object down in the stack \ If we get here then the player has just transferred \ into a robot on the Sentinel's tower LDA objectTypes,X \ Set playerIsOnTower = 6 (which is the object type of STA playerIsOnTower \ the Sentinel's tower, whose object number is in X) .ptow2 \ Fall through into part 2 of ProcessActionKeys to \ continue with the robot transferName: SetPlayerIsOnTower [Show more] Type: Subroutine Category: Cracker protection Summary: Set up the playerIsOnTower value for checking the game is won, as part of the anti-cracker codeContext: See this subroutine on its own page References: No direct references to this subroutine in this source file\ If we get here then the player has just transferred \ into a robot LDA #25 \ Set A = 25 to pass to the PlayMusic routine so it \ plays the music for when the player transfers into a \ new robot .pkey4 JSR PlayMusic \ Call PlayMusic to play the music specified in A (so \ that's either the music for transferring into a new \ robot or the music for a U-turn) LDA #%10000000 \ Set bit 7 of playerHasMovedTile to indicate that the STA playerHasMovedTile \ player has moved to a new tile SEC \ Set the C flag to denote that no object has been added \ or removed by the routine RTS \ Return from the subroutine .pkey5 \ If we get here then we are absorbing an object of type \ X from the tile anchored at (xTile, zTile) LDA objectFlags \ The Sentinel is always object #0, so this checks BMI pkey7 \ whether bit 7 of the Sentinel's object is set, which \ indicates that the Sentinel's object number is not \ allocated \ \ This means that the Sentinel no longer exists and has \ been absorbed by the player, at which point the player \ is no longer allowed to absorb other objects, so jump \ to pkey7 to make an error sound and return from the \ subroutine LDA objectTypes,X \ If the player is trying to absorb a meanie (an object CMP #4 \ of type 4), jump to pkey11 to implement this BEQ pkey11 CMP #6 \ If the player is trying to absorb the Sentinel's tower BEQ pkey7 \ (an object of type 6), jump to pkey7 to make an error \ sound and return from the subroutine .pkey6 JSR DeleteObject \ Delete object #X and remove it from the landscape STX currentObject \ Set currentObject to the number of the deleted object CLC \ Call UpdatePlayerEnergy with the C flag clear to add JSR UpdatePlayerEnergy \ the amount of energy in the now-deleted object #X to \ the player's energy CLC \ Clear the C flag to denote that an object has been \ removed by the routine RTS \ Return from the subroutine .pkey7 \ If we get here then the player has tried to: \ \ * Absorb or transfer to an empty tile \ \ * Create, absorb or transfer to a tile or platform \ that they see directly \ \ * Transfer to an object that is not a robot \ \ * Absorb an object after the Sentinel has been \ absorbed \ \ * Absorb the Sentinel's tower \ \ * Create an object when there are no unallocated \ object numbers \ \ * Create an object that takes their energy level \ below zero \ \ * Create an object that can't be added to the tile \ (if the tile is occupied by an object that is not \ a boulder or tower, for example) \ \ In each case we make an error sound and return from \ the subroutine LDA #170 \ Set the third parameter of sound data block #3 (the STA soundData+28 \ pitch) to 170 LDA #5 \ Make sound #5 (ping) JSR MakeSound LDA #144 \ Set the third parameter of sound data block #3 (the STA soundData+28 \ pitch) to 144 SEC \ Set the C flag to denote that no object has been added \ or removed by the routine RTS \ Return from the subroutine .pkey8 \ If we get here then the player is trying to create an \ object of the type given in keyPress JSR SpawnObject+3 \ Spawn an object of type keyPress, returning the object \ number of the new object in X and currentObject BCS pkey7 \ If there are no free object numbers then the call to \ SpawnObject will return with the C flag set and the \ object will not have been created, so jump to pkey7 to \ make an error sound and return from the subroutine SEC \ Call UpdatePlayerEnergy with the C flag set to JSR UpdatePlayerEnergy \ subtract the amount of energy in object #X from the \ player's energy, so this subtracts the energy required \ to create the object from the player BCS pkey7 \ If the creation of object #X reduces the player's \ energy below zero, then the call to UpdatePlayerEnergy \ will return with the C flag set, so jump to pkey7 to \ make an error sound and return from the subroutine LDX currentObject \ Set X to the object number of the object we just \ created LDA xCoordHi \ Set (xTile, zTile) to the tile coordinates of the tile STA xTile \ in the sights, which we set in part 1 with the call to LDA zCoordHi \ FollowGazeVector STA zTile JSR PlaceObjectOnTile \ Place object #X on the tile anchored at (xTile, zTile) BCC pkey9 \ If the object was successfully placed on the tile then \ the call to PlaceObjectOnTile will return with the C \ flag clear, so jump to pkey9 to keep going CLC \ Otherwise we failed to add the object to the tile, so JSR UpdatePlayerEnergy \ call UpdatePlayerEnergy with the C flag clear to \ refund the energy that we used to create the object \ back to the player JMP pkey7 \ We failed to place the object on the tile, so jump to \ pkey7 to make an error sound and return from the \ subroutine .pkey9 \ If we get here then a new object has been successfully \ created and added to a tile, as object #X LDA objectTypes,X \ If the type of object that was added is not a robot BNE pkey10 \ (type 0), jump to pkey10 to return from the subroutine \ with the C flag clear \ We just created a robot, so we now rotate it so that \ it faces the player LDY playerObject \ Set A to the yaw angle of the player object LDA objectYawAngle,Y EOR #%10000000 \ Flip bit 7 to rotate the angle through 180 degrees \ \ 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 \ \ Flipping bit 7 changes 0 into 128, +32 into -96, +64 \ into -64 and so on, so it's the same as rotating by \ 180 degrees STA objectYawAngle,X \ 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 .pkey10 CLC \ Clear the C flag to denote that an object has been \ added by the routine RTS \ Return from the subroutine .pkey11 \ If we get here then the player is trying to absorb a \ meanie in object #X \ We now loop through all the enemy objects, of which \ there are up to eight, looking for the object whose \ enemyMeanieTree entry is X (so we are looking for the \ enemy that turned a tree into this meanie) LDY #7 \ The enemies have object numbers 0 (for the Sentinel) \ or 1 to 7 (for any sentries in the landscape), so set \ a counter in Y to work through the enemy object \ numbers .pkey12 LDA objectFlags,Y \ If bit 7 is set for object #Y then this object number BMI pkey14 \ is not allocated to an object, so jump to pkey14 to \ move on to the next enemy object LDA objectTypes,Y \ Set A to the object type for object #Y CMP #1 \ If object #Y is a sentry (an object of type 1) then BEQ pkey13 \ jump to pkey13 CMP #5 \ If object #Y is not the Sentinel (an object of type BNE pkey14 \ 5), then jump to pkey14 to move on to the next enemy \ object .pkey13 \ If we get here then object #Y is the Sentinel or a \ sentry TXA \ Set A to the object number of the meanie that the \ player is trying to absorb CMP enemyMeanieTree,Y \ If enemyMeanieTree for object #Y does not match the BNE pkey14 \ object number of the meanie we are absorbing, then \ this means object #Y did not create this meanie, so \ jump to pkey14 to move on to the next enemy object \ If we get here then we have found the enemy that \ turned a tree into the meanie that is being absorbed 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 \ (as the meanie has been absorbed) BNE pkey6 \ Jump to pkey6 to delete the meanie in object #X, add \ the meanie's energy to the player's energy, and return \ from the subroutine with the C flag clear (this BNE is \ effectively a JMP as A is never zero) .pkey14 DEY \ Decrement the counter in Y to move on to the next \ enemy object number BPL pkey12 \ Loop back until we have processed all eight possible \ enemy objects BMI pkey6 \ Jump to pkey6 to delete the meanie in object #X, add \ the meanie's energy to the player's energy, and return \ from the subroutine with the C flag clearName: ProcessActionKeys (Part 2 of 2) [Show more] Type: Subroutine Category: Keyboard Summary: Process an action key press from key logger entry 1 (absorb, transfer, create, hyperspace, U-turn)Context: See this subroutine on its own page References: No direct references to this subroutine in this source file.GetSightsVector LDA xSights \ Set (U A) = xSights STA U LDA #0 LSR U \ Set (U A) = (U A) / 8 ROR A \ = xSights / 8 LSR U ROR A LSR U ROR A \ We now calculate the following: \ \ (U A) + (objectYawAngle,X 0) - (10 0) \ \ and store it in vectorYawAngle(Hi Lo) CLC \ Clear the C flag for the following STA vectorYawAngleLo \ Store the low byte of the calculation (which we know \ will be A) in vectorYawAngleLo LDA U \ Calculate the high byte of the calculation as ADC objectYawAngle,X \ follows: SEC \ SBC #10 \ U + objectYawAngle,X - 10 STA vectorYawAngleHi \ \ and store it in vectorYawAngleHi \ So vectorYawAngle(Hi Lo) is now equal to the \ following: \ \ (xSights / 8) + (objectYawAngle,X 0) - (10 0) LDA ySights \ Set (U A) = ySights - 5 SEC SBC #5 STA U LDA #0 LSR U \ Set (U A) = (U A) / 16 ROR A \ = (ySights - 5) / 16 LSR U ROR A LSR U ROR A LSR U ROR A \ We now calculate the following: \ \ (U A) + (objectPitchAngle,X 0) + (3 32) \ \ and store it in both (A T) and in \ vectorPitchAngle(Hi Lo) CLC \ Calculate the low byte and store it in both T and ADC #32 \ vectorPitchAngleLo STA vectorPitchAngleLo STA T LDA U \ Calculate the high byte, keep it in A and store it in ADC objectPitchAngle,X \ vectorPitchAngleHi CLC ADC #3 STA vectorPitchAngleHi \ So by this point we have the following: \ \ vectorYawAngle(Hi Lo) \ = (xSights / 8) + (objectYawAngle,X 0) - (10 0) \ \ vectorPitchAngle(Hi Lo) \ = (ySights-5) / 16 + (objectPitchAngle,X 0) + (3 32) \ \ We now fall through into GetVectorForAngles to convert \ these two angles into a cartesian vector: \ \ [ xVector(Lo Bot) ] \ [ yVector(Lo Bot) ] \ [ zVector(Lo Bot) ]Name: GetSightsVector [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the angles of the vector from the player's eyes to the sightsContext: See this subroutine on its own page References: This subroutine is called as follows: * ProcessActionKeys (Part 1 of 2) calls GetSightsVector
The vector from the player's eyes to the sights is calculated as follows: vectorYawAngle = (xSights * 32) + (objectYawAngle,X 0) - (10 0) vectorPitchAngle = (ySights - 5) * 16 + (objectPitchAngle,X 0) + (3 32) The (10 0) element in the yaw angle calculation represents half a screen width, as the screen is 20 yaw angles wide, so subtracting (10 0) from the object's yaw angle makes it to the left edge of the screen, and then we add the x-coordinate of the sights on-screen to get the vector's yaw angle. The -5 in the pitch angle calculation caters for the distance between the top of the sights and the centre of the sights, and the (3 32) element represents something I have yet to work out.
Arguments: X This is always set to the object number of the player (it is set in ProcessActionKeys before it calls this routine).GetVectorForAngles \ In the following commentary I am talking about the \ vector from the player's eyes to the sights, as that \ makes it easier to describe the various points, but \ this routine can convert any vector into angles using \ the same process \ We start by converting the pitch angle of the vector \ from the player's eyes to the sights into a cartesian \ y-coordinate in the global 3D coordinate system, where \ the y-axis is the up-down axis \ \ We store the resulting y-coordinate in the 16-bit \ variable yVector(Lo Bot) \ \ We calculate yVector by considering a triangle with \ the sights vector as the hypotenuse, and we drop the \ end point down onto the y = 0 plane (i.e. onto the \ ground) \ \ Consider the case where the player is looking up at an \ angle of vectorPitchAngle, so the vector from the \ player's eye to the sights is from the bottom-left to \ the top-right in the following triangle: \ \ sights \ _.-+ ^ \ _.-´ | | \ vector _.-´ | y-axis (up) \ _.-´ | \ _.-´ | y \ _.-´ | \ .-´ vectorPitchAngle | \ eye +--------------------------+ \ p \ \ To make the calculations easier, let's say the length \ of the vector is 1 \ \ Trigonometry gives us the following: \ \ sin(vectorPitchAngle) = opposite / hypotenuse \ = y / 1 \ \ So the y-coordinate is given by: \ \ y = sin(vectorPitchAngle) \ \ which is what we calculate now \ \ Incidentally, the adjacent side p, which is the length \ of the vector projected down onto the y = 0 plane, is \ calculated in a similar way: \ \ cos(vectorPitchAngle) = adjacent / hypotenuse \ = p / 1 \ \ So p = cos(vectorPitchAngle), which we will use to \ calculate the x- and z-coordinates of the vector later JSR GetRotationMatrix \ This routine is taken from Revs, where it calculates a \ rotation matrix from an angle \ \ We don't need a full rotation matrix here, but the \ Revs routine calculates the values that we do need \ here, specifically: \ \ cosVectorPitchAngle = cos(vectorPitchAngle) \ \ sinVectorPitchAngle = sin(vectorPitchAngle) \ \ These calculations return 16-bit sign-magnitude \ numbers with the sign in bit 0, which the DivideBy16 \ routine converts into normal 16-bit signed numbers \ \ Note that because GetRotationMatrix is copied from \ Revs, where we only ever rotate through the yaw angle, \ the matrix values are actually returned in the various \ yawAngle variables, but for simplicity let's pretend \ they are returned as above LDY #1 \ Set (A X) = cosVectorPitchAngle / 16 JSR DivideBy16 STA cosVectorPitchHi \ Set cosVectorPitch(Hi Lo) = cosVectorPitchAngle / 16 STX cosVectorPitchLo LDY #0 \ Set (A X) = sinVectorPitchAngle / 16 JSR DivideBy16 STA yVectorLo \ Set yVector(Lo Bot) = sinVectorPitchAngle / 16 STX yVectorBot \ \ So we now have the y-coordinate of the sights vector \ as follows: \ \ y = sin(vectorPitchAngle) \ Now we convert the yaw angle of the sights vector \ into cartesian x- and z-coordinates, where the x-axis \ is the left-right axis and the z-axis goes into the \ screen \ \ We store the resulting x-coordinate in the 16-bit \ variable xVector(Lo Bot) and the z-coordinate in \ zVector(Lo Bot) \ \ We calculate xVector and zVector by considering a \ triangle on the y = 0 plane, so that's a triangle on \ the ground \ \ The hypotenuse of this triangle is side p from the \ first calculation, which is the sights vector \ projected down onto the ground - the shadow from a \ light source directly above, if you like \ \ The opposite and adjacent sides of this tringle will \ give us the x- and z-coordinates of the vector \ \ To see this, consider the same vector as before, with \ p as the vector projected down onto the ground, and \ where the player is looking sideways at a yaw angle of \ vectorYawAngle \ \ This gives us a triangle like this, when viewed from \ above, so it's as if we are the light source directly \ above the sights vector, projecting the vector down \ onto p, and with the vector going from the top-left to \ the bottom-right, and the sights end of the vector \ higher than the eye end: \ \ z \ eye +--------------------------+ z-axis --> \ `-._ vectorYawAngle | into screen \ `-._ | \ `-._ | x \ p `-._ | \ `-._ | x-axis left \ `-._ | to right \ `-+ | \ sights v \ \ Again, simple trigonometry gives us the following: \ \ sin(vectorYawAngle) = opposite / hypotenuse \ = x / p \ \ So: \ \ x = p * sin(vectorYawAngle) \ \ And: \ \ cos(vectorYawAngle) = adjacent / hypotenuse \ = z / p \ \ So: \ \ z = p * cos(vectorYawAngle) \ \ We calculated above that: \ \ p = cos(vectorPitchAngle) \ \ So substituting that into our result gives us: \ \ x = p * sin(vectorYawAngle) \ = cos(vectorPitchAngle) * sin(vectorYawAngle) \ \ z = p * cos(vectorYawAngle) \ = cos(vectorPitchAngle) * cos(vectorYawAngle) \ \ which is what we calculate now LDA vectorYawAngleLo \ Set (A T) = vectorYawAngle(Hi Lo) STA T LDA vectorYawAngleHi JSR GetRotationMatrix \ Calculate the following: \ \ cosVectorYawAngle = cos(vectorYawAngle) \ \ sinVectorYawAngle = sin(vectorYawAngle) \ \ Again these are returned as 16-bit sign-magnitude \ numbers with the sign in bit 0, which the \ MultiplyCoords converts into normal 16-bit signed \ numbers LDY #1 \ Call MultiplyCoords with Y = 1 and X = 2 to calculate LDX #2 \ the following: JSR MultiplyCoords \ \ zVector(Lo Bot) \ = cosVectorPitchAngle * cosVectorYawAngle / 16 LDY #0 \ Zero X and Y and fall through into MultiplyCoords to LDX #0 \ calculate the following: \ \ xVector(Lo Bot) \ = cosVectorPitchAngle * sinVectorYawAngle / 16 \ \ and return from the subroutine using a tail callName: GetVectorForAngles [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Convert a vector from pitch and yaw angles into a 3D cartesian vectorContext: See this subroutine on its own page References: This subroutine is called as follows: * CheckEnemyGaze (Part 2 of 2) calls GetVectorForAngles
This routine uses the rotation matrix routine from Revs to convert a vector from a pair of pitch and yaw angles into a cartesian [x y z] vector. The pitch and yaw angles are 16-bit numbers as follows: vectorPitchAngle(Hi Lo) vectorYawAngle(Hi Lo) The same vector, but expressed as a cartesian vector, is calculated as follows: [ xVector(Lo Bot) ] [ yVector(Lo Bot) ] [ zVector(Lo Bot) ] The calculation is this: yVector = sinVectorPitchAngle / 16 zVector = cosVectorPitchAngle * cosVectorYawAngle / 16 xVector = cosVectorPitchAngle * sinVectorYawAngle / 16.MultiplyCoords LDA #0 \ Set H to sign to apply to the result of Multiply16x16 STA H \ (in bit 7), so setting H 0 ensures that that the \ result is positive LDA cosVectorPitchLo \ Set (QQ PP) = cosVectorPitch(Hi Lo) STA PP \ LDA cosVectorPitchHi \ where (QQ PP) is a 16-bit signed number STA QQ LDA sinAngleLo,Y \ Set (SS RR) to the 16-bit sign-magnitude number STA RR \ pointed to by Y LDA sinAngleHi,Y STA SS JSR Multiply16x16 \ Set (A T) = (QQ PP) * (SS RR) \ \ And apply the sign from bit 7 of H to ensure the \ result is positive STA xVectorLo,X \ Store the result in: LDA T \ STA xVectorBot,X \ * xVector(Lo Bot) when X = 0 \ \ * zVector(Lo Bot) when X = 2 RTS \ Return from the subroutineName: MultiplyCoords [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Multiply a 16-bit signed number and a 16-bit sign-magnitude valueContext: See this subroutine on its own page References: This subroutine is called as follows: * GetVectorForAngles calls MultiplyCoords
This routine multiplies two 16-bit values and stores the result according to the arguments, as follows. When Y = 0, it calculates: cosVectorPitch(Hi Lo) * sinAngle(Hi Lo) i.e. cosVectorPitch * sinAngle When Y = 1, it calculates: cosVectorPitch(Hi Lo) * cosAngle(Hi Lo) i.e. cosVectorPitch * cosAngle When X = 0, store the result in xVector(Lo Bot). When X = 2, store the result in zVector(Lo Bot).
Arguments: cosVectorPitchHi The 16-bit signed number to multiply (high byte) cosVectorPitchLo The 16-bit signed number to multiply (low byte) Y Offset of the 16-bit sign-magnitude value to multiply: * 0 = sinAngle * 1 = cosAngle X Offset of the variable to store the result in: * 0 = xVector(Lo Bot) * 2 = zVector(Lo Bot).DivideBy16 LDA sinAngleLo,Y \ Set (A T) to the 16-bit sign-magnitude number pointed STA T \ to by Y LDA sinAngleHi,Y LSR A \ Set (A T) = (A T) / 16 ROR T \ PHP \ We store bit 0 of the original 16-bit sign-magnitude LSR A \ number on the stack in the C flag (as it gets rotated ROR T \ out from bit 0 on the first ROR T) LSR A ROR T LSR A ROR T PLP \ We stored the sign bit from the original 16-bit \ sign-magnitude number on the stack, so fetch it into \ the C flag BCC divi1 \ If the sign bit was 0 then the original number was \ positive, so skip the following JSR Negate16Bit \ The original 16-bit sign-magnitude number was negative \ so call Negate16Bit to negate the result as follows: \ \ (A T) = -(A T) .divi1 LDX T \ Set (A X) = (A T) RTS \ Return from the subroutineName: DivideBy16 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Divide a 16-bit sign-magnitude number by 16Context: See this subroutine on its own page References: This subroutine is called as follows: * GetVectorForAngles calls DivideBy16
This routine divides a 16-bit sign-magnitude number by 16 and returns the result as a signed 16-bit number.
Arguments: Y Offset of the 16-bit sign-magnitude number to divide: * 0 = sinAngle * 1 = cosAngle
Returns: (A X) The 16-bit signed number containing the result.AddVectorToCoord LDX #2 \ We now work through all three axes in turn, so set an \ axis counter in X to iterate through 2, 1 and 0 (for \ the z-axis, y-axis and x-axis respectively) \ \ The comments in the following loop will concentrate on \ the x-axis to keep things simple .addv1 \ We now perform the following addition of a 24-bit \ coordinate and a 16-bit vector: \ \ xCoord = xCoord + xVector \ \ where: \ \ * xCoord is xCoord(Hi Lo Bot) \ \ * xVector is xVector(Lo Bot) \ \ We do this for each axis in turn, but let's talk about \ the x-axis LDA #0 \ Set T = 0 STA T \ \ We use T as the high byte of xVector, which we either \ set to 0 (if xVector is positive) or &FF (if xVector \ is negative) \ \ We set T for positive numbers here, and change it to \ &FF during the addition if xVector turns out to be \ negative \ \ The addition therefore supports signed numbers LDA xCoordBot,X \ Add the bottom bytes of the calculation CLC ADC xVectorBot,X STA xCoordBot,X LDA xVectorLo,X \ Set A to xVectorLo so we can check its sign BPL addv2 \ If xVectorLo is negative, set T = &FF so we can use it DEC T \ as the high byte in the negative 24-bit number, like \ this: \ \ (&FF xVectorLo xVectorBot) .addv2 ADC xCoordLo,X \ Now add the low bytes of the calculation STA xCoordLo,X LDA xCoordHi,X \ And then add the high bytes, incorporating the high ADC T \ byte of xVector that we set in T STA xCoordHi,X DEX \ Decrement the axis counter in X to move on to the next \ axis BPL addv1 \ Loop back until we have processed all three axes RTS \ Return from the subroutine EQUB &00 \ This byte appears to be unusedName: AddVectorToCoord [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Add a vector to a coordinateContext: See this subroutine on its own page References: This subroutine is called as follows: * FollowGazeVector (Part 1 of 5) calls AddVectorToCoord
This routine adds a vector to a coordinate: [ xCoord ] [ xCoord ] [ xVector ] [ yCoord ] = [ yCoord ] + [ yVector ] [ zCoord ] [ zCoord ] [ zVector ] where the coordinate consists of 24-bit signed numbers: [ xCoord ] [ xCoord(Hi Lo Bot) ] [ yCoord ] = [ yCoord(Hi Lo Bot) ] [ zCoord ] [ zCoord(Hi Lo Bot) ] and the vector consists of 16-bit signed numbers: [ xVector ] [ xVector(Lo Bot) ] [ yVector ] = [ yVector(Lo Bot) ] [ zVector ] [ zVector(Lo Bot) ].FollowGazeVector LDX viewingObject \ Set X to the number of the object that is performing \ the gaze (i.e. doing the looking or scanning) LSR targetOnTile \ Clear bit 7 of targetOnTile so it can be set if the \ tile contains the target object whose number is in \ targetObject LSR gazeCanSeeTree \ Clear bit 7 of gazeCanSeeTree so it can be set if \ there is tree on the tile that can be seen by the \ gaze vector JSR GetObjectCoords \ Fetch the cartesian coordinates of the viewing object \ as three 24-bit numbers, as follows: \ \ xCoord(Hi Lo Bot) \ \ yCoord(Hi Lo Bot) \ \ zCoord(Hi Lo Bot) \ \ We now repeatedly add the scaled-down gaze vector to \ this coordinate to step along the gaze from the \ viewing object and see if it hits anything .gaze1 JSR AddVectorToCoord \ Add the coordinates and the vector as follows: \ \ [ xCoord ] [ xCoord ] [ xVector ] \ [ yCoord ] = [ yCoord ] + [ yVector ] \ [ zCoord ] [ zCoord ] [ zVector ] \ \ where: \ \ [ xCoord ] [ xCoord(Hi Lo Bot) ] \ [ yCoord ] = [ yCoord(Hi Lo Bot) ] \ [ zCoord ] [ zCoord(Hi Lo Bot) ] \ \ and: \ \ [ xVector ] [ xVector(Lo Bot) ] \ [ yVector ] = [ yVector(Lo Bot) ] \ [ zVector ] [ zVector(Lo Bot) ] \ \ This adds the scaled-down gaze vector to the \ coordinate to perform a step along the object's gaze LDA xCoordHi \ Set xTile to the high byte of the result (i.e. the STA xTile \ tile x-coordinate below our current position along the \ viewer's gaze) CMP #31 \ If we just stepped beyond the right edge of the BCS gaze4 \ landscape then the gaze does not land upon a tile, so \ jump to gaze4 to return from the subroutine with the \ C flag set to indicate that the viewer is not looking \ at a tile LDA zCoordHi \ Set zTile to the high byte of the result (i.e. the STA zTile \ tile z-coordinate below our current position along the \ viewer's gaze) CMP #31 \ If we just stepped beyond the rear edge of the BCS gaze4 \ landscape then the gaze does not land upon a tile, so \ jump to gaze4 to return from the subroutine with the \ C flag set to indicate that the viewer is not looking \ at a tile LDA #%10000000 \ Set bit 7 and clear bit 6 of considerObjects so the STA considerObjects \ following call to GetTileAltitude will include trees \ and platform objects in its calculations STA yAccuracyLo \ Set yAccuracyLo = 128, so by default the gaze vector \ has to be no more than half a tile's height above a \ tile for us to consider it as potentially seeing the \ tile (this figure is changed for the Sentinel's tower \ to make the player have to be much more accurate when \ trying to view the Sentinel's tile) LDA #0 \ Set yPlatformLo = 0, so if the tile doesn't contain STA yPlatformLo \ a platform, yPlatformLo will be zero STA boulderOnTile \ Clear bit 7 of boulderOnTile to denote that the tile \ does not contain a boulder, so GetTileAltitude can \ set bit 7 if it does contain a boulder JSR GetTileAltitude \ Call GetTileAltitude with bit 7 of considerObjects \ set to extract the following tile data: \ \ * (A yPlatformLo) = if the tile contains a boulder \ or the Sentinel's tower, then this is set to the \ altitude of the platform object, plus 32 for the \ tower or plus 96 for the boulder \ \ * A = if the tile contains a non-platform object \ then this is set to the high byte of the \ tile's altitude, which is the same as the high \ byte of the object's altitude \ \ * A = if the tile is not flat then this is set to \ the tile altitude from the tile data \ \ * C flag = the tile's shape, clear if the tile is \ flat or set if the tile is not flat \ \ * boulderOnTile has bit 7 set if the tile contains a \ boulder \ \ * considerObjects has bit 6 if the tile contains a \ boulder or the Sentinel's tower and the current \ position along the gaze vector is not within the \ platform object (so the gaze vector is passing \ close by the platform object but is not hitting \ it) \ \ * yAccuracyLo is changed from 128 to 16 if the tile \ contains the Sentinel's tower and the gaze vector \ is pointing at the sides of the tower BCS gaze5 \ If the tile is not flat, jump to gaze5 to calculate \ the gaze vector's interaction with the tile slopes \ If we get here then the tile is flat and may contain \ an object TAX \ Set (X A) to the altitude of the platform (if there is LDA yPlatformLo \ one) or the altitude of the tile (is there isn't) SEC \ Set the following: SBC yCoordLo \ STA yPlatformLo \ (A yPlatformLo) = (X A) - yCoord(Hi Lo) TXA \ SBC yCoordHi \ So this contains the relative altitude of the tile or \ platform compared to our current position along the \ viewer's gaze (as we are subtracting the altitude of \ the gaze from the altitude of the tile/platform) BMI gaze1 \ If the high byte of the result is negative then the \ gaze is passing through the y-coordinate above the \ tile or platform, in terms of whole numbers (so it is \ essentially more than one "tile cube" above the tile) \ \ This means it hasn't hit the tile and is still passing \ through empty space above the landscape, so loop back \ to gaze1 to move along the gaze vector and restart the \ checks BNE gaze4 \ If the high byte of the result is non-zero and \ positive, then the gaze is passing through the \ y-coordinate below the tile or platform, in terms of \ whole numbers (so it is essentially in the "tile cube" \ beneath the tile) \ \ This means the gaze must have passed through a slope \ and "into" the ground beneath the tile, so jump to \ gaze4 to return from the subroutine with the C flag \ set to indicate that the viewer is not looking at a \ tile \ If we get here then the gaze is currently sitting \ within the "tile cube" above the tile LDA yPlatformLo \ If yPlatformLo >= yAccuracyLo, then the vertical CMP yAccuracyLo \ distance between the gaze and the platform is greater BCS gaze4 \ than the accuracy specified in yAccuracyLo, so jump to \ gaze4 to return from the subroutine with the C flag \ set to indicate that the viewer is not looking at a \ tile or object \ \ Note that yAccuracyLo = 128 for all tiles (i.e. no \ more than 0.5 of a tile's height above the tile) apart \ from the tile containing the Sentinel's tower, when it \ is reduced to 16 if the gaze vector is pointing at the \ sides of the tower (i.e. no more than 0.025 of a \ tile's height above the tile) \ \ This makes the player have to be much more accurate \ when trying to view the Sentinel's tile BIT considerObjects \ If bit 6 of considerObjects is set then the tile BVS gaze4 \ contains a boulder or the Sentinel's tower and the \ current position along the gaze vector is not within \ the platform object (so the gaze vector is passing \ close by the platform object but is not hitting it), \ so jump to gaze4 to return from the subroutine with \ the C flag set to indicate that the viewer is not \ looking at a tile, as the platform will be at least \ partially obscuring any tiles that are visible to the \ sides of the boulder or tower LDA enemyCheckingRobot \ If bit 7 is set in either of enemyCheckingRobot and ORA boulderOnTile \ boulderOnTile, skip the following test, as we do not BMI gaze2 \ care if the gaze is upwards when either of the \ following is true: \ \ * This is an enemy is looking for a robot, in which \ case we are still interested even if the enemy is \ below the robot and can't see the tile that the \ robot is on \ \ * We are looking at a boulder, in which case it can \ still be above us and a suitable target for an \ action (as we can create or absorb from a boulder \ stack by looking at any of the boulders on the \ stack, even if they are above us) \ If we get here then neither of the above special cases \ are true, so we now check whether the viewer is \ looking upwards, as that will mean they can't see down \ onto the tile LDA yVectorLo \ If the low byte (i.e. the fractional part) of the gaze BPL gaze4 \ vector's y-coordinate is positive, then the viewer is \ looking upwards and therefore can't see down onto the \ tile, so jump to gaze4 to return from the subroutine \ with the C flag set to indicate that the viewer is not \ looking at a tile .gaze2 \ If we get here then the viewer's gaze has landed on a \ tile, so the final check is to make sure the viewer is \ not looking at their own tile LDX viewingObject \ Set X to the number of the object that is performing \ the gaze (i.e. doing the looking or scanning) LDA xTile \ If the x-coordinate of the viewing object is not the CMP xObject,X \ same as the x-coordinate of the tile that the gaze has BNE gaze3 \ hit, jump to gaze3 to return from the subroutine with \ the C flag clear, to indicate that the viewer is \ looking at a tile \ If we get here then the x-coordinates match, so we now \ need to check the z-coordinates LDA zTile \ If the z-coordinate of the viewing object is the same CMP zObject,X \ as the x-coordinate, then the viewer is looking at BEQ gaze1 \ their own tile, so loop back to gaze1 to move along \ the gaze vector and restart the checks \ If we get here then the z-coordinates do not match, so \ we can now return from the subroutine with the C flag \ clear, to indicate that the viewer is looking at a \ tile .gaze3 CLC \ Set the C flag to indicate that the gaze hit \ something, so the viewing object is looking at \ something RTS \ Return from the subroutine .gaze4 SEC \ Set the C flag to indicate that the gaze didn't hit \ anything, so the viewing object isn't looking at \ anything RTS \ Return from the subroutineName: FollowGazeVector (Part 1 of 5) [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Follow a gaze vector from a viewing object to determine whether the viewer can see a flat tile or platform (i.e. boulder or tower)Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckEnemyGaze (Part 2 of 2) calls FollowGazeVector * ProcessActionKeys (Part 1 of 2) calls FollowGazeVector
This routine follows the gaze of a viewing object to see if it hits a tile or a platform (where a platform is a boulder or the Sentinel's tower) The gaze is defined by the following vector: [ xVector ] [ xVector(Lo Bot) ] [ yVector ] = [ yVector(Lo Bot) ] [ zVector ] [ zVector(Lo Bot) ] This contains the gaze vector, scaled down to be very small, so we can trace the line of the gaze by repeatedly adding this vector to the coordinates of the viewing object to step along the line of the gaze. At each step we check to see whether the gaze has hit a tile, repeating this until we either hit a tile or fall off the edge of the landscape.
Arguments: viewingObject The number of the object that is performing the gaze xVector(Lo Bot) The x-coordinate of the scaled-down gaze vector yVector(Lo Bot) The y-coordinate of the scaled-down gaze vector zVector(Lo Bot) The z-coordinate of the scaled-down gaze vector enemyCheckingRobot Set bit 7 if this routine is being called when an enemy is checking to see if it can see a robot object (i.e. in the CheckEnemyGaze routine) targetObject The target object
Returns: C flag Status flag: * Clear if the viewing object can see a tile along the gaze vector (or, if enemyCheckingRobot is set, if the viewing object can see a robot) * Set if the viewing object can't see a tile along the gaze vector targetOnTile Records whether the gaze vector can see a tile containing the target object: * Bit 7 clear = gaze vector cannot see the target object * Bit 7 set = gaze vector can see the target object gazeCanSeeTree Records whether the gaze vector can see a tree: * Bit 7 clear = gaze vector cannot see a tree * Bit 7 set = gaze vector can see a tree xCoordHi The tile x-coordinate of the tile that can be seen zCoordHi The tile z-coordinate of the tile that can be seen.gaze5 \ If we get here then the tile is not flat and A is set \ to the tile altitude from the tile data STA S \ Set S to the tile altitude STA W \ Set W to the tile altitude \ \ This ensures that when we access the altitudes of the \ four tile corners in part 5 using variables S, T, U \ and V, the list wraps around in memory into variable W \ to support corner pairs for all four edges - see part \ 5 for details LSR considerObjects \ Clear bit 7 of considerObjects so GetTileAltitude will \ only extract the altitude and flatness of the tiles \ when we call it below, ignoring any objects on the \ landscape INC xTile \ Move along the x-axis to fetch the next tile to the \ right JSR GetTileAltitude \ Call GetTileAltitude with bit 7 of considerObjects \ clear to extract the following tile data: \ \ * A = the high byte of the tile's altitude (which \ is also the altitude of the tile corner) \ \ * C flag = the tile's shape, clear if the tile is \ flat or set if the tile is not flat STA V \ Set V to the altitude of the tile to the right INC zTile \ Move along the x-axis to fetch the next tile into the \ screen JSR GetTileAltitude \ Call GetTileAltitude with bit 7 of considerObjects \ clear to extract the following tile data: \ \ * A = the high byte of the tile's altitude (which \ is also the altitude of the tile corner) \ \ * C flag = the tile's shape, clear if the tile is \ flat or set if the tile is not flat STA U \ Set U to the altitude of the tile behind DEC xTile \ Move back along the x-axis to fetch the next tile to \ the left JSR GetTileAltitude \ Call GetTileAltitude with bit 7 of considerObjects \ clear to extract the following tile data: \ \ * A = the high byte of the tile's altitude (which \ is also the altitude of the tile corner) \ \ * C flag = the tile's shape, clear if the tile is \ flat or set if the tile is not flat STA T \ Set V to the altitude of the tile to the left DEC zTile \ Move out of the screen, back along the z-axis to take \ us back to the tile we are processing \ So at this point we have the altitudes of four tile \ corners, as follows, with the view from above: \ \ ^ [T] [U] \ | \ | [S] [V] \ z-axis \ into \ screen x-axis from left to right ---> \ \ S is the altitude of the tile corner that anchors the \ tile below the current position along the viewer's \ gaze, and T, U and V are the altitudes of the tile's \ other three corners, so now we can analyse how the \ gaze interacts with the shape of the tile JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile) AND #%00001111 \ The tile shape is in the low nibble of the tile data, \ so extract the tile shape into A CMP #4 \ If the tile shape is 4 then jump to gaze6 BEQ gaze6 CMP #12 \ If the tile shape is 12 then keep going, otherwise BNE gaze8 \ jump to gaze8 .gaze6 \ The tile shape is 4 or 12, so fall through into part 3 \ to process this shapeName: FollowGazeVector (Part 2 of 5) [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the altitudes of the four corners in a non-flat tileContext: See this subroutine on its own page References: No direct references to this subroutine in this source file\ If we get here then the tile shape is 4 or 12, so the \ tile has one horizontal edge with the other two points \ being arbitrary (but not at the same height as the \ horizontal edge) LDA yCoordHi \ If yCoordHi is higher than any of the four tile CMP S \ corners, then the current position along the viewer's BCS gaze7 \ gaze is above the tile surface, so jump to gaze1 via CMP T \ gaze7 to move along the gaze vector and restart the BCS gaze7 \ checks CMP U BCS gaze7 CMP V BCS gaze7 JMP gaze4 \ Otherwise the current position along the viewer's \ gaze is below the height of at least one tile corner, \ so we consider this to be enough interference to be \ blocking the viewer's gaze of any tiles that might be \ partially visible beyond (which might be possible if \ the slope is from left to right, for example) \ \ So jump to gaze4 to return from the subroutine with \ the C flag set to indicate that the viewer is not \ looking at a tile .gaze7 JMP gaze1 \ Jump to gaze1 to move along the gaze vector and \ restart the checks (this jump point is for use by \ branching instructions)Name: FollowGazeVector (Part 3 of 5) [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate whether the viewing object's gaze is obstructed by a tile of shape 4 or 12 (i.e. a tile with one horizontal edge)Context: See this subroutine on its own page References: No direct references to this subroutine in this source file.gaze8 \ If we get here then the tile shape in A is not 4 or 12 \ \ It also isn't 0, as the tile is not flat, and it isn't \ 8 either, as that shape number isn't used \ \ The tile shape is therefore one of the following: \ \ 1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15 \ \ All of these shapes have two horizontal edges and two \ sloping edges, so we now work out which of the sloping \ edges we should use to check against the gaze vector \ to see if the slope is obstructing the gaze \ \ We pass this information to part 5 as a single tile \ corner number in A, which tells part 5 to use the edge \ from corner A to corner A + 1 in the calculation \ \ To recap, we set the tile altitudes in part 2 like \ this: \ \ ^ [T] [U] \ | \ | [S] [V] \ z-axis \ into \ screen x-axis from left to right ---> \ \ We can also number these corners so we can pass a \ corner number to part 5, so let's number them like \ this: \ \ [T] [U] [1] [2] \ = \ [S] [V] [0] [3] \ \ As mentioned above, when we pass a corner number in A \ to part 5, this tells part 5 to use the edge from \ corner A to corner A + 1 in the calculation, so if we \ pass A = 0 to part 5, that will tell it to use the \ left edge (from 0 to 1) in the calculation, while \ passing A = 2 will make it use the right edge (from 2 \ to 3) \ \ This part is therefore all about setting A to the \ correct corner number for the edge that we want to use \ in the gaze vector calculation \ \ We start by working out which two edges in the tile \ shape are the sloping edges LSR A \ If bit 0 of the shape number is clear, jump to gaze10 BCC gaze10 \ If we get here then the tile shape is one of the \ following (i.e. %xxxxxxx1 in binary): \ \ 1, 3, 5, 7, 9, 11, 13, 15 \ \ and A contains the shape >> 1 LSR A \ If bit 1 of the shape number is set, jump to gaze9 BCS gaze9 \ If we get here then the tile shape is one of the \ following (i.e. %xxxxxx01 in binary): \ \ 1, 5, 9, 13 \ \ and A contains the shape >> 2 to give: \ \ 0, 1, 2, 3 AND #1 \ Set A to bit 0 of A, which is bit 2 of the original \ shape number, so: \ \ * A = 0 if the tile shape is 1 or 9 \ (i.e. %xxxxx001 in binary) \ \ * A = 1 if the tile shape is 5 or 13 \ (i.e. %xxxxx101 in binary) \ At this point we have a suitable value of A to pass \ to part 5, as we have: \ \ * A = 0 to denote the left edge for shapes 1 and 9 \ \ 0 0 or 1 1 \ 1 1 0 0 \ \ * A = 1 to denote the top edge for shapes 5 and 13 \ \ 1 0 or 0 1 \ 1 0 0 1 \ \ As the sloping edges in these shapes have the exact \ same slope gradient, either of them can be used in the \ calculation JMP gaze13 \ Jump to gaze13 in part 5 to check the gaze vector \ against the edge specified in A .gaze9 \ If we get here then the tile shape is one of the \ following (i.e. %xxxxxx11 in binary): \ \ 3, 7, 11, 15 \ \ and A contains the shape >> 2 to give: \ \ 0, 1, 2, 3 \ \ and the C flag is set ADC #1 \ Set A = (A + 2) mod 4, to give: AND #3 \ \ 2, 3, 0, 1 \ \ The addition adds 2 because the C flag is set JMP gaze11 \ Jump to gaze11 to analyse this shape .gaze10 \ If we get here then the tile shape is one of the \ following (i.e. %xxxxxxx0 in binary): \ \ 2, 6, 10, 14 \ \ and A contains the shape >> 1 LSR A \ Shift A right by one place, so the tile shape is one \ of the following: \ \ 2, 6, 10, 14 \ \ and A contains the shape >> 2 to give: \ \ 0, 1, 2, 3 .gaze11 \ If we get here then the tile shape is one of: \ \ 2, 3, 6, 7, 10, 11, 14, 15 \ \ and A contains a number that tells us which two edges \ are the sloping edges: \ \ * If A = 0, the bottom and left edges slope \ \ * If A = 1, the top and left edges slope \ \ * If A = 2, the top and right edges slope \ \ * If A = 3, the bottom and right edges slope \ \ We either jump here with: \ \ * A = 0 if the tile shape is 11 0 0 Bottom \ 1 0 Left \ \ * A = 1 if the tile shape is 15 0 1 Top \ 1 1 Left \ \ * A = 2 if the tile shape is 3 1 0 Top \ 1 1 Right \ \ * A = 3 if the tile shape is 7 1 1 Bottom \ 1 0 Right \ \ or we fall through from above with: \ \ * A = 0 if the tile shape is 2 1 1 Bottom \ 0 1 Left \ \ * A = 1 if the tile shape is 6 1 0 Top \ 0 0 Left \ \ * A = 2 if the tile shape is 10 0 1 Top \ 0 0 Right \ \ * A = 3 if the tile shape is 14 0 0 Bottom \ 0 1 Right \ The next step is to choose which of these sloping \ edges is the closest to the current position along \ the gaze vector \ \ The tileEdges table helps us do this \ \ It is made up of two numbers for each pair of edges \ represented by A; these are the corner numbers at the \ start of each of the two edges in the pair, so for \ each value of A, we need to choose one of the corner \ numbers from the pair to pass to part 5 \ \ We choose the correct value according to whether the \ gaze vector is closer to the edge along the x-axis \ or the z-axis \ \ For example, consider tile shape 11, for which we will \ will have A = 0 to denote that the bottom and left \ edges are the sloping edges in this shape: \ \ 0 0 \ 1 0 \ \ The tileEdges table contains 0 and 3 for this shape \ (to denote the left and bottom edges respectively) \ \ Zooming in on the tile, we have the following, where \ [x] is the current position along the gaze vector when \ looking at the tile from above: \ \ [1] [2] \ | xCoordLo \ |<--------> [x] \ | ^ \ | | \ | | zCoordLo \ | v \ [0] --------------- [3] \ \ The calculation below clears the C flag so we compare \ xCoordLo and zCoordLo, so the calculation is: \ \ * If xCoordLo < zCoordLo, we pick the first entry \ from tileEdges, i.e. 0, which is the left edge \ (because the gaze point in [x] is closer to the \ left edge than the bottom edge) \ \ * If xCoordLo >= zCoordLo, we pick the second entry \ from tileEdges, i.e. 3, which is the bottom edge \ (because the gaze point in [x] is closer to the \ bottom edge than the left edge) \ \ The comparison is flipped around for tiles where the \ slopes are along the top/left or bottom/right edges, \ so we compare ~xCoordLo instead, like this example \ when the slopes are along the bottom and right edges: \ \ [1] [2] \ ~xCoordLo | \ [x] <--------> | \ ^ | \ | | \ zCoordLo | | \ v | \ [0] --------------- [3] STA G \ Store the value of A in G so we can retrieve it below LSR A \ Set the C flag to bit 0 of A, so it is: \ \ * 0 if G = 0 or 2 (bottom/left or top/right) \ \ * 1 if G = 1 or 3 (top/left or bottom/right) \ \ So we compare against ~xCoordLo in the following for \ the top/left or bottom/right edge pairs LDA xCoordLo \ Set A to xCoordLo when C = 0 or ~xCoordLo when C = 1 BCC gaze12 \ EOR #%11111111 \ This ensures that the correct x-axis distance is used \ in the comparison .gaze12 CMP zCoordLo \ Set the C flag as follows: \ \ * 0 if xCoordLo < zCoordLo (i.e. gaze vector is \ closer to the left or right edge) \ \ * 1 if xCoordLo >= zCoordLo i.e. gaze vector is \ closer to the top or bottom edge) \ \ So we choose the first entry from tileEdges when the \ gaze vector is closer to the left or right edge, or \ the second entry when the gaze vector is closer to the \ top or bottom edge LDA G \ Set Y = (G * 2) + C ROL A \ TAY \ So Y can be used as an index into the tileEdges table \ that points to the entry in pair number G as specified \ by the C flag LDA tileEdges,Y \ Set A to the edge number that we should test against \ the gaze vector \ Fall into part 5 to check whether the gaze vector is \ obscured by the edge we just choseName: FollowGazeVector (Part 4 of 5) [Show more] Type: Subroutine Category: Maths (Geometry) Summary: For non-flat tiles with two horizontal edges, work out which tile edge to use when checking for obstruction of the gaze vectorContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.gaze13 \ We jump here with A set to the number of the tile edge \ that we can test against the gaze vector (where the \ edge goes from corner A to corner A + 1) TAX \ Copy A into X so we can use it as an index into the \ corner height variables we set up above \ By this point the value of X (and, currently, of A) \ is in the range 0 to 3, and it represents the first \ corner of the edge within the tile we are analysing \ \ Here are the tile's corner heights that we set in the \ S, T, U and V variables above: \ \ ^ [T] [U] \ | \ | [S] [V] \ z-axis \ into \ screen x-axis from left to right ---> \ \ The variables S, T, U and V are consecutive in memory, \ so LDA S,X can be used to select corner altitudes for \ values of X, like this: \ \ [T] [U] [1] [2] \ = \ [S] [V] [0] [3] \ \ Similarly, LDA T,X will select the altitude for tile \ corner X + 1 \ \ Note that back in part 2, we set W to the same value \ as S, so this approach will still work if X = 3, as \ LDA T,X will fetch the value of W, which is the tile \ altitude of corner 0, just like S \ \ So X (and A) represent the starting corner, and we now \ calculate the coordinates and gradient of the tile \ edge from corner X to corner X + 1, so we can work out \ whether the gaze passes above or below the edge \ \ We start by calculating the distance along the tile \ edge that corresponds to the current position of the \ gaze vector \ \ To see how this works, consider the axes of the tile \ corners: \ \ ^ [1] [2] \ | \ | [0] [3] \ z-axis \ into \ screen x-axis from left to right ---> \ \ The low bytes of the current gaze vector in xCoordLo \ and zCoordLo give us the fractional part of the \ coordinate of the current position along the gaze, and \ the fractional part gives us the position of the \ coordinate within the tile (as the tile corners are on \ the integer coordinates) \ \ Zooming in on the tile, we have the following, where \ [x] is the current position along the gaze vector when \ looking at the tile from above: \ \ [1] [2] \ xCoordLo \ <----------> [x] \ ^ \ | \ | zCoordLo \ | \ [0] v [3] \ \ We now set edgeGazeDistance to the corner-relative \ coordinate of the gaze vector along the edge that we \ are considering (so if we are examining the edge from \ corner X to corner X + 1, we're looking for the \ distance between corner X and the [x] of the gaze \ vector) \ \ For example, if we are considering the left edge from \ 0 to 1, the distance of the [x] relative to corner 0 \ and along the edge is zCoordLo, while on the top edge \ from 1 to 2, the distance of the [x] relative to \ corner 1 and along the edge is xCoordLo \ \ The other two edges are similar but the distances are \ in the opposite direction to the axes \ \ So if we are considering the bottom edge from 3 to 0, \ the distance is ~xCoordLo, because: \ \ xCoordLo + ~xCoordLo = 1 \ \ So adding xCoordLo and ~xCoordLo together gives us the \ distance between the tile corners (which is 1), so it \ follows that ~xCoordLo is the distance from corner 3 \ to the gaze point, as xCoordLo is the distance from \ corner 0 to the gaze point \ \ We now set edgeGazeDistance to the correct value LSR A \ Set Y as follows: LDY xCoordLo \ BCS gaze14 \ * xCoordLo if A = 1 or 3 (i.e. bit 0 of A is 1) LDY zCoordLo \ \ * zCoordLo if A = 0 or 2 (i.e. bit 0 of A is 1) .gaze14 LSR A \ Set A as follows: TYA \ BCC gaze15 \ * A = Y if A = 0 or 1 (i.e. bit 1 of A is 0) EOR #%11111111 \ \ * A = ~Y if A = 2 or 3 (i.e. bit 1 of A is 1) .gaze15 STA edgeGazeDistance \ Set edgeGazeDistance to the result in A, so we set it \ as follows: \ \ * edgeGazeDistance = zCoordLo if A = 0 \ \ * edgeGazeDistance = xCoordLo if A = 1 \ \ * edgeGazeDistance = ~zCoordLo if A = 2 \ \ * edgeGazeDistance = ~xCoordLo if A = 3 \ \ We use edgeGazeDistance below to calculate where the \ gaze vector crosses the edge \ We now calculate the gradient of the tile edge LDA S,X \ Set G to the altitude of corner X, which we use in the STA G \ calculation at the end of the routine LDA T,X \ Set A to the altitude of corner X + 1 minus the SEC \ altitude of corner X SBC S,X \ \ So this is the gradient of the edge between corner \ X and corner X + 1 PHP \ Store the flags of the result on the stack, so we can \ retrieve the sign below BPL gaze16 \ If the result is negative then negate it to make it EOR #%11111111 \ positive, so A now contains the absolute value of the CLC \ edge gradient ADC #1 .gaze16 STA U \ Set U to the absolute gradient of the edge LDA edgeGazeDistance \ Set A to the fractional distance along the edge that \ corresponds to the current position along the gaze \ vector JSR Multiply8x8 \ Set (A T) = A * U \ = fractional distance * |gradient| PLP \ Restore the sign of the gradient which we stored on \ the stack above, so the N flag reflects the sign of \ the gradient JSR Absolute16Bit \ Set the sign of (A T) to match the gradient, so A is \ now the correct sign for the calculation and we have \ the following: \ \ (A T) = fractional distance * gradient \ \ The fractional distance is a fractional value that \ represents how far along the edge we would need to go \ in order to be at the corresponding coordinate as the \ current position along the gaze vector \ \ The gradient is the change in altitude as we move from \ one end of the edge to the other \ \ Multiplying these two therefore gives us the height of \ the point along the edge that corresponds to the \ current position along the gaze vector \ \ This height is relative to the tile corner at the \ start of the edge (i.e. corner X), so to get the \ altitude of the point in the 3D world, we need to add \ the result in (A T) to the altitude of corner X \ \ We set G above to the altitude of corner X, which came \ from the call to GetTileAltitude, so G contains the \ high byte of the altitude and is therefore an integer \ value, so we add a low byte of 0 in the addition CLC \ Set (U T) = (G 0) + (A T) ADC G \ STA U \ So (U T) contains the altitude of the point on the \ edge that corresponds to the current position of the \ gaze vector LDA yCoordLo \ Set (A *) = yCoord(Hi Lo) - (U T) SEC \ SBC T \ So A contains the high byte of the difference in LDA yCoordHi \ altitude between the current position along the gaze SBC U \ vector and the point on the edge that corresponds to \ the vector BPL gaze17 \ If the current position along the gaze vector is \ higher than the corresponding point on the edge, then \ the viewer's gaze is not being obstructed by the edge, \ so jump to gaze1 via gaze17 to move along the gaze \ vector and restart the checks JMP gaze4 \ Otherwise the current position along the gaze vector \ is below the corresponding point on the edge, and the \ viewer's gaze is being obstructed by the edge, so jump \ to gaze4 to return from the subroutine with the C flag \ set to indicate that the viewer is not looking at a \ tile .gaze17 JMP gaze1 \ Jump to gaze1 to move along the gaze vector and \ restart the checks (this jump point is for use by \ branching instructions)Name: FollowGazeVector (Part 5 of 5) [Show more] Type: Subroutine Category: Maths (Geometry) Summary: For non-flat tiles with two horizontal edges, work out whether the tile edge obstructs the gaze vectorContext: See this subroutine on its own page References: No direct references to this subroutine in this source file.tileEdges EQUB 0, 3 \ Left and bottom edges (G = 0) \ \ First entry (left edge) for xCoordLo < zCoordLo \ \ Second entry (bottom edge) for xCoordLo >= zCoordLo EQUB 1, 0 \ Top and left edges (G = 1) \ \ First entry (top edge) for ~xCoordLo < zCoordLo \ \ Second entry (left edge) for ~xCoordLo >= zCoordLo EQUB 1, 2 \ Top and right edges (G = 2) \ \ First entry (top edge) for xCoordLo < zCoordLo \ \ Second entry (right edge) for xCoordLo >= zCoordLo EQUB 2, 3 \ Right and bottom edges (G = 3) \ \ First entry (right edge) for ~xCoordLo < zCoordLo \ \ Second entry (bottom edge) for ~xCoordLo >= zCoordLoName: tileEdges [Show more] Type: Variable Category: Landscape Summary: A table to map tile shapes and gaze vector direction to tile edgesContext: See this variable on its own page References: This variable is used as follows: * FollowGazeVector (Part 4 of 5) uses tileEdges
This table contains tile corner numbers from a tile: ^ [1] [2] | | [0] [3] z-axis into screen x-axis from left to right ---> Each tile corner number represents an edge from the number to the number + 1, so 1 represents the edge from corner 1 to corner 2, for example. The table is used to work out which edge should be chosen for the calculation in the FollowGazeVector routine, where we work out whether the gaze vector is hitting a tile slope in a tile with two horizontal edges. See part 4 of the FollowGazeVector routine for details of how this table works..GetTileAltitude 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 tile3 \ If the tile contains an object then jump to tile3 PHA \ Store the tile data on the stack AND #%00001111 \ The tile shape is in the low nibble of the tile data, TAY \ so extract the tile shape into Y PLA \ Retrieve the tile data from the stack LSR A \ Set A to the tile altitude, which is in the top nibble LSR A \ of the tile data LSR A LSR A CPY #1 \ Clear the C flag if Y < 1, which will only happen when \ Y = 0, so this clears the C flag if the tile shape is \ flat, or sets the C flag if the tile shape is not flat RTS \ Return from the subroutine .tile1 \ If we get here then the tile contains object #Y and \ bit 7 of considerObjects is set, so we need to process \ the objects on the stack CPY targetObject \ If Y = targetObject then the target object is on the BNE tile2 \ tile, so set bit 7 of targetOnTile to indicate this ROR targetOnTile .tile2 LDA objectTypes,Y \ Set A to the type of object that's already on the tile \ (i.e. the type of object #Y) CMP #3 \ If the tile contains a boulder (an object of type 3), BEQ tile4 \ jump to tile4 to extract details about the boulder CMP #2 \ If the tile contains a tree (an object of type 2), BEQ tile4 \ jump to tile4 to extract details about the tree CMP #6 \ If the tile doesn't contain the Sentinel's tower (type BNE tile7 \ 6) then it must contain a robot, sentry, meanie or the \ Sentinel, so jump to tile7 to return the altitude of \ the tile rather than the object \ If we get here then the tile contains the Sentinel's \ tower in object #Y JSR CheckForTileCentre \ Set T = max(|xCoordLo - 128|, |zCoordLo - 128|) \ \ and return the same value in A \ \ This calculates how close the current position along \ the gaze vector is to the centre of the tile, in terms \ of the x-coordinate and z-coordinate CMP #100 \ If A >= 100 then the gaze vector is a long way from BCS tile6 \ the centre of the tile (i.e. more than 100/128 = 78% \ of the distance from the centre to the tile edge, \ which is outside the body of the tower), so jump to \ tile6 to set bit 6 of considerObjects and return the \ altitude of the tile \ If we get here then the gaze vector is pointing at the \ sides of the tower, so we return the altitude of the \ platform on top of the tower LDA #16 \ Set yAccuracyLo = 16, so the gaze vector has to be STA yAccuracyLo \ no more than 0.025 of a tile's height above the tile \ for us to consider it as potentially hitting the tile \ when we return to the FollowGazeVector routine (this \ makes the player have to be much more accurate when \ trying to view the Sentinel's tile) LDA yObjectLo,Y \ Set the following: CLC \ ADC #&20 \ (A yPlatformLo) = yObject(Hi Lo) + 32 STA yPlatformLo \ LDA yObjectHi,Y \ where yObject is the altitude of the Sentinel's tower ADC #&00 \ \ So we return the altitude of the top of the tower by \ effectively adding the tower's height to the tower \ object's altitude, bearing in mind that objects are \ spawned at a height of 224 above their tiles, so this \ returns the altitude of the top of the tower, with \ the tower being 224 + 32 = 256 fractional parts above \ the tile (so the tower is the height of one entire \ integer y-coordinate) CLC \ Clear the C flag to indicate that the tile is flat RTS \ Return from the subroutine .tile3 \ If we get here then the tile contains an object AND #%00111111 \ Because the tile has an object on it, the tile data TAY \ contains the number of the top object on the tile in \ bits 0 to 5, so extract the object number into Y (so \ the tile effectively contains object #Y) BIT considerObjects \ If bit 7 of considerObjects is clear, jump to tile7 to BPL tile7 \ return the altitude of the bottom object on the tile, \ iterating down through the stack of objects if there \ is more than one object \ \ The altitude of the bottom object is the same as the \ altitude of the tile itself, so this ensures that we \ return the tile's altitude from the subroutine, as per \ bit 7 of considerObjects BMI tile1 \ Otherwise bit 7 of considerObjects is set and we need \ to take any objects on the tile into consideration, so \ jump to tile1 to process the objects on the stack \ (this BMI is effectively a JMP as we just passed \ through a BPL) .tile4 \ If we get here then the tile contains a tree or a \ boulder in object #Y JSR CheckForTileCentre \ Set T = max(|xCoordLo - 128|, |zCoordLo - 128|) \ \ and return the same value in A \ \ This calculates how close the current position along \ the gaze vector is to the centre of the tile, in terms \ of the x-coordinate and z-coordinate CMP #64 \ If A >= 64 then the gaze vector is more than half way BCS tile6 \ from the centre of the tile (i.e. more than 64/128 = \ 50% of the distance from the centre to the tile edge, \ which is outside the body of the tree or boulder), so \ jump to tile6 to set bit 6 of considerObjects (if this \ is a boulder) and return the altitude of the tile LDA objectTypes,Y \ If object #Y is a tree (an object of type 2), jump to CMP #2 \ tile5 BEQ tile5 \ If we get here then the tile contains a boulder in \ object #Y SEC \ Set bit 7 of boulderOnTile to indicate that the tile ROR boulderOnTile \ contains a boulder LDA yObjectLo,Y \ Set the following: SEC \ SBC #&60 \ (A yPlatformLo) = yObject(Hi Lo) - 96 STA yPlatformLo \ LDA yObjectHi,Y \ where yObject is the altitude of the boulder SBC #&00 \ So we return the altitude of the top of the boulder by \ effectively adding the boulder's height to the boulder \ object's altitude, bearing in mind that objects are \ spawned at a height of 224 above their tiles, so this \ returns the altitude of the top of the boulder, with \ the boulder being 224 - 96 = 128 fractional parts \ above the tile (so the boulder is the height of one \ half of a y-coordinate) CLC \ Clear the C flag to indicate that the tile is flat RTS \ Return from the subroutine .tile5 \ If we get here then the tile contains a tree in \ object #Y LDA yObjectLo,Y \ Set the following: SEC \ SBC yCoordLo \ (A U) = yObject(Hi Lo) - yCoord(Hi Lo) STA U \ LDA yObjectHi,Y \ where yObject is the altitude of the tree SBC yCoordHi \ \ So (A U) contains the relative altitude of the current \ position along the gaze vector compared to the tree, \ with a positive value indicating that the gaze is \ is below the tree, and a negative value indicating \ that the gaze is above the tree PHA \ Set (A U) = (A U) + 224 LDA U \ CLC \ This adds the height of the tree to (A U), as trees ADC #&E0 \ have a height of 224 fractional y-coordinates, so the STA U \ value in (A U) is now relative to the top of the tree, PLA \ so it is effectively the height difference between the ADC #&00 \ gaze and the tree top BMI tile6 \ If A is negative then the current position along the \ gaze vector is above the top of the tree, so jump to \ tile6 to return the altitude of the tile LSR A \ Set (A U) = (A U) / 2 ROR U \ \ So (A U) is now half the height difference between the \ gaze and tree top LSR A \ If any of bits 1 to 7 of A are set then A >> 1 will be BNE tile6 \ non-zero and the original value of (A U) must have \ been at least %100, so that's a positive value with a \ high byte of at least 4 \ \ This means the gaze vector is too far below the tree \ for it to be visible, so jump to tile6 to return the \ altitude of the tile LDA U \ If we get here then we know A >> 1 = 0, so we can ROR A \ halve (U A) again and discard the top byte, as it will \ be zero, so this sets: \ \ A = (U A) / 2 \ \ So this is the original height difference between the \ gaze and tree top, divided by 4 \ We set T above to the maximum distance between the \ gaze vector and the centre of the tile in terms of the \ horizontal axes CMP T \ If A < T then the gaze is further from the centre of BCC tile6 \ the tile than the height difference to the tree top \ divided by 4, so jump to tile6 to return the altitude \ of the tile \ \ The centre point of the tile is the tree trunk, so \ this test means that gazes at the lower parts of the \ tree can be further from the centre point than gazes \ at the upper parts of the tree, while still being \ considered gazes that fall upon the tree \ \ This fits in with the tree's shape and makes the tree \ detection code more accurate BIT targetOnTile \ If bit 7 of targetOnTile is set then the tree is BMI tile6 \ the targeted object, so skip the following so that we \ only set bit 7 of gazeCanSeeTree if the tree is not \ the target (i.e. if bit 7 of targetOnTile is clear) SEC \ Set bit 7 of gazeCanSeeTree to indicate that the tree ROR gazeCanSeeTree \ can be seen by the gaze vector .tile6 LDA objectTypes,Y \ Set A to the type of object #Y CMP #2 \ If the tile contains a tree (an object of type 2), BEQ tile7 \ jump to tile7 to skip the following instruction \ If we get here then the tile contains a boulder or the \ Sentinel's tower, and in either case the gaze vector \ is not close enough to the platform object to hit it LDA #%11000000 \ Set bit 6 of considerObjects to denote that the gaze STA considerObjects \ vector is passing close by the platform object but is \ not hitting it, and set bit 7 so that it is unchanged .tile7 LDA objectFlags,Y \ Set A to the object flags for object #Y CMP #%01000000 \ If bit 6 of the object flags for object #Y is set BCS tile3 \ then object #Y is stacked on top of another object, \ so jump to tile3 with the object number in bits 0 to \ 5 of the object flags in A, so we can process that \ object instead LDA yObjectHi,Y \ At this point we have reached the object on the tile \ itself, so set A to the y-coordinate of that object, \ which will be the tile altitude, and return from the \ subroutine with the C flag clear to denote a flat \ tile, as objects are only ever placed on flat tiles RTS \ Return from the subroutineName: GetTileAltitude [Show more] Type: Subroutine Category: Landscape Summary: Calculate the altitude of a tile, optionally including platform objects and trees in the calculationContext: See this subroutine on its own page References: This subroutine is called as follows: * FollowGazeVector (Part 1 of 5) calls GetTileAltitude * FollowGazeVector (Part 2 of 5) calls GetTileAltitude * GetTileAltitudes calls GetTileAltitude
Arguments: (xTile, zTile) The coordinates of the tile to analyse considerObjects The data to extract: * Bit 7 clear = extract the tile's altitude and flatness, ignoring any objects on the tile * Bit 7 set = if the tile contains a platform object (i.e. boulder or tower) or a tree, then extract data for the object instead of the tile when applicable (this option is only set when calling this routine from FollowGazeVector) xCoordLo The low byte (i.e. fractional part) of the x-coordinate of the current position along the gaze vector (when the routine is called from FollowGazeVector) zCoordLo The low byte (i.e. fractional part) of the z-coordinate of the current position along the gaze vector (when the routine is called from FollowGazeVector)
Returns: A The high byte of the tile's altitude (though if bit 7 of considerObjects is set, see yPlatformLo below) C flag The tile's shape: * Clear if the tile is flat * Set if the tile is not flat yPlatformLo If bit 7 of considerObjects is set and the tile contains the Sentinel's tower or a boulder, the altitude of the platform on the top of the tower or boulder is returned in (A yPlatformLo) boulderOnTile If bit 7 of considerObjects is set, this records whether the tile contains a boulder: * Bit 7 clear = tile does not contain a boulder * Bit 7 set = tile contains a boulder targetOnTile If bit 7 of considerObjects is set, this records whether the tile contains the target object whose number is in targetOnTile: * Bit 7 clear = tile does not contain the target object * Bit 7 set = tile does contain the target object gazeCanSeeTree If bit 7 of considerObjects is set and bit 7 of targetOnTile is clear, this records whether the tile contains a tree that can be seen by the gaze vector: * Bit 7 clear = tile does not contain a tree that can be seen by the gaze vector * Bit 7 set = tile contains a tree that can be seen by the gaze vector considerObjects If bit 7 of considerObjects is set then bit 6 will also be set by the routine if the tile contains a platform object (i.e. boulder or Sentinel's tower) and the current position along the gaze vector is not within the platform object (so the gaze vector is passing close by the platform object but is not hitting it) yAccuracyLo Set to 16 if the tile contains the Sentinel's tower and the gaze vector is pointing at the sides of the tower[X]Subroutine AbortWhenVisible (category: Gameplay)Abort applying the tactics for this gameplay loop if updating the object on-screen will corrupt a screen pan[X]Subroutine Absolute16Bit (category: Maths (Arithmetic))Calculate the absolute value (modulus) of a 16-bit number[X]Subroutine AddVectorToCoord (category: Maths (Geometry))Add a vector to a coordinate[X]Subroutine CheckEnemyGaze (Part 1 of 2) (category: Gameplay)Check to see whether the current enemy can see a specific target object of a specific type[X]Subroutine CheckForTileCentre (category: Maths (Geometry))Calculate max(|xCoordLo - 128|, |zCoordLo - 128|)[X]Subroutine CheckObjVisibility (category: Drawing objects)Check whether an object is visible on-screen and should therefore not be changed if a pan operation is about to happen[X]Subroutine CrackerSeed (category: Cracker protection)Obfuscated storage for the high byte of the landscape number as part of the anti-cracker code[X]Subroutine DeleteObject (category: 3D objects)Delete an object, removing it from the landscape and vacating its object number[X]Subroutine DivideBy16 (category: Maths (Arithmetic))Divide a 16-bit sign-magnitude number by 16[X]Subroutine DrawTitleScreen (category: Title screen)Draw the title screen or the screen showing the secret code[X]Subroutine FinishEnemyTactics (category: Gameplay)Stop applying tactics to the current enemy and return to the ProcessGameplay routine to continue with the gameplay loop[X]Subroutine FlushBuffer (category: Keyboard)Flush the specified buffer[X]Subroutine FocusOnKeyAction (category: Keyboard)Tell the game to start focusing effort on the action that has been initiated, such as a pan of the landscape, absorb, transfer etc.[X]Subroutine FollowGazeVector (Part 1 of 5) (category: Maths (Geometry))Follow a gaze vector from a viewing object to determine whether the viewer can see a flat tile or platform (i.e. boulder or tower)[X]Subroutine GenerateLandscape (category: Landscape)Generate tile data for the landscape[X]Subroutine GetObjVisibility (category: Gameplay)Calculate whether any part of an object is visible on-screen, and if so, which character columns it spans on the screen[X]Subroutine GetObjectAngles (category: 3D objects)Calculate the angles and distances of the vector from the viewer to a specific object[X]Subroutine GetObjectCoords (category: 3D objects)Get an object's coordinates[X]Subroutine GetPitchAngleDelta (category: Maths (Geometry))Calculate the pitch angle of a vector relative to an object's pitch angle[X]Subroutine GetPlayerEnergyBCD (category: Maths (Arithmetic))Fetch the player's energy in binary coded decimal (BCD)[X]Subroutine GetRotationMatrix (Part 1 of 5) (category: Maths (Geometry))Calculate the rotation matrix for rotating the pitch or yaw angle for the sights into the global 3D coordinate system[X]Subroutine GetSightsVector (category: Maths (Geometry))Calculate the angles of the vector from the player's eyes to the sights[X]Subroutine GetTileAltitude (category: Landscape)Calculate the altitude of a tile, optionally including platform objects and trees in the calculation[X]Subroutine GetTileData (category: Landscape)Get the tile data and tile data address for a specific tile[X]Subroutine GetVectorForAngles (category: Maths (Geometry))Convert a vector from pitch and yaw angles into a 3D cartesian vector[X]Subroutine InitialiseSeeds (category: Landscape)Initialise the seed number generator so it generates the sequence of seed numbers for a specific landscape number[X]Subroutine MakeSound (category: Sound)Make a sound[X]Subroutine MoveOnToNextEnemy (category: Gameplay)Update enemyObject so the next time we consider applying enemy tactics, we apply them to the next enemy, looping from 7 to 0[X]Subroutine Multiply16x16 (category: Maths (Arithmetic))Multiply a sign-magnitude 16-bit number and a signed 16-bit number[X]Subroutine Multiply8x8 (category: Maths (Arithmetic))Calculate (A T) = T * U[X]Subroutine MultiplyCoords (category: Maths (Arithmetic))Multiply a 16-bit signed number and a 16-bit sign-magnitude value[X]Subroutine Negate16Bit (category: Maths (Arithmetic))Negate a 16-bit number[X]Subroutine PerformHyperspace (category: Gameplay)Hyperspace the player to a brand new tile, ideally at the same altitude as the current tile[X]Subroutine PlaceObjectBelow (category: 3D objects)Attempt to place an object on a tile that is below the maximum altitude specified in A[X]Subroutine PlaceObjectOnTile (category: 3D objects)Place an object on a tile, putting it on top of any existing boulders or towers[X]Subroutine PlayMusic (category: Sound)Play a piece of music[X]Subroutine PrintLandscapeNum (category: Text)Print the four-digit landscape number (0000 to 9999)[X]Subroutine PrintTextToken (category: Text)Print a recursive text token[X]Subroutine SpawnEnemies (category: Landscape)Calculate the number of enemies for this landscape, add them to the landscape and set the palette accordingly[X]Subroutine SpawnObject (category: 3D objects)Add a new object of the specified type to the objectTypes table[X]Entry point SpawnObject+3 in subroutine SpawnObject (category: 3D objects)Spawn an object of the type specified in keyPress[X]Subroutine SpawnPlayer (category: Landscape)Add the player object to the landscape, ideally placing it below all the enemies and in the bottom half of the landscape[X]Subroutine UpdateIconsScanner (category: Scanner/energy row)Update the icons in the top-left corner of the screen to show the player's current energy level and redraw the scanner box[X]Subroutine UpdatePlayerEnergy (category: Gameplay)Update the player's energy levels by adding or subtracting the amount of energy in a specific object[X]Label addv1 in subroutine AddVectorToCoord[X]Label addv2 in subroutine AddVectorToCoord[X]Variable alteredSeed in workspace Main variable workspaceAn altered version of the anti-cracker seed-related data that gets created in AlterCrackerSeed and checked in CheckCreckerSeed as part of the anti-cracker code[X]Label alts1 in subroutine AlterCrackerSeed[X]Variable boulderOnTile in workspace Main variable workspaceA flag to record whether the tile being analysed in the GetTileAltitude routine contains a boulder[X]Variable considerObjects in workspace Zero pageControls whether the GetTileAltitude routine takes platform objects into consideration when calculating tile altitudes (bit 7) and returns details about the gaze vector (bit 6)[X]Variable cosVectorPitchHi in workspace Zero pageThe high byte of cos(vectorPitchAngle) when converting pitch and yaw angles to cartesian vectors[X]Variable cosVectorPitchLo in workspace Zero pageThe low byte of cos(vectorPitchAngle) when converting pitch and yaw angles to cartesian vectors[X]Variable currentObject in workspace Zero pageThe number of the object we are currently processing[X]Entry point cvis1 in subroutine CheckObjVisibility (category: Drawing objects)Contains an RTS[X]Label divi1 in subroutine DivideBy16[X]Label dobj1 in subroutine DrainObjectEnergy[X]Label dobj2 in subroutine DrainObjectEnergy[X]Label dobj3 in subroutine DrainObjectEnergy[X]Label dobj4 in subroutine DrainObjectEnergy[X]Label dobj5 in subroutine DrainObjectEnergy[X]Label dobj6 in subroutine DrainObjectEnergy[X]Label dobj7 in subroutine DrainObjectEnergy[X]Label dran1 in subroutine FindObjectToDrain[X]Label dran2 in subroutine FindObjectToDrain[X]Label dran3 in subroutine FindObjectToDrain[X]Label dran4 in subroutine FindObjectToDrain[X]Label dren1 in subroutine ExpendEnemyEnergy[X]Label dren2 in subroutine ExpendEnemyEnergy[X]Variable edgeGazeDistance in workspace Zero pageThe fractional distance along a tile edge that matches the current position along the gaze vector[X]Label egaz1 in subroutine CheckEnemyGaze (Part 2 of 2)[X]Label egaz2 in subroutine CheckEnemyGaze (Part 2 of 2)[X]Label egaz3 in subroutine CheckEnemyGaze (Part 2 of 2)[X]Label egaz4 in subroutine CheckEnemyGaze (Part 2 of 2)[X]Variable enemyCheckingRobot in workspace Main variable workspaceA flag to pass to the FollowGazeVector routine, with bit 7 set if the routine is being called when an enemy is checking to see if it can see a robot object[X]Variable enemyDrainTimer in workspace Main variable workspaceA timer for each enemy that counts down every 0.06[X]Variable enemyEnergy in workspace Main variable workspaceEnemy energy levels (one byte per enemy)[X]Variable enemyFailCounter in workspace Main variable workspaceEnemy failed to find meanie: counter (one byte per[X]Variable enemyFailTarget in workspace Main variable workspaceEnemy failed to find meanie: target object (one byte[X]Variable enemyMeanieScan in workspace Main variable workspaceEnemy meanie scan object counter (one byte per enemy)[X]Variable enemyMeanieTree in workspace Main variable workspaceEnemy has turned a tree into a meanie (one byte per[X]Variable enemyObject in workspace Zero pageThe object number of the enemy to which we are applying tactics in this iteration around the main loop (0-7)[X]Variable enemyTarget in workspace Main variable workspaceThe object number of the enemy's target (one byte per[X]Variable enemyViewingArc in workspace Main variable workspaceThe viewing arc of the enemy that is being processed in the ApplyTactics routine[X]Variable enemyVisibility in workspace Main variable workspaceVisibility of the enemy's target (one byte per enemy)[X]Variable gameplayStack in workspace Main variable workspaceThe value of the stack pointer at the start of the ApplyEnemyTactics routine, so we can return back to the ProcessGameplay routine from deep within the tactics routines if required[X]Label gaze1 in subroutine FollowGazeVector (Part 1 of 5)[X]Label gaze10 in subroutine FollowGazeVector (Part 4 of 5)[X]Label gaze11 in subroutine FollowGazeVector (Part 4 of 5)[X]Label gaze12 in subroutine FollowGazeVector (Part 4 of 5)[X]Label gaze13 in subroutine FollowGazeVector (Part 5 of 5)[X]Label gaze14 in subroutine FollowGazeVector (Part 5 of 5)[X]Label gaze15 in subroutine FollowGazeVector (Part 5 of 5)[X]Label gaze16 in subroutine FollowGazeVector (Part 5 of 5)[X]Label gaze17 in subroutine FollowGazeVector (Part 5 of 5)[X]Label gaze2 in subroutine FollowGazeVector (Part 1 of 5)[X]Label gaze3 in subroutine FollowGazeVector (Part 1 of 5)[X]Label gaze4 in subroutine FollowGazeVector (Part 1 of 5)[X]Label gaze5 in subroutine FollowGazeVector (Part 2 of 5)[X]Label gaze6 in subroutine FollowGazeVector (Part 2 of 5)[X]Label gaze7 in subroutine FollowGazeVector (Part 3 of 5)[X]Label gaze8 in subroutine FollowGazeVector (Part 4 of 5)[X]Label gaze9 in subroutine FollowGazeVector (Part 4 of 5)[X]Variable gazeCanSeeTree in workspace Main variable workspaceA flag to record whether the gaze vector can see a tree[X]Variable gazeCheckCounter in workspace Zero pageA counter for the number of checks that are performed when following an enemy's gaze in CheckEnemyGaze[X]Variable keyPress in workspace Main variable workspaceThe key logger value for a key press[X]Variable landscapeNumberHi in workspace Main variable workspaceThe high byte of the four-digit binary coded decimal landscape number (0000 to 9999)[X]Variable landscapeNumberLo in workspace Main variable workspaceThe low byte of the four-digit binary coded decimal landscape number (0000 to 9999)[X]Label mean1 in subroutine ScanForMeanieTree[X]Label mean2 in subroutine ScanForMeanieTree[X]Label mean3 in subroutine ScanForMeanieTree[X]Label mean4 in subroutine ScanForMeanieTree[X]Label mean5 in subroutine ScanForMeanieTree[X]Variable minEnemyAltitude in workspace Main variable workspaceThe altitude of the lowest enemy on the landscape[X]Variable minObjWidth in workspace Main variable workspaceThe on-screen width to use when updating objects[X]Variable objTypeToAnalyse in workspace Zero pageThe type of the object being analysed in the GetObjectAngles routine[X]Variable objectFlags in workspace Stack variablesObject flags for up to 64 objects[X]Variable objectPitchAngle in workspace Stack variablesThe pitch angle for each object (i.e. the vertical direction in which they are facing)[X]Variable objectTypes (category: 3D objects)The object types table for up to 64 objects[X]Variable objectViewYawHi in workspace Main variable workspaceThe yaw angle of the object being analysed, relative to the current viewer (high byte)[X]Variable objectYawAngle (category: 3D objects)The yaw angle for each object (i.e. the horizontal direction in which they are facing)[X]Label pdra1 in subroutine GetPlayerDrain[X]Label pdra2 in subroutine GetPlayerDrain[X]Label pdra3 in subroutine GetPlayerDrain[X]Label pdra4 in subroutine GetPlayerDrain[X]Label pdra5 in subroutine GetPlayerDrain[X]Label pkey1 in subroutine ProcessActionKeys (Part 1 of 2)[X]Label pkey10 in subroutine ProcessActionKeys (Part 2 of 2)[X]Label pkey11 in subroutine ProcessActionKeys (Part 2 of 2)[X]Label pkey12 in subroutine ProcessActionKeys (Part 2 of 2)[X]Label pkey13 in subroutine ProcessActionKeys (Part 2 of 2)[X]Label pkey14 in subroutine ProcessActionKeys (Part 2 of 2)[X]Label pkey2 in subroutine ProcessActionKeys (Part 1 of 2)[X]Label pkey3 in subroutine ProcessActionKeys (Part 1 of 2)[X]Label pkey4 in subroutine ProcessActionKeys (Part 2 of 2)[X]Label pkey5 in subroutine ProcessActionKeys (Part 2 of 2)[X]Label pkey6 in subroutine ProcessActionKeys (Part 2 of 2)[X]Label pkey7 in subroutine ProcessActionKeys (Part 2 of 2)[X]Label pkey8 in subroutine ProcessActionKeys (Part 2 of 2)[X]Label pkey9 in subroutine ProcessActionKeys (Part 2 of 2)[X]Variable playerEnergy in workspace Main variable workspaceThe player's energy level (in the range 0 to 63)[X]Variable playerHasMovedTile in workspace Main variable workspaceA flag to record whether the player has moved to a new tile by transferring or hyperspacing, so we can decide whether to regenerate the player's landscape view[X]Variable playerIsOnTower in workspace Main variable workspaceA flag to record whether the player is on top of the the Sentinel's tower[X]Variable playerObject in workspace Zero pageThe number of the player object[X]Label ptow1 in subroutine SetPlayerIsOnTower[X]Label ptow2 in subroutine SetPlayerIsOnTower[X]Variable samePanKeyPress in workspace Main variable workspaceRecords whether the same pan key is being held down after we have just finished panning the landscape view[X]Variable scannerUpdate in workspace Main variable workspaceA flag to control whether the scanner gets updated[X]Variable sentinelHasWon in workspace Main variable workspaceA flag to record when the player runs out of energy (i.e. the energy level goes negative), at which point the Sentinel wins[X]Variable sinAngleHi in workspace Main variable workspaceThe high byte of the sine of a pitch or yaw angle, as calculated by the GetRotationMatrix routine[X]Variable sinAngleLo in workspace Main variable workspaceThe low byte of the sine of a pitch or yaw angle, as calculated by the GetRotationMatrix routine[X]Variable soundData (category: Sound)OSWORD blocks for making the various game sounds[X]Variable soundEffect in workspace Main variable workspaceDetermines how the current sound is processed by the ProcessSound routine[X]Variable targetObject in workspace Main variable workspaceThe number of the object that is being targeted in the DrainObjectEnergy routine[X]Variable targetOnTile in workspace Main variable workspaceA flag to record whether the tile being analysed in the GetTileAltitude routine contains the target object whose number is in targetObject[X]Variable targetVisibility in workspace Zero pageReports whether a target object is visible from an enemy in the CheckEnemyGaze routine[X]Label tile1 in subroutine GetTileAltitude[X]Label tile2 in subroutine GetTileAltitude[X]Label tile3 in subroutine GetTileAltitude[X]Label tile4 in subroutine GetTileAltitude[X]Label tile5 in subroutine GetTileAltitude[X]Label tile6 in subroutine GetTileAltitude[X]Label tile7 in subroutine GetTileAltitude[X]Variable tileEdges (category: Landscape)A table to map tile shapes and gaze vector direction to tile edges[X]Variable titleObjectToDraw in workspace Main variable workspaceThe object we are drawing in the DrawTitleView routine[X]Variable treeVisibility in workspace Main variable workspaceReports whether a gaze is interrupted by a tree in the CheckEnemyGaze routine[X]Variable uTurnStatus in workspace Main variable workspaceA flag to record whether we are performing or have just performed a U-turn[X]Variable vectorPitchAngleHi in workspace Zero pageThe pitch angle of a vector (high byte)[X]Variable vectorPitchAngleLo in workspace Zero pageThe pitch angle of a vector (low byte)[X]Variable vectorYawAngleHi in workspace Zero pageThe yaw angle of a vector (high byte)[X]Variable vectorYawAngleLo in workspace Zero pageThe yaw angle of a vector (low byte)[X]Variable viewingObject in workspace Zero pageThe number of the viewing object[X]Variable xObject (category: 3D objects)The x-coordinates in 3D space for the 3D objects[X]Variable xSights in workspace Main variable workspaceThe pixel x-coordinate of the sights on-screen[X]Variable xStoreEnemyGaze (category: Gameplay)Temporary storage for X so it can be preserved through calls to CheckEnemyGaze[X]Variable xVectorBot in workspace Zero pageThe x-coordinate of a vector (bottom byte)[X]Variable yAccuracyLo in workspace Zero pageThe vertical accuracy for considering a tile as being seen by the viewer, as a fractional part[X]Variable yObjectHi (category: 3D objects)The y-coordinates in 3D space for the 3D objects (high byte)[X]Variable yObjectLo (category: 3D objects)The y-coordinates in 3D space for the 3D objects (low byte)[X]Variable yPlatformLo in workspace Zero pageThe low byte of the altitude of the Sentinel's tower or boulder when returning tile data from the GetTileAltitude routine[X]Variable ySights in workspace Main variable workspaceThe pixel y-coordinate of the sights on-screen[X]Variable yVectorBot in workspace Zero pageThe y-coordinate of a vector (bottom byte)[X]Variable zObject (category: 3D objects)The z-coordinates in 3D space for the 3D objects