Skip to navigation

Lander on the Acorn Archimedes

Lander A source

LANDER MAIN GAME CODE Produces the binary file GameCode.bin.
DIM CODE% &A000 + STORE * 2 \ Reserve a block in memory for the \ assembled code FOR pass% = 4 TO 6 STEP 2 \ Perform a two-pass assembly, using both \ P% and O%, with errors enabled on the \ second pass only O% = CODE% \ Assemble the code for deployment to \ address O% P% = CODE \ Assemble the code into the block at P% [ \ Switch from BASIC into assembly language OPT pass% \ Set the assembly option for this pass
Name: landscapeOffset [Show more] Type: Variable Category: Landscape Summary: The offset we apply to the on-screen landscape to push it away from us and to the left, so the visible tiles fit nicely on-screen
Context: See this variable on its own page References: This variable is used as follows: * landscapeOffsetAddr uses landscapeOffset
.landscapeOffset EQUD -LANDSCAPE_X EQUD LANDSCAPE_Y EQUD LANDSCAPE_Z
Name: landscapeOffsetAddr [Show more] Type: Variable Category: Landscape Summary: The address of the landscape offset
Context: See this variable on its own page References: This variable is used as follows: * DrawLandscapeAndBuffers (Part 1 of 4) uses landscapeOffsetAddr
.landscapeOffsetAddr EQUD landscapeOffset
Name: landscapeConfig [Show more] Type: Variable Category: Landscape Summary: The configuration data for each tile row in the landscape
Context: See this variable on its own page References: This variable is used as follows: * landscapeConfigAddr uses landscapeConfig

This table contains configuration data for the tile rows that make up the landscape. The first byte is read but is never used. The second byte is the number of points (i.e. corners) in each tile row. This is the same for all tile rows, so while this table would allow us to tailor the number of tiles plotted on each row, perhaps to make them taper off into the distance, this isn't actually done.
.landscapeConfig \ We need a configuration for each tile \ corner row EQUB &3A, TILES_X \ Tile row #0 ] FOR I% = 1 TO (TILES_Z - 1) / 2 [ OPT pass% EQUB &E3, TILES_X \ Tile row data (even) EQUB &E4, TILES_X \ Tile row data (odd) ] NEXT [ OPT pass% EQUB &E3, TILES_X \ Tile row #TILES_Z ALIGN
Name: landscapeConfigAddr [Show more] Type: Variable Category: Landscape Summary: The address of the landscapeConfig table
Context: See this variable on its own page References: This variable is used as follows: * DrawLandscapeAndBuffers (Part 2 of 4) uses landscapeConfigAddr
.landscapeConfigAddr EQUD landscapeConfig
Name: graphicsBuffers [Show more] Type: Variable Category: Graphics buffers Summary: The addresses of each of the graphics buffers (these values do not change) Deep dive: Depth-sorting with the graphics buffers
Context: See this variable on its own page References: This variable is used as follows: * graphicsBuffEndAddr2 uses graphicsBuffers * graphicsBufferAddr uses graphicsBuffers
.graphicsBuffers \ We need a graphics buffer for each tile \ corner row, numbered 0 to 10 (and the game \ also includes an extra buffer that is \ unused) ] buffer = workspace + buffers \ The graphics buffers live at the address \ given in the buffersAddr variable FOR I% = 1 TO TILES_Z + 1 \ Add a buffer for each corner row (plus 1) [ OPT pass% EQUD buffer \ Insert the address of the buffer ] buffer = buffer + BUFFER_SIZE \ Move on to the next buffer NEXT \ Repeat until we have inserted addresses of \ all the graphics buffers [ OPT pass%
Name: graphicsBuffersEnd [Show more] Type: Variable Category: Graphics buffers Summary: The end addresses of each of the graphics buffers (these values get updated as objects are drawn into the buffers) Deep dive: Depth-sorting with the graphics buffers
Context: See this variable on its own page References: This variable is used as follows: * graphicsBuffEndAddr2 uses graphicsBuffersEnd * graphicsBufferEndAddr uses graphicsBuffersEnd
.graphicsBuffersEnd \ We need a graphics buffer for each tile \ corner row, numbered 0 to 10 (and the game \ also includes an extra buffer that is \ unused) ] buffer = workspace + buffers \ The graphics buffers live at the address \ given in the buffersAddr variable FOR I% = 1 TO TILES_Z + 1 \ Add a buffer for each corner row (plus 1) [ OPT pass% EQUD buffer \ Insert the address of the buffer ] buffer = buffer + BUFFER_SIZE \ Move on to the next buffer NEXT \ Repeat until we have inserted addresses of \ all the graphics buffers [ OPT pass%
Name: DrawLandscapeAndBuffers (Part 1 of 4) [Show more] Type: Subroutine Category: Landscape Summary: Draw the landscape and the contents of the graphics buffers, from the back of the screen to the front Deep dive: Drawing the landscape Depth-sorting with the graphics buffers
Context: See this subroutine on its own page References: This subroutine is called as follows: * LoseLife calls DrawLandscapeAndBuffers * MainLoop calls DrawLandscapeAndBuffers

Both the landscape and objects are drawn one tile row at a time, working from the back to the front. In each iteration, we process a horizontal row of tile corners, working from left to right and back to front. For each corner we draw a landscape tile where possible (i.e. when we have already processed the other three corners in that tile, so we don't start drawing until we reach the second corner on the second row). Objects are drawn after the tiles on which they sit. This is achieved by staggering the drawing of objects so they are drawn two rows later than the landscape, so we draw an object two iterations after we have finished drawing all four of its surrounding tiles. This ensures that the landscape always appears behind the objects that sit on it. More specifically, we do the following: * Part 1: Start by setting up all the variables * We now step through each row of tile corners, working through the tile corners from left to right, one row at a time, keeping track of the row number in tileCornerRow = 0 to 10 For each row of tile corners, we do the following: * Part 2: Work along the current row of tile corners, from left to right, one corner coordinate at a time, and draw each tile as a quadrilateral once we have four valid corner coordinates from the previous row and previous column * Part 3: If tileCornerRow >= 2, also draw the contents of the graphics buffer with number tileCornerRow - 2 * Part 4: Finish by drawing the objects in graphics buffer 9 and graphics buffer 10 So we draw the objects in graphics buffer 0 just after we process tile corner row 2, so that's just after we draw the tiles between corner rows 1 and 2. Note that the game allocates memory to an extra graphics buffer, but only buffers numbers 0 to TILE_Z are used.
.DrawLandscapeAndBuffers STMFD R13!, {R5-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R3, [R11, #zCamera] \ Set R3 to the z-coordinate of the camera, \ which is the 3D coordinate in the game \ world at the middle-back of the on-screen \ view AND R9, R3, #&FF000000 \ Set R9 to the z-coordinate of the tile \ containing the camera, as this rounds the \ coordinate down to the nearest tile SUB R3, R3, R9 \ Set R3 = R3 - R9 \ = zCamera - tile containing zCamera \ \ So R3 contains the fractional element of \ zCamera in terms of whole tiles STR R9, [R11, #zCameraTile] \ Set zCameraTile to the z-coordinate of the \ tile containing the camera (though this \ isn't actually used - the value of R9 is \ used below, though) LDR R4, [R11, #xCamera] \ Set R4 to the x-coordinate of the camera, \ which is the 3D coordinate in the game \ world at the middle-back of the on-screen \ view AND R8, R4, #&FF000000 \ Set R8 to the x-coordinate of the tile \ containing the camera, as this rounds the \ coordinate down to the nearest tile SUB R4, R4, R8 \ Set R4 = R4 - R8 \ = xCamera - tile containing xCamera \ \ So R4 contains the fractional element of \ xCamera in terms of whole tiles STR R8, [R11, #xCameraTile] \ Set xCameraTile to the x-coordinate of the \ tile containing the camera ADD R5, R11, #xLandscapeRow \ Set R5 to the address of xLandscapeRow LDR R0, landscapeOffsetAddr \ Fetch the landscape offset vector into LDMFD R0, {R0-R2} \ (R0, R1, R2), which defines the offset \ that we apply to the on-screen landscape \ to move it away from the viewer so it \ fits nicely on-screen SUB R0, R0, R4 \ Subtract the fractional element of xCamera \ from the landscape offset x-coordinate in \ R0 SUB R2, R2, R3 \ Subtract the fractional element of zCamera \ from the landscape offset z-coordinate in \ R2 STMIA R5, {R0-R2} \ (R0, R1, R2) now contains the coordinate \ of the far-left corner of the back row of \ the landscape, which is the first corner \ row that we will process, so store it in \ (xLandscapeRow, yLandscapeRow, \ zLandscapeRow) MOV R0, #0 \ Set tileCornerRow = 0 to use as the STRB R0, [R11, #tileCornerRow] \ number of the tile corner row we are \ currently processing, working through the \ corner rows from row 0 (at the back) to \ row 10 (at the front) MOV R6, #0 \ Set R6 = 0 to use as the address of the \ previous row's coordinates, which we set \ to zero as we don't have a previous row \ yet ADD R7, R11, #cornerStore1 \ Set R7 to the address of cornerStore1 STRB R6, [R11, #tileRowOddEven] \ Set tileRowOddEven = 0, which we will flip \ between 0 and 1 for each tile corner row \ that we process \ So we now have the following variables set \ up, ready for the iteration through each \ row of tile corners, stepping from left to \ right, column by column: \ \ * R6 = 0 \ \ R6 always points to the set of corner \ pixel coordinates from the previous \ tile corner row, so it starts out as \ zero as there is no previous row at \ this point \ \ * R7 = address of cornerStore1 \ \ R7 always points to the place where we \ store the pixel coordinates for the \ current row corner as we work our way \ along the row it (we store these \ coordinates so we can draw the tiles \ when we're on the next corner row in \ the next iteration) \ \ * R9 = the z-coordinate of the tile \ containing the camera \ \ So (x, R9) is the coordinate of each \ corner on the corner row we're working \ along, starting from the far-left \ corner (as the camera position is \ actually at the back of the on-screen \ landscape) \ \ * zLandscapeRow = the z-coordinate of \ the back corner row of the landscape \ \ So zLandscapeRow keeps track of the \ z-coordinate of the tile corner row \ that we are processing, decreasing as \ we move towards the front of the tile \ landscape \ \ * tileRowOddEven = 0 \ \ tileRowOddEven flips between 0 and 1 \ for each tile corner row, so we can \ set the correct values of R6 and R7 at \ the end of each iteration \ \ * tileCornerRow = 0 \ \ tileCornerRow contains the number of \ the tile corner row that we are \ currently processing, which increments \ on each iteration as we move forwards \ \ * xCameraTile is the x-coordinate of the \ tile containing the camera \ \ This is the same as the x-coordinate of \ the middle of each tile corner row, \ rounded down to the nearest tile \ \ We now fall through into part 2
Name: DrawLandscapeAndBuffers (Part 2 of 4) [Show more] Type: Subroutine Category: Landscape Summary: Draw a row of landscape tiles Deep dive: Drawing the landscape Depth-sorting with the graphics buffers
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
\ We're now ready to iterate through the \ landscape, from left to right and back to \ front, looping back to land1 after \ processing each corner row .land1 LDRB R1, [R11, #tileCornerRow] \ Set R1 to the number of the tile corner \ row we are currently processing LDR R0, landscapeConfigAddr \ Set R0 to the address of the pair of ADD R0, R0, R1, LSL #1 \ bytes in the landscapeConfig table for the \ tile row number in R1 LDRB R10, [R0, #1] \ Set R10 to the second byte from the pair \ in landscapeConfig, which is the number \ of tile corners in the row \ \ We now use R10 as a loop counter when \ working our way along the row, counting \ tile corners as we progress LDRB R0, [R0] \ Set unusedConfig to the first byte from STRB R0, [R11, #unusedConfig] \ the pair in landscapeConfig, though this \ value is not used, so this has no effect ADD R0, R11, #xLandscapeRow \ Fetch the coordinate from (xLandscapeRow, LDMIA R0, {R0-R2} \ yLandscapeRow, zLandscapeRow) into \ (R0, R1, R2), so they contain the \ coordinates of the left end of the corner \ row that we need to process ADD R4, R11, #xLandscapeCol \ Store the coordinates in (xLandscapeCol, STMIA R4, {R0-R2} \ yLandscapeCol, zLandscapeCol) so we can \ start processing from the left end of this \ tile corner row, using these coordinates \ as we work through the columns, updating \ yLandscapeCol with the landscape height as \ we go LDR R8, [R11, #xCameraTile] \ Set R8 = xCameraTile - LANDSCAPE_X, so SUB R8, R8, #LANDSCAPE_X \ R8 is now the coordinate of the tile \ corner at the left end of the corner row, \ as subtracting the offset of the landscape \ effectively moves the camera to the left \ by that amount MOV R0, #&80000000 \ Set previousColumn = &80000000 to denote STR R0, [R11, #previousColumn] \ that we don't have corner coordinates from \ the previous column yet (as we are \ starting a new row) \ We now do the following loop R10 times, \ once for each of the tile corners in the \ row, looping back to land2 as we move \ right through the columns .land2 BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates (x, z) = (R8, R9), which is \ the current corner on this row LDR R14, [R11, #yCamera] \ Set yLandscapeCol = R0 - yCamera SUB R0, R0, R14 \ = altitude - yCamera STR R0, [R11, #yLandscapeCol] \ \ So this stores the y-coordinate of the \ landscape at this point, as we're seeing \ it from the point of view of the camera \ (so this will move it down the screen if \ the player is flying high, for example) ADD R0, R11, #xLandscapeCol \ Set R0 to the address of xLandscapeCol BL ProjectVertexOntoScreen \ Project (xLandscapeCol, yLandscapeCol, \ zLandscapeCol) onto the screen, returning \ the results in (R0, R1), so this projects \ the tile corner as it sits on the \ landscape \ \ This also sets the C flag if the vertex is \ too far away to draw BCS land4 \ If the vertex is too far away to draw then \ jump to land4 to skip the following STMIA R7!, {R0-R1} \ Store the corner coordinates in (R0, R1) \ in R7, updating R7 as we go, as R7 is \ where we store the tile corner pixel \ coordinates as we go (so this stores the \ corner coordinates in either cornerStore1 \ or cornerStore2, so we can fetch them when \ we process the next tile corner row) CMP R6, #0 \ If R6 = 0 then this is either the very BEQ land4 \ first tile corner row, or we have already \ fetched all the data for the previous row \ from the storage at R6 \ \ In either case, jump to land4 to skip \ drawing this tile as we can't draw tiles \ without the corresponding corner \ coordinates from the previous row and \ previous column LDMIA R6!, {R2-R3} \ Load the corner pixel coordinates from R6 \ into (R2, R3), updating R6 as we go, so \ this fetches the corresponding corner \ pixel coordinates from the previous tile \ corner row (i.e. from the opposite corner \ store to the one where we just stored this \ row's coordinate) CMP R2, #&80000000 \ If R2 = &80000000 then we have reached the MOVEQ R6, #0 \ end of the storage in R6, so set R6 = 0 BEQ land4 \ to prevent any more access attempts from \ the storage in R6, and jump to land4 to \ skip drawing this tile \ If we get here then we have the \ following pixel corner coordinates \ calculated: \ \ (R0, R1) = new corner from this row \ \ (R2, R3) = corresponding corner from \ previous row (i.e. the corner \ that's back by one row) STMFD R13!, {R6-R8} \ Store the registers that we want to use on \ the stack so they can be preserved ADD R14, R11, #previousColumn \ We now need to fetch the previous corners, LDMIA R14, {R4-R7} \ looking left along the tile row to the \ previous column of corners, so we can draw \ a tile from there to the new corner \ \ We will have stored these previous corners \ in the previousColumn table in the \ previous iteration, so fetch them into \ R4 to R7 like this: \ \ (R4, R5) = corner from the column to the \ left on this row \ \ (R6, R7) = corresponding corner from the \ previous row STMIA R14, {R0-R3} \ Store the new corners from this corner row \ in (R0, R1) and (R2, R3) in previousColumn \ so we can fetch them in the next iteration \ in the same way CMP R4, #&80000000 \ If R4 = &80000000 then this is actually BEQ land3 \ the first column of corner coordinates in \ this row, so we can't yet draw the tile, \ so jump to land3 to skip the drawing part \ If we get here then we actually have a \ tile to draw BL GetLandscapeTileColour \ Calculate the tile colour, depending on \ the slope, and return it in R8 BL DrawQuadrilateral \ Draw the tile by drawing a quadrilateral \ in colour R8 with corners at: \ \ (R0, R1) = the corner we're processing \ (R2, R3) = same corner in previous row \ (R4, R5) = previous column in this row \ (R6, R7) = previous row, previous column \ \ So this draws the tile we want on-screen .land3 LDMFD R13!, {R6-R8} \ Retrieve the registers that we stored on \ the stack .land4 SUBS R10, R10, #1 \ Decrement the loop counter in R10, which \ counts the number of tile corners in this \ row LDRNE R0, [R11, #xLandscapeCol] \ If we haven't yet processed all the tile ADDNE R0, R0, #TILE_SIZE \ corners in this row, add a tile's width to STRNE R0, [R11, #xLandscapeCol] \ xLandscapeCol and R8 to move them along ADDNE R8, R8, #TILE_SIZE \ the row to the next tile corner, and jump BNE land2 \ back to land2 to process the next corner \ By this point have drawn all the tiles in \ this row, so we now move on to the objects \ in the graphics buffers
Name: DrawLandscapeAndBuffers (Part 3 of 4) [Show more] Type: Subroutine Category: Landscape Summary: Draw the objects in the graphics buffers for two rows behind the current corner row Deep dive: Drawing the landscape Depth-sorting with the graphics buffers
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
LDRB R0, [R11, #tileCornerRow] \ Set R0 to the number of the tile corner \ row we are currently processing SUBS R0, R0, #2 \ Subtract 2 from the corner row to get the \ number of the row that's two tile rows \ behind the current row BLPL DrawGraphicsBuffer \ If this results in a valid graphics buffer \ number, i.e. one that is positive, draw \ the contents of the buffer LDRB R0, [R11, #tileCornerRow] \ Set R0 to the number of the tile corner \ row we are currently processing ADDS R0, R0, #1 \ Increment the tile row number in R0 to \ move forwards by one tile row CMP R0, #TILES_Z \ If R0 = TILES_Z then we just drew the last BEQ land5 \ tile row, so jump to land5 to finish off \ by drawing graphics buffers 9 and 10 STRB R0, [R11, #tileCornerRow] \ Otherwise store the updated tile corner \ row number in tileCornerRow, ready for the \ next corner row LDR R0, [R11, #zLandscapeRow] \ Subtract a tile's width from zLandscapeRow SUB R0, R0, #TILE_SIZE \ to move the coordinates of the current row STR R0, [R11, #zLandscapeRow] \ forward by one whole tile SUB R9, R9, #TILE_SIZE \ Subtract a tile's width from R9 to step \ along the z-axis by one whole tile, going \ from back to front MOV R0, #&80000000 \ Store &80000000 in the address in R7 to STR R0, [R7] \ reset the store, so it's ready to be used \ to store the new row's pixel corner \ coordinates LDRB R0, [R11, #tileRowOddEven] \ Flip the value of tileRowOddEven between EORS R0, R0, #1 \ 0 and 1, setting the flags to feed into STRB R0, [R11, #tileRowOddEven] \ the following logic ADDNE R6, R11, #cornerStore1 \ Do the following after drawing the first ADDEQ R6, R11, #cornerStore2 \ tile row and then every other row: ADDEQ R7, R11, #cornerStore1 \ ADDNE R7, R11, #cornerStore2 \ * R6 = address of cornerStore1 \ * R7 = address of cornerStore2 \ \ Or do the following after drawing the \ second tile row and then every other row: \ \ * R6 = address of cornerStore2 \ * R7 = address of cornerStore1 \ \ So R6 and R7 swap over after each row, so \ R6 always points to the set of pixel \ corner coordinates from the previous row, \ and R7 points to the place where we store \ this row's pixel corner coordinates B land1 \ Loop back to land1 to process the next row \ of tile corners
Name: DrawLandscapeAndBuffers (Part 4 of 4) [Show more] Type: Subroutine Category: Landscape Summary: Draw the remaining graphics buffers Deep dive: Drawing the landscape Depth-sorting with the graphics buffers
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.land5 MOV R0, #TILES_Z - 2 \ Draw the contents of the penultimate BL DrawGraphicsBuffer \ graphics buffer MOV R0, #TILES_Z - 1 \ Draw the contents of the last graphics BL DrawGraphicsBuffer \ buffer LDMFD R13!, {R5-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine
Name: GetLandscapeAltitude [Show more] Type: Subroutine Category: Landscape Summary: Calculate the altitude of the landscape for a given coordinate Deep dive: Generating the landscape Drawing the landscape
This routine calculates the altitude of the landscape at a given coordinate. The altitude is inverse, with lower values indicating higher altitudes. The launchpad is at LAUNCHPAD_ALTITUDE, while the sea is at SEA_LEVEL. The altitude at landscape coordinate (x, z) is calculated as follows: LAND_MID_HEIGHT - ( 2*sin(x - 2z) + 2*sin(4x + 3z) + 2*sin(3z - 5x) + 2*sin(3x + 3z) + sin(5x + 11z) + sin(10x + 7z) ) / 256 Note that the object map is flat, like a paper map, so the x- and z-axes on the map correspond to the x- and z-axes in the three-dimensional space when the map is laid out on the landscape (as the 3D z-axis goes into the screen). When talking about the map, we are talking about (x, z) coordinates that are a bit like longitude and latitude, and this routine returns the y-coordinate of the point on the landscape (as the y-axis goes down the screen and determines the altitude). Note that more negative values denote lower altitudes, as the y-axis goes down the screen, working down from zero at the very highest altitude in space. This is the opposite to conventional altitudes in the real world. The altitude is capped to a maximum value of SEA_LEVEL, and the altitude on the launchpad is set to LAUNCHPAD_ALTITUDE. The launchpad is defined as (x, z) where 0 <= x < LAUNCHPAD_SIZE and 0 <= z < LAUNCHPAD_SIZE, so that's tiles 0 to 7 along each axis (with the origin being at the front-left corner of the launchpad).
Arguments: R8 The x-coordinate of the landscape coordinate R9 The z-coordinate of the landscape coordinate
Returns: R0 The altitude (y-coordinate) of the landscape at these coordinates, with more negative values denoting lower altitudes, as the y-axis points downwards
.GetLandscapeAltitude LDR R0, [R11, #altitude] \ Set prevAltitude = altitude, so we store STR R0, [R11, #prevAltitude] \ the altitude from the previous calculation \ In the following commentary, we will refer \ to the coordinates in R8 and R9 as x and z SUB R0, R8, R9, LSL #1 \ Set R0 = R8 - R9 << 1 \ = x - 2z LDR R2, sinTableAddr \ Set R2 to the address of the sine lookup \ table BIC R0, R0, #&00300000 \ Clear bits 21 and 22 of R0 so when we \ shift R0 to the right by 20 places in the \ following lookup, the address is aligned \ to the words in the lookup table LDR R0, [R2, R0, LSR #20] \ Set \ \ R0 = (2^31 - 1) \ * SIN(2 * PI * ((R0 >> 20) / 1024)) \ \ So R0 = sin(R0) \ = sin(x - 2z) MOV R0, R0, ASR #7 \ Set R0 = R0 >> 7 \ = sin(x - 2z) / 128 ADD R1, R9, R8, LSL #1 \ R1 = R9 + R8 << 1 \ = 2x + z ADD R1, R9, R1, LSL #1 \ R1 = R9 + R1 << 1 \ = z + (2x + z) << 1 \ = z + 4x + 2z \ = 4x + 3z ADD R3, R1, R8 \ R3 = R1 + R8 \ = 4x + 3z + x \ = 5x + 3z \ So the above gives us: \ \ R0 = sin(x - 2z) / 128 \ R1 = 4x + 3z \ R3 = 5x + 3z LDR R2, sinTableAddr \ Using the same approach as above, set: BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin(R1) \ = sin(4x + 3z) ADD R0, R0, R1, ASR #7 \ R0 = R0 + R1 >> 7 \ = (sin(x - 2z) + sin(4x + 3z)) / 128 SUB R1, R9, R8, LSL #1 \ R1 = R9 - R8 << 1 \ = z - 2x RSB R1, R8, R1, LSL #1 \ R1 = (R1 << 1) - R8 \ = (z - 2x) << 1 - x \ = 2z - 4x - x \ = 2z - 5x ADD R1, R1, R9 \ R1 = R1 + R9 \ = 2z - 5x + z \ = 3z - 5x \ So the above gives us: \ \ R0 = (sin(x - 2z) + sin(4x + 3z)) / 128 \ R1 = 3z - 5x LDR R2, sinTableAddr \ Using the same approach as above, set: BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin(R1) \ = sin(3z - 5x) ADD R0, R0, R1, ASR #7 \ R0 = R0 + R1 >> 7 \ = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x)) / 128 ADD R1, R9, R8, LSL #1 \ R1 = R9 + R8 << 1 \ = z + 2x \ = 2x + z ADD R1, R9, R1, LSL #2 \ R1 = R9 + R1 << 2 \ = z + (2x + z) << 2 \ = z + 4x + 2z \ = 4x + 3z SUB R1, R1, R8 \ R1 = R1 - R8 \ = 4x + 3z - x \ = 3x + 3z \ So the above gives us: \ \ R0 = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x)) / 128 \ R1 = 3x + 3z LDR R2, sinTableAddr \ Using the same approach as above, set: BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin(R1) \ = sin(3x + 3z) ADD R0, R0, R1, ASR #7 \ R0 = R0 + R1 >> 7 \ = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x) \ + sin(3x + 3z)) / 128 ADD R1, R3, R9, LSL #3 \ R1 = R3 + R9 << 3 \ = 5x + 3z + 8z \ = 5x + 11z \ So the above gives us: \ \ R0 = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x) \ + sin(3x + 3z)) / 128 \ R1 = 5x + 11z LDR R2, sinTableAddr \ Using the same approach as above, set: BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin(R1) \ = sin(5x + 11z) ADD R0, R0, R1, ASR #8 \ R0 = R0 + R1 >> 8 \ = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x) \ + sin(3x + 3z)) / 128 \ + sin(5x + 11z) / 256 ADD R1, R9, R3, LSL #1 \ R1 = R9 + R3 << 1 \ = z + (5x + 3z) << 1 \ = z + 10x + 6z \ = 10x + 7z \ So the above gives us: \ \ R0 = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x) \ + sin(3x + 3z)) / 128 \ + sin(5x + 11z) / 256 \ R1 = 10x + 7z LDR R2, sinTableAddr \ Using the same approach as above, set: BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin(R1) \ = sin(10x + 7z) ADD R0, R0, R1, ASR #8 \ R0 = R0 + R1 >> 8 \ = (sin(x - 2z) + sin(4x + 3z) \ + sin(3z - 5x) \ + sin(3x + 3z)) / 128 \ + \ (sin(5x + 11z) + sin(10x + 7z)) / 256 RSB R0, R0, #LAND_MID_HEIGHT \ R0 = LAND_MID_HEIGHT - R0 \ \ = LAND_MID_HEIGHT - \ (2*sin(x - 2z) + 2*sin(4x + 3z) \ + 2*sin(3z - 5x) \ + 2*sin(3x + 3z) \ + sin(5x + 11z) \ + sin(10x + 7z)) / 256 \ \ which is the result that we want CMP R0, #SEA_LEVEL \ If R0 > SEA_LEVEL, set R0 = SEA_LEVEL so MOVGT R0, #SEA_LEVEL \ we don't create landscapes lower then sea \ level CMP R8, #LAUNCHPAD_SIZE \ If R8 and R9 are both < LAUNCHPAD_SIZE, CMPLO R9, #LAUNCHPAD_SIZE \ then this coordinate is on the launchpad, MOVLO R0, #LAUNCHPAD_ALTITUDE \ so set R0 = LAUNCHPAD_ALTITUDE STR R0, [R11, #altitude] \ Set altitude = R0, so we return the result \ in R0 and set the altitude variable MOV PC, R14 \ Return from the subroutine
Name: GetLandscapeBelowVertex [Show more] Type: Subroutine Category: Landscape Summary: Calculate the landscape altitude directly below an object's vertex Deep dive: Generating the landscape Drawing 3D objects
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObject (Part 2 of 5) calls GetLandscapeBelowVertex

Arguments: R0 The address containing the object's vertex (x, y, z), relative to the camera position
Returns: R0 The altitude (y-coordinate) of the landscape directly below the coordinate
.GetLandscapeBelowVertex STMFD R13!, {R8-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R8, [R0] \ Add xCamera to the x-coordinate in R8 to LDR R1, [R11, #xCamera] \ get the vertex position in the game's ADD R8, R8, R1 \ world coordinate system LDR R9, [R0, #8] \ Add zCamera to the z-coordinate in R8 to LDR R1, [R11, #zCamera] \ get the vertex position in the game's ADD R9, R9, R1 \ world coordinate system SUB R9, R9, #LANDSCAPE_Z \ Move the z-coordinate forward by the \ landscape offset, as the altitude \ calculation needs the coordinate to be \ relative to the front-centre point of the \ landscape \ The (x, z) coordinate in (R8, R9) is now \ relative to the game's coordinate system, \ rather than the camera or the landscape \ offset, which is what we need in order \ to calculate the altitude of the landscape \ at this point BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates (x, z) = (R8, R9), which is \ the point directly below the vertex LDR R14, [R11, #yCamera] \ Subtract yCamera from the altitude so the SUB R0, R0, R14 \ result is relative to the camera position LDMFD R13!, {R8-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine
Name: GetLandscapeTileColour [Show more] Type: Subroutine Category: Landscape Summary: Calculate the colour of the landscape tile currently being drawn Deep dive: Drawing the landscape Screen memory in the Archimedes
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawLandscapeAndBuffers (Part 2 of 4) calls GetLandscapeTileColour

Returns: R8 The colour of the landscape tile
.GetLandscapeTileColour STMFD R13!, {R0-R4, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R3, [R11, #prevAltitude] \ Set R3 to the altitude of the previous \ point on the landscape LDR R4, [R11, #altitude] \ Set R4 to the altitude of the current \ point on the landscape SUBS R14, R3, R4 \ Set R14 to the slope of the tile, which we MOVMI R14, #0 \ get from the change of altitude from the \ previous point to this one, making sure \ the slope value is greater than zero: \ \ R14 = min(0, prevAltitude - altitude) \ \ The y-axis that measures altitude goes \ down the screen (counterintuitively), so \ if prevAltitude has a bigger value than \ altitude, this means the previous point \ is lower down than the current point \ \ As we draw the landscape from left to \ right, this means the slope value will be \ non-zero for tiles that face left, with a \ greater value for steeper slopes, while \ tiles that face right will have a slope \ value of zero \ We now calculate the colour, with the red, \ green and blue channels in R0, R1 and R2 \ respectively AND R2, R4, #&10 \ This instruction appears to have no \ effect, as we overwrite the result in the \ next instruction, but it looks like it's \ all that remains of an experiment to add \ blue to the landscape MOV R2, #0 \ Set the blue channel in R2 to zero, as we \ only use blue for the sea \ We now set the green and red channels \ depending on bits 2 and 3 of the altitude, \ for red and green respectively \ \ This makes the green channel change more \ slowly between neighbouring tiles, with \ the red channel changing more quickly, \ giving the overall effect of a gentle \ green landscape pockmarked with small \ groups of red-brown dirt AND R1, R4, #%00001000 \ Set the green channel in R1 to bit 3 of MOV R1, R1, LSR #1 \ the current point's altitude, as follows: ADD R1, R1, #4 \ \ R1 = (bit 3) * 4 + 4 \ \ So it's 4 if bit 3 is clear, or 8 if bit 3 \ is set AND R0, R4, #%00000100 \ Set the red channel in R0 to bit 2 of the \ current point's altitude CMP R4, #LAUNCHPAD_ALTITUDE \ If the current point is on the launchpad, MOVEQ R0, #4 \ set the colour to grey, i.e. red, green MOVEQ R1, #4 \ and blue all have the same value of 4 MOVEQ R2, #4 CMP R4, #SEA_LEVEL \ If both the previous and current points CMPEQ R3, #SEA_LEVEL \ are at sea level, set the colour to blue, MOVEQ R1, #0 \ i.e. the blue channel has value 4 while MOVEQ R2, #4 \ red and green are zero MOVEQ R0, #0 LDRB R8, [R11, #tileCornerRow] \ Set R8 to the number of the tile corner \ row that we're processing, so it goes from \ 1 at the back to TILES_Z - 1 at the front, \ or 1 to 10 (it doesn't start at zero as we \ don't draw any tiles for the very first \ row of tile corners, so we don't call this \ routine) ADD R3, R8, R14, LSR #22 \ Set the tile's brightness in R3 to: \ \ tileCornerRow + slope >> 22 \ \ where tileCornerRow is 1 at the back and \ TILES_Z - 1 at the front \ \ As we draw the landscape from back to \ front and left to right, this means that: \ \ * Tiles nearer the front will have a \ higher brightness level than those at \ the back (as tileCornerRow will be \ higher for closer tiles) \ \ * Tiles that face to the left will have \ a higher brightness level than those \ that face to the right, with steeper \ sloping tiles being brighter than \ shallow ones (as slope will be higher \ for steeper sloping tiles) \ \ * Tiles that face to the right will have \ fixed brightness levels that only vary \ with distance and not with slope (as \ slope is zero for tiles that face \ right) \ \ This implements a light source that is \ directly above and slightly to the left ADD R0, R0, R3 \ Add the brightness in R3 to all three ADD R1, R1, R3 \ channels ADD R2, R2, R3 CMP R0, #16 \ Ensure that the red channel in R0 fits MOVHS R0, #15 \ into four bits CMP R1, #16 \ Ensure that the green channel in R1 fits MOVHS R1, #15 \ into four bits CMP R2, #16 \ Ensure that the blue channel in R2 fits MOVHS R2, #15 \ into four bits \ We now build a VIDC colour number in R8 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form: \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R8 from \ the red, green and blue values in R0, R1 \ and R2 ORR R8, R1, R2 \ Set R8 to the bottom three bits of: AND R8, R8, #%00000011 \ ORR R8, R8, R0 \ (the bottom two bits of R1 OR R2) OR R0 AND R8, R8, #%00000111 \ \ So this sets bits 0, 1 and 2 of R8 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R8, R8, #%00010000 \ set bit 4 of R8 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R8, R8, R1, LSL #3 \ And stick them into bits 5-6 of R8 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R8, R8, #%00001000 \ set bit 3 of R8 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R8, R8, #%10000000 \ set bit 7 of R8 ORR R8, R8, R8, LSL #8 \ Duplicate the lower byte of R8 into the ORR R8, R8, R8, LSL #16 \ other three bytes in the word to produce ORR R8, R8, R8, LSL #24 \ a four-pixel colour word containing four \ pixels of this colour LDMFD R13!, {R0-R4, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine
Name: MoveAndDrawPlayer (Part 1 of 5) [Show more] Type: Subroutine Category: Player Summary: Process player movement and draw the player's ship into the graphics buffers, starting with reading the mouse position Deep dive: Flying by mouse
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainLoop calls MoveAndDrawPlayer
.MoveAndDrawPlayer STMFD R13!, {R14} \ Store the return address on the stack \ We start by reading the position of the \ mouse and generating a rotation matrix for \ the player's ship, based on its new \ orientation SWI OS_Mouse \ Read the mouse coordinates, returning: \ \ R0 = x-coordinate \ \ R1 = y-coordinate \ \ R2 = mouse buttons (%lmr) STRB R2, [R11, #fuelBurnRate] \ Set the fuel burn rate to the mouse button \ result, so it's set to: \ \ * %000 (0) if no buttons are being \ pressed \ \ * %001 (1) if the right button is being \ pressed (i.e. fire bullets) \ \ * %010 (2) if the middle button is being \ pressed (i.e. hover) \ \ * %100 (4) if the left button is being \ pressed (i.e. full thrust) \ \ Bit 0 of the fuel rate is ignored in the \ fuel calculations, so firing bullets does \ not burn fuel, even though fuelBurnRate is \ set to a non-zero value LDR R14, [R11, #fuelLevel] \ If the fuel level is zero, also zero the CMP R14, #0 \ fuel burn rate, as we can't burn fuel that STREQB R14, [R11, #fuelBurnRate] \ we don't have \ At the start of each new life, the mouse \ is initialised to coordinates (511, 511) CMP R0, #1024 \ Cap the mouse x-coordinate in R0 so it's MOVHS R0, #1024 \ in the range 0 to 1023 SUBHS R0, R0, #1 SUB R0, R0, #512 \ Convert R0 into the range -512 to +511 RSB R1, R1, #1024 \ Set R1 = 1024 - R1 - 512 SUB R1, R1, #512 \ = 512 - R1 \ \ So the mouse y-coordinate in R1 is now in \ the range -512 to +512 MOV R0, R0, LSL #22 \ Scale both mouse coordinates up as far as MOV R1, R1, LSL #22 \ possible without losing data (512 << 22 is \ &80000000, so this is as high as we can \ go) BL GetMouseInPolarCoordinates \ Convert the mouse coordinates into polar \ coordinates, returning the polar angle in \ R1 and the polar distance in R0 CMP R0, #&40000000 \ Cap the polar distance in R0 so it's in MOVHS R0, #&40000000 \ the range 0 to &3FFFFFFF SUBHS R0, R0, #1 MOV R0, R0, LSL #1 \ Scale R0 to the range 0 to &7FFFFFFE LDR R2, [R11, #shipPitch] \ Set R2 = shipPitch LDR R3, [R11, #shipDirection] \ Set R3 = shipDirection SUBS R4, R3, R1 \ Set R4 = R3 - R1 \ = shipDirection - polar angle BMI ship1 \ If the result is negative, jump to ship1 \ so we cap R4 against negative values CMP R4, #&30000000 \ Cap the value in R4 to a maximum magnitude MOVHS R4, #&30000000 \ of &30000001 B ship2 \ Jump to ship2 to skip the following and \ keep going .ship1 CMN R4, #&30000000 \ Cap the value in R4 to a maximum magnitude MVNLO R4, #&30000000 \ of -&30000001 .ship2 SUBS R5, R2, R0 \ Set R5 = R2 - R0 \ = shipPitch - polar distance BLE ship3 \ If the result is zero or negative, jump to \ ship3 so we cap R5 against negative values CMP R5, #&30000000 \ Cap the value in R5 to a maximum magnitude MOVHS R5, #&30000000 \ of &30000001 B ship4 \ Jump to ship4 to skip the following and \ keep going .ship3 CMN R5, #&30000000 \ Cap the value in R5 to a maximum magnitude MVNLO R5, #&30000000 \ of -&30000001 .ship4 \ We now update the rotation angles with the \ latest mouse values (in the form of the \ distance and angle) \ \ We do this by adding half of the new value \ and half of the old value, which seems to \ apply some kind of damping to the controls SUB R0, R2, R5, ASR #1 \ Set R0 = R2 - R5 / 2 \ = shipPitch \ - (shipPitch - distance) / 2 SUB R1, R3, R4, ASR #1 \ Set R1 = R3 - R4 / 2 \ = shipDirection \ - (shipDirection - angle) / 2 STR R0, [R11, #shipPitch] \ Store the updated value in shipPitch STR R1, [R11, #shipDirection] \ Store the updated value in shipDirection BL CalculateRotationMatrix \ Calculate the rotation matrix from the \ updated angles given in R0 and R1, so \ this sets the rotation matrix for the \ player's ship \ Now that we have the player's rotation \ matrix, we can move on to the ship's \ movement in space
Name: MoveAndDrawPlayer (Part 2 of 5) [Show more] Type: Subroutine Category: Player Summary: Update the player's velocity and coordinates Deep dive: Flying by mouse
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
ADD R14, R11, #xPlayer \ Set R0 to R5 as follows: LDMIA R14, {R0-R5} \ \ R0 = xPlayer \ R1 = yPlayer \ R2 = zPlayer \ \ R3 = xVelocity \ R4 = yVelocity \ R5 = zVelocity \ \ So (R0, R1, R2) is the player's coordinate \ and (R3, R4, R5) is the player's velocity \ vector \ \ We now update the velocity by adding in \ various factors such as friction, thrust \ and gravity CMN R1, #HIGHEST_ALTITUDE \ If R1 is higher than the altitude where LDRLTB R9, [R11, #fuelBurnRate] \ the engines cut out, clear bits 2 and 3 of BICLT R9, R9, #%00000110 \ the fuel burn rate to cut the engines STRLTB R9, [R11, #fuelBurnRate] LDR R6, [R11, #xRoofV] \ Set (R6, R7, R8) to the roof vector from LDR R7, [R11, #yRoofV] \ the rotation matrix, which is the vector LDR R8, [R11, #zRoofV] \ that points directly down through the \ ship's floor as the y-axis is inverted \ (so it's in the direction of thrust, as \ the thrusters are on the bottom of the \ ship) \ \ Let's refer to this thrust vector as \ follows: \ \ R6 = xExhaust \ R7 = yExhaust \ R8 = zExhaust LDRB R9, [R11, #fuelBurnRate] \ Set R9 to the fuel burn rate TST R9, #%00000100 \ Set the flags according to bit 2 of the \ fuel burn rate, which is set if the left \ button is being pressed (i.e. full thrust) \ We start by updating xVelocity in R3 SUB R3, R3, R3, ASR #6 \ Set R3 = R3 - R3 >> 6 \ = xVelocity - (xVelocity / 64) \ \ This reduces the velocity along the x-axis \ by 1/64, so this applies a deceleration in \ this direction (due to friction) SUBNE R3, R3, R6, ASR #11 \ Increase R3 by a further xExhaust / 2048 \ if full thrust is being pressed, so this \ applies the engine thrust \ \ We subtract the exhaust vector to apply \ thrust because the direction of the thrust \ applied by the plume is in the opposite \ direction to the plume (in other words, a \ rocket's plume pushes downwards so the \ rocket can rise up) ADD R0, R0, R3 \ Set R0 = R0 + R3 \ = xPlayer + R3 \ \ This applies the newly calculated velocity \ to the player's x-coordinate, which moves \ the player in 3D space \ Next we update yVelocity in R4 SUB R4, R4, R4, ASR #6 \ Set R4 = R4 - R4 >> 6 \ = yVelocity - (yVelocity / 64) \ \ This reduces the velocity along the y-axis \ by 1/64, so this applies a deceleration in \ this direction (due to friction) SUBNE R4, R4, R7, ASR #11 \ Increase R3 by a further yExhaust / 2048 \ if full thrust is being pressed, so this \ applies the engine thrust \ \ We subtract the exhaust vector to apply \ thrust because the direction of the thrust \ applied by the plume is in the opposite \ direction to the plume (in other words, a \ rocket's plume pushes downwards so the \ rocket can rise up) ADD R1, R1, R4 \ Set R1 = R1 + R4 \ = yPlayer + R4 \ \ This applies the newly calculated velocity \ to the player's y-coordinate, which moves \ the player in 3D space \ And finally we update zVelocity in R4 SUB R5, R5, R5, ASR #6 \ Set R5 = R5 - R5 >> 6 \ = zVelocity - (zVelocity / 64) \ \ This reduces the velocity along the z-axis \ by 1/64, so this applies a deceleration in \ this direction (due to friction) SUBNE R5, R5, R8, ASR #11 \ Increase R3 by a further zExhaust / 2048 \ if full thrust is being pressed, so this \ applies the engine thrust \ \ We subtract the exhaust vector to apply \ thrust because the direction of the thrust \ applied by the plume is in the opposite \ direction to the plume (in other words, a \ rocket's plume pushes downwards so the \ rocket can rise up) ADD R2, R2, R5 \ Set R2 = R2 + R5 \ = zPlayer + R5 \ \ This applies the newly calculated velocity \ to the player's z-coordinate, which moves \ the player in 3D space \ By this point we have updated the player's \ coordinates in (R0, R1, R2) by the new \ velocity in (R3, R4, R5) TST R9, #%00000010 \ Set the flags according to bit 1 of the \ fuel burn rate, which is set if the middle \ button is being pressed (i.e. hover) SUBNE R3, R3, R6, ASR #13 \ If the middle button is being pressed, for SUBNE R4, R4, R7, ASR #13 \ hovering mode, then apply a quarter of the SUBNE R5, R5, R8, ASR #13 \ full thrust vector to the velocity, rather \ than the full thrust vector we apply for \ the left button \ \ Note that the hover thrust is applied \ after we applied the velocity vector to \ the player's coordinates, so hovering has \ a slightly delayed impact on the ship, to \ simulate the effects of inertia LDR R9, [R11, #gravity] \ Add the effect of gravity to yVelocity in ADD R4, R4, R9 \ R4, adding it to R4 as it is a downwards \ force along the z-axis STMIA R14, {R0-R8} \ Store the updated player coordinates, ship \ velocity and thrust vector as follows: \ \ xPlayer = R0 \ yPlayer = R1 \ zPlayer = R2 \ \ xVelocity = R3 \ yVelocity = R4 \ zVelocity = R5 \ \ xExhaust = R6 \ yExhaust = R7 \ zExhaust = R8 \ \ We use these values in part 4
Name: MoveAndDrawPlayer (Part 3 of 5) [Show more] Type: Subroutine Category: Player Summary: Check for collisions and draw the ship Deep dive: Drawing 3D objects Collisions and bullets Flying by mouse
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
CMP R1, #0 \ If R1 (i.e. the y-coordinate of the ship) MOVPL R1, #0 \ is positive, then set it to zero \ \ As we store this value as the camera's \ y-coordinate below, this ensures that the \ camera doesn't drop down all the way with \ the ship in last few moments before it \ lands, and similarly it doesn't start \ rising up with the ship as it takes off \ until the ship is at a reasonable height ADD R2, R2, #CAMERA_PLAYER_Z \ Set R2 to the z-coordinate we want to use \ for the camera, which is at the back of \ the landscape ADD R14, R11, #xCamera \ Set (xCamera, yCamera, zCamera) to the STMIA R14, {R0-R2} \ coordinates in (R0, R1, R2), which is at \ the back of the landscape and in the \ middle \ \ This sets the camera so that it follows \ the player's ship as it flies around, \ and is positioned at the back of the \ visible screen MOV R8, R0 \ Set (R8, R9) to the point on the landscape MOV R9, R2 \ that's directly below the player, by SUB R9, R9, #CAMERA_PLAYER_Z \ setting (R8, R9) to the ship's (x, z) \ coordinates in (R0, R2) and subtracting \ the five tiles we added above BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates (x, z) = (R8, R9) SUB R0, R0, #UNDERCARRIAGE_Y \ Set R0 to the altitude of a point that's \ the height of the ship's undercarriage \ above the point on the landscape beneath \ the player, which is the lowest altitude \ the player can be without being in danger \ of hitting the ground with the bottom of \ the ship LDR R1, [R11, #yPlayer] \ Set R14 to the y-coordinate of the player \ in yPlayer SUB R14, R0, R1 \ If R0 - yPlayer >= SAFE_HEIGHT (1.5 tile CMP R14, #SAFE_HEIGHT \ sizes) then this means the bottom of the BHS ship6 \ player's ship is safely clear of objects \ on the ground, as SAFE_HEIGHT is the \ minimum safe height for avoiding objects \ on the ground, and the undercarriage of \ the player's ship is above this height \ \ So if this is the case, jump to ship6 to \ skip the following set of collision \ checks, as we know we are safe STMFD R13!, {R0} \ Store R0 on the stack so we can retrieve \ it below ADD R14, R11, #objectMap \ Set R14 to the address of the object map AND R9, R9, #&FF000000 \ Clip R9 down to the nearest tile ADD R14, R14, R8, LSR #24 \ Set R14 = R6 + (R8 >> 24) + (R9 >> 16) ADD R14, R14, R9, LSR #16 \ \ R8 is shifted into the bottom byte of R14, \ so that's the x-coordinate, and R9 is \ shifted into the second byte of R14, so \ that's the z-coordinate, so R14 points to \ the object map entry for coordinate \ (R8, R9) LDRB R0, [R14] \ Set R0 to the byte in the object map at \ the coordinate (R8, R9) CMP R0, #0 \ If the object map entry is zero, jump to BEQ ship5 \ ship5 to skip the following (though this \ shouldn't ever happen, as an object type \ of zero is never added to the object map) CMP R0, #12 \ If R0 < 12 then the object map entry is a ADDLO R13, R13, #4 \ valid object number in the range 1 to 11, BLO LoseLife \ so the player's ship just hit the object \ below it on the ground, so jump to \ LoseLife to show the explosion animation \ and lose a life .ship5 LDMFD R13!, {R0} \ Retrieve R0 from the stack, so it is once \ again the lowest altitude the player can \ be without being in danger of hitting the \ ground .ship6 \ If we get here then R0 is the lowest \ altitude the player can be without being \ in danger of hitting the ground, and R1 is \ the y-coordinate of the player, i.e. the \ player's altitude CMP R1, R0 \ If R1 > R0 then the player is lower down BLGT LandOnLaunchpad \ then the safe altitude, so call \ LandOnLaunchpad to check whether we have \ landed on the launchpad (as we need to be \ within the danger zone in order to land) CMP R1, #0 \ If R1 (i.e. the y-coordinate of the ship) MOVMI R1, #0 \ is negative, then set it to zero \ \ This is the converse of the test we did at \ the start of this part, as it sets R1 to \ zero only when the altitude of the player \ is higher than zero, so this only keeps \ R1 set to the player's y-coordinate when \ they are really close to the ground \ \ As we pass this value to DrawObject below, \ this means we draw the player's ship in \ the middle of the screen most of the time \ (by passing an R1 of zero to DrawObject), \ but when the ship is close to the ground, \ we draw the ship lower down the screen ADD R3, R11, #rotationMatrix \ Set R3 to the address of the ship's \ rotation matrix to pass to DrawObject LDR R14, objectPlayerAddr \ Set objectData to the object blueprint STR R14, [R11, #objectData] \ for the player's ship MOV R0, #0 \ Set the coordinates in (R0, R1, R2) so MOV R2, #LANDSCAPE_Z_MID \ the ship gets drawn in the middle of the \ screen or slightly below if the ship is \ near the ground (as R0 = 0 and R1 is set \ as described above), at a position into \ the screen of LANDSCAPE_Z_MID, which \ places it above the middle of the \ landscape \ \ This works because the landscape offset \ pushes the far edge of the landscape 20 \ tiles into the screen, and the landscape \ is ten tiles deep, so the centre is 15 \ tiles into the screen BL DrawObject \ Draw the player's ship with the correct \ orientation and position LDRB R10, [R11, #crashedFlag] \ DrawObject sets crashedFlag to -1 if the CMP R10, #0 \ ship is lower down than its shadow, to BLNE LoseLife \ indicate that it has crashed, so jump to \ LoseLife is this is the case
Name: MoveAndDrawPlayer (Part 4 of 5) [Show more] Type: Subroutine Category: Player Summary: Spawn the particles in the exhaust plume if the engine is engaged Deep dive: Flying by mouse
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
LDRB R10, [R11, #fuelBurnRate] \ If both bits 1 and 2 of the fuel burn rate TST R10, #%00000110 \ are clear then the engine is not running, BEQ ship7 \ so jump to ship7 to skip generating an \ exhaust plume ADD R14, R11, #xVelocity \ Set R0 to R2 and R6 to R8 as follows by LDMIA R14, {R0-R2, R6-R8} \ fetching the values we stored in part 2: \ \ R0 = xVelocity \ R1 = yVelocity \ R2 = zVelocity \ \ R6 = xExhaust \ R7 = yExhaust \ R8 = zExhaust \ \ So (R0, R1, R2) is the player's velocity \ and (R6, R7, R8) is the player's thrust \ vector ADD R3, R0, R6, ASR #7 \ Set (R3, R4, R5) as follows: ADD R4, R1, R7, ASR #7 \ ADD R5, R2, R8, ASR #7 \ [ (xVelocity + xExhaust / 128) / 2 ] MOV R3, R3, ASR #1 \ [ (yVelocity + yExhaust / 128) / 2 ] MOV R4, R4, ASR #1 \ [ (zVelocity + zExhaust / 128) / 2 ] MOV R5, R5, ASR #1 \ \ So this sets (R3, R4, R5) to a vector in \ the direction of the player's velocity \ (so the particles move along with the \ ship) and in the direction of the exhaust \ plume (so that's heading away from the \ engine, in the direction that it's \ pointing), and we halve the result so they \ shoot out of the engine but soon get left \ behind as we blast away ADD R0, R11, #xPlayer \ Set R0 to R2 as follows: LDMIA R0, {R0-R2} \ \ R0 = xPlayer \ R1 = yPlayer \ R2 = zPlayer \ \ So (R0, R1, R2) is the player's coordinate SUB R0, R0, R3 \ Set (R0, R1, R2) as follows: SUB R1, R1, R4 \ SUB R2, R2, R5 \ [ xPlayer - R3 + (xExhaust / 128) ] ADD R0, R0, R6, ASR #7 \ [ yPlayer - R4 + (yExhaust / 128) ] ADD R1, R1, R7, ASR #7 \ [ zPlayer - R5 + (zExhaust / 128) ] ADD R2, R2, R8, ASR #7 \ \ So this sets (R0, R1, R2) to the position \ of the player's ship, but a little way in \ the direction of the exhaust plume (so \ that's below the engine in the direction \ that it's pointing), and we also subtract \ the velocity in (R3, R4, R5) because the \ first thing that happens when we process \ the particle in MoveAndDrawParticles is \ to add the velocity, so this cancels that \ out to ensure the particle starts out \ along the line of the exhaust plume \ By this stage we have: \ \ (R0, R1, R2) = particle coordinate \ \ (R3, R4, R5) = particle velocity \ \ We now set the values of R6 to R9 to pass \ to the AddExhaustParticleToBuffer routine MOV R7, #&001D0000 \ Set bits 16, 18, 19 and 20 of the particle \ flags, so that's: \ \ * Bit 16 set = colour fades white to red \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle MOV R6, #8 \ Set the particle's lifespan counter to 8 \ iterations of the main loop MOV R8, #10 \ Set the random element of the particle's \ velocity to the range +/- 2^(32 - 10), \ i.e. -&400000 to +&400000 MOV R9, #29 \ Set the random element of the particle's \ lifespan to the range 0 to 2^(32 - 29), \ i.e. 0 to 8 STMFD R13!, {R0-R9} \ Store R0 to R9 on the stack so we can \ fetch this set of values before each call \ to AddExhaustParticleToBuffer \ We now call the AddExhaustParticleToBuffer \ routine eight times if full thrust is \ engaged, or twice if hover mode is being \ used (so the exhaust plume is four times \ denser when full thrust is engaged) BL AddExhaustParticleToBuffer \ Call AddExhaustParticleToBuffer with the \ set of parameters in R0 to R9 to add the \ first exhaust plume particle to the \ particle data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the second particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the third particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the fourth particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the fifth particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the sixth particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the seventh particle to the particle \ data buffer LDMFD R13!, {R0-R9} \ Fetch the same parameters and add the BL AddExhaustParticleToBuffer \ final particle to the particle data buffer
Name: MoveAndDrawPlayer (Part 5 of 5) [Show more] Type: Subroutine Category: Player Summary: Spawn a bullet particle if the fire button is being pressed Deep dive: Collisions and bullets
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.ship7 TST R10, #%00000001 \ If bit 0 of the fuel burn rate is clear BEQ ship8 \ then the fire button is not being pressed, \ so jump to ship8 to skip the bullet-firing \ process and return from the subroutine LDR R8, [R11, #currentScore] \ Decrement the current score by one, as we SUB R8, R8, #1 \ are about to fire a bullet STR R8, [R11, #currentScore] ADD R14, R11, #xPlayer \ Set R0 to R5 as follows: LDMIA R14, {R0-R5} \ \ R0 = xPlayer \ R1 = yPlayer \ R2 = zPlayer \ \ R3 = xVelocity \ R4 = yVelocity \ R5 = zVelocity \ \ So (R0, R1, R2) is the player's coordinate \ and (R3, R4, R5) is the player's velocity LDR R6, [R11, #xNoseV] \ Set (R6, R7, R8) to the nose vector from LDR R7, [R11, #yNoseV] \ the rotation matrix, which is the vector LDR R8, [R11, #zNoseV] \ that points out through the ship's nose, \ just like the ship's gun \ \ Let's refer to this gun vector as follows: \ \ R6 = xGun \ R7 = yGun \ R8 = zGun ADD R3, R3, R6, ASR #8 \ Set (R3, R4, R5) as follows: ADD R4, R4, R7, ASR #8 \ ADD R5, R5, R8, ASR #8 \ [ xVelocity + xGun / 256 ] \ [ yVelocity + yGun / 256 ] \ [ zVelocity + zGun / 256 ] \ \ So this sets (R3, R4, R5) to a vector in \ the direction of the player's velocity \ (so the bullet particles move along with \ the ship from which they are fired) and \ then in the direction that the gun is \ pointing (so they leave the barrel in the \ correct direction) SUB R0, R0, R3 \ Set (R0, R1, R2) as follows: SUB R1, R1, R4 \ SUB R2, R2, R5 \ [ xPlayer - R3 + (xGun / 128) ] ADD R0, R0, R6, ASR #7 \ [ yPlayer - R4 + (yGun / 128) ] ADD R1, R1, R7, ASR #7 \ [ zPlayer - R5 + (zGun / 128) ] ADD R2, R2, R8, ASR #7 \ \ So this sets (R0, R1, R2) to the position \ of the player's ship, but a little way in \ the direction of the gun (so the bullet \ fires out of the end of the gun), and we \ also subtract the velocity in (R3, R4, R5) \ because the first thing that happens when \ we process the particle in \ MoveAndDrawParticles is to add the \ velocity, so this cancels that out to \ ensure the particle starts out at the end \ of the gun MOV R6, #20 \ Set the bullet particle's lifespan \ counter to 20 iterations of the main loop MOV R7, #&01BC0000 \ Set bits 18, 19, 20, 21, 23 and 24 of the \ particle flags, so that's: \ \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size is big \ * Bit 24 set = explode on hitting ground ORR R7, R7, #&FF \ Set the particle colour to white in bits 0 \ to 7 of the particle flag BL AddBulletParticleToBuffer \ Add a bullet particle to the particle data \ buffer .ship8 LDMFD R13!, {PC} \ Return from the subroutine
Name: LandOnLaunchpad [Show more] Type: Subroutine Category: Player Summary: Check to see if the player has landed on the launchpad Deep dive: Collisions and bullets
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawPlayer (Part 3 of 5) calls LandOnLaunchpad

We call this routine from MoveAndDrawPlayer when the player's altitude matches that of the launchpad, so this performs additional checks to see if the player just landed.
Returns: R1 The y-coordinate of the player's ship
.LandOnLaunchpad ADD R3, R11, #xPlayer \ Fetch the six words at xPlayer as follows: LDMIA R3, {R0-R5} \ \ (R0, R1, R2) = the player's coordinates \ from (xPlayer, yPlayer, zPlayer) \ \ (R3, R4, R5) = the player's velocity \ from (xVelocity, yVelocity, zVelocity) CMP R0, #LAUNCHPAD_SIZE \ If either of xPlayer or zPlayer is >= CMPLO R2, #LAUNCHPAD_SIZE \ LAUNCHPAD_SIZE then we are not over the BHS LoseLife \ launchpad, as the launchpad is defined as \ this part of the map: \ \ 0 <= x \ \ and: \ \ 0 <= z \ \ So we just crashed into the ground rather \ than the launchpad, so jump to LoseLife to \ process the crash CMP R3, #0 \ Set R3 = |R3| RSBMI R3, R3, #0 \ = |xVelocity| CMP R4, #0 \ Set R4 = |R4| RSBMI R4, R4, #0 \ = |yVelocity| CMP R5, #0 \ Set R5 = |R5| RSBMI R5, R5, #0 \ = |zVelocity| ADD R3, R3, R4 \ If R3 + R4 + R5 >= LANDING_SPEED, then: ADD R3, R3, R5 \ CMP R3, #LANDING_SPEED \ |xVelocity| + |yVelocity| + |zVelocity| MOVHS PC, R14 \ \ is greater than the safe landing speed, so \ we can't be performing a safe landing, so \ return from the subroutine \ If we get here then we have landed, as the \ altitude checks were made before this \ routine was called, we know we are above \ the launchpad, and our speed is slow \ enough to land \ \ So now we start refuelling MOV R1, #LAUNCHPAD_Y \ Set R1 to the y-coordinate of the player's \ ship as it sits on the launchpad (which is \ set to the launchpad altitude plus the \ height of the ship's undercarriage) LDR R3, [R11, #fuelLevel] \ Bump up the fuel level by 1/160 of a full ADD R3, R3, #&20 \ tank CMP R3, #&1400 STRLO R3, [R11, #fuelLevel] MOV R3, #0 \ Zero the player's velocity MOV R4, #0 MOV R5, #0 STR R3, [R11, #xVelocity] STR R4, [R11, #yVelocity] STR R5, [R11, #zVelocity] STR R1, [R11, #yPlayer] \ Set the y-coordinate of the player to the \ y-coordinate of the launchpad, so the \ player's ship is resting correctly on the \ pad MOV PC, R14 \ Return from the subroutine
Name: LoseLifeFromParticleLoop [Show more] Type: Subroutine Category: Main loop Summary: Lose a life when a crash is detected in the particle processing loop Deep dive: Collisions and bullets
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawParticles (Part 3 of 4) calls LoseLifeFromParticleLoop
.LoseLifeFromParticleLoop LDMFD R13!, {R10, R12} \ Remove the R10 and R12 registers that the \ MoveAndDrawParticles put on the stack, \ leaving just the return address \ Fall through into LoseLife to lose a life
Name: LoseLife [Show more] Type: Subroutine Category: Main loop Summary: Display a crash animation when we lose a life and end the game if this is our last life Deep dive: The main game loop Collisions and bullets
Context: See this subroutine on its own page References: This subroutine is called as follows: * LandOnLaunchpad calls LoseLife * MoveAndDrawPlayer (Part 3 of 5) calls LoseLife
.LoseLife MOV R0, #0 \ Set playingGame = 0 to flag that the game STR R0, [R11, #playingGame] \ is no longer being played and that this is \ the crash animation MOV R0, #30 \ Set crashLoopCount = 30 to act as a loop STR R0, [R11, #crashLoopCount] \ the crash animation below MOV R8, #81 \ Set R8 = 81 to use as the size of the \ explosion in AddExplosionToBuffer ADD R0, R11, #xPlayer \ Set (R0, R1, R2) to the coordinate in LDMIA R0, {R0-R2} \ xPlayer SUB R1, R1, #CRASH_CLOUD_Y \ Subtract CRASH_CLOUD_Y from the ship's \ y-coordinate so the explosion occurs just \ above the player's ship (5/16 tile sizes \ above the ship, to be precise) BL AddExplosionToBuffer \ Draw a large explosion in place of the \ player's ship .lose1 \ We now run a cut-down version of the main \ loop to display the crash animation (this \ is like the main loop but without the \ calls to drop rocks from the sky, draw the \ player's ship or update the fuel level) \ We now set up the rotation matrix for the \ rocks, using the main loop counter to \ generate rotation angles that change along \ with the main loop (so the rocks spin at a \ nice steady speed) LDR R0, [R11, #mainLoopCount] \ Set R0 = mainLoopCount << 24 MOV R0, R0, LSL #24 MOV R1, R0, LSL #1 \ Set R1 = mainLoopCount << 25 BL CalculateRotationMatrix \ Calculate the rotation matrix from the \ "angles" given in R0 and R1, which we can \ apply to any rocks we draw in the \ MoveAndDrawParticles routine (as rocks are \ only rotating 3D objects apart from the \ player, and the player calculates its own \ rotation matrix) BL MoveAndDrawParticles \ Move and draw all the particles, such as \ smoke clouds and bullets, into the \ graphics buffers BL DrawObjects \ Draw all the objects, such as trees and \ buildings, into the graphics buffers BL AddTerminatorsToBuffers \ Add terminators to the ends of the \ graphics buffers so we know when to stop \ drawing BL DrawLandscapeAndBuffers \ Draw the landscape and the contents of the \ graphics buffers BL PrintCurrentScore \ Print the number of remaining bullets at \ the left end of the score bar BL SwitchScreenBank \ Switch screen banks and clear the newly \ hidden screen bank to black LDR R0, [R11, #mainLoopCount] \ Increment the main loop counter ADD R0, R0, #1 STR R0, [R11, #mainLoopCount] LDR R0, [R11, #crashLoopCount] \ Decrement the loop counter for the crash SUBS R0, R0, #1 \ animation above STR R0, [R11, #crashLoopCount] BPL lose1 \ Loop back to keep running the crash \ animation until the loop counter runs down LDR R0, [R11, #remainingLives] \ Decrement the number of remaining lives SUBS R0, R0, #1 \ and set the flags accordingly STR R0, [R11, #remainingLives] \ ADD R13, R13, #4 \ Increment the stack pointer by one word so \ we discard the return address from the top \ of the stack, so we rejoin the main loop \ without keeping the return address of the \ subroutine we were in before we jumped \ here (i.e. MoveAndDrawPlayer or \ LandOnLaunchpad) BNE PlacePlayerOnLaunchpad \ If we still have one or more lives left, \ jump to PlacePlayerOnLaunchpad to play the \ next life
Name: GameOver [Show more] Type: Subroutine Category: Main loop Summary: Print a Game Over message and start a new game
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.GameOver MOV R0, #112 \ Set the VDU driver screen bank to bank 1 MOV R1, #1 SWI OS_Byte MOV R0, #31 \ Print the following VDU command: SWI OS_WriteC \ MOV R0, #1 \ VDU 31, 1, 16 SWI OS_WriteC \ MOV R0, #16 \ which moves the text cursor to column 1 on SWI OS_WriteC \ row 16, halfway down the screen SWI OS_WriteS \ Print the Game Over message EQUS "GAME OVER - press a " EQUS "key to start again" EQUB 0 ALIGN MOV R0, #112 \ Set the VDU driver screen bank to bank 2 MOV R1, #2 SWI OS_Byte MOV R0, #31 \ Print the following VDU command: SWI OS_WriteC \ MOV R0, #1 \ VDU 31, 1, 16 SWI OS_WriteC \ MOV R0, #16 \ which moves the text cursor to column 1 on SWI OS_WriteC \ row 16, i.e. the same text coordinates as \ the text we printed in screen bank 1 above SWI OS_WriteS \ Print the Game Over message EQUS "GAME OVER - press a " EQUS "key to start again" EQUB 0 ALIGN SWI OS_ReadC \ Wait for a key press B StartNewGame \ Jump to StartNewGame to start a brand new \ game
Name: graphicsBuffEndAddr2 [Show more] Type: Variable Category: Graphics buffers Summary: The addresses of the tables containing the graphics buffer addresses (same as graphicsBufferEndAddr and graphicsBufferAddr) Deep dive: Depth-sorting with the graphics buffers
Context: See this variable on its own page References: This variable is used as follows: * MoveAndDrawParticles (Part 1 of 4) uses graphicsBuffEndAddr2
.graphicsBuffEndAddr2 EQUD graphicsBuffersEnd EQUD graphicsBuffers
Name: MoveAndDrawParticles (Part 1 of 4) [Show more] Type: Subroutine Category: Particles Summary: Process particle movement and draw the particles into the graphics buffers, starting with the movement of particles Deep dive: Particles and particle clouds
Context: See this subroutine on its own page References: This subroutine is called as follows: * LoseLife calls MoveAndDrawParticles * MainLoop calls MoveAndDrawParticles * DeleteParticleData calls via dpar1 * MoveAndDrawParticles (Part 2 of 4) calls via dpar1 * MoveAndDrawParticles (Part 3 of 4) calls via dpar1

Other entry points: dpar1 The start of the main particle-processing loop
.MoveAndDrawParticles STMFD R13!, {R10, R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved ADD R10, R11, #particleData \ Set R10 to the address of the particle \ data buffer, which we update as we work \ through the particle data buffer, so this \ points to the data of the particle being \ processed LDR R12, graphicsBuffEndAddr2 \ Set R1 to the address of the table that \ contains the end addresses of the graphics \ buffers \ We now work our way through the particle \ data buffer, processing each particle in \ turn .dpar1 LDMIA R10, {R0-R7} \ Fetch the eight words of particle data \ from the particle data buffer at R10, so \ this sets the following: \ \ (R0, R1, R2) = particle coordinate \ \ (R3, R4, R5) = particle velocity \ \ R6 = particle lifespan counter \ \ R7 = particle flags CMP R7, #0 \ If the last word of the data is zero, then LDMEQFD R13!, {R10, R12, PC} \ this is a null terminator and we have \ reached the end of the buffer, so retrieve \ the registers that we stored on the stack \ and return from the subroutine .dpar2 SUBS R6, R6, #1 \ Decrement the particle's lifespan counter \ in R6 BEQ DeleteParticleData \ If R6 is now zero then the particle just \ expired, so jump to DeleteParticleData to \ delete this particle from the particle \ data buffer \ \ DeleteParticleData then jumps back to \ dpar1 to move on to the next particle ADD R0, R0, R3 \ Move the particle by its current velocity ADD R1, R1, R4 \ by adding the particle's velocity vector ADD R2, R2, R5 \ in (R3, R4, R5) to the particle's \ coordinates in (R0, R1, R2) TST R7, #&00100000 \ If bit 20 of the particle flags is set, LDRNE R14, [R11, #gravity] \ add gravity to the velocity's y-coordinate ADDNE R4, R4, R14 \ in R4, so the particle accelerates towards \ the ground TST R7, #&00010000 \ If bit 16 of the particle flags is set, BLNE SetParticleColourToFade \ call SetParticleColourToFade to fade the \ particle colour from white to red, \ according to the particle's age, and store \ the colour in the bottom byte of the \ particle flags (bits 0 to 7) MOV R8, R0 \ Set (R8, R9) = (R0, R2) so we can fetch MOV R9, R2 \ the landscape altitude directly below the \ particle STMFD R13!, {R0-R3} \ Store R0 to R3 on the stack so they don't \ get corrupted by the following call to \ GetLandscapeAltitude BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates (x, z) = (R8, R9) MOV R9, R0 \ Copy the altitude into R9 LDMFD R13!, {R0-R3} \ Retrieve the values of R0 to R3 that we \ stored above TST R7, #&00200000 \ If bit 21 of the particle flags is set BLNE ProcessObjectDestruction \ then the particle can destroy objects that \ it hits, so call ProcessObjectDestruction \ to process this CMP R1, R9 \ If R1 > R9 then the particle is below the BLGT BounceParticle \ level of the landscape, so bounce the \ particle off the ground STMIA R10!, {R0-R7} \ Store the updated particle data in the \ particle data buffer, updating R10 so it \ points to the next particle, ready for the \ next iteration around the loop
Name: MoveAndDrawParticles (Part 2 of 4) [Show more] Type: Subroutine Category: Particles Summary: Draw particles (including rocks) into the graphics buffers Deep dive: Particles and particle clouds
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
LDR R8, [R11, #xCamera] \ Set R0 = R0 - xCamera SUB R0, R0, R8 \ = x - xCamera \ \ So R0 contains the x-coordinate of the \ particle relative to the camera LDR R8, [R11, #zCamera] \ Set R2 = R2 - zCamera SUB R2, R2, R8 \ \ So R2 contains the z-coordinate of the \ particle relative to the camera ADD R2, R2, #LANDSCAPE_Z \ Move the coordinate back by the landscape \ offset, so (R0, R2) contains the \ coordinate of the particle relative to the \ back-centre point of the landscape LDR R8, [R11, #yCamera] \ Set R1 = R1 - yCamera SUB R1, R1, R8 \ = y - yCamera \ \ So R1 contains the y-coordinate of the \ particle relative to the camera CMP R2, #LANDSCAPE_Z \ If the z-coordinate of the particle in R2 BHS dpar1 \ is further into the screen than the \ landscape offset in LANDSCAPE_Z, then it \ is beyond the back of the visible \ landscape, so jump to dpar1 to move on to \ the next particle in the buffer CMP R2, #LANDSCAPE_Z_FRONT \ If the z-coordinate of the particle in R2 BLO dpar1 \ is closer to us than LANDSCAPE_Z_FRONT, \ then it is closer than the front of the \ visible landscape, so jump to dpar1 to \ move on to the next particle in the buffer MOVS R14, R0 \ Set R14 = |R0| RSBMI R14, R0, #0 \ = |particle x-coordinate| CMP R14, #LANDSCAPE_X_HALF \ If the x-coordinate of the particle in R14 BHS dpar1 \ is more than half the x-axis width of the \ landscape to the left or right, then it is \ past the edge of the landscape and is too \ far to be drawn, so jump to dpar1 to move \ on to the next particle in the buffer TST R7, #&00020000 \ If bit 17 of the particle flags is clear BEQ dpar4 \ then this is not a rock, so jump to dpar4 \ to draw the particle and its shadow into \ the graphics buffers in part 4 \ Otherwise this is a rock, so fall through \ into part 3 to process collisions
Name: MoveAndDrawParticles (Part 3 of 4) [Show more] Type: Subroutine Category: Particles Summary: Process rocks by checking for collisions and drawing them as 3D objects Deep dive: Drawing 3D objects Particles and particle clouds Collisions and bullets
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
\ If we get here then the particle is a \ rock, so we need to check whether it has \ hit the player's ship STMFD R13!, {R0-R2} \ Store the rock coordinates in (R0, R1, R2) \ on the stack so we can retrieve them below LDR R14, [R11, #playingGame] \ If playingGame = 0 then this is the crash CMP R14, #0 \ animation, so jump to dpar3 to draw the BEQ dpar3 \ rock, skipping the call to lose a life (as \ the EQ condition is true, which does not \ match BLO) CMP R0, #0 \ Set R0 = |R0| RSBMI R0, R0, #0 \ = |rock x-coordinate| SUBS R2, R2, #LANDSCAPE_Z_MID \ Set R2 = |R2 - LANDSCAPE_Z_MID| RSBMI R2, R2, #0 \ = |rock z-coordinate - 15 tiles| \ \ Because the player's ship is always at \ x-coordinate 0 and the z-coordinate at \ the mid-point of the landscape, this works \ out the rock's coordinate relative to the \ ship (for the x- and z-coordinates) \ \ The rock object is one tile in size along \ each axis, so we can do a check against \ the file size to see if we are being hit \ by the rock CMP R0, #TILE_SIZE \ If either of R0 or R2 is bigger than one CMPLO R2, #TILE_SIZE \ tile size, jump to dpar3 as the rock is BHS dpar3 \ missing us, skipping the call to lose a \ life (as the HS condition is true, which \ does not match BLO) \ The rock is overlapping the player in \ either the x- or z-coordinate, so now we \ need to check its altitude LDR R0, [R11, #yCamera] \ Set R1 = |R1 + yCamera - yPlayer| ADD R1, R1, R0 \ LDR R0, [R11, #yPlayer] \ So R1 contains the y-coordinate of the SUBS R1, R1, R0 \ rock relative to the player, which we can RSBMI R1, R1, #0 \ also test against the tile size to check \ for a collision CMP R1, #TILE_SIZE \ If R1 < TILE_SIZE then the LO condition \ will be true, which will send us to \ LoseLifeFromParticleLoop below to lose a \ life, as the rock has hit us .dpar3 LDMFD R13!, {R0-R2} \ Retrieve the rock coordinates that we \ stored above into (R0, R1, R2) BLO LoseLifeFromParticleLoop \ Jump to LoseLifeFromParticleLoop if the LO \ condition is true, which will only be the \ case if we reached the CMP just before \ dpar3 and R1 < TILE_SIZE LDR R14, objectRockAddr \ Store the address of the rock's object STR R14, [R11, #objectData] \ blueprint in objectData to pass to the \ DrawObject routine ADD R3, R11, #rotationMatrix \ Set R3 to the address of the rock's \ rotation matrix, to pass to DrawObject BL DrawObject \ Draw the rock into the graphics buffers B dpar1 \ Loop back to dpar1 to process the next \ particle in the particle data buffer
Name: MoveAndDrawParticles (Part 4 of 4) [Show more] Type: Subroutine Category: Particles Summary: Draw particles into the graphics buffers Deep dive: Particles and particle clouds
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.dpar4 \ If we get here then we draw the particle \ and its shadow into the graphics buffers STMFD R13!, {R0-R2, R7} \ Store R0, R1, R2 and R7 on the stack so \ they don't get corrupted by the following \ calls LDR R14, [R11, #yCamera] \ Set R1 = R9 - yCamera SUB R1, R9, R14 \ = landscape altitude - yCamera \ \ We set R9 in part 1 to the altitude of the \ landscape below the particle, so this sets \ R1 to the y-coordinate of the particle's \ shadow relative to the camera MOV R8, R2 \ Set R8 to the z-coordinate of the particle \ relative to the camera BL ProjectParticleOntoScreen \ Project the coordinates of the particle's \ shadow in (R0, R1, R2) onto the screen, \ returning the results in (R0, R1) \ \ This also clears the C flag if the \ particle coordinates are on-screen BLCC DrawParticleShadowToBuffer \ If the projected coordinates fit onto the \ screen, draw the particle's projected \ shadow into the graphics buffers LDMFD R13!, {R0-R2, R7} \ Retrieve the values of R0, R1, R2 and R7 \ that we stored above MOV R8, R2 \ Set R8 to the z-coordinate of the particle \ relative to the camera BL ProjectParticleOntoScreen \ Project the coordinates of the particle in \ (R0, R1, R2) onto the screen, returning \ the results in (R0, R1) \ \ This also clears the C flag if the \ particle coordinates are on-screen BLCC DrawParticleToBuffer \ If the projected coordinates fit onto the \ screen, draw the projected particle into \ the graphics buffers LDMIA R10, {R0-R7} \ Fetch the eight words of particle data \ for the next particle from the particle \ data buffer at R10 CMP R7, #0 \ If the last word of the data is not zero BNE dpar2 \ then this is a valid particle rather than \ a null terminator, so loop back to dpar2 \ to process the next particle LDMFD R13!, {R10, R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine
Name: SetParticleColourToFade [Show more] Type: Subroutine Category: Particles Summary: Set the flags for a particle whose colour fades from white to red over time, to give a white-hot explosion particle that cools down Deep dive: Particles and particle clouds Screen memory in the Archimedes
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawParticles (Part 1 of 4) calls SetParticleColourToFade

Arguments: R6 Particle lifespan counter (i.e. how many iterations around the main loop before the particle expires)
Returns: R7 Particle flags for a fading colour particle * Bits 0-7 = particle colour * Bit 16 set = colour fades white to red * Bit 18 set = splash on impact with sea * Bit 19 set = bounce on ground * Bit 20 set = apply gravity to particle
.SetParticleColourToFade STMFD R13!, {R0-R2, R6} \ Store R0, R1, R2 and R6 on the stack so \ they don't get corrupted by the following \ We start by setting up a three-channel \ colour, with the red channel in R0, the \ green channel in R1 and the blue channel \ in R2 MOV R0, #15 \ Set the red channel to 15 CMP R6, #8 \ If R6 >= 8, set: MOVHS R1, #15 \ MOVLO R1, R6, LSL #1 \ Green channel in R1 = 15 SUBHS R2, R6, #8 \ Blue channel in R2 = (R6 - 8) * 2 MOVHS R2, R2, LSL #1 \ MOVLO R2, #0 \ otherwise set: \ \ Green channel in R1 = R6 * 2 \ Blue channel in R2 = 0 \ \ So particles start out white (R6 > 8) \ with a fading level of blue, until the \ blue disappears entirely (R6 = 8) and \ then the green fades away (R6 < 8) to \ leave a pure red particle (R6 = 0) \ \ So this is a fading particle from white \ to red, so this looks like a burning \ particle from an explosion that cools \ over time \ We now build a VIDC colour number in R7 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form: \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R7 from \ the red, green and blue values in R0, R1 \ and R2 ORR R7, R1, R2 \ Set R7 to the bottom three bits of: AND R7, R7, #%00000011 \ ORR R7, R7, R0 \ (the bottom two bits of R1 OR R2) OR R0 AND R7, R7, #7 \ \ So this sets bits 0, 1 and 2 of R7 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R7, R7, #%00010000 \ set bit 4 of R7 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R7, R7, R1, LSL #3 \ And stick them into bits 5-6 of R7 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R7, R7, #%00001000 \ set bit 3 of R7 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R7, R7, #%10000000 \ set bit 7 of R7 ORR R7, R7, #&001D0000 \ Set bits 16, 18, 19 and 20 of the particle \ flags, so that's: \ \ * Bit 16 set = colour fades white to red \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle LDMFD R13!, {R0-R2, R6} \ Retrieve the registers that we stored on \ the stack above MOV PC, R14 \ Return from the subroutine
Name: BounceParticle [Show more] Type: Subroutine Category: Particles Summary: Bounce a particle off the ground Deep dive: Particles and particle clouds
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawParticles (Part 1 of 4) calls BounceParticle

Arguments: (R0, R1, R2) Particle coordinates (R3, R4, R5) Particle velocity R7 Particle flags R9 The altitude of the landscape directly below the particle
.BounceParticle MOV R1, R9 \ Set R1 to R9, so (R0, R1, R2) now contains \ the coordinates of the point on the \ landscape directly below the particle CMP R9, #SEA_LEVEL \ If the particle is above the sea, jump to BEQ SplashParticleIntoSea \ SplashParticleIntoSea to splash the \ particle into the sea, returning from the \ subroutine using a tail call TST R7, #&00080000 \ If bit 19 of the particle flags is clear, BEQ DeleteParticleData \ jump to DeleteParticleData to delete the \ particle without bouncing or exploding TST R7, #&01000000 \ If bit 24 of the particle flags is set, BNE AddSmallExplosionToBuffer \ jump to AddSmallExplosionToBuffer to \ destroy the particle in a small explosion MOV R3, R3, ASR #1 \ Otherwise we bounce the particle off the MOV R4, R4, ASR #1 \ ground by setting the particle's velocity MOV R5, R5, ASR #1 \ vector to half its previous speed, and in RSB R4, R4, #0 \ the opposite direction in the y-axis MOV PC, R14 \ Return from the subroutine
Name: ProcessObjectDestruction [Show more] Type: Subroutine Category: Particles Summary: If this particle has hit an object, destroy the object and the particle in an explosion, scoring points if it's a bullet Deep dive: Particles and particle clouds Collisions and bullets
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawParticles (Part 1 of 4) calls ProcessObjectDestruction

Arguments: (R0, R1, R2) The particle's coordinates R7 Particle flags R9 The altitude of the landscape directly below the particle
.ProcessObjectDestruction SUB R8, R9, R1 \ Set R8 = altitude - y-coordinate \ \ So R8 contains the vertical distance \ between the particle and the ground CMP R8, #SAFE_HEIGHT \ If R8 is higher than the minimum safe MOVHS PC, R14 \ height for avoiding objects on the ground \ in SAFE_HEIGHT (which is set to 1.5 tile \ sizes) then return from the subroutine as \ the particle is too high off the ground to \ be hitting any objects STMFD R13!, {R2} \ Store R2 on the stack so we can retrieve \ it below STR R14, [R11, #objectType] \ Store the return address in objectType \ (the choice of variable is not important, \ we are just using it as temporary storage \ here) ADD R14, R11, #objectMap \ Set R14 to the address of the object map AND R2, R2, #&FF000000 \ Set the bottom three bytes of R2 to zero, \ leaving just the top byte, so we can use \ it in the following ADD R14, R14, R0, LSR #24 \ Set R14 = R14 + (R0 >> 24) + (R2 >> 16) ADD R14, R14, R2, LSR #16 \ \ R0 is shifted into the bottom byte of R14, \ so that's the x-coordinate, and R2 is \ shifted into the second byte of R14, so \ that's the z-coordinate, so R14 points to \ the object map entry for coordinate \ (R0, R2) LDMFD R13!, {R2} \ Retrieve the value of R2 that we stored \ above, so it contains the particle \ y-coordinate once again LDRB R8, [R14] \ Set R8 to the byte in the object map at \ the coordinate (R0, R2) CMP R8, #&FF \ If the object map entry is &FF, then there LDREQ PC, [R11, #objectType] \ is no object on the map at the particle's \ location, so we haven't hit anything, so \ return from the subroutine by fetching the \ return address that we stored above CMP R8, #12 \ If the object being hit by the particle is BHS AddSmallExplosionToBuffer \ 12 or greater, then the object has already \ been destroyed, so draw a small explosion \ by calling AddSmallExplosionToBuffer, and \ return from the subroutine using a tail \ call ADD R8, R8, #12 \ Otherwise the particle has just hit an STRB R8, [R14] \ undestroyed object, so add 12 to the \ object's type in the object map to denote \ that it has been destroyed TST R7, #&00020000 \ If bit 17 the particle flags is clear then LDREQ R8, [R11, #currentScore] \ the particle doing the hitting is not a ADDEQ R8, R8, #20 \ rock, so it must be a bullet, so increment STREQ R8, [R11, #currentScore] \ the current score by 20 MOV R8, #20 \ Set R8 = 20 and call AddExplosionToBuffer BL AddExplosionToBuffer \ to draw a medium-sized explosion where the \ particle hit B DeleteParticleData \ Jump to DeleteParticleData to delete the \ particle, as it gets destroyed in the \ explosion, and return from the subroutine \ using a tail call
Name: AddSmallExplosionToBuffer [Show more] Type: Subroutine Category: Particles Summary: Add a small explosion to the particle data buffer Deep dive: Particles and particle clouds
Context: See this subroutine on its own page References: This subroutine is called as follows: * BounceParticle calls AddSmallExplosionToBuffer * ProcessObjectDestruction calls AddSmallExplosionToBuffer

Arguments: (R0, R1, R2) Explosion coordinates R7 Particle flags
.AddSmallExplosionToBuffer MOV R8, #3 \ Set R8 = 3 and call AddExplosionToBuffer BL AddExplosionToBuffer \ to draw a small explosion at the given \ coordinates B DeleteParticleData \ Jump to DeleteParticleData to delete the \ particle, as it gets destroyed in the \ explosion, and return from the subroutine \ using a tail call
Name: SplashParticleIntoSea [Show more] Type: Subroutine Category: Particles Summary: Splash a particle into the sea, creating a spray particle Deep dive: Particles and particle clouds
Context: See this subroutine on its own page References: This subroutine is called as follows: * BounceParticle calls SplashParticleIntoSea

Arguments: (R0, R1, R2) Particle coordinates (on the sea's surface) R7 Particle flags
.SplashParticleIntoSea TST R7, #&00040000 \ If bit 18 of the particle flags is clear, BEQ DeleteParticleData \ jump to DeleteParticleData to delete the \ particle without splashing TST R7, #&00800000 \ If bit 23 of the particle flags is set, MOVNE R8, #65 \ set the number of spray particles in R8 MOVEQ R8, #4 \ to 65, otherwise set it to 4 SUB R1, R1, #SPLASH_HEIGHT \ Set R1 to a point just above the sea's \ surface (R1 is on the surface, so this \ moves it to SPLASH_HEIGHT above the waves, \ or 1/16 of a tile size) .psea1 BL AddSprayParticleToBuffer \ Add a spray particle to the particle data \ buffer SUBS R8, R8, #1 \ Decrement the particle counter in R8 BNE psea1 \ Loop back until we have added all the \ spray particles \ Fall through into DeleteParticleData to \ delete the particle and return from the \ subroutine, as the particle has now \ crashed into the sea
Name: DeleteParticleData [Show more] Type: Subroutine Category: Particles Summary: Delete a particle from the particle data buffer and move the last particle's data down to take its place Deep dive: Particles and particle clouds
Arguments: R10 The address in the particle data buffer of the particle to delete
.DeleteParticleData LDR R8, [R11, #particleEnd] \ Decrease the address of the end of the SUB R8, R8, #8*4 \ particle data buffer by eight words, so it STR R8, [R11, #particleEnd] \ points to the last particle's data in the \ buffer LDMIA R8, {R0-R7} \ Copy the eight bytes of particle data from STMIA R10, {R0-R7} \ the end of the buffer to the address in \ R10 MOV R7, #0 \ Zero the last word of the last particle's STR R7, [R8, #7*4] \ data, so the last particle in the buffer \ now acts as a null terminator LDR R8, [R11, #particleCount] \ Decrement the particle counter to reflect SUB R8, R8, #1 \ the new number of particles on-screen STR R8, [R11, #particleCount] B dpar1 \ Jump to the start of the main loop in \ MoveAndDrawParticles to process the next \ particle in the particle data buffer
Name: AddBulletParticleToBuffer [Show more] Type: Subroutine Category: Particles Summary: Add a bullet particle to the particle data buffer Deep dive: Particles and particle clouds Collisions and bullets
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawPlayer (Part 5 of 5) calls AddBulletParticleToBuffer

Arguments: (R0, R1, R2) Particle coordinate (R3, R4, R5) Particle velocity R6 Particle lifespan counter (i.e. how many iterations around the main loop before the particle expires) R7 Particle flags R8 Magnitude of the random element that's added to the velocity, with a larger figure giving a smaller random element; the actual range is +/- 2^(32 - R8) R9 Magnitude of the random element that's added to the particle lifespan, with a larger figure giving a smaller random element; the actual range is 0 to 2^(32 - R9)
.AddBulletParticleToBuffer STMFD R13!, {R8, R14} \ Store R8 and the return address on the \ stack, so the call to StoreParticleData \ can return properly B StoreParticleData \ Jump to StoreParticleData to store the \ particle in the particle data buffer, \ returning from the subroutine using a tail \ call
Name: AddExhaustParticleToBuffer [Show more] Type: Subroutine Category: Particles Summary: Add one of the moving particles in the exhaust plume to the particle data buffer Deep dive: Particles and particle clouds Flying by mouse
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawPlayer (Part 4 of 5) calls AddExhaustParticleToBuffer

Arguments: (R0, R1, R2) Particle coordinate (R3, R4, R5) Particle velocity R6 Particle lifespan counter (i.e. how many iterations around the main loop before the particle expires) R7 Particle flags R8 Magnitude of the random element that's added to the velocity, with a larger figure giving a smaller random element; the actual range is +/- 2^(32 - R8) R9 Magnitude of the random element that's added to the particle lifespan, with a larger figure giving a smaller random element; the actual range is 0 to 2^(32 - R9)
.AddExhaustParticleToBuffer STMFD R13!, {R8, R14} \ Store R8 and the return address on the \ stack, so the following call to the \ particle adding routine below can return \ properly B AddMovingParticleToBuffer \ Add a moving particle to the particle data \ buffer that starts off moving along the \ exhaust vector, and with a random element \ being added to its velocity and lifespan \ counter, returning from the subroutine \ using a tail call
Name: AddStaticParticleToBuffer [Show more] Type: Subroutine Category: Particles Summary: Add a particle to the particle data buffer that starts off static, adding a random element to its velocity and lifespan counter Deep dive: Particles and particle clouds
Arguments: (R0, R1, R2) Particle coordinate R6 Particle lifespan counter (i.e. how many iterations around the main loop before the particle expires) R7 Particle flags: * Bits 0-7 = particle colour * Bit 16 set = colour fades white to red * Bit 17 set = particle is a rock * Bit 18 set = splash on impact with sea * Bit 19 set = bounce on hitting ground * Bit 20 set = apply gravity to particle * Bit 21 set = can destroy objects * Bit 23 set = splash size (big when set) * Bit 24 set = explode on hitting ground R8 Magnitude of the random element that's added to the velocity, with a larger figure giving a smaller random element; the actual range is +/- 2^(32 - R8) R9 Magnitude of the random element that's added to the particle lifespan, with a larger figure giving a smaller random element; the actual range is 0 to 2^(32 - R9) Stack Contains a value to restore into R8 at the end, and the return address
.AddStaticParticleToBuffer MOV R4, #0 \ Set R4 = 0 \ Fall into AddRisingParticleToBuffer so we \ pass a velocity of (0, 0, 0) to the \ AddMovingParticleToBuffer routine
Name: AddRisingParticleToBuffer [Show more] Type: Subroutine Category: Particles Summary: Add a particle to the particle data buffer that initially drifts up, with a random element to its velocity and lifespan counter Deep dive: Particles and particle clouds
Context: See this subroutine on its own page References: This subroutine is called as follows: * AddSmokeParticleToBuffer calls AddRisingParticleToBuffer

Arguments: (R0, R1, R2) Particle coordinate R4 Particle velocity in the y-axis (up-down) R6 Particle lifespan counter (i.e. how many iterations around the main loop before the particle expires) R7 Particle flags: * Bits 0-7 = particle colour * Bit 16 set = colour fades white to red * Bit 17 set = particle is a rock * Bit 18 set = splash on impact with sea * Bit 19 set = bounce on hitting ground * Bit 20 set = apply gravity to particle * Bit 21 set = can destroy objects * Bit 23 set = splash size (big when set) * Bit 24 set = explode on hitting ground R8 Magnitude of the random element that's added to the velocity, with a larger figure giving a smaller random element; the actual range is +/- 2^(32 - R8) R9 Magnitude of the random element that's added to the particle lifespan, with a larger figure giving a smaller random element; the actual range is 0 to 2^(32 - R9) Stack Contains a value to restore into R8 at the end, and the return address
.AddRisingParticleToBuffer MOV R3, #0 \ Set R3 = 0 MOV R5, #0 \ Set R5 = 0 \ Fall into AddMovingParticleToBuffer with \ a velocity of (0, R4, 0)
Name: AddMovingParticleToBuffer [Show more] Type: Subroutine Category: Particles Summary: Add a moving particle to the particle data buffer, adding a random element to its velocity and lifespan counter Deep dive: Particles and particle clouds
Context: See this subroutine on its own page References: This subroutine is called as follows: * AddExhaustParticleToBuffer calls AddMovingParticleToBuffer

Arguments: (R0, R1, R2) Particle coordinate (R3, R4, R5) Particle velocity R6 Particle lifespan counter (i.e. how many iterations around the main loop before the particle expires) R7 Particle flags: * Bits 0-7 = particle colour * Bit 16 set = colour fades white to red * Bit 17 set = particle is a rock * Bit 18 set = splash on impact with sea * Bit 19 set = bounce on hitting ground * Bit 20 set = apply gravity to particle * Bit 21 set = can destroy objects * Bit 23 set = splash size (big when set) * Bit 24 set = explode on hitting ground R8 Magnitude of the random element that's added to the velocity, with a larger figure giving a smaller random element; the actual range is +/- 2^(32 - R8) R9 Magnitude of the random element that's added to the particle lifespan, with a larger figure giving a smaller random element; the actual range is 0 to 2^(32 - R9) Stack Contains a value to restore into R8 at the end, and the return address
.AddMovingParticleToBuffer STMFD R13!, {R0-R1} \ Store R0 and R1 on the stack so we can \ restore them after the following \ We now add a random signed element to the \ particle velocity in (R3, R4, R5), with \ the scale of the random element determined \ by R8 (so a larger R8 adds a smaller \ random element to the velocity) BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R3, R3, R0, ASR R8 \ Set R3 = R3 + R0 >> R8 \ \ We keep the sign in R0, so this can either \ increase or decrease R3 BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R4, R4, R0, ASR R8 \ Set R4 = R4 + R0 >> R8 \ \ We keep the sign in R0, so this can either \ increase or decrease R4 BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R5, R5, R0, ASR R8 \ Set R5 = R5 + R0 >> R8 \ \ We keep the sign in R0, so this can either \ increase or decrease R5 \ We now add a random positive element to \ the particle's lifespan counter in R6, \ with the scale determined by R9 (so a \ larger R9 adds a smaller random element \ to the particle's lifespan) BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R6, R6, R0, LSR R9 \ Set R6 = R6 + R0 >> R9 \ \ We do not keep the sign in R0, so the \ addition is always positive LDMFD R13!, {R0-R1} \ Retrieve the values of R0 and R1 that we \ stored above