Skip to navigation

Landscape: GenerateLandscape

Name: GenerateLandscape [Show more] Type: Subroutine Category: Landscape Summary: Generate tile data for the landscape
Context: See this subroutine in context in the source code References: This subroutine is called as follows: * FinishLandscape calls GenerateLandscape * MainTitleLoop calls GenerateLandscape

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