Skip to navigation

The Sentinel E source

Name: GetTileAltitudes [Show more] Type: Subroutine Category: Drawing the landscape Summary: Calculate tile corner altitudes and maximum tile corner altitudes for each tile in the landscape
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetTileVisibility calls GetTileAltitudes * CheckSecretStash calls via talt2

This routine calculates tile altitudes for use when drawing the landscape. The altitude of each tile corner and the shape of the anchored tile (if any) are stored as follows: * &6000 to &601F for row zTile = 0 * &6100 to &611F for row zTile = 1 ... * &7E00 to &7E1F for row zTile = 30 * &7F00 to &7F1F for row zTile = 31 The altitude of the highest tile corner for each tile is stored as follows: * &6020 to &603E for tile row anchored by zTile = 0 * &6120 to &613E for tile row anchored by zTile = 1 ... * &7D20 to &7E3E for tile row anchored by zTile = 29 * &7E20 to &7E3E for tile row anchored by zTile = 30
Returns: (Q P) (Q P) is set to &6000 (S R) (Q P) is set to &6020
Other entry points: talt2 Jump to MainTitleLoop via game10
.GetTileAltitudes LDA #0 \ Set the low byte of (Q P) = &7F00 STA P STA 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 LDA #&7F \ Set the high byte of (Q P) = &7F00 STA Q \ \ This is the address where we will store the altitude \ data for the back row of tile corners in the landscape \ We now iterate through the tile corners with a nested \ loop, with zTile going from 31 to 0 (so that's from \ back to front) \ \ For each zTile, xTile also goes from 31 to 0, so \ that's from right to left \ \ So we work through the landscape, starting with the \ row of tile corners at the back (which we work through \ from right to left), and then doing the next row \ forward, looping until we reach the front row LDA #31 \ Set zTile = 31 so we start iterating from the back row STA zTile \ (so zTile iterates from 31 to 0 in the outer loop) .talt1 LDA #31 \ Set xTile = 31 so we start iterating from the right STA xTile \ end of the current row (so xTile iterates from 31 to 0 \ in the inner loop) BNE talt3 \ Jump to talt3 to join the loop below (this BNE is \ effectively a JMP as A is never zero) .talt2 JMP game10 \ Jump to MainTitleLoop to restart the game (this has \ nothing to do with the GetTileAltitudes routine, but \ is all part of the anti-cracker code) .talt3 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 LDY xTile \ Set Y to the tile corner x-coordinate, to use as an \ index so we store the tile corner data like this: \ \ * Column xTile = 31 is stored in (Q P) + &1F \ * Column xTile = 30 is stored in (Q P) + &1E \ ... \ * Column xTile = 1 is stored in (Q P) + &01 \ * Column xTile = 0 is stored in (Q P) + &00 \ \ (Q P) starts at &7F00 for zTile = 31, so the back row \ of the landscape is stored in &7F00 to &7F1F \ \ (Q P) is decremented for each tile row (see below), so \ the next row forward is stored in &7E00 to &7E1F, for \ example ROL A \ Rotate the C flag into bit 0 of A, so we have the \ following: \ \ * Bit 0 = clear if the tile is flat or set if the \ tile is not flat \ \ * Bits 1-4 = the tile corner's altitude STA (P),Y \ Store the tile corner data in A in the Y-th entry in \ the variable at (Q P), so this populates the extracted \ data for the tile corner at (xTile, zTile) DEC xTile \ Decrement the tile corner x-coordinate in the inner \ loop BPL talt3 \ Loop back until we have processed all the tile corners \ in the tile row at z-coordinate zTile, working from \ right to left DEC Q \ Decrement the high byte of (Q P), so we store the \ tile corner data like this: \ \ * Row zTile = 31 is stored in &7F00 to &7F1F \ * Row zTile = 30 is stored in &7E00 to &7E1F \ ... \ * Row zTile = 1 is stored in &6100 to &611F \ * Row zTile = 0 is stored in &6000 to &601F DEC zTile \ Decrement the tile corner z-coordinate in the outer \ loop BPL talt1 \ Loop back until we have processed all the tile rows in \ the landscape, working from the back row of the \ landscape all the way to the front row \ \ This leaves (Q P) set to &6000 \ We now iterate through each tile to calculate the \ altitude of the highest tile corner, so we can store \ it after the altitude data that we just extracted LDA #&20 \ Set the low byte of (S R) = &20 STA R \ \ At this point the low byte of (Q P) is still zero, so \ the following loop will start with the following \ values: \ \ (Q P) = &7E00 \ \ (S R) = &7E20 \ \ In other words (Q P) points to the altitude data that \ we just extracted, and (S R) points to the next set of \ bytes just after the end of the altitude data \ We now work our way through the tiles, using X as the \ row number iterating from the rear, and Y as the \ column number iterating from right to left along each \ row in turn LDX #30 \ Set X = 30 to use as the row number, so we start \ iterating from the rear, skipping the row right at the \ back as the tile corners in that row do not anchor any \ tiles (so X iterates from 30 to 0 in the outer loop) .talt4 TXA \ Set A = &60 + X CLC \ ADC #&60 \ So this is the high byte of the address of the \ extracted altitude for row X, starting from &7E and \ working down to &60 as we iterate over each row STA Q \ Set the high byte of (Q P) to A, so (Q P) points to \ the extracted altitude data for row X STA S \ Set the high byte of (S R) to A, so (S R) points to \ the address just after the extracted altitude data \ for row X LDY #30 \ Set Y = 30 to use as the column number, so we start \ iterating from the right, skipping the rightmost \ column as the tile corners in that column do not \ anchor any tiles (so Y iterates from 30 to 0 in the \ inner loop) .talt5 \ We now calculate the altitude of the highest corner \ for the tile that we are analysing, i.e. the tile \ that's anchored by the tile corner at tile coordinates \ (X, Y) \ \ We do this by working through all four corners in the \ tile, starting with the anchor point, and then \ checking the corner to the right, then the corner \ behind, and then the corner to the left LDA (P),Y \ Fetch the extracted altitude data for the anchor of \ the tile that we are analysing LSR A \ Shift bit 0 into the C flag, so it contains the shape, \ and set A as the altitude BCC talt9 \ If the tile is flat then the C flag will be clear, so \ jump to talt9 to set (S R) to the altitude of the tile \ anchor, as the tile is flat and this altitude will do \ for the highest point on the tile ROL A \ Otherwise the tile is not flat, so rotate the C flag \ back into the extracted altitude data in A INY \ Increment Y to the tile corner to the right of the one \ we are analysing CMP (P),Y \ If this corner's altitude is less than the tile BCC talt6 \ anchor's altitude, jump to talt6 to skip the following LDA (P),Y \ Set A to the new corner's height, so that A contains \ the highest altitude of the tile's front two corners .talt6 INC Q \ Increment the high byte of (Q P), so it now points to \ the extracted altitude data for the row of tile \ corners behind the one we are currently analysing \ \ Y is still incremented from the anchor point, so this \ points us to the corner behind the one we just \ analysed CMP (P),Y \ If this corner's altitude is less than the highest BCC talt7 \ altitude, jump to talt7 to skip the following LDA (P),Y \ Set A to the new corner's height, so that A contains \ the highest altitude of the tile's front two corners \ and the one at the rear-right of the tile .talt7 DEY \ Decrement Y to the tile corner to the left of the one \ we just analysed CMP (P),Y \ If this corner's altitude is less than the highest BCC talt8 \ altitude, jump to talt8 to skip the following LDA (P),Y \ Set A to the new corner's height, so that A contains \ the highest altitude of the tile's front two corners \ and the two rear corners .talt8 DEC Q \ Decrement the high byte of (Q P) so it once again \ points to the extracted altitude data for the anchor \ point of the tile we are analysing LSR A \ Shift the highest altitude in A to remove the tile \ shape from bit 0 and set A to the actual altitude from \ bits 2 to 4 .talt9 STA (R),Y \ Set the entry in (S R) for this tile to the value in A \ so it contains the highest altitude of the tile \ anchored by the relevant tile corner DEY \ Decrement the column counter in Y to move left by one \ tile BPL talt5 \ Loop back until we have processed the whole row from \ right to left DEX \ Decrement the row counter in X to move forward by one \ row BPL talt4 \ Loop back to process the next row until we have \ processed all tiles in all rows in the landscape \ \ This leaves (S R) set to &6020 RTS \ Return from the subroutine
Name: DrawLandscapeView (Part 1 of 3) [Show more] Type: Subroutine Category: Drawing the landscape Summary: Set up a number of variables for drawing the landscape view
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTitleView calls DrawLandscapeView * DrawUpdatedObject calls DrawLandscapeView * MainGameLoop calls DrawLandscapeView * PanLandscapeView calls DrawLandscapeView

Arguments: viewingObject The number of the object that is viewing the landscape
Returns: C flag The status of the drawing operation: * Clear = the whole landscape has been drawn * Set = the whole landscape has not been drawn as the panning key is no longer being held down
.DrawLandscapeView LDX viewingObject \ Set X to the number of the object that is viewing the \ landscape LDA #LO(polygonPoint) \ Set drawViewAngles(1 0) = polygonPoint STA drawViewAngles \ LDA #HI(polygonPoint) \ This sets drawViewAngles(1 0) so DrawPolygon will STA drawViewAngles+1 \ implement the default behaviour of drawing using point \ data for tile faces rather than object polygons LDA objectYawAngle,X \ Set A to the yaw angle of the viewer's object CLC \ Set viewingArcRightYaw = yaw angle + 32 ADC #32 \ STA viewingArcRightYaw \ This gives us the yaw angle of the right edge of the \ viewing arc, where the arc is 90 degrees wide (as a \ full circle is represented by 256) AND #63 \ Set T = (yaw angle + 32) mod 64 - 32 SEC \ SBC #32 \ This gives us the yaw angle of the centre of the STA T \ viewing arc (i.e. the direction of gaze), reduced \ into the range of a single 90-degree quadrant LDA viewingArcRightYaw \ Set bits 0-1 of Y to bits 6-7 of viewingArcRightYaw ASL A \ ROL A \ This gives us a value in the range 0 to 3, giving ROL A \ the number of the quadrant that contains the right AND #3 \ edge of the viewing arc, numbered clockwise and TAY \ starting from 12 o'clock to 3pm LDA quadrantOffsets,Y \ Set quadrantOffset to 0, 1, 33 or 32 depending on the STA quadrantOffset \ quadrant containing the right edge of the viewing arc \ \ This is used by the DrawTileAndObjects routine TYA \ Set viewingQuadrantx4 = Y * 4 ASL A \ ASL A \ So viewingQuadrantx4 contains the quadrant number STA viewingQuadrantx4 \ containing the right edge of the viewing arc, \ multiplied by 4 \ \ This is used by the DrawSlopingTile routine to work \ out the number of faces in a sloping tile TYA \ Set viewingQuadrantOpp = Y - 2 SEC \ SBC #2 \ So it viewingQuadrantOpp contains the number of the STA viewingQuadrantOpp \ quadrant opposite the quadrant containing the right \ edge of the viewing arc \ \ This is used by the DrawTileAndObjects routine LDA T \ Set screenLeftYawHi = T - 10 SEC \ SBC #10 \ So screenLeftYawHi contains the yaw angle of the gaze STA screenLeftYawHi \ in the centre of the viewing arc, less 14.0625 degrees \ (i.e. 360 * 10 / 256) \ \ The screen is 20 yaw angle units across, so this sets \ screenLeftYawHi to the high byte of the yaw angle of \ the left edge of the screen, reduced into the range of \ a single 90-degree quadrant (so it's relative to the \ 90-degree viewing arc) \ We now set (xTileViewer, zTileViewer) to the tile \ coordinate of the viewer (object #X), but with the \ axes rotated to match the orientation of the viewer, \ so the x- and z-coordinate axes are from the \ perspective of the viewer rather than of the 3D world \ \ As a reminder, the degree system in the Sentinel looks \ like this, with the z-axis pointing into the screen \ and the x-axis pointing right: \ \ 0 \ -32 | +32 Overhead view of player \ \ | / \ \ | / 0 = looking straight ahead \ \|/ +64 = looking sharp right \ -64 -----+----- +64 -64 = looking sharp left \ /|\ \ / | \ ^ x-axis left --> \ / | \ | to right \ -96 | +96 z-axis \ 128 into screen \ \ To make this easier to describe, let's label it like a \ clock: \ \ 12 \ 10.30 | 1.30 \ \ | / \ \ | / \ \|/ \ 9 -----+----- 3 \ /|\ \ / | \ \ / | \ \ 7.30 | 4.30 \ 6 \ \ We now work out which quadrant contains the viewing \ arc and set (xTileViewer, zTileViewer) to the tile \ coordinates of the viewer, but using the axes from the \ viewer's frame of reference/point of view \ \ Note that in the following, we subtract from 30 rather \ than 31 because we are working with tiles rather than \ tile corners BIT viewingArcRightYaw \ If bit 7 of the quadrant containing the right edge of BMI dlan2 \ the viewing arc is set, jump to dlan2 BVS dlan1 \ If bit 6 of the quadrant containing the right edge of \ the viewing arc is set, jump to dlan1 \ If we get here then: \ \ * Bit 7 of the right edge's quadrant is clear \ * Bit 6 of the right edge's quadrant is clear \ \ This means that the right edge of the viewing arc is \ in the 12 o'clock to 3 o'clock quadrant \ \ So the viewing direction in the middle of the arc is \ between 10.30 and 1.30, or broadly in the direction of \ 12 o'clock \ \ This is the standard orientation of the landscape, \ so we can simply set the viewer coordinates to those \ of object #X LDA xObject,X \ Set (xTileViewer, zTileViewer) = (x, z) STA xTileViewer \ LDA zObject,X \ where object #X is on tile (x, z) STA zTileViewer JMP dlan4 \ Jump to dlan4 to keep going .dlan1 \ If we get here then: \ \ * Bit 7 of the right edge's quadrant is clear \ * Bit 6 of the right edge's quadrant is set \ \ This means that the right edge of the viewing arc is \ in the 3 o'clock to 6 o'clock quadrant \ \ So the viewing direction in the middle of the arc is \ between 1.30 and 4.30, or broadly in the direction of \ 3 o'clock \ \ If you imagine the standard 3D world, with the z-axis \ going into the screen and the x-axis going from left \ to right, then turning right means that from our new \ perspective: \ \ * The axis we now see running from left to right is \ the 3D world z-axis, but in the opposite direction \ \ * The axis we now see going away from us is the 3D \ world x-axis \ \ Therefore, from the perspective of the viewer: \ \ * The x-axis is the 3D world z-axis in the opposite \ direction, which is 30 - z \ \ * The z-axis is the 3D world x-axis, which is x \ \ So that's what we set now CLC \ Set (xTileViewer, zTileViewer) = (30 - z, x) LDA #31 \ SBC zObject,X \ where object #X is on tile (x, z) STA xTileViewer LDA xObject,X STA zTileViewer JMP dlan4 \ Jump to dlan4 to keep going .dlan2 \ If we get here then bit 7 of the quadrant containing \ the right edge of the viewing arc is set BVS dlan3 \ If bit 6 of the quadrant containing the right edge of \ the viewing arc is set, jump to dlan3 \ If we get here then: \ \ * Bit 7 of the right edge's quadrant is set \ * Bit 6 of the right edge's quadrant is clear \ \ This means that the right edge of the viewing arc is \ in the 6 o'clock to 9 o'clock quadrant \ \ So the viewing direction in the middle of the arc is \ between 4.30 and 7.30, or broadly in the direction of \ 6 o'clock \ \ If you imagine the standard 3D world, with the z-axis \ going into the screen and the x-axis going from left \ to right, then turning around to face out of the \ screen means that from our new perspective: \ \ * The axis we now see running from left to right is \ the 3D world x-axis, but in the opposite direction \ \ * The axis we now see going away from us is the 3D \ world z-axis, but in the opposite direction \ \ Therefore, from the perspective of the viewer: \ \ * The x-axis is the 3D world x-axis in the opposite \ direction, which is 30 - x \ \ * The z-axis is the 3D world z-axis in the opposite \ direction, which is 30 - z \ \ So that's what we set now CLC \ Set (xTileViewer, zTileViewer) = (30 - x, 30 - z) LDA #31 \ SBC xObject,X \ where object #X is on tile (x, z) STA xTileViewer CLC LDA #31 SBC zObject,X STA zTileViewer JMP dlan4 \ Jump to dlan4 to keep going .dlan3 \ If we get here then: \ \ * Bit 7 of the right edge's quadrant is set \ * Bit 6 of the right edge's quadrant is set \ \ This means that the right edge of the viewing arc is \ in the 9 o'clock to 12 o'clock quadrant \ \ So the viewing direction in the middle of the arc is \ between 7.30 and 10.30, or broadly in the direction of \ 9 o'clock \ \ If you imagine the standard 3D world, with the z-axis \ going into the screen and the x-axis going from left \ to right, then turning left means that from our new \ perspective: \ \ * The axis we now see running from left to right is \ the 3D world z-axis \ \ * The axis we now see going away from us is the 3D \ world x-axis, but in the opposite direction \ \ Therefore, from the perspective of the viewer: \ \ * The x-axis is the 3D world z-axis, which is z \ \ * The z-axis is the 3D world x-axis in the opposite \ direction, which is 30 - x \ \ So that's what we set now LDA zObject,X \ Set (xTileViewer, zTileViewer) = (z, 30 - x) STA xTileViewer \ CLC \ where object #X is on tile (x, z) LDA #31 SBC xObject,X STA zTileViewer
Name: DrawLandscapeView (Part 2 of 3) [Show more] Type: Subroutine Category: Drawing the landscape Summary: Work through the landscape, drawing one row of tiles/objects at a time, from the back row to the front row
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.dlan4 LDA #31 \ We now iterate through all the tile rows, from back to STA zTile \ front towards the viewer, so set a row counter in \ zTile to iterate from 31 to 0 LDA xTileLeftPrevious \ Set xTileViewLeft = xTileLeftPrevious, so we start STA xTileViewLeft \ checking for the view edges, starting from the left \ edge from the previous calculation (or from tile zero \ if this is the first time) \ \ This makes the search for edges more efficient as the \ edges in neighbouring rows will be close together LDA #0 \ Set drawingTableOffset = 0 so the call to first call STA drawingTableOffset \ GetTileViewEdges (for tile row 31) will populate the \ tables at tileViewData+0, tileViewYaw+0 and \ tileViewPitch+0 with the angles of the tile being \ analysed JSR GetTileViewEdges \ For the row of tile corners at the very back of the \ landscape from the point of view of the viewer, work \ out the edges of the visible portion of the row in \ the current player view, as left to right tile \ numbers in xTileViewLeft and xTileViewRight \ \ We don't draw this row as it doesn't have any tiles \ anchored by the corners, but we generate the data so \ we can use it when drawing the tile rows below LDA xTileViewLeft \ Store the column number for the left edge of the STA xTileLeftPrevious \ visible portion of the row in xTileLeftPrevious, so \ we can refer to it above when we move on to the next \ row in front \ We now loop through each row of tile corners that has \ a row of tiles anchored, drawing each row in turn, \ from the back of the view to the front .dlan5 LDA drawingTableOffset \ Flip drawingTableOffset between 0 and 32, so each call EOR #32 \ to GetTileViewEdges and GetTileViewAngles alternates STA drawingTableOffset \ between storing the tile view data in: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ and storing it in: \ \ * (drawViewYawHi+32 drawViewYawLo+32) \ \ * (drawViewPitchHi+32 drawViewPitchLo+32) \ \ * tileViewData+32 \ \ This lets us store tile view data for both the current \ row that we are drawing and the previously drawn row LDA xTileViewLeft \ Set xTileViewLeftEdge to the tile number of the left STA xTileViewLeftEdge \ edge of the visible portion of the row we are \ analysing (i.e. zTile) so that we can generate results \ for other rows without losing this information LDA xTileViewRight \ Set xTileViewRightEdge to the tile number of the right STA xTileViewRightEdge \ edge of the visible portion of the row we are \ analysing (i.e. zTile) so that we can generate results \ for other rows without losing this information JSR ProcessSound \ Process any sounds or music that are being made in the \ background DEC zTile \ Decrement zTile to the z-coordinate of the next tile \ row forward, towards the viewer, so we can draw this \ new row BMI dlan6 \ If we have already drawn all the rows from 31 to 0, \ jump to dlan6 to return from the subroutine with the \ C flag clear LDY zTile \ If the new tile row is not the tile row that contains CPY zTileViewer \ the viewer, jump to dlan7 to draw it BNE dlan7 JMP dlan18 \ The new tile row contains the viewer, so jump to \ dlan18 to draw this row as a special case .dlan6 CLC \ Clear the C flag to indicate that we have drawn the \ whole landscape RTS \ Return from the subroutine .dlan7 \ We now check whether the new row, which we are about \ to draw, has the same visible portion as the \ previously drawn row, or if whether the new row's \ visible portion extends beyond the previous visible \ row or doesn't extend as far \ \ If the visible portions don't match, then we either \ need to extend the tile data for the current row to \ match, or we need to go back and extend the tile data \ on the previous row to match the current row \ \ This is so we can draw the tiles properly, as tiles \ are made up of tiles from both the current and \ previous rows of tile corners, so the calculate tile \ view data needs to match between the two rows JSR GetTileViewEdges \ For the new tile row, work out the edges of the \ visible portion of the row in the current player view, \ as left to right tile numbers in xTileViewLeft and \ xTileViewRight LDY xTileViewLeft \ If xTileViewLeft = xTileViewLeftEdge then the left CPY xTileViewLeftEdge \ edge of the visible row in this new row is at the same BEQ dlan11 \ place as in the previous row, so jump to dlan11 to \ do the same check on the right edge BCC dlan9 \ If xTileViewLeft < xTileViewLeftEdge then the left \ edge of the visible row in this new row is less than \ (i.e. to the left of) the edge in the previous row, so \ jump to dlan9 to go back to the previous row to fetch \ the tile data needed to make the datasets match \ Otherwise xTileViewLeft > xTileViewLeftEdge and the \ left edge of the visible row in this new row is \ greater (i.e. to the right of) the edge in the \ previous row, so we need to fetch more data on the \ left end of the current row to make the datasets match .dlan8 DEY \ Decrement Y to move left along the row by one tile JSR GetTileViewAngles \ Calculate the pitch and yaw angles for the tile corner \ at (Y, zTile), from the perspective of the viewer, and \ store them in the following tables in the relevant \ entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) CPY xTileViewLeftEdge \ Loop back until we have moved left along the tile row BNE dlan8 \ all the way to the left edge in the previous row BEQ dlan11 \ Jump to dlan11 to move on to the checks on the right \ edge (this BEQ is effectively a JMP as we just passed \ through a BNE) .dlan9 \ If we get here then xTileViewLeft < xTileViewLeftEdge, \ so we need to go back to the previous row to fetch \ more tile data for the left end of the row, so we can \ use it to work out what to draw for the left end of \ the new row we are trying to draw \ \ This is because the visible part of the new row (the \ row in front) is extending left beyond the left edge \ of the visible part of the previous row (the one \ behind), so we won't have calculated the required \ tile data for the non-visible part of the previous \ row \ \ So we do that now by switching back to the previous \ row and calculating all the tile data for the tiles on \ the left of the previous row that have visible tiles \ in front of them in the new row LDA drawingTableOffset \ Flip drawingTableOffset between 0 and 32, so the calls EOR #32 \ to GetTileViewEdges and GetTileViewAngles store their STA drawingTableOffset \ data into the storage area that we used for the \ previous row, so we effectively extend the data to the \ left for the previous row INC zTile \ Increment xTile to the tile row number behind the one \ we are drawing, i.e. the previous row in this process LDY xTileViewLeftEdge \ Set Y to the left edge for the previous row .dlan10 DEY \ Decrement Y to move left along the row by one tile JSR GetTileViewAngles \ Calculate the pitch and yaw angles for the tile corner \ at (Y, zTile), from the perspective of the viewer, and \ store them in the following tables in the relevant \ entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) CPY xTileViewLeft \ Loop back until we have moved left along the tile row BNE dlan10 \ all the way to the left edge in the new row in front STY xTileViewLeftEdge \ Update xTileViewLeftEdge to store the newly moved edge \ for the previous row DEC zTile \ Decrement zTile once again to move back to the row \ that we are drawing LDA drawingTableOffset \ Flip drawingTableOffset back again, so the calls EOR #32 \ to GetTileViewEdges and GetTileViewAngles once again STA drawingTableOffset \ store data in the new row's storage area .dlan11 \ By this point we have checked the left edges of the \ current and previous rows to fill in gaps in the tile \ data caused by the new row overlapping the previous \ row, and we now do the exact same thing for the right \ edges LDY xTileViewRight \ If xTileViewRight = xTileViewRightEdge then the right CPY xTileViewRightEdge \ edge of the visible row in this new row is at the same BEQ dlan15 \ place as in the previous row, so jump to dlan15 to \ draw the new row as we have all the tile information \ we need BCS dlan13 \ If xTileViewRight > xTileViewRightEdge then the right \ edge of the visible row in this new row is greater \ then (i.e. to the right of) the edge in the previous \ row, so jump to dlan13 to go back to the previous row \ to fetch the tile data needed to make the datasets \ match \ Otherwise xTileViewRight < xTileViewRightEdge and the \ right edge of the visible row in this new row is less \ than (i.e. to the left of) the edge in the previous \ row, so we need to fetch more data on the right end of \ the current row to make the datasets match .dlan12 INY \ Increment Y to move right along the row by one tile JSR GetTileViewAngles \ Calculate the pitch and yaw angles for the tile corner \ at (Y, zTile), from the perspective of the viewer, and \ store them in the following tables in the relevant \ entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) CPY xTileViewRightEdge \ Loop back until we have moved right along the tile row BNE dlan12 \ all the way to the right edge in the previous row BEQ dlan15 \ Jump to dlan15 to draw the new row as we have all the \ tile information we need (this BEQ is effectively a \ JMP as we just passed through a BNE) .dlan13 \ If we get here, xTileViewRight > xTileViewRightEdge, \ so we need to go back to the previous row to fetch \ more tile data for the right end of the row, so we can \ use it to work out what to draw for the right end of \ the new row we are trying to draw \ \ This is because the visible part of the new row (the \ row in front) is extending right beyond the right edge \ of the visible part of the previous row (the one \ behind), so we won't have calculated the required \ tile data for the non-visible part of the previous \ row \ \ So we do that now by switching back to the previous \ row and calculating all the tile data for the tiles on \ the right of the previous row that have visible tiles \ in front of them in the new row LDA drawingTableOffset \ Flip drawingTableOffset between 0 and 32, so the calls EOR #32 \ to GetTileViewEdges and GetTileViewAngles store their STA drawingTableOffset \ data into the storage area that we used for the \ previous row, so we effectively extend the data to the \ left for the previous row INC zTile \ Increment xTile to the tile row number behind the one \ we are drawing, i.e. the previous row in this process LDY xTileViewRightEdge \ Set Y to the right edge for the previous row .dlan14 INY \ Increment Y to move right along the row by one tile JSR GetTileViewAngles \ Calculate the pitch and yaw angles for the tile corner \ at (Y, zTile), from the perspective of the viewer, and \ store them in the following tables in the relevant \ entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) CPY xTileViewRight \ Loop back until we have moved right along the tile row BNE dlan14 \ all the way to the right edge in the new row in front STY xTileViewRightEdge \ Update xTileViewRightEdge to store the newly moved \ edge for the previous row DEC zTile \ Decrement zTile once again to move back to the row \ that we are drawing LDA drawingTableOffset \ Flip drawingTableOffset back again, so the calls EOR #32 \ to GetTileViewEdges and GetTileViewAngles once again STA drawingTableOffset \ store data in the new row's storage area .dlan15 \ By this point we have ensured that we have all the \ tile data that we need to draw the new row JSR DrawLandscapeRow \ Draw the tile row at z-coordinate zTile, between tiles \ xTileViewLeftEdge and xTileViewRightEdge, including \ any objects on any of the tiles BIT keepCheckingPanKey \ If bit 7 of keepCheckingPanKey is clear then we should BPL dlan16 \ keep drawing the landscape irrespective of whether the \ pan key is still being pressed, so jump to dlan16 to \ jump back to dlan5 to keep drawing the landscape \ If we get here then we need to check whether the same \ pan key is still being held down, and abort the \ drawing process if it isn't (this happens if the \ player initiates a pan, thus triggering this drawing \ process, but releases the key before the drawing has \ finished, in which case we don't need to finish off \ drawing the landscape) JSR CheckForSamePanKey \ Check to see whether the same pan key is being \ held down compared to the last time we checked BNE dlan17 \ If the same pan key is not being held down, jump to \ dlan17 to return from the subroutine with the C flag \ set to indicate that this is the case .dlan16 JMP dlan5 \ Loop back to dlan5 to analyse and draw the next tile \ row forward, towards the viewer .dlan17 SEC \ Set the C flag to indicate that we are aborting the \ drawing of the landscape as the panning key is no \ longer being held down RTS \ Return from the subroutine
Name: DrawLandscapeView (Part 3 of 3) [Show more] Type: Subroutine Category: Drawing the landscape Summary: Draw a tile row in two parts, one on either side of the viewer
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.dlan18 \ If we get here then the tile row we are drawing \ contains the viewer \ \ The viewer can only see one half of the row, because \ they are standing on it, so we need to work out which \ half this is (if they can see any of the row at all) \ and then draw it \ \ We don't draw the viewer's tile yet, as we do that \ later LDY xTileViewLeftEdge \ Set Y to the tile number just inside the left edge of INY \ the visible portion of the row CPY xTileViewer \ If this isn't the viewer's tile, jump to dlan19 to BNE dlan19 \ perform the same check on the right side \ If we get here then the visible part of the viewer's \ tile row starts on the tile just to the left of their \ position, so we are looking left and should draw that \ part of the visible row STY xTileViewRightEdge \ Set the right edge to the viewer's tile, so we draw \ from the left edge up to (but not including) the \ viewer's tile JMP dlan20 \ Jump to dlan20 to draw the tile row containing the \ viewer .dlan19 \ If we get here then the left edge is not just to the \ left of the viewer, so now we check the right edge LDY xTileViewRightEdge \ Set Y to the tile number that's two to the left of the DEY \ right edge of the visible portion DEY CPY xTileViewer \ If this isn't the viewer's tile, jump to dlan21 to BNE dlan21 \ skip drawing the tile row altogether, as we can't see \ it to the left or the right (so instead we move on to \ drawing the player's tile) \ If we get here then the visible part of the viewer's \ tile row starts on the tile just to the right of their \ position, so we are looking right and should draw that \ part of the visible row INY \ Set the left edge to the tile just right of the STY xTileViewLeftEdge \ viewer's tile, so we draw from just right of the \ viewer's tile to the right edge .dlan20 \ We now draw the tile row containing the viewer, but \ first we need to ensure we have the data for the tiles \ at each end LDY xTileViewLeftEdge \ Set Y to the left edge of the viewer's tile row JSR GetTileViewAngles \ Calculate the pitch and yaw angles for the tile corner \ at (Y, zTile), from the perspective of the viewer, and \ store them in the following tables in the relevant \ entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) LDY xTileViewRightEdge \ Set Y to the right edge of the viewer's tile row JSR GetTileViewAngles \ Calculate the pitch and yaw angles for the tile corner \ at (Y, zTile), from the perspective of the viewer, and \ store them in the following tables in the relevant \ entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) JSR DrawLandscapeRow \ Draw the tile row at z-coordinate zTile, between tiles \ xTileViewLeftEdge and xTileViewRightEdge, including \ any objects on any of the tiles .dlan21 \ We now draw the tile row beneath the viewer, but first \ we need to ensure we have the data for the tile just \ in front of it LDA #0 \ Set drawingTableOffset = 0 so the following call to STA drawingTableOffset \ GetTileViewAngles (which we call to fetch the data for \ the tile corner row in front of viewer) will populate \ the tables at tileViewYaw+0 and tileViewPitch+0 INC zTile \ Increment the row number so it's the row in front of \ the viewer LDY xTileViewer \ Set Y to the tile column for the viewer's tile, so we \ can fetch the tile data for the tile directly in front \ of the viewer JSR GetTileViewAngles \ Calculate the pitch and yaw angles for the tile corner \ at (Y, zTile), from the perspective of the viewer, and \ store them in the following tables in the relevant \ entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) \ By this point we have the following: \ \ * The tables at tileViewYaw+0 and tileViewPitch+0 \ contain data for the tile corner row in front of \ the viewer, i.e. for the front edge of the tile on \ which the viewer sits \ \ * The tables at tileViewYaw+32 and tileViewPitch+32 \ contain data for the tile corner row containing \ the viewer, i.e. for the rear edge of the tile on \ which the viewer sits \ \ Y is the offset of the viewer's tile within these \ tables, so, for example: \ \ * drawViewPitchHi,Y is the corner at the front \ left of the viewer's tile \ \ * drawViewPitchHi+1,Y is the corner at the front \ right of the viewer's tile \ \ * drawViewPitchHi+32,Y is the corner at the rear \ left of the viewer's tile \ \ * drawViewPitchHi+32+1,Y is the corner at the rear \ right of the viewer's tile \ \ We now set up the angles for the tile to ensure that \ it looks correct LDA drawViewPitchHi,Y \ If the high byte of the pitch angle of the front-left CMP #2 \ edge of the viewer's tile is 2 or more, then it is off BCS dlan22 \ the top of the screen, so jump to dlan22 to skip \ drawing it and instead return from the subroutine STA drawViewPitchHi+1,Y \ Set the pitch angle for the tile corner in the LDA drawViewPitchLo,Y \ front-right corner of the viewer's tile to be the STA drawViewPitchLo+1,Y \ same as the front-left corner, so this ensures \ that the two front corners of the tile containing \ the viewer are horizontally level LDA #32 \ Set drawingTableOffset = 0 so the following call to STA drawingTableOffset \ DrawFlatTile (for the tile row beneath the viewer) \ will fetch data from the tables at tileViewData+32, \ tileViewYaw+32 and tileViewPitch+32 DEC zTile \ Decrement the row number so it goes back to the row \ containing the viewer \ We now position the corners of the viewer's tile so \ it spreads to the left and right screen edges and \ appears to dip down behind the viewer (so it spreads \ down to the bottom of the screen as well) LDA #&FF \ Set the pitch angle for the two rear tile STA drawViewPitchHi+32,Y \ corners to be as low down as possible (as the STA drawViewPitchHi+32+1,Y \ high byte of &FF makes the angle negative) STA drawViewYawHi+32,Y \ Set the yaw angle for the two left tile corners to be STA drawViewYawHi,Y \ as far left as possible LDA #20 \ Set the yaw angle for the two right tile corners STA drawViewYawHi+32+1,Y \ to 20, which is a full screen width, so this puts STA drawViewYawHi+1,Y \ them on the right edge of the screen LDA xTileViewer \ Set xTileToDraw to the column of the viewer's tile, so STA xTileToDraw \ the call to DrawFlatTile draws this tile JSR DrawFlatTile \ Draw the flat tile under the viewer .dlan22 CLC \ Clear the C flag to indicate that we have drawn the \ whole landscape RTS \ Return from the subroutine
Name: quadrantOffsets [Show more] Type: Variable Category: Drawing the landscape Summary: Offsets into the tile view data tables for the four different viewing directions
Context: See this variable on its own page References: This variable is used as follows: * DrawLandscapeView (Part 1 of 3) uses quadrantOffsets
.quadrantOffsets EQUB 0 \ Looking at 12 o'clock (front left corner) EQUB 1 \ Looking at 3 o'clock (front right corner) EQUB 32 + 1 \ Looking at 6 o'clock (rear right corner) EQUB 32 \ Looking at 9 o'clock (rear left corner)
Name: GetTileViewEdges [Show more] Type: Subroutine Category: Drawing the landscape Summary: For a given tile row, work out the edges of the visible portion of the row in the current player view, as left to right tile numbers
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawLandscapeView (Part 2 of 3) calls GetTileViewEdges

Arguments: xTileViewLeft The tile column of the tile we are analysing, from the perspective of the viewer, to be used as a starting point for working out the visible edges of the row zTile The tile row of the tile we are analysing, from the perspective of the viewer drawingTableOffset Defines where we store the results of the analysis in the tileViewData, tileViewYaw and tileViewPitch drawing data tables; each table contains two complete sets of tile data, with the first table at offset 0 and the second table at offset 32, so we store the results as follows: * 0 = store the results in the first table e.g. in the 32-byte table at tileViewData * 32 = store the results in the second table e.g. in the 32-byte table at tileViewData+32
Returns: xTileViewLeft The number of the tile column on this row that appears at the left edge of the screen xTileViewRight The number of the tile column on this row that appears at the right edge of the screen tileViewData Tile data for all the tile corners analysed while looking for the edges drawViewYaw(Hi Lo) The yaw angles for all the tile corners analysed while looking for the edges drawViewPitchHi High byte of the pitch angles for all the tile corners analysed while looking for the edges drawViewPitchLo Low byte of the pitch angles for all the tile corners analysed while looking for the edges tileIsOnScreen Information on whether a tile corner is on-screen for all the tile corners analysed while looking for the edges
.GetTileViewEdges \ This routine may need clarifying, as the algorithm \ doesn't seem to make sense - it's possible that I've \ misinterpreted how GetTileViewAngles sets the bits in \ tileIsOnScreen, as that uses bufferMinYaw(Hi Lo) for \ the calculation and those values are not completely \ understood LDY xTileViewLeft \ Set Y to the tile column so we can pass it to the \ GetTileViewAngles routine JSR GetTileViewAngles \ Calculate the pitch and yaw angles for the tile corner \ at (Y, zTile), from the perspective of the viewer, and \ store them in the following tables in the relevant \ entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) BEQ edge5 \ If A = %00000000 then the tile is not on-screen and is \ past the left edge of the screen, so jump to edge5 to \ move right from this point, looking for a tile that's \ on-screen \ If we get here then our chosen starting tile is either \ on-screen or off the right edge of the screen CMP #%10000000 \ If A = %10000000 then the tile is on-screen, so jump BEQ edge4 \ to edge4 to update xTileViewRight to this position and \ start moving left, so we work through the portion to \ the left of the starting point \ If we get here then tileIsOnScreen = %10000001 and the \ tile is not on-screen and is past the right edge of \ the screen, so fall through into edge1 to start moving \ right .edge1 \ If we get here then we keep moving right, updating \ xTileViewLeft as we go \ \ We keep moving until: \ \ * We find a tile that's on-screen or reach the end \ of the tile row, in which case we jump to edge3 to \ set xTileViewRight and return from the subroutine \ \ * We find a tile that's off the left of the screen, \ in which case we fall through into edge2 LDA xTile \ Store the current column number in xTileViewLeft STA xTileViewLeft JSR GetTileEdgeToRight \ Move one tile to the right and calculate the pitch and \ yaw angles for the tile corner from the perspective of \ the viewer, and store them in the following tables in \ the relevant entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) \ \ The C flag is set if we have already reached the end \ of the row and can't move any further right BCS edge3 \ If we just moved off the right end of the row then the \ right end of the row is visible on-screen, so jump to \ edge3 to set xTileViewRight accordingly and return \ from the subroutine CMP #%10000001 \ If A = %10000001 then the tile is past the right edge BEQ edge1 \ of the screen, so loop back to move right by one more \ tile CMP #%10000000 \ If A = %10000000 then the tile is on-screen, so jump BEQ edge3 \ to edge3 to set xTileViewRight accordingly and return \ from the subroutine \ If we get here then A must be %00000000 and the tile \ is off the left edge of the screen, so fall through \ into edge2 to keep moving right .edge2 \ If we get here then we keep moving right until: \ \ * We find a tile that's on-screen or reach the end \ of the tile row, in which case we jump to edge3 to \ set xTileViewRight and return from the subroutine JSR GetTileEdgeToRight \ Move one tile to the right and calculate the pitch and \ yaw angles for the tile corner from the perspective of \ the viewer, and store them in the following tables in \ the relevant entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) \ \ The C flag is set if we have already reached the end \ of the row and can't move any further right BCS edge3 \ If we just moved off the right end of the row then the \ right end of the row is visible on-screen, so jump to \ edge3 to set xTileViewRight accordingly and return \ from the subroutine BEQ edge2 \ If A = %00000000 then the new tile is not visible, so \ loop back to edge2 to keep moving right \ Otherwise the tile is either visible or off the right \ edge, so fall through into edge3 to set xTileViewRight \ accordingly and return from the subroutine .edge3 LDA xTile \ Store the current column number in xTileViewRight STA xTileViewRight RTS \ Return from the subroutine .edge4 \ If we get here then we keep moving left, updating \ xTileViewRight as we go \ \ We keep moving until: \ \ * We reach the start of the tile row, in which case \ we jump to edge9 to set xTileViewLeft and return \ from the subroutine \ \ * We find a tile that's on-screen, in which case we \ jump to edge4 to update xTileViewRight and keep \ moving left \ \ * We find a tile that's off the right of the screen, \ in which case jump to edge9 to set xTileViewLeft \ and return from the subroutine \ \ * We find a tile that's off the left of the screen, \ in which case jump to edge7 to move left until we \ find a visible tile or run out of row LDA xTile \ Store the current column number in xTileViewRight STA xTileViewRight JSR GetTileEdgeToLeft \ Move one tile to the left and calculate the pitch and \ yaw angles for the tile corner from the perspective of \ the viewer, and store them in the following tables in \ the relevant entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) \ \ The C flag is set if we have already reached the start \ of the row and can't move any further left BCS edge9 \ If we just moved off the left end of the row then the \ left end of the row is visible on-screen, so jump to \ edge9 to set xTileViewLeft accordingly and return from \ the subroutine CMP #%10000000 \ If A = %10000000 then the tile is on-screen, so jump BEQ edge4 \ to edge4 to update xTileViewRight and keep moving left CMP #%00000000 \ If A = %10000001 then the tile is off the right edge JMP edge8 \ of the screen, so jump to edge8 to set xTileViewLeft \ accordingly and return from the subroutine \ \ If A = %00000000 then the tile is off the left edge of \ the screen, so jump to edge8 and on to edge7 to move \ left until we find a visible tile or run out of row .edge5 \ If we get here then we keep moving right until: \ \ * We find a tile that's on-screen or reach the end \ of the tile row, in which case we jump to edge6 to \ set xTileViewRight and start working left from \ xTileViewLeft until we find a visible tile or run \ out of row JSR GetTileEdgeToRight \ Move one tile to the right and calculate the pitch and \ yaw angles for the tile corner from the perspective of \ the viewer, and store them in the following tables in \ the relevant entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) \ \ The C flag is set if we have already reached the end \ of the row and can't move any further right BCS edge6 \ If we just moved off the right end of the row then the \ right end of the row is visible on-screen, so jump to \ edge3 to set xTileViewRight accordingly and start \ moving left from the current left edge value in \ xTileViewLeft BEQ edge5 \ If the new tile is not visible and off to the left of \ the screen then loop back to edge5 to keep moving \ right \ If we get here then we have now found a visible tile \ by moving right, so store that in xTileViewRight and \ start moving left .edge6 LDA xTile \ Store the current column number in xTileViewRight STA xTileViewRight LDA xTileViewLeft \ Set xTile to the value of xTileViewLeft so we can STA xTile \ start moving left from this point .edge7 \ If we get here then we keep moving left until: \ \ * We find a tile that's on-screen or reach the end \ of the tile row, in which case we set \ xTileViewLeft and return from the subroutine JSR GetTileEdgeToLeft \ Move one tile to the left and calculate the pitch and \ yaw angles for the tile corner from the perspective of \ the viewer, and store them in the following tables in \ the relevant entry for this tile corner: \ \ * drawViewYaw(Hi Lo) \ \ * drawViewPitch(Hi Lo) \ \ * tileViewData \ \ * tileIsOnScreen (also returned in A and the Z flag) \ \ The C flag is set if we have already reached the start \ of the row and can't move any further left BCS edge9 \ If we just moved off the left end of the row then the \ left end of the row is visible on-screen, so jump to \ edge9 to set xTileViewLeft accordingly and return from \ the subroutine .edge8 BEQ edge7 \ If the new tile is not visible then loop back to edge7 \ to keep moving left .edge9 LDA xTile \ Store the current column number in xTileViewLeft STA xTileViewLeft RTS \ Return from the subroutine
Name: GetTileEdgeToLeft [Show more] Type: Subroutine Category: Drawing the landscape Summary: Move one tile to the left along the tile row that we are analysing for view edges and tile angles
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetTileViewEdges calls GetTileEdgeToLeft * GetTileEdgeToRight calls via prev1

Other entry points: prev1 Return from the subroutine with the C flag set
.prev1 SEC \ Set the C flag to indicate that we have reached the \ end of the tile row RTS \ Return from the subroutine .GetTileEdgeToLeft LDY xTile \ Set Y to the column of the tile corner we are \ currently analysing when looking for the view edges \ and calculating the tile's pitch and yaw angles BEQ prev1 \ If Y = 0 then we are already at the left end of the \ tile row and can't move any further left, so jump to \ prev1 to return from the subroutine with the C flag \ set DEY \ Otherwise decrement Y to move one tile to the left JMP GetTileViewAngles \ Jump to GetTileViewAngles to analyse the new corner to \ look for the view edges and calculate the tile's pitch \ and yaw angles, and return from the subroutine with \ the C flag clear
Name: GetTileEdgeToRight [Show more] Type: Subroutine Category: Drawing the landscape Summary: Move one tile to the right along the tile row that we are analysing for view edges and tile angles
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetTileViewEdges calls GetTileEdgeToRight
.GetTileEdgeToRight LDY xTile \ Set Y to the column of the tile corner we are \ currently analysing when looking for the view edges \ and calculating the tile's pitch and yaw angles INY \ Increment Y to move one tile to the right CPY #32 \ If Y = 32 then we have gone past the end of the tile BEQ prev1 \ row and can't move any further tight, so jump to prev1 \ to return from the subroutine with the C flag set \ Otherwise fall through into GetTileViewAngles to \ analyse the new corner to look for the view edges and \ calculate the tile's pitch and yaw angles, and return \ from the subroutine with the C flag clear
Name: GetTileViewAngles (Part 1 of 4) [Show more] Type: Subroutine Category: Drawing the landscape Summary: Calculate the pitch and yaw angles for a tile corner, relative to a viewer object (e.g. the player), and whether it is on-screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawLandscapeView (Part 2 of 3) calls GetTileViewAngles * DrawLandscapeView (Part 3 of 3) calls GetTileViewAngles * GetTileEdgeToLeft calls GetTileViewAngles * GetTileViewEdges calls GetTileViewAngles

Arguments: Y The tile column of the tile we are analysing, from the perspective of the viewer zTile The tile row of the tile we are analysing, from the perspective of the viewer drawingTableOffset Defines where we store the results of the analysis in the tileViewData, tileViewYaw and tileViewPitch drawing data tables; each table contains two complete sets of tile data, with the first table at offset 0 and the second table at offset 32, so we store the results as follows: * 0 = store the results in the first table e.g. in the 32-byte table at tileViewData * 32 = store the results in the second table e.g. in the 32-byte table at tileViewData+32
Returns: tileViewData Tile data for this tile corner, stored in the relevant entry for this tile corner drawViewYaw(Hi Lo) The yaw angle, stored in the relevant entry for this tile corner drawViewPitchHi High byte of the pitch angle, stored in the relevant entry for this tile corner drawViewPitchLo Low byte of the pitch angle, stored in the relevant entry for this tile corner tileIsOnScreen Determines whether the tile corner is on-screen (i.e. within the max and min yaw angle limits): * %00000000 = tile is not on-screen and is past the left edge of the screen * %10000000 = tile is on-screen * %10000001 = tile is not on-screen and is past the right edge of the screen A The value of tileIsOnScreen is also returned in A Z flag The Z flag is set according to the value of tileIsOnScreen, so a BNE or BEQ following the call to GetTileViewAngles will act accordingly C flag The C flag is clear to indicate that we have not gone past the end of the tile row, which is used when calling this routine via GetTileEdgeToLeft or GetTileEdgeToRight Y Y is preserved
.GetTileViewAngles STY xTile \ Store the tile column in xTile, so we can analyse the \ tile at (xTile, zTile) STY yStoreTileView \ Store Y in yStoreTileView so it can be preserved \ across calls to the routine TYA \ Set drawingTableIndex = Y + drawingTableOffset ORA drawingTableOffset \ STA drawingTableIndex \ So drawingTableIndex is the index into the drawing \ tables for the tile we are analysing LDA #0 \ Set tileIsOnScreen = 0, so the default is that the STA tileIsOnScreen \ tile is not on-screen (we change this in part 3 if it \ is on-screen) LDX viewingObject \ Set X to the number of the object that is viewing the \ landscape \ We start by calculating the difference (the delta) in \ the x-axis between the viewer and the tile we are \ analysing LDA #128 \ Set the low byte of xDelta(Hi Lo) to 128 STA xDeltaLo CLC \ Set the high byte of xDelta(Hi Lo) to the LDA xTile \ following: SBC xTileViewer \ SEC \ xTile - xTileViewer - 1 - xTitleOffset SBC xTitleOffset \ STA xDeltaHi \ Note that xTitleOffset is zero during gameplay, and is \ only non-zero when we are drawing large 3D text on the \ title screen, so let's ignore it for now to keep \ things simple \ \ Setting the low byte to 128 effectively adds 0.5 to \ the result, so we get this: \ \ xDelta(Hi Lo) = xTile - xTileViewer - 0.5 \ \ So this is the delta along the x-axis between the \ viewer and the tile that we are analysing BPL tang1 \ If the result is positive then jump to tang1 to skip \ the following LDA #0 \ Negate the result to make it positive, so we now have: SEC \ SBC xDeltaLo \ (A xDeltaLo) = |xTile - xTileViewer - 0.5| STA xDeltaLo LDA #0 SBC xDeltaHi .tang1 STA xDeltaAbsoluteHi \ Set xDeltaAbsoluteHi = |xDeltaHi| \ \ So we now have the absolute z-axis length in: \ \ (xDeltaAbsoluteHi xDeltaLo) \ \ and the original high byte of the signed x-axis length \ is still in xDeltaHi \ We now do the same thing but for the z-axis, so that \ zDelta contains the difference (the delta) in the \ z-axis between the viewer and the tile we are \ analysing LDA #128 \ Set the low byte of zDelta(Hi Lo) to 128 STA zDeltaLo CLC \ Set the high byte of zDelta(Hi Lo) to the LDA zTile \ following: SBC zTileViewer \ STA zDeltaHi \ zTile - zTileViewer - 1 \ \ Setting the low byte to 128 effectively adds 0.5 to \ the result, so we get this: \ \ zDelta(Hi Lo) = zTile - zTileViewer - 0.5 \ \ So this is the delta along the z-axis between the \ viewer and the tile that we are analysing BPL tang2 \ If the result is positive then jump to tang2 to skip \ the following LDA #0 \ Negate the result to make it positive, so we now have: SEC \ SBC zDeltaLo \ (A zDeltaLo) = |zTile - zTileViewer - 0.5| STA zDeltaLo LDA #0 SBC zDeltaHi .tang2 STA zDeltaAbsoluteHi \ Set zDeltaAbsoluteHi = |zDeltaHi| \ \ So we now have the absolute z-axis length in: \ \ (zDeltaAbsoluteHi zDeltaLo) \ \ and the original high byte of the signed z-axis length \ is still in zDeltaHi \ We now have both deltas, so we now can calculate the \ angle of the hypotenuse of the triangle formed by \ these axes, which is the projection of the 3D vector \ from the viewer to the tile 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) JSR GetHypotenuseAngle \ Calculate the angle of the hypotenuse in the triangle \ with the following non-hypotenuse sides: \ \ * xDelta(Hi Lo) \ \ * zDelta(Hi Lo) \ \ and return the angle in angle(Hi Lo), the tangent in \ angleTangent, the length of the longer side in \ a(Hi Lo) and the length of the shorter side in \ b(Hi Lo) \ The angle of the hypotenuse is the yaw angle of the \ 3D vector from the viewer to the tile corner we are \ analysing, so store it in the table at \ drawViewYaw(Hi Lo) \ \ We also subtract the following: \ \ * screenLeftYawHi from the high byte so the result \ is the relative yaw angle from the left edge of \ the screen, so the result is effectively a screen \ x-coordinate where zero is the left screen edge \ \ * yawAdjustmentLo from the low byte so if we are \ drawing an object for updating, we include the \ correct yaw offset to enable the object to be \ drawn flush with the left edge of the screen \ buffer (see the DrawUpdatedObject routine) LDY drawingTableIndex \ Set Y to the drawing table index for this tile LDA angleLo \ Set drawViewYaw(Hi Lo) for this tile to: SEC \ SBC yawAdjustmentLo \ angle(Hi Lo) - (screenLeftYawHi 0) STA drawViewYawLo,Y \ - (0 yawAdjustmentLo) \ \ starting with the low bytes LDA angleHi \ And then the high bytes SBC screenLeftYawHi STA drawViewYawHi,Y JSR GetHypotenuse \ Calculate the length of the hypotenuse in the triangle \ with side lengths of a(Hi Lo) and b(Hi Lo) and angle \ angleTangent, which are still set from the call to \ GetHypotenuseAngle above to the values for the 3D \ vector from the viewer to the tile corner we are \ analysing \ \ The hypotenuse length is returned in hypotenuse(Hi Lo) \ so we can use it to calculate the pitch angle of the \ viewer-to-tile vector in part 3 \ Fall through into part 2 to start the pitch angle \ calculations
Name: GetTileViewAngles (Part 2 of 4) [Show more] Type: Subroutine Category: Drawing the landscape Summary: Fetch the tile data for the tile corner we are analysing
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
\ We have calculated the yaw angle of the vector from \ the viewer to the tile corner we are analysing, so \ now for the pitch angle \ \ We start by fetching the tile data for the tile corner \ we are analysing, and for that we need to know the \ tile coordinates in terms of the 3D world, as that's \ how the tileData table is organised \ \ The following code sets (X, Y) to the coordinate of \ the tile corner we are analysing, i.e. (xTile, zTile), \ but with the axes rotated to match the orientation of \ the 3D world rather than the viewer (as xTile and \ zTile contain the coordinates from the perspective of \ the viewer, not the 3D world) \ \ This is the reverse of the process described in part 1 \ of the DrawLandscapeView routine, which changes the \ frame of reference from the 3D world to the viewer \ \ In that routine, we map the following coordinate \ changes, depending on the direction in which the \ viewer is facing compared to the 3D world's default \ axes: \ \ * If the viewer has not turned: \ (x, z) maps to (x, z) \ \ * If the viewer has turned right: \ (x, z) maps to (30 - z, x) \ \ * If the viewer has turned around: \ (x, z) maps to (30 - x, 30 - z) \ \ * If the viewer has turned left: \ (x, z) maps to (z, 30 - x) \ \ So we apply the same logic here, but in the reverse \ direction, as we want to move from the viewer's frame \ of reference into the 3D world's frame of reference \ \ Note that the logic in DrawLandscapeView subtracts \ from 30, while here we subtract from 31, because this \ logic is working with tile corners, while the logic in \ DrawLandscapeView is working with tiles BIT viewingArcRightYaw \ If bit 7 of the quadrant containing the right edge of BMI tang4 \ the viewing arc is set, jump to tang4 BVS tang3 \ If bit 6 of the quadrant containing the right edge of \ the viewing arc is set, jump to tang3 \ If we get here then: \ \ * Bit 7 of the right edge's quadrant is clear \ * Bit 6 of the right edge's quadrant is clear \ \ This means that the right edge of the viewing arc is \ in the 12 o'clock to 3 o'clock quadrant \ \ This means that the viewer has not turned, so we can \ use the tile coordinates unchanged LDX xTile \ Set X = xTile LDY zTile \ Set Y = zTile JMP tang6 \ Jump to tang6 to keep going .tang3 \ If we get here then: \ \ * Bit 7 of the right edge's quadrant is clear \ * Bit 6 of the right edge's quadrant is set \ \ This means that the right edge of the viewing arc is \ in the 3 o'clock to 6 o'clock quadrant \ \ This means that the viewer has turned right, so we can \ turn left to go back to the 3D world \ \ Turning left maps (x, z) maps to (z, 31 - x), so \ that's what we do now LDX zTile \ Set X = zTile LDA #31 \ Set Y = 31 - xTile SEC SBC xTile TAY JMP tang6 \ Jump to tang6 to keep going .tang4 \ If we get here then bit 7 of the quadrant containing \ the right edge of the viewing arc is set BVS tang5 \ If bit 6 of the quadrant containing the right edge of \ the viewing arc is set, jump to tang5 \ If we get here then: \ \ * Bit 7 of the right edge's quadrant is set \ * Bit 6 of the right edge's quadrant is clear \ \ This means that the viewer has turned around, so we \ turn back around to go back to the 3D world \ \ Turning around maps (x, z) maps to (31 - x, 31 - z), \ so that's what we do now LDA #31 \ Set X = 31 - xTile SEC SBC xTile TAX LDA #31 \ Set Y = 31 - zTile SEC SBC zTile TAY JMP tang6 \ Jump to tang6 to keep going .tang5 \ If we get here then: \ \ * Bit 7 of the right edge's quadrant is set \ * Bit 6 of the right edge's quadrant is set \ \ This means that the right edge of the viewing arc is \ in the 9 o'clock to 12 o'clock quadrant \ \ This means that the viewer has turned left, so we can \ turn right to go back to the 3D world \ \ Turning right maps (x, z) maps to (31 - z, x), so \ that's what we do now LDA #31 \ Set X = 31 - xTile SEC SBC zTile TAX LDY xTile \ Set Y = yTile .tang6 \ We now have the tile coordinates for the tile that we \ are analysing, but in 3D world coordinates, so we can \ now fetch the tile data \ \ The 3D world coordinates for the tile are in (X, Y), \ so we can fetch the tile data using code that's very \ similar to the GetTileData routine \ \ In the following comments I will refer to (X, Y) as \ (xTile, zTile), just as in GetTileData, as that's a \ bit easier to follow than X and Y STY T \ Store zTile in T, so we can use it in the following \ calculation TXA \ Set Y = (xTile << 3 and %11100000) + zTile ASL A \ = (xTile >> 2 and %00000111) << 5 + zTile ASL A \ = (xTile div 4) * &20 + zTile ASL A AND #%11100000 ORA T TAY TXA \ Set A = bits 0-1 of xTile AND #%00000011 \ = xTile mod 4 STA T \ Store A in T so we can use it in part 3 to calculate \ the address of the tile's visibility bit \ The low byte of tileDataPage(1 0) gets set to zero in \ ResetVariables and is never changed \ \ The low byte of tileData is also zero, as we know that \ tileData is &0400 \ \ So in the following, we are just adding the high bytes \ to get a result that is on a page boundary CLC \ Set the following: ADC #HI(tileData) \ STA tileDataPage+1 \ tileDataPage(1 0) = tileData + (A 0) \ = tileData + (xTile mod 4) * &100 \ So we now have the following: \ \ tileDataPage(1 0) = tileData + (xTile mod 4) * &100 \ \ Y = (xTile div 4) * &20 + zTile \ \ The address in tileDataPage(1 0) is the page within \ tileData for the tile anchored at (xTile, zTile), and \ is always one of &0400, &0500, &0600 or &0700 because \ (xTile mod 4) is one of 0, 1, 2 or 3 \ \ The value of Y is the offset within that page of the \ tile data for the tile anchored at (xTile, zTile) \ \ We can therefore fetch the tile data for the specified \ tile using Y as an index offset from tileDataPage(1 0) LDA (tileDataPage),Y \ Set A to the tile data for the tile anchored at \ (xTile, zTile) LDX drawingTableIndex \ Store the tile data in the correct place in the STA tileViewData,X \ tileViewData table \ Fall through into part 3 to finish the pitch angle \ calculations
Name: GetTileViewAngles (Part 3 of 4) [Show more] Type: Subroutine Category: Drawing the landscape Summary: Calculate the pitch angle for the tile corner
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
\ By this point, A contains the tile data for the tile \ we are analysing and Y contains the index offset of \ the tile data from tileDataPage(1 0) CMP #%11000000 \ If both bits 6 and 7 are set in the tile data then the BCC tang8 \ tile we are analysing contains an object, in which \ case keep going, otherwise there is no object on the \ tile so jump to tang8 .tang7 \ If we get here then the tile we are analysing contains \ an object and the tile data is in A 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) LDA objectFlags,Y \ Set A to the object flags for the object on the tile CMP #%01000000 \ If bit 6 of the object flags for object #Y is set BCS tang7 \ then object #Y is stacked on top of another object, \ and that object number is in bits 0 to 5 of the object \ flags, so jump to tang7 to extract that object number \ from A and check the flags again (so this works down \ through the stack of objects until we reach the object \ at the bottom of the stack) LDA yObjectHi,Y \ At this point we have reached the object on the tile STA U \ itself, so set U 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 JMP tang9 \ Jump to tang9 to keep going, leaving the tile's entry \ in the tileViewData table alone (so it still contains \ the tile data that we stored in part 2) .tang8 \ If we get here then the tile we are analysing does not \ contain an object and the tile data is in A, with Y \ containing the index offset of the tile data from \ tileDataPage(1 0) \ \ From part 2, we have: \ \ * T = bits 0-1 of xTile \ \ * Y = (xTile div 4) * &20 + zTile LSR A \ The high nibble of the tile data contains the altitude LSR A \ of the tile's anchor, so shift this into U LSR A LSR A STA U \ We now fetch the tile's visibility bit from the \ tileVisibility table, using the reverse of the logic \ in the GetTileVisibility routine for calculating the \ address of the visibility bit TYA \ Shift Y right by one place, so the C flag is set to LSR A \ bit 0 of zTile (the row number), and Y contains the TAY \ following: \ \ * Bits 4 to 6 contain bits 2 to 4 of xTile (the \ column number) \ \ * Bits 0 to 3 contain bits 1 to 4 of zTile (the \ row number) \ \ This therefore gives us the offset in the visibility \ table for the tile's visibility byte, as per the \ GetTileVisibility routine ROL T \ In part 2 we set T to bits 0-1 of xTile (the column \ number), so this sets T as follows: \ \ * Bit 0 contains bit 0 of the of the tile row in \ zTile (via the C flag from above) \ \ * Bits 1-2 contain bits 0-1 of the column number in \ xTile \ \ This therefore gives us the bit number within the byte \ in the visibility table for the tile's visibility, as \ per the GetTileVisibility routine LDA tileVisibility,Y \ Set A to the visibility byte that contains the \ visibility bit for this tile LDY T \ Fetch the Y-th bit from the visibility byte, which AND visibileBitMask,Y \ contains the specific visibility bit for this tile BNE tang9 \ If the visibility bit is set then A will be non-zero, \ so skip the following instruction, leaving the entry \ for this tile to contain the non-zero tile data that \ we stored in tileViewData,X at the end of part 2 STA tileViewData,X \ The visibility bit is zero, so zero the entry in the \ tileViewData table for this tile (which otherwise \ would contain the tile data that we set in part 2) .tang9 LDX viewingObject \ Set X to the number of the object that is viewing the \ landscape LDA #0 \ Set (A xDeltaLo) = (U 0) - y-coordinate of object #X SEC \ SBC yObjectLo,X \ We set U above to the altitude of the tile that we are STA xDeltaLo \ analysing, so (A xDeltaLo) now contains the vertical LDA U \ distance between the viewer and the tile we are SBC yObjectHi,X \ analysing, ready to pass to GetPitchAngleDelta JSR GetPitchAngleDelta \ Set pitchDelta(Hi Lo) to the pitch angle of the vector \ relative to the viewer's pitch angle \ \ The vector in question has x- and z-axis elements from \ part 1: \ \ * xDelta(Hi Lo) \ \ * zDelta(Hi Lo) \ \ and we calculated the hypotenuse(Hi Lo) for this in \ part 1 \ \ The vector also has y-axis element of (A xDeltaLo), \ which we just calculated \ \ So this call calculates the relative pitch angle for \ the vector between the viewer and the tile that we are \ analysing, so store it in the correct entry in the \ table at drawViewPitch(Hi Lo) LDY drawingTableIndex \ Set Y to the drawing table index for this tile LDA pitchDeltaHi \ Store the high byte of the pitch vector in the correct STA drawViewPitchHi,Y \ part of the drawViewPitchHi table LDA pitchDeltaLo \ Store the low byte of the pitch vector in the correct STA drawViewPitchLo,Y \ part of the drawViewPitchLo table \ Fall through into part 4 to work out how much of the \ tile is on-screen
Name: GetTileViewAngles (Part 4 of 4) [Show more] Type: Subroutine Category: Drawing the landscape Summary: Calculate how much of the tile is on-screen
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
\ By this point we have pitch and yaw angles for the \ vector between the viewer and the tile that we are \ analysing, relative to the viewing gaze, so we now \ need to work out the value of tileIsOnScreen to return \ \ We set tileIsOnScreen to zero at the start of the \ routine, to indicate that the tile is not on-screen \ \ We now set the following bits if applicable: \ \ * Set bit 7 when \ drawViewYaw(Hi Lo) >= bufferMinYaw(Hi Lo) \ \ * Set bit 0 when drawViewYawHi >= bufferMaxYawHi \ \ So bit 7 is set when the tile corner is inside the \ minimum yaw limit, and bit 0 is set when the tile \ corner is inside (but not on) the maximum yaw limit \ \ If the yaw limit is the screen size, then the two bits \ determine whether the tile is on-screen LDA drawViewYawHi,Y \ If drawViewYawHi < bufferMinYawHi, jump to tang11 to CMP bufferMinYawHi \ return with tileIsOnScreen = 0, as the tile is off the BCC tang11 \ left of the screen edge BNE tang10 \ If drawViewYawHi > bufferMinYawHi, jump to tang10 with \ the C flag set to set bit 7 of tileIsOnScreen \ If we get here then drawViewYawHi = bufferMinYawHi, so \ we now check the low bytes LDA drawViewYawLo,Y \ If drawViewYawLo < bufferMinYawLo, jump to tang11 to CMP bufferMinYawLo \ return with tileIsOnScreen = 0 BCC tang11 LDA drawViewYawHi,Y \ If we get here then drawViewYawLo >= bufferMinYawLo, \ so the C flag is set, and we set A to drawViewYawHi \ for the comparison below .tang10 \ If we get here then the C flag is set, as we have to \ pass through a BCC to get here ROR tileIsOnScreen \ Set bit 7 of tileIsOnScreen, so bit 7 is set if the \ tile is on or to the right of the minimum yaw limit, \ i.e. to the right of the left edge of the screen CMP bufferMaxYawHi \ If drawViewYawHi < bufferMaxYawHi, jump to tang11 to BCC tang11 \ return from the routine with bit 0 of tileIsOnScreen \ clear \ If we get here then drawViewYawHi >= bufferMaxYawHi, \ so the tile is off-screen to the right INC tileIsOnScreen \ Set bit 0 of tileIsOnScreen to indicate that the tile \ is to the right of the maximum yaw limit, i.e. to the \ right of the right edge of the screen .tang11 LDY yStoreTileView \ Restore Y so that it's preserved LDA tileIsOnScreen \ Set A to the value of tileIsOnScreen, to return from \ the subroutine CLC \ Clear the C flag to indicate that we have not gone \ past the end of the tile row, which is used when \ calling this routine via GetTileEdgeToLeft or \ GetTileEdgeToRight RTS \ Return from the subroutine
Name: DrawLandscapeRow [Show more] Type: Subroutine Category: Drawing the landscape Summary: Draw a row of tiles between the left visible edge and the right visible, in two parts towards each side of the viewer
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawLandscapeView (Part 2 of 3) calls DrawLandscapeRow * DrawLandscapeView (Part 3 of 3) calls DrawLandscapeRow
.DrawLandscapeRow LDA xTileViewLeftEdge \ Set xTileToDraw to the column number of the tile at STA xTileToDraw \ the left edge of the visible row we want to draw .draw1 CMP xTileViewRightEdge \ If we have gone past the right edge, jump to draw4 to BCS draw4 \ return from the subroutine CMP xTileViewer \ If we have gone past the viewer's tile column, jump to BCS draw2 \ draw2 to draw the second half JSR DrawTileAndObjects \ Draw the tile and any objects stacked on it INC xTileToDraw \ Increment the column number to move right along the \ row LDA xTileToDraw \ Set A to the updated column number of the tile we want \ to draw JMP draw1 \ Loop back to draw1 the next tile to the right until we \ reach the viewer's tile column or reach the end of the \ row .draw2 LDA xTileViewRightEdge \ Set A to the column number of the tile at the right \ edge of the visible row we want to draw .draw3 SEC \ Decrement the column counter in A to move left along SBC #1 \ the row BMI draw4 \ If we have reached the start of the row, jump to draw4 \ to return from the subroutine STA xTileToDraw \ Store the updated column number of the tile we want to \ draw CMP xTileViewLeftEdge \ If we have gone past the left edge, jump to draw4 to BCC draw4 \ return from the subroutine CMP xTileViewer \ If we have gone past the viewer's tile column, jump to BCC draw4 \ draw4 to return from the subroutine JSR DrawTileAndObjects \ Draw the tile and any objects stacked on it LDA xTileToDraw \ Set A to the column number of the tile we just drew JMP draw3 \ Loop back to draw1 the next tile to the left until we \ reach the viewer's tile column or reach the end of the \ row .draw4 RTS \ Return from the subroutine
Name: FlipBufferType [Show more] Type: Subroutine Category: Screen buffer Summary: Flip the buffer type between buffer type 0 (left row buffer) and buffer type 1 (right row buffer)
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawPolygon calls FlipBufferType
.FlipBufferType LDA screenBufferType \ Flip bit 0 of screenBufferType to swap between buffer AND #1 \ type 0 (left row buffer) and buffer type 1 (right row EOR #1 \ buffer) \ Fall through into ConfigureBuffer to configure the new \ buffer type
Name: ConfigureBuffer [Show more] Type: Subroutine Category: Screen buffer Summary: Set up the variables required to configure the screen buffer to a specific buffer type
Context: See this subroutine on its own page References: This subroutine is called as follows: * ClearScreen calls ConfigureBuffer * MainGameLoop calls ConfigureBuffer * UseColumnBuffer calls ConfigureBuffer * UseRowBuffer calls ConfigureBuffer

Arguments: A The type of buffer to configure: * 0 = left row buffer (for up/down pan) for the first 256 bytes of the 320-byte row * 1 = right row buffer (for up/down pan) for the last 64 bytes of the 320-byte row * 2 = column buffer (for left/right pan)
.ConfigureBuffer STA screenBufferType \ Set screenBufferType to the new buffer type TAY \ Copy the buffer type into Y so we can use it as an \ index into the various buffer configuration tables LDA buffersMinYaw,Y \ Set the high byte of bufferMinYaw(Hi Lo) to the value STA bufferMinYawHi \ from the buffersMinYaw table for this screen buffer LSR A \ Set bufferMaxYawHi = bufferMinYawHi / 2 EOR #%10000000 \ STA bufferMaxYawHi \ and with bit 7 flipped LDA buffersOrigin,Y \ Set the high byte of bufferOrigin(Hi Lo) to the value STA bufferOriginHi \ from the buffersOrigin table for this screen buffer LDA xBuffersWidth,Y \ Set xBufferWidth to the value from the xBuffersWidth STA xBufferWidth \ table for this screen buffer LDA xBuffersLeft,Y \ Set xBufferLeft to the value from the xBuffersLeft STA xBufferLeft \ table for this screen buffer CLC \ Set xBufferRight = xBufferLeft + xBufferWidth ADC xBufferWidth \ STA xBufferRight \ So this sets the right edge of the screen buffer LDA #0 \ Zero the low byte of bufferMinYaw(Hi Lo) STA bufferMinYawLo STA bufferOriginLo \ Zero the low byte of bufferOrigin(Hi Lo) RTS \ Return from the subroutine
Name: buffersOrigin [Show more] Type: Variable Category: Screen buffer Summary: The offset to add to yaw angles for each screen buffer to convert from the origin in the buffer centre to the origin on the left
Context: See this variable on its own page References: This variable is used as follows: * ConfigureBuffer uses buffersOrigin
.buffersOrigin EQUB 10 \ Left row buffer EQUB 2 \ Right row buffer EQUB 12 \ Column buffer
Name: xBuffersLeft [Show more] Type: Variable Category: Screen buffer Summary: The left edge of each screen buffer in pixels
Context: See this variable on its own page References: This variable is used as follows: * ConfigureBuffer uses xBuffersLeft
.xBuffersLeft EQUB 80 \ Left row buffer EQUB 64 \ Right row buffer EQUB 96 \ Column buffer
Name: xBuffersWidth [Show more] Type: Variable Category: Screen buffer Summary: The width of each screen buffer in pixels
Context: See this variable on its own page References: This variable is used as follows: * ConfigureBuffer uses xBuffersWidth
.xBuffersWidth EQUB 112 \ Left row buffer EQUB 112 \ Right row buffer EQUB 64 \ Column buffer
Name: buffersMinYaw [Show more] Type: Variable Category: Screen buffer Summary: Minimum allowed yaw angles for points in the screen buffer
Context: See this variable on its own page References: This variable is used as follows: * ConfigureBuffer uses buffersMinYaw
.buffersMinYaw EQUB 20 \ Left row buffer EQUB 20 \ Right row buffer EQUB 8 \ Column buffer
Name: ConfigureObjBuffer [Show more] Type: Subroutine Category: Screen buffer Summary: Set up the variables required to configure the screen buffer for updating an object
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawUpdatedObject calls ConfigureObjBuffer * DrawTileAndObjects calls via buff1

Arguments: A The number of the character columns to configure
Other entry points: buff1 Contains an RTS
.ConfigureObjBuffer STA T \ Set T to the number of character columns we need to \ configure to contain the object we are drawing LSR A \ This converts the number of character columns into \ a yaw angle by dividing by 2 \ \ The custom screen mode 5 used by the game is 40 \ character columns wide, and the screen is 20 yaw \ angles wide in terms of the game's coordinate system \ \ So we can simply double the character column count in \ A to convert it into a yaw angle STA bufferMinYawHi \ Set bufferMinYaw(Hi Lo) = A LDA #0 \ = T / 2 ROR A \ STA bufferMinYawLo \ So this stores the character column count as a yaw \ angle in bufferMinYaw(Hi Lo), where the low byte is \ effectively a fractional part \ \ This gives us the yaw angle of the left edge of the \ screen buffer LDA bufferMinYawHi \ Set bufferMaxYawHi = bufferMinYawHi / 2 LSR A \ EOR #%10000000 \ and with bit 7 flipped STA bufferMaxYawHi LDA T \ Set xBufferWidth = T * 4 ASL A \ ASL A \ This gives us the width of the buffer in pixels STA xBufferWidth LSR A \ Set xBufferRight = 128 + T * 2 AND #%11111100 \ ORA #%10000000 \ rounded down to a multiple of 4 STA xBufferRight SEC \ Set xBufferLeft = xBufferRight - xBufferWidth SBC xBufferWidth STA xBufferLeft LSR A \ Set bufferOrigin(Hi Lo) = xBufferLeft / 8 LSR A \ LSR A \ starting with the high byte STA bufferOriginHi LDA #0 \ And then the low byte ROR A STA bufferOriginLo LDA #2 \ Configure the screen buffer as a column buffer STA screenBufferType .buff1 RTS \ Return from the subroutine
Name: DrawTileAndObjects [Show more] Type: Subroutine Category: Drawing the landscape Summary: Draw a tile and any objects stacked on it
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawLandscapeRow calls DrawTileAndObjects

Arguments: xTileToDraw The tile x-coordinate (i.e. the tile column) zTile The tile z-coordinate (i.e. the tile row)
.tobj1 \ If we get here then we need to draw a block in the \ title screen's 3D text \ \ We use object #63 for this purpose, and we set it to \ the object number for the 3D text block pair that the \ SpawnCharacter3D put into bits 0 to 3 of the tile data LDA tileViewData,X \ Set the object type for object #63 to the bottom AND #%00001111 \ nibble of the tile data, which contains the object STA objectTypes+63 \ number for the 3D text block pair for this part of the \ text: \ \ * 0 for no blocks in the pair \ \ * 7 for no block (left), block (right) \ \ * 8 for block (left), no block (right) \ \ * 9 for block (left), no block (right) BEQ buff1 \ If the object type is zero then jump to buff1 to \ return from the subroutine without drawing anything \ Otherwise we have set the type of object #63 to the \ correct 3D text block object, so now we set the tile \ coordinate and draw the object LDA xTileToDraw \ Set the x-coordinate for the block in object #63 to STA xObject+63 \ the tile column in xTileToDraw LDA zTile \ Set the z-coordinate for the block in object #63 to STA zObject+63 \ the tile row in zTile LDY #63 \ Set Y = 63 to pass to the DrawObject routine so we \ draw object #63 JMP DrawObject \ Jump to DrawObject to draw the 3D text block we've set \ up in object #63, returning from the subroutine using \ a tail call .DrawTileAndObjects JSR ProcessSound \ Process any sounds or music that are being made in the \ background LDA xTileToDraw \ Set X to the index of the tile data that we set up for ORA drawingTableOffset \ this tile in part 2 of the GetTileViewAngles routine CLC ADC quadrantOffset AND #%00111111 TAX BIT drawingTitleScreen \ If bit 7 of drawingTitleScreen is set then we are BMI tobj1 \ drawing a title screen, so jump up to tobj1 to draw \ this tile as a block in the title screen's 3D text LDA tileViewData,X \ Set A to the tile data for the current view, which we \ set in the GetTileViewAngles routine to the tile data \ for this tile, but zeroed if the tile is not visible \ (which will happen if the tile does not contain an \ object and is marked as not being visible from the \ player's point of view in the tileVisibility table) BEQ buff1 \ If A is zero then we marked this tile as being hidden \ in part 3 of the GetTileViewAngles routine, so jump to \ buff1 to return from the subroutine without drawing \ anything CMP #%11000000 \ If both bits 6 and 7 are set in the tile data then the BCC tobj2 \ tile we are analysing contains an object, in which \ case keep going, otherwise there is no object on the \ tile so jump to tobj2 \ If we get here then the tile we are drawing contains \ an object PHA \ Store the tile data on the stack JSR DrawFlatTile \ Draw the flat tile that's beneath the object PLA \ Retrieve the tile data from the stack JMP DrawObjectStack \ Draw the stack of objects on top of the tile, and \ return from the subroutine using a tail call .tobj2 \ If we get here then the tile we are drawing does not \ contain an object AND #%00001111 \ Set A to the tile shape for the tile, which is in the \ bottom nibble of the tile data BEQ DrawFlatTile \ If the tile shape is zero then the tile is flat, so \ jump to DrawFlatTile to draw the flat tile, returning \ from the subroutine using a tail call CMP #12 \ If the tile shape is 12 then we know the tile consists BEQ tobj3 \ of two faces, so jump to tobj3 CMP #4 \ If the tile shape is 4 then we know the tile consists BNE DrawSlopingTile \ of two faces, so keep going, otherwise we don't know \ how many faces it contains, so jump to DrawSlopingTile \ to work this out .tobj3 \ If we get here then the tile shape is 4 or 12, both of \ which we know consist of two triangular faces PHA \ Store the tile shape on the stack LDA viewingQuadrantOpp \ Set triangleStartPoint to bit 0 of viewingQuadrantOpp AND #1 \ to pass to the DrawTwoFaceTile routine STA triangleStartPoint PLA \ Retrieve the tile shape from the stack BNE DrawTwoFaceTile \ Jump to DrawTwoFaceTile to draw the tile, returning \ from the subroutine using a tail call (this BNE is \ effectively a JMP as we know the tile shape is \ non-zero)
Name: DrawFlatTile [Show more] Type: Subroutine Category: Drawing the landscape Summary: Draw a flat tile in the correct colour for the chess board effect that we use to draw the landscape
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawLandscapeView (Part 3 of 3) calls DrawFlatTile * DrawTileAndObjects calls DrawFlatTile
.DrawFlatTile LDX #0 \ If bit 0 of xTileToDraw and zTile are the same, then LDA xTileToDraw \ the tile's x-coordinate and z-coordinate are either EOR zTile \ both odd or both even, so jump to DrawOneFaceTile AND #1 \ with X set to 0 to draw this tile in colour 3 (white, BEQ DrawOneFaceTile \ yellow, cyan or red) LDX #8 \ Otherwise the tile's x-coordinate and z-coordinate are \ different (i.e. one is odd and one is even), so set \ X to 8 to draw this tile in colour 0 (blue) \ We now have a very short interlude to set the value of \ stashOffset as part of the game's anti-cracker code, \ and we pick up the tile-drawing process again in the \ DrawOneFaceTile routine
Name: SetSecretStash [Show more] Type: Subroutine Category: Cracker protection Summary: Alter the secret code stash, as part of the anti-cracker code
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.SetSecretStash \ This routine sets the value of stashOffset, which is \ the offset into the secretCodeStash where we store a \ set of generated values for later checking in the \ GetRowVisibility routine \ \ The offset is set to the middle byte from the \ landscape seed linear feedback shift register (LFSR), \ overwriting previous values until we get here for the \ last time \ \ As we only reach this routine when drawing flat tiles \ in colour 0, so stashOffset is set to the middle byte \ from the landscape seed linear feedback shift register \ for the last flat colour 0 tile to be drawn \ \ The value itself doesn't matter, it's just another way \ of throwing crackers off the trail of working out how \ to generate landscape codes, as it changes the address \ of the secret code stash for each landscape LDA seedNumberLFSR+2-8,X \ At this point, X = 8 from DrawFlatTile, so this STA stashOffset-8,X \ sets the following: \ \ stashOffset = seedNumberLFSR+2 \ seedNumberLFSR+2 is the middle byte of the LFSR at \ seedNumberLFSR(4 3 2 1 0), so this sets the secret \ code stash offset to a unique and consistent value for \ each landscape \ Fall through into DrawOneFaceTile to finish drawing \ the flat tile
Name: DrawOneFaceTile [Show more] Type: Subroutine Category: Drawing the landscape Summary: Draw a tile with one quadrilateral (four-sided) face
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawFlatTile calls DrawOneFaceTile * DrawSlopingTile calls DrawOneFaceTile

Arguments: X The reference shape to use when picking the tile's colour from the tileShapeColour table
.DrawOneFaceTile LDA tileShapeColour,X \ Set polygonColours to the entry for the reference STA polygonColours \ shape from the tileShapeColour or tileShapeColour+16 \ table, so we draw the polygon in the correct colour LDA #%00000000 \ Clear bits 6 and 7 of polygonType so the following STA polygonType \ call to DrawPolygon draws a quadrilateral tile face JMP DrawPolygon \ Jump to DrawPolygon to draw the quadrilateral and \ return from the subroutine using a tail call
Name: DrawSlopingTile [Show more] Type: Subroutine Category: Drawing the landscape Summary: Draw a sloping tile that is not shape 4 or 12
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTileAndObjects calls DrawSlopingTile

Arguments: A The shape of the tile to draw (not 0, 4 or 12)
.DrawSlopingTile TAX \ Set X to the tile shape so we can retrieve it later SEC \ Set Y = (A - viewingQuadrantx4) mod 16 SBC viewingQuadrantx4 \ AND #%00001111 \ Tiles are grouped into four groups, so this sets the TAY \ correct group for the orientation of the viewer \ \ In other words, this makes the shape numbers relative \ to the viewer's orientation, so we can set the correct \ colours for the shape's faces \ \ Specifically, it realigns the following tile shape \ numbers so that the numbering starts in the quadrant \ where the player is facing: \ \ 1 0 0 Y = 1-3 when facing 12 o'clock \ 1 1 = 5-7 when facing 9 o'clock \ = 9-11 when facing 6 o'clock \ 2 1 1 = 13-15 when facing 3 o'clock \ 0 1 \ \ 3 1 0 \ 1 1 \ \ \ 5 1 0 Y = 1-3 when facing 3 o'clock \ 1 0 = 5-7 when facing 12 o'clock \ = 9-11 when facing 9 o'clock \ 6 1 0 = 13-15 when facing 6 o'clock \ 0 0 \ \ 7 1 1 \ 1 0 \ \ \ 9 1 1 Y = 1-3 when facing 6 o'clock \ 0 0 = 5-7 when facing 3 o'clock \ = 9-11 when facing 12 o'clock \ 10 0 1 = 13-15 when facing 9 o'clock \ 0 0 \ \ 11 0 0 \ 1 0 \ \ \ 13 0 1 Y = 1-3 when facing 9 o'clock \ 0 1 = 5-7 when facing 6 o'clock \ = 9-11 when facing 3 o'clock \ 14 0 0 = 13-15 when facing 12 o'clock \ 0 1 \ \ 15 0 1 \ 1 1 \ \ We use this value below when calculating the colours \ of the two faces in two-face tiles AND #%00000011 \ If Y mod 4 = 1 then the tile shape is the first shape CMP #%00000001 \ in one of the groups above, so it's a single sloping BEQ DrawOneFaceTile \ face from one horizontal edge to another, so jump to \ DrawOneFaceTile to draw this one-face tile \ If we get here then the tile shape is the second or \ third shape in one of the groups above, so one corner \ is a different altitude to the others \ \ We now use the value of Y to pick the correct value \ for triangleStartPoint and the correct tileShapeColour \ table offset for the tile colour, so we can pass them \ to DrawTwoFaceTile to draw the tile LDA triangleStart,Y \ Set triangleStartPoint = 1 for Y = 6, 7, 14, 15 STA triangleStartPoint \ 0 for Y = 2, 3, 10, 11 TXA \ Set A = 1 for tile shapes 6, 7, 14, 15 AND #%00000100 \ 0 for tile shapes 2, 3, 10, 11 LSR A LSR A CLC \ Set C flag if viewingQuadrantOpp = 1 and A = 1 ADC viewingQuadrantOpp \ or viewingQuadrantOpp = 2 CMP #2 \ or viewingQuadrantOpp = 3 \ \ Clear C flag if viewingQuadrantOpp = 0 \ or viewingQuadrantOpp = 1 and A = 0 TXA \ Set A to the tile shape to pass to DrawTwoFaceTile BCS DrawTwoFaceTile \ Add 16 to the tile shape if the C flag is clear ORA #16 \ Fall through into DrawTwoFaceTile to draw the tile \ with two triangular (three-sided) faces
Name: DrawTwoFaceTile [Show more] Type: Subroutine Category: Drawing the landscape Summary: Draw a tile with two triangular (three-sided) faces
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawSlopingTile calls DrawTwoFaceTile * DrawTileAndObjects calls DrawTwoFaceTile

Arguments: A The shape of the tile to draw, amended to use as an offset into the tileShapeColour table Add 16 if either of these is true, so we use the colours from tileShapeColour+16: * viewingQuadrantOpp = 0 * viewingQuadrantOpp = 1, shape = 2, 3, 10, 11 triangleStartPoint The starting point to use when drawing the two triangles (0 or 1) * For shapes 4 or 12 = bit 0 of viewingQuadrantOpp * Otherwise: * 1 for shapes 6, 7, 14, 15 (viewer-relative) * 0 for all other shapes
.DrawTwoFaceTile STA tileShapeOffset \ Set tileShapeOffset to the tile shape that's been \ amended to use as an offset into the tileShapeColour \ table TAX \ Set X to the tile shape so we can use it as an index \ into the tileShapeColour table LDA #%10000000 \ Set bit 7 and clear bit 6 of polygonType so the call STA polygonType \ to DrawPolygon draws the first triangle in the tile \ face LDA tileShapeColour,X \ Set polygonColours to the entry for this shape from STA polygonColours \ the tileShapeColour or tileShapeColour+16 table, so ww \ draw the first polygon in the correct colour JSR DrawPolygon \ Draw the first triangular face LDA tileShapeOffset \ Set polygonColours to the entry for this shape from EOR #16 \ the other tileShapeColour table (so if the previous TAX \ colour was from tileShapeColour then we pick the next LDA tileShapeColour,X \ colour from tileShapeColour+16) STA polygonColours LDA polygonType \ Set bit 6 of polygonType so both bits 6 and 7 are set, ORA #%01000000 \ so the call to DrawPolygon draws the second triangle STA polygonType \ in the tile face \ Fall into DrawPolygon to draw the second triangular \ face
Name: DrawPolygon [Show more] Type: Subroutine Category: Drawing polygons Summary: Draw a polygon
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObject calls DrawPolygon * DrawOneFaceTile calls DrawPolygon * DrawTwoFaceTile calls DrawPolygon

Arguments: polygonType Bits 6 and 7 determine the type of polygon to draw (the calling subroutine is in brackets): * %00xxxxxx = quadrilateral (DrawOneFaceTile) * %01xxxxxx = object polygon (DrawObject) * %10xxxxxx = first triangle (DrawTwoFaceTile) * %11xxxxxx = second triangle (DrawTwoFaceTile) screenBufferType The type of buffer to use: * 0 = left row buffer (for up/down pan) for the first 256 bytes of the 320-byte row * 1 = right row buffer (for up/down pan) for the last 64 bytes of the 320-byte row * 2 = column buffer (for left/right pan) drawViewAngles The point data: * When drawing tile faces, this points to polygonPoint * When drawing object polygons, this points to a list of object-relative numbers of polygon points triangleStartPoint When drawing a two-face tile as a pair of triangles, this is the number of the starting point to use (0 or 1)
.DrawPolygon LDY screenBufferType \ If screenBufferType >= 2 then we are drawing into the CPY #2 \ column buffer, so jump to poly2 as the column is BCS poly2 \ always drawn in one stage \ If we get here then screenBufferType must be 0 or 1 \ and we are drawing into the left or right row buffer \ \ The left row buffer covers the leftmost 256 bytes of \ each screen row, while the right row buffer covers the \ remaining 64 bytes \ \ It would be possible to draw the polygon into both \ buffers at once, but drawing it separately is more \ efficient as we don't need to manage the x-coordinate \ as a 16-bit value JSR GetPolygonLines \ Calculate the horizontal lines that make up the filled \ polygon and prepare them for drawing into the screen \ buffer BCS poly1 \ If the call to GetPolygonLines set the C flag then the \ polygon is not visible in the screen buffer and should \ not be drawn, so jump to poly1 to skip the following JSR DrawPolygonLines \ Draw the polygon into the screen buffer, drawing the \ shape from top to bottom, horizontal and line by line LDY screenBufferType \ Set A to the following: LDA polygonGoesRight,Y \ \ * polygonGoesRight when screenBufferType = 0 (we \ have just drawn into the left row buffer) \ \ * polygonIsToLeft when screenBufferType = 1 (we \ have just drawn into the right row buffer) CMP #1 \ If A = 1 then we do not need to draw in the other row BEQ poly3 \ buffer, because the polygon we just drew does not \ extend beyond the relevant edge of the buffer \ \ More specifically: \ \ * If we have just drawn into the left row buffer and \ polygonGoesRight = 1, then the polygon does not \ extend right out of the left row buffer, so we \ don't need to draw anything into the right row \ buffer \ \ * If we have just drawn into the right row buffer \ and polygonGoesLeft = 1, then the polygon does not \ extend left out of the right row buffer, so we \ don't need to draw anything into the left row \ buffer \ \ In either case, jump to poly3 to return from the \ subroutine without drawing the polygon into the other \ row buffer .poly1 \ We have drawn the relevant parts of the polygon into \ either the left row buffer or right row buffer and the \ polygon extends into the other row buffer, so now we \ draw the polygon into the other row buffer JSR FlipBufferType \ Flip the buffer type between 0 and 1 to swap between \ the left row buffer and right row buffer, and \ configure the new buffer accordingly .poly2 JSR GetPolygonLines \ Calculate the horizontal lines that make up the filled \ polygon and prepare them for drawing into the screen \ buffer BCS poly3 \ If the call to GetPolygonLines set the C flag then the \ polygon is not visible in the screen buffer and should \ not be drawn, so jump to poly3 to skip the following JSR DrawPolygonLines \ Draw the polygon into the screen buffer, drawing the \ shape from top to bottom, horizontal and line by line .poly3 RTS \ Return from the subroutine
Name: GenerateLandscape [Show more] Type: Subroutine Category: Landscape Summary: Generate tile data for the landscape
Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishLandscape calls GenerateLandscape * MainTitleLoop calls GenerateLandscape

This routine populates the tileData table with tile data for each of the tile corners in the landscape. The landscape consists of 31x31 square tiles, made up of a 32x32 grid of tile corners. One byte of tile data is generated for each tile corner in the landscape. Each byte of tile data contains two pieces of information: * The low nibble of each byte contains the tile shape, which describes the layout and structure of the landscape on that tile. * The high nibble of each byte contains the altitude of the tile corner in the front-left corner of the tile (i.e. the corner closest to the landscape origin). We call this tile corner the "anchor". As each tile is defined by a tile corner and a shape, we tend to use the terms "tile" and "tile corner" interchangeably, depending on the context. That said, for tile corners along the furthest back and rightmost edges of the landscape, the shape data is ignored, as there is no landscape beyond the edges. See the GetTileShape routine for information on the different types of tile shape.
Arguments: doNotPlayLandscape Controls how we return from the subroutine: * If bit 7 is set, return from the subroutine normally * If bit 7 is clear, jump to PreviewLandscape once the landscape is generated
.GenerateLandscape \ We start by generating 81 seed numbers, though these \ are ignored (with one exception) \ \ These numbers get stored in the stripData table, from \ stripData+80 down to stripData+0, but there's no \ specific reason for this - they could just as easily \ be discarded \ \ The purpose of this step is to get the seed number \ generator to a point where the output is predictable \ and stable, so that every time we generate a sequence \ of seed numbers for a landscape, they are exactly the \ same each time while being unique to that landscape \ number \ \ That said, the third seed number that's generated and \ stored at stripData+78 is used by the anti-cracker \ code in the SetCrackerSeed and CheckCrackerSeed \ routines, as it contains the high byte of the BCD \ landscape number (this is because the seed generator \ is initialised using the landscape number, and the \ third number out of the shift register is unchanged \ and still contains the initial value of that byte - \ see the InitialiseSeeds routine for more details) LDX #80 \ Set a counter in X so we can generate 81 seed numbers .land1 JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers STA stripData,X \ Set the X-th entry in the stripData table to the seed \ number in A DEX \ Decrement the counter BPL land1 \ Loop back until we have generated all 81 seed numbers \ We now set the value of tileDataMultiplier for this \ landscape, which is a multiplier that we apply to the \ altitudes of the tile corners to alter the steepness \ of the landscape LDA landscapeZero \ If this is not landscape 0000, jump to land2 BNE land2 LDA #24 \ This is landscape 0000, so set A = 24 to use for the \ tile data multiplier in tileDataMultiplier BNE land3 \ Jump to land3 to skip the following (this BNE is \ effectively a JMP as A is never zero) .land2 JSR GetNextSeed0To22 \ Set A to the next number from the landscape's sequence \ of seed numbers, converted to the range 0 to 22 CLC \ Set A = A + 14 ADC #14 \ \ So A is now a number in the range 14 to 36 .land3 STA tileDataMultiplier \ Set tileDataMultiplier = A \ \ So this is 24 for landscape 0000 and in the range 14 \ to 36 for all other landscapes \ We now populate the tileData table with tile corner \ altitudes, which we store in the low nibble of the \ tile data (for now) LDA #&80 \ Call ProcessTileData with A = &80 to set the tile data JSR ProcessTileData \ for the whole landscape to the next set of numbers \ from the landscape's sequence of seed numbers LDA #%00000000 \ Call SmoothTileData with bit 6 of A clear, to smooth JSR SmoothTileData \ the landscape in lines of tile corners, working along \ rows from left to right and along columns from front \ to back, and smoothing each tile by setting each tile \ corner's altitude to the average of its altitude with \ the three following tile corners \ \ This process is repeated twice by the single call to \ SmoothTileData LDA #1 \ Call ProcessTileData with A = 1 to scale the tile data JSR ProcessTileData \ for the whole landscape by the tileDataMultiplier \ before capping each byte of data to between 1 and 11 \ \ This capping process ensures that when we place the \ tile altitude in the top nibble of the tile data, we \ never have both bits 6 and 7 set (these bits can \ therefore be used to identify whether or not a tile \ contains an object) LDA #%01000000 \ Call SmoothTileData with bit 6 of A set, to smooth JSR SmoothTileData \ the landscape in lines of tile corners, from the rear \ row to the front row and then from the right column to \ the left column, smoothing each outlier tile corner by \ setting its altitude to that of its closest immediate \ neighbour (where "closest" is in terms of altitude) \ \ This smoothes over any single-point spikes or troughs \ in each row and column \ \ This process is repeated twice by the single call to \ SmoothTileData \ The tileData table now contains the altitude of each \ tile corner, with each altitude in the range 1 to 11, \ so the altitude data is in the low nibble of each byte \ of tile data \ \ We now calculate the tile shape for the tiles anchored \ at each tile corner in turn, where the anchor is in \ the front-left corner of the tile (i.e. nearest the \ origin) \ \ Note that the last tile corners at the right end of \ each row or at the back of each column do not anchor \ any tiles, as they are at the edge (so their shapes \ are not calculated) \ \ We put the tile shape into the high nibble of the tile \ data (for now) LDA #30 \ Set zTile = 30 so we start iterating from the rear, STA zTile \ skipping the row right at the back as the tile corners \ in that row do not anchor any tiles (so zTile iterates \ from 30 to 0 in the outer loop) .land4 LDA #30 \ Set xTile = 30 so we start iterating from the right, STA xTile \ skipping the rightmost column as the tile corners \ in that column do not anchor any tiles (so xTile \ iterates from 30 to 0 in the inner loop) .land5 JSR GetTileShape \ Set X to the shape of the tile anchored at \ (xTile, zTile) \ \ This will be in the range 1 to 11 (so it fits into \ the low nibble) JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile), which we ignore, but this also sets \ the tile page in tileDataPage and the index in Y, so \ tileDataPage+Y now points to the tile data entry in \ the tileData table \ We now put the tile shape into the high nibble of the \ tile data, so the low nibble of the tile data contains \ the tile altitude and the high nibble contains the \ tile shape (for now) TXA \ Put the tile shape in X into the high nibble of A by ASL A \ shifting X to the left by three spaces and OR'ing the ASL A \ result into the tile data at tileData + Y ASL A \ ASL A \ This works because both the tile altitude and tile ORA (tileDataPage),Y \ shape fit into the range 0 to 15, or four bits STA (tileDataPage),Y DEC xTile \ Decrement the tile x-coordinate in the inner loop BPL land5 \ Loop back until we have processed all the tile corners \ in the tile row at z-coordinate zTile, working from \ right to left DEC zTile \ Decrement outer loop counter BPL land4 \ Loop back until we have processed all the tile rows in \ the landscape, working from the back of the landscape \ all the way to the front row \ By this point the high nibble of each byte of tile \ data contains the tile shape and the low nibble \ contains the tile altitude, so now we swap these \ around \ \ We do this so that we can reuse bits 6 and 7 to in \ each byte of tile data to store the presence of an \ object on the tile, as moving the tile altitude into \ the high nibble means that bits 6 and 7 will never \ be set (as the altitude is in the range 0 to 11) \ \ We can therefore set both bit 6 and 7 to indicate that \ a tile contains an object, and we can reuse the other \ bits to store the object information (as we only ever \ place objects on flat tiles, so we can discard the \ shape data) LDA #2 \ Call ProcessTileData with A = 2 to swap the high and JSR ProcessTileData \ low nibbles of all the tile data for the whole \ landscape \ \ So now the low nibble of each byte of tile data \ contains the tile shape and the high nibble contains \ the tile altitude, as required \ \ This also sets the N flag, so a BMI branch would be \ taken at this point (see the following instruction) RTS \ Return from the subroutine \ \ If the SmoothTileCorners routine has modified the \ return address on the stack, then this RTS instruction \ will actually take us to JumpToPreview+1, and the BMI \ branch instruction at JumpToPreview+1 will be taken \ because the call to ProcessTileData sets the N flag, \ so this RTS will end up taking us to PreviewLandscape \ \ If the SmoothTileCorners routine has not modified the \ return address, then the RTS will take us to the \ SecretCodeError routine, just after the original \ caller, i.e. just after the JSR GenerateLandscape \ instruction (which will either be at the end of the \ main title loop if the player enters an incorrect \ secret code, or when displaying a landscape's secret \ code after the level is completed)
Name: ProcessTileData [Show more] Type: Subroutine Category: Landscape Summary: Process the tile data for all tiles in the landscape
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTitleScreen calls ProcessTileData * GenerateLandscape calls ProcessTileData

Arguments: A Controls what we do to the tile data: * 0 = zero all the tile data * 1 = scale all the tile data by the multiplier in tileDataMultiplier before capping it to a value between 1 and 11 * 2 = swap the high and low nibbles of all the tile data * &80 = set the tile data to the next set of numbers from the landscape's sequence of seed numbers
Returns: N flag The N flag is set (so a BMI branch will be taken)
.ProcessTileData STA processAction \ Store the action in processAction for later \ We now loop through all the tiles in the landscape \ \ The landscape consists of 31x31 square tiles, like a \ chess board that's sitting on a table in front of us, \ going into the screen \ \ The landscape is defined by the altitudes of the \ corners of each of the tile, so that's a 32x32 grid of \ altitudes \ \ The x-axis is along the front edge, from left to \ right, while the z-axis goes into the screen, away \ from us \ \ We iterate through the tile corners with a nested \ loop, with zTile going from 31 to 0 (so that's from \ back to front) \ \ For each zTile, xTile also goes from 31 to 0, so \ that's from right to left \ \ So we work through the landscape, starting with the \ row of tile corners at the back (which we work through \ from right to left), and then doing the next row \ forward, looping until we reach the front row LDA #31 \ Set zTile = 31 so we start iterating from the back row STA zTile \ (so zTile iterates from 31 to 0 in the outer loop) .proc1 LDA #31 \ Set xTile = 31 so we start iterating from the right STA xTile \ end of the current row (so xTile iterates from 31 to 0 \ in the inner loop) .proc2 JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile), which we ignore, but this also sets \ the tile page in tileDataPage and the index in Y, so \ tileDataPage+Y now points to the tile data entry in \ the tileData table LDA processAction \ Set A to the argument that was passed to the routine \ and which we stored in processAction, \ \ This specifies how we process the tile data BEQ proc8 \ If processAction = 0 then jump to proc8 to zero the \ tile data for the tile anchored at (xTile, zTile) BMI proc7 \ If processAction = &80 then jump to proc7 to set the \ tile data for the tile anchored at (xTile, zTile) to \ the next number from the landscape's sequence of seed \ numbers \ If we get here then processAction must be 1 or 2 (as \ the routine is only ever called with A = 0, 1, 2 or \ &80) LSR A \ If processAction = 1 then this sets the C flag, \ otherwise processAction = 2 and this clears the C flag LDA (tileDataPage),Y \ Set A to the tile data for the tile anchored at \ (xTile, zTile) BCS proc3 \ If the C flag is set then processAction = 1, so jump \ to proc3 \ If we get here then processAction = 2, so now we swap \ the high and low nibble of the tile data LSR A \ Set bits 0-3 of T to the high nibble (bits 4-7) of the LSR A \ tile data in A LSR A LSR A STA T LDA (tileDataPage),Y \ Set A once again to the tile data for the tile \ anchored at (xTile, zTile) ASL A \ Set bits 4-7 of A to the low nibble (bits 0-3) of the ASL A \ tile data in A ASL A ASL A ORA T \ Merge A and T, so A now contains its original high \ nibble in bits 0-3 (from T) and its original low \ nibble in bits 4-7 (from A) \ \ So this swaps the high and low nibbles around in the \ tile data in A JMP proc8 \ Jump to proc8 to store A as the tile data for the tile \ we are processing .proc3 \ If we get here then processAction = 1, so we now do \ various manipulations, including multiplying the \ tile data by the multiplier in tileDataMultiplier \ and capping the result to a positive number between \ 1 and 11 \ \ At this point the tile data contains a seed number, \ so this process converts it into a value that we can \ use as the altitude of the tile corner SEC \ Set A = tile data - 128 SBC #128 PHP \ Store the flags from the subtraction, so we can set \ the sign of the scaled altitude below BPL proc4 \ If the result of the subtraction in A is positive, \ skip the following as A is already positive EOR #%11111111 \ The result in A is negative, so negate it using two's CLC \ complement, so we have: ADC #1 \ \ A = |tile data - 128| \ \ This negation reflects negative altitudes from below \ sea level to the equivalent altitude above sea level .proc4 STA U \ Set U = A \ = |tile data - 128| LDA tileDataMultiplier \ Set A to the multiplier that we need to apply to the \ tile data, which is in the range 14 to 36 JSR Multiply8x8 \ Set (A T) = A * U \ = tileDataMultiplier * |tile data - 128| PLP \ Restore the flags from the subtraction above, so \ the N flag contains the sign of (tile data - 128) \ (clear if it is positive, set if it is negative) JSR Absolute16Bit \ Set the sign of (A T) to match the result of the \ subtraction above, so we now have: \ \ (A T) = tileDataMultiplier * (tile data - 128) \ So if the original tile data represents a landscape \ altitude, with "sea level" at altitude 128, then the \ high byte of this calculation in A represents a \ scaling of the altitude by tileDataMultiplier / 256, \ with the scaling centred around sea level \ \ This means that mountain peaks get higher and marine \ trenches get deeper, stretching away from sea level \ at altitude 128 in the original data \ \ As tileDataMultiplier is in the range 14 to 36, this \ transforms the tile data values as follows: \ \ * Values start out in the range 0 to 255 \ \ * Converting to |tile data - 128| translates them \ into values in the range 0 to 127, representing \ magnitudes of altitude (0 = sea level, 127 = top \ of Everest or bottom of Mariana Trench, stack \ contains flags denoting high altitude or murky \ depths) \ \ * Multiplying by 14/256 (the minimum multiplier) \ changes the range into 0 to 6 \ \ * Multiplying by 36/256 (the maximum multiplier) \ changes the range into 0 to 17 \ \ * Reapplying the sign converts the magnitudes back \ into depths or heights \ \ So the above takes the seed numbers in the original \ tile data and transforms then into values of A with a \ maximum range of -17 to +17 (for higher multipliers) \ or -6 to +6 (for lower multipliers) \ \ We now take this result and do various additions and \ cappings to change the result into a positive number \ between 1 and 11 CLC \ Set A = A + 6 ADC #6 \ \ So the ranges for A are now: \ \ * Minimum multiplier range is -1 to +13 \ \ * Maximum multiplier range is -11 to +23 BPL proc5 \ If A is positive then jump to proc5 to skip the \ following LDA #0 \ Otherwise A is negative, so set A = 0 .proc5 \ By this point A is a positive number and the ranges \ for A are now: \ \ * Minimum multiplier range is 0 to 13 \ \ * Maximum multiplier range is 0 to 23 CLC \ Set A = A + 1 ADC #1 \ By this point A is a positive number and the ranges \ for A are now: \ \ * Minimum multiplier range is 1 to 14 \ \ * Maximum multiplier range is 1 to 24 CMP #12 \ If A < 12 then jump to proc6 to skip the following BCC proc6 LDA #11 \ Otherwise A >= 12, so set A = 11 .proc6 \ By this point, A is a positive number between 1 and 11 \ \ For minimum values of the multiplier we have only lost \ the very low and very high values in the range \ \ For maximum values of the multiplier we have lost \ around one-third at the top end and one-third at the \ bottom end \ \ We can now use this as the altitude of the tile \ corner, which we can feed into the smoothing routines \ to generate a gently rolling landscape that is \ suitable for the game JMP proc8 \ Jump to proc8 to store A as the tile data for the tile \ we are processing .proc7 \ If we get here then the argument in A is &80, so we \ fill the tile data table with seed numbers JSR GetNextSeedNumber \ Set A to the next number from the landscape's sequence \ of seed numbers .proc8 STA (tileDataPage),Y \ Store A as the tile data for the tile anchored at \ (xTile, zTile) DEC xTile \ Decrement the tile x-coordinate in the inner loop BPL proc2 \ Loop back until we have processed all the tiles in the \ tile row at z-coordinate zTile, working from right to \ left DEC zTile \ Decrement the outer loop counter BPL proc1 \ Loop back until we have processed all the tile rows in \ the landscape, working from the back row of the \ landscape all the way to the front row \ Note that by this point the N flag is set, which means \ a BMI branch would be taken (this is important when \ analysing the intentionally confusing flow of the main \ title loop created by the stack modifications in the \ GenerateLandscape, SmoothTileCorners and JumpToPreview \ routines) RTS \ Return from the subroutine
Name: SmoothTileData [Show more] Type: Subroutine Category: Landscape Summary: Smooth the entire landscape
Context: See this subroutine on its own page References: This subroutine is called as follows: * GenerateLandscape calls SmoothTileData

Arguments: A Controls how we smooth the tile data: * Bit 6 clear = smooth each row/column of tile corners by working along the row/column and setting each tile corner's altitude to the average of its altitude with the three following tile corners, working along rows from left to right and along columns from front to back * Bit 6 set = smooth each row/column of tile corners by working along the row/column and setting the altitude of each outlier tile corner to that of its closest immediate neighbour in terms of altitude (i.e. smooth out single-point spikes or troughs in the row/column)
.SmoothTileData STA smoothingAction \ Store the action in smoothingAction so the calls to \ SmoothTileCorners can access it LDA #2 \ We perform the smoothing process twice, so set a loop STA loopCounter \ counter in loopCounter to count down from 2 .smoo1 \ We start by working our way through the landscape, \ smoothing the row of tile corners at the back (i.e. at \ tile z-coordinate 31), and then smoothing the next row \ forward, looping until we reach the front row LDA #31 \ Set zTile = 31 so we start iterating from the back row STA zTile \ (so zTile iterates from 31 to 0 in the following loop) .smoo2 LDA #00000000 \ Call SmoothTileCorners with bit 7 of A clear to smooth JSR SmoothTileCorners \ the row of tile corners at z-coordinate zTile DEC zTile \ Decrement the tile z-coordinate to move forward by one \ tile row BPL smoo2 \ Loop back until we have smoothed all 32 rows \ Next we work our way through the landscape from right \ to left, smoothing the column of tile corners on the \ right (i.e. the column of tile corners going into the \ screen at tile x-coordinate 31), and then smoothing \ the next column to the left, looping until we reach \ the column along the left edge of the landscape LDA #31 \ Set xTile = 31 so we start iterating from the right STA xTile \ column (so xTile iterates from 31 to 0 in the \ following loop) .smoo3 LDA #%10000000 \ Call SmoothTileCorners with bit 7 of A set to smooth JSR SmoothTileCorners \ the column of tile corners at x-coordinate xTile DEC xTile \ Decrement the tile x-coordinate to move left by one \ tile column BPL smoo3 \ Loop back until we have smoothed all 32 columns DEC loopCounter \ Decrement the loop counter BNE smoo1 \ Loop back until we have done the whole smoothing \ process twice RTS \ Return from the subroutine
Name: GetTileData [Show more] Type: Subroutine Category: Landscape Summary: Get the tile data and tile data address for a specific tile
The tile data table at tileData is made up of sequences of 32 columns of tile corners going into the screen, where each column goes from z = 0 to 31 along the same x-coordinate, with the columns interleaved in steps of 4 like this: &0400-&041F = 32-corner column going into the screen at x = 0 &0420-&043F = 32-corner column going into the screen at x = 4 &0440-&045F = 32-corner column going into the screen at x = 8 &0460-&047F = 32-corner column going into the screen at x = 12 &0480-&049F = 32-corner column going into the screen at x = 16 &04A0-&04BF = 32-corner column going into the screen at x = 20 &04C0-&04DF = 32-corner column going into the screen at x = 24 &04E0-&04FF = 32-corner column going into the screen at x = 28 &0500-&051F = 32-corner column going into the screen at x = 1 &0520-&053F = 32-corner column going into the screen at x = 5 &0540-&055F = 32-corner column going into the screen at x = 9 &0560-&057F = 32-corner column going into the screen at x = 13 &0580-&059F = 32-corner column going into the screen at x = 17 &05A0-&05BF = 32-corner column going into the screen at x = 21 &05C0-&05DF = 32-corner column going into the screen at x = 25 &05E0-&05FF = 32-corner column going into the screen at x = 29 &0600-&061F = 32-corner column going into the screen at x = 2 &0620-&063F = 32-corner column going into the screen at x = 6 &0640-&065F = 32-corner column going into the screen at x = 10 &0660-&067F = 32-corner column going into the screen at x = 14 &0680-&069F = 32-corner column going into the screen at x = 18 &06A0-&06BF = 32-corner column going into the screen at x = 22 &06C0-&06DF = 32-corner column going into the screen at x = 26 &06E0-&06FF = 32-corner column going into the screen at x = 30 &0700-&071F = 32-corner column going into the screen at x = 3 &0720-&073F = 32-corner column going into the screen at x = 7 &0740-&075F = 32-corner column going into the screen at x = 11 &0760-&077F = 32-corner column going into the screen at x = 15 &0780-&079F = 32-corner column going into the screen at x = 19 &07A0-&07BF = 32-corner column going into the screen at x = 23 &07C0-&07DF = 32-corner column going into the screen at x = 27 &07E0-&07FF = 32-corner column going into the screen at x = 31
Arguments: xTile A tile corner x-coordinate (0 to 31) zTile A tile corner z-coordinate (0 to 31)
Returns: A The tile data for the tile anchored at (xTile, zTile): * If the tile does not contain an object, then: * The tile shape is in the low nibble (0 to 15) * The tile altitude is in the high nibble (1 to 11) * If the tile contains an object, then: * Bits 0 to 5 contain the object number of the object on the tile (0 to 63) * Bits 6 and 7 are both set tileDataPage(1 0) The address of the page containing the tile data Y The offset from tileDataPage(1 0) of the tile data C flag Determines whether the tile contains an object: * Set if this tile contains an object * Clear if this tile does not contain an object
.GetTileData LDA xTile \ Set Y = (xTile << 3 and %11100000) + zTile ASL A \ = (xTile >> 2 and %00000111) << 5 + zTile ASL A \ = (xTile div 4) * &20 + zTile ASL A AND #%11100000 ORA zTile TAY LDA xTile \ Set A = bits 0-1 of xTile AND #%00000011 \ = xTile mod 4 \ The low byte of tileDataPage(1 0) gets set to zero in \ ResetVariables and is never changed \ \ The low byte of tileData is also zero, as we know that \ tileData is &0400 \ \ So in the following, we are just adding the high bytes \ to get a result that is on a page boundary CLC \ Set the following: ADC #HI(tileData) \ STA tileDataPage+1 \ tileDataPage(1 0) = tileData + (A 0) \ = tileData + (xTile mod 4) * &100 \ So we now have the following: \ \ tileDataPage(1 0) = tileData + (xTile mod 4) * &100 \ \ Y = (xTile div 4) * &20 + zTile \ \ The address in tileDataPage(1 0) is the page within \ tileData for the tile anchored at (xTile, zTile), and \ is always one of &0400, &0500, &0600 or &0700 because \ (xTile mod 4) is one of 0, 1, 2 or 3 \ \ The value of Y is the offset within that page of the \ tile data for the tile anchored at (xTile, zTile) \ \ We can therefore fetch the tile data for the specified \ tile using Y as an index offset from tileDataPage(1 0) LDA (tileDataPage),Y \ Set A to the tile data for the tile anchored at \ (xTile, zTile) CMP #%11000000 \ Set the C flag if A >= %11000000, which will be the \ case if both bit 6 and bit 7 of A are set RTS \ Return from the subroutine