static sint32 map_get_corner_height(sint32 x, sint32 y, sint32 corner) { rct_map_element *mapElement = map_get_surface_element_at(x, y); sint32 baseHeight = mapElement->base_height; sint32 slope = mapElement->properties.surface.slope; sint32 doubleCorner = slope & 16; if (doubleCorner) { if (!(slope & 1)) doubleCorner = 4; else if (!(slope & 2)) doubleCorner = 8; else if (!(slope & 4)) doubleCorner = 1; else if (!(slope & 8)) doubleCorner = 2; } switch (corner) { case 0: return baseHeight + ((slope & 1) ? (doubleCorner == 1 ? 4 : 2) : 0); case 1: return baseHeight + ((slope & 8) ? (doubleCorner == 8 ? 4 : 2) : 0); case 2: return baseHeight + ((slope & 2) ? (doubleCorner == 2 ? 4 : 2) : 0); case 3: return baseHeight + ((slope & 4) ? (doubleCorner == 4 ? 4 : 2) : 0); default: return baseHeight; } }
int map_get_corner_height(int x, int y, int corner) { rct_map_element *mapElement = map_get_surface_element_at(x, y); int baseHeight = mapElement->base_height; int slope = mapElement->properties.surface.slope; int doubleCorner = slope & 16; if (doubleCorner) { if (!(slope & 1)) doubleCorner = 4; else if (!(slope & 2)) doubleCorner = 8; else if (!(slope & 4)) doubleCorner = 1; else if (!(slope & 8)) doubleCorner = 2; } switch (corner) { case 0: return baseHeight + (slope & 1 ? (doubleCorner == 1 ? 4 : 2) : 0); case 1: return baseHeight + (slope & 8 ? (doubleCorner == 8 ? 4 : 2) : 0); case 2: return baseHeight + (slope & 2 ? (doubleCorner == 2 ? 4 : 2) : 0); case 3: return baseHeight + (slope & 4 ? (doubleCorner == 4 ? 4 : 2) : 0); default: return baseHeight; } }
/** * Sets the height of the actual game map tiles to the height map. */ static void mapgen_set_height() { sint32 x, y, heightX, heightY, mapSize; rct_map_element *mapElement; mapSize = _heightSize / 2; for (y = 1; y < mapSize - 1; y++) { for (x = 1; x < mapSize - 1; x++) { heightX = x * 2; heightY = y * 2; uint8 q00 = get_height(heightX + 0, heightY + 0); uint8 q01 = get_height(heightX + 0, heightY + 1); uint8 q10 = get_height(heightX + 1, heightY + 0); uint8 q11 = get_height(heightX + 1, heightY + 1); uint8 baseHeight = (q00 + q01 + q10 + q11) / 4; mapElement = map_get_surface_element_at(x, y); mapElement->base_height = max(2, baseHeight * 2); mapElement->clearance_height = mapElement->base_height; if (q00 > baseHeight) mapElement->properties.surface.slope |= 4; if (q01 > baseHeight) mapElement->properties.surface.slope |= 8; if (q10 > baseHeight) mapElement->properties.surface.slope |= 2; if (q11 > baseHeight) mapElement->properties.surface.slope |= 1; } } }
/** * Sets each tile's water level to the specified water level if underneath that water level. */ static void mapgen_set_water_level(int waterLevel) { int x, y, mapSize; rct_map_element *mapElement; mapSize = RCT2_GLOBAL(RCT2_ADDRESS_MAP_SIZE, sint16); for (y = 1; y < mapSize - 1; y++) { for (x = 1; x < mapSize - 1; x++) { mapElement = map_get_surface_element_at(x, y); if (mapElement->base_height < waterLevel) mapElement->properties.surface.terrain |= (waterLevel / 2); } } }
/** * Sets each tile's water level to the specified water level if underneath that water level. */ static void mapgen_set_water_level(sint32 waterLevel) { sint32 x, y, mapSize; rct_map_element *mapElement; mapSize = gMapSize; for (y = 1; y < mapSize - 1; y++) { for (x = 1; x < mapSize - 1; x++) { mapElement = map_get_surface_element_at(x, y); if (mapElement->base_height < waterLevel) mapElement->properties.surface.terrain |= (waterLevel / 2); } } }
void mapgen_generate_blank(mapgen_settings *settings) { sint32 x, y; rct_map_element *mapElement; map_clear_all_elements(); map_init(settings->mapSize); for (y = 1; y < settings->mapSize - 1; y++) { for (x = 1; x < settings->mapSize - 1; x++) { mapElement = map_get_surface_element_at(x, y); map_element_set_terrain(mapElement, settings->floor); map_element_set_terrain_edge(mapElement, settings->wall); mapElement->base_height = settings->height; mapElement->clearance_height = settings->height; } } mapgen_set_water_level(settings->waterLevel); }
static void cheat_set_grass_length(int length) { int x, y; rct_map_element *mapElement; for (y = 0; y < 256; y++) { for (x = 0; x < 256; x++) { mapElement = map_get_surface_element_at(x, y); if (!(mapElement->properties.surface.ownership & OWNERSHIP_OWNED)) continue; if (map_element_get_terrain(mapElement) != TERRAIN_GRASS) continue; if ((mapElement->properties.surface.terrain & 0x1F) > 0) continue; mapElement->properties.surface.grass_length = length; } } gfx_invalidate_screen(); }
static void cheat_set_grass_length(sint32 length) { sint32 x, y; rct_tile_element *tileElement; for (y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++) { for (x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++) { tileElement = map_get_surface_element_at(x, y); if (!(tileElement->properties.surface.ownership & OWNERSHIP_OWNED)) continue; if (tile_element_get_terrain(tileElement) != TERRAIN_GRASS) continue; if (map_get_water_height(tileElement) > 0) continue; tileElement->properties.surface.grass_length = length; } } gfx_invalidate_screen(); }
/** * Returns the audio parameters to use when playing the specified sound at a virtual location. * @param soundId The sound effect to be played. * @param location The location at which the sound effect is to be played. * @return The audio parameters to be used when playing this sound effect. */ rct_audio_params audio_get_params_from_location(int soundId, const rct_xyz16 *location) { int volumeDown = 0; rct_audio_params params; params.in_range = true; params.volume = 0; params.pan = 0; rct_map_element *element = map_get_surface_element_at(location->x / 32, location->y / 32); if (element && (element->base_height * 8) - 5 > location->z) volumeDown = 10; uint8 rotation = get_current_rotation(); rct_xy16 pos2 = coordinate_3d_to_2d(location, rotation); rct_window *window = RCT2_GLOBAL(RCT2_ADDRESS_NEW_WINDOW_PTR, rct_window*); while (true) { window--; if (window < RCT2_ADDRESS(RCT2_ADDRESS_WINDOW_LIST, rct_window)) break; rct_viewport *viewport = window->viewport; if (!viewport || !(viewport->flags & VIEWPORT_FLAG_SOUND_ON)) continue; sint16 vy = pos2.y - viewport->view_y; sint16 vx = pos2.x - viewport->view_x; params.pan = viewport->x + (vx >> viewport->zoom); params.volume = RCT2_ADDRESS(0x0099282C, int)[soundId] + ((-1024 * viewport->zoom - 1) << volumeDown) + 1; if (vy < 0 || vy >= viewport->view_height || vx < 0 || vx >= viewport->view_width || params.volume < -10000) { params.in_range = false; return params; } } return params; }
/** * Randomly places a selection of preset trees on the map. Picks the right tree for the terrain it is placing it on. */ static void mapgen_place_trees() { sint32 numGrassTreeIds = 0, numDesertTreeIds = 0, numSnowTreeIds = 0; sint32 *grassTreeIds = (sint32*)malloc(countof(GrassTrees) * sizeof(sint32)); sint32 *desertTreeIds = (sint32*)malloc(countof(DesertTrees) * sizeof(sint32)); sint32 *snowTreeIds = (sint32*)malloc(countof(SnowTrees) * sizeof(sint32)); for (sint32 i = 0; i < object_entry_group_counts[OBJECT_TYPE_SMALL_SCENERY]; i++) { rct_scenery_entry *sceneryEntry = get_small_scenery_entry(i); rct_object_entry_extended *entry = &object_entry_groups[OBJECT_TYPE_SMALL_SCENERY].entries[i]; if (sceneryEntry == (rct_scenery_entry*)-1 || sceneryEntry == NULL) continue; sint32 j; for (j = 0; j < countof(GrassTrees); j++) if (strncmp(GrassTrees[j], entry->name, 8) == 0) break; if (j != countof(GrassTrees)) { grassTreeIds[numGrassTreeIds++] = i; continue; } for (j = 0; j < countof(DesertTrees); j++) if (strncmp(DesertTrees[j], entry->name, 8) == 0) break; if (j != countof(DesertTrees)) { desertTreeIds[numDesertTreeIds++] = i; continue; } for (j = 0; j < countof(SnowTrees); j++) if (strncmp(SnowTrees[j], entry->name, 8) == 0) break; if (j != countof(SnowTrees)) { snowTreeIds[numSnowTreeIds++] = i; continue; } } sint32 availablePositionsCount = 0; struct { sint32 x; sint32 y; } tmp, *pos, *availablePositions; availablePositions = malloc(256 * 256 * sizeof(tmp)); // Create list of available tiles for (sint32 y = 1; y < gMapSize - 1; y++) { for (sint32 x = 1; x < gMapSize - 1; x++) { rct_map_element *mapElement = map_get_surface_element_at(x, y); // Exclude water tiles if ((mapElement->properties.surface.terrain & 0x1F) != 0) continue; pos = &availablePositions[availablePositionsCount++]; pos->x = x; pos->y = y; } } // Shuffle list for (sint32 i = 0; i < availablePositionsCount; i++) { sint32 rindex = util_rand() % availablePositionsCount; if (rindex == i) continue; tmp = availablePositions[i]; availablePositions[i] = availablePositions[rindex]; availablePositions[rindex] = tmp; } // Place trees float treeToLandRatio = (10 + (util_rand() % 30)) / 100.0f; sint32 numTrees = max(4, (sint32)(availablePositionsCount * treeToLandRatio)); for (sint32 i = 0; i < numTrees; i++) { pos = &availablePositions[i]; sint32 type = -1; rct_map_element *mapElement = map_get_surface_element_at(pos->x, pos->y); switch (map_element_get_terrain(mapElement)) { case TERRAIN_GRASS: case TERRAIN_DIRT: case TERRAIN_GRASS_CLUMPS: if (numGrassTreeIds == 0) break; type = grassTreeIds[util_rand() % numGrassTreeIds]; break; case TERRAIN_SAND: case TERRAIN_SAND_DARK: case TERRAIN_SAND_LIGHT: if (numDesertTreeIds == 0) break; if (util_rand() % 4 == 0) type = desertTreeIds[util_rand() % numDesertTreeIds]; break; case TERRAIN_ICE: if (numSnowTreeIds == 0) break; type = snowTreeIds[util_rand() % numSnowTreeIds]; break; } if (type != -1) mapgen_place_tree(type, pos->x, pos->y); } free(availablePositions); free(grassTreeIds); free(desertTreeIds); free(snowTreeIds); }
void mapgen_generate(mapgen_settings *settings) { sint32 x, y, mapSize, floorTexture, wallTexture, waterLevel; rct_map_element *mapElement; util_srand((sint32)SDL_GetTicks()); mapSize = settings->mapSize; floorTexture = settings->floor; wallTexture = settings->wall; waterLevel = settings->waterLevel; if (floorTexture == -1) floorTexture = BaseTerrain[util_rand() % countof(BaseTerrain)]; if (wallTexture == -1) { // Base edge type on surface type switch (floorTexture) { case TERRAIN_DIRT: wallTexture = TERRAIN_EDGE_WOOD_RED; break; case TERRAIN_ICE: wallTexture = TERRAIN_EDGE_ICE; break; default: wallTexture = TERRAIN_EDGE_ROCK; break; } } map_clear_all_elements(); // Initialise the base map map_init(mapSize); for (y = 1; y < mapSize - 1; y++) { for (x = 1; x < mapSize - 1; x++) { mapElement = map_get_surface_element_at(x, y); map_element_set_terrain(mapElement, floorTexture); map_element_set_terrain_edge(mapElement, wallTexture); mapElement->base_height = settings->height; mapElement->clearance_height = settings->height; } } // Create the temporary height map and initialise _heightSize = mapSize * 2; _height = (uint8*)malloc(_heightSize * _heightSize * sizeof(uint8)); memset(_height, 0, _heightSize * _heightSize * sizeof(uint8)); if (1) { mapgen_simplex(settings); mapgen_smooth_height(2 + (util_rand() % 6)); } else { // Keep overwriting the map with rough cicular blobs of different sizes and heights. // This procedural method can produce intersecting contour like land and lakes. // Large blobs, general shape of map mapgen_blobs(6, _heightSize / 2, _heightSize * 4, 4, 16); // Medium blobs mapgen_blobs(12, _heightSize / 16, _heightSize / 8, 4, 18); // Small blobs, small hills and lakes mapgen_blobs(32, _heightSize / 32, _heightSize / 16, 4, 18); // Smooth the land so that their aren't cliffs round every blob. mapgen_smooth_height(2); } // Set the game map to the height map mapgen_set_height(); free(_height); // Set the tile slopes so that there are no cliffs while (map_smooth(1, 1, mapSize - 1, mapSize - 1)) { } // Add the water mapgen_set_water_level(waterLevel); // Add sandy beaches sint32 beachTexture = floorTexture; if (settings->floor == -1 && floorTexture == TERRAIN_GRASS) { switch (util_rand() % 4) { case 0: beachTexture = TERRAIN_SAND; break; case 1: beachTexture = TERRAIN_SAND_LIGHT; break; } } for (y = 1; y < mapSize - 1; y++) { for (x = 1; x < mapSize - 1; x++) { mapElement = map_get_surface_element_at(x, y); if (mapElement->base_height < waterLevel + 6) map_element_set_terrain(mapElement, beachTexture); } } // Place the trees if (settings->trees != 0) mapgen_place_trees(); map_reorganise_elements(); }
/** * Return the absolute height of an element, given its (x,y) coordinates * * rct2: 0x00662783 */ int map_element_height(int x, int y) { rct_map_element *mapElement; // Off the map if (x >= 8192 || y >= 8192) return 16; // Truncate subtile coordinates int x_tile = x & 0xFFFFFFE0; int y_tile = y & 0xFFFFFFE0; // Get the surface element for the tile mapElement = map_get_surface_element_at(x_tile / 32, y_tile / 32); uint32 height = ((mapElement->properties.surface.terrain & MAP_ELEMENT_WATER_HEIGHT_MASK) << 20) | (mapElement->base_height << 3); uint32 slope = (mapElement->properties.surface.slope & MAP_ELEMENT_SLOPE_MASK); uint8 extra_height = (slope & 0x10) >> 4; // 0x10 is the 5th bit - sets slope to double height // Remove the extra height bit slope &= 0xF; uint8 quad, quad_extra; // which quadrant the element is in? // quad_extra is for extra height tiles uint8 xl, yl; // coordinates across this tile uint8 TILE_SIZE = 31; xl = x & 0x1f; yl = y & 0x1f; // Slope logic: // Each of the four bits in slope represents that corner being raised // slope == 15 (all four bits) is not used and slope == 0 is flat // If the extra_height bit is set, then the slope goes up two z-levels // We arbitrarily take the SW corner to be closest to the viewer // One corner up if ((slope == 1) || (slope == 2) || (slope == 4) || (slope == 8)) { switch (slope) { case 1: // NE corner up quad = xl + yl - TILE_SIZE; break; case 2: // SE corner up quad = xl - yl; break; case 4: // SW corner up quad = TILE_SIZE - yl - xl; break; case 8: // NW corner up quad = xl - yl; break; } // If the element is in the quadrant with the slope, raise its height if (quad > 0) { height += quad / 2; } } // One side up switch (slope) { case 3: // E side up height += xl / 2; break; case 6: // S side up height += (TILE_SIZE - yl) / 2; break; case 9: // N side up height += yl / 2; height++; break; case 12: // W side up height += (TILE_SIZE - xl) / 2; break; } // One corner down if ((slope == 7) || (slope == 11) || (slope == 13) || (slope == 14)) { switch (slope) { case 7: // NW corner down quad_extra = xl + TILE_SIZE - yl; quad = xl - yl; break; case 11: // SW corner down quad_extra = xl + yl; quad = xl + yl - TILE_SIZE; break; case 13: // SE corner down quad_extra = TILE_SIZE - xl + yl; quad = xl - yl; break; case 14: // NE corner down quad_extra = (TILE_SIZE - xl) + (TILE_SIZE - yl); quad = TILE_SIZE - yl - xl; break; } if (extra_height) { height += quad_extra / 2; height++; return height; } // This tile is essentially at the next height level height += 0x10; // so we move *down* the slope if (quad < 0) { height += quad / 2; height += 0xFF00; } } // Valleys if ((slope == 5) || (slope == 10)) { switch (slope) { case 5: // NW-SE valley if (xl + yl <= TILE_SIZE + 1) { return height; } quad = TILE_SIZE - xl - yl; break; case 10: // NE-SW valley quad = xl - yl; break; } if (quad > 0) { height += quad / 2; } } return height; }
/** * Not perfect, this still leaves some particular tiles unsmoothed. */ sint32 map_smooth(sint32 l, sint32 t, sint32 r, sint32 b) { sint32 i, x, y, highest, count, cornerHeights[4], doubleCorner, raisedLand = 0; rct_map_element *mapElement, *mapElement2; for (y = t; y < b; y++) { for (x = l; x < r; x++) { mapElement = map_get_surface_element_at(x, y); mapElement->properties.surface.slope &= ~0x1F; // Raise to edge height - 2 highest = mapElement->base_height; highest = max(highest, map_get_surface_element_at(x - 1, y + 0)->base_height); highest = max(highest, map_get_surface_element_at(x + 1, y + 0)->base_height); highest = max(highest, map_get_surface_element_at(x + 0, y - 1)->base_height); highest = max(highest, map_get_surface_element_at(x + 0, y + 1)->base_height); if (mapElement->base_height < highest - 2) { raisedLand = 1; mapElement->base_height = mapElement->clearance_height = highest - 2; } // Check corners doubleCorner = -1; cornerHeights[0] = map_get_surface_element_at(x - 1, y - 1)->base_height; cornerHeights[1] = map_get_surface_element_at(x + 1, y - 1)->base_height; cornerHeights[2] = map_get_surface_element_at(x + 1, y + 1)->base_height; cornerHeights[3] = map_get_surface_element_at(x - 1, y + 1)->base_height; highest = mapElement->base_height; for (i = 0; i < 4; i++) highest = max(highest, cornerHeights[i]); if (highest >= mapElement->base_height + 4) { count = 0; sint32 canCompensate = 1; for (i = 0; i < 4; i++) if (cornerHeights[i] == highest){ count++; // Check if surrounding corners aren't too high. The current tile // can't compensate for all the height differences anymore if it has // the extra height slope. sint32 highestOnLowestSide; switch (i){ default: case 0: highestOnLowestSide = max( map_get_surface_element_at(x + 1, y)->base_height, map_get_surface_element_at(x, y + 1)->base_height); break; case 1: highestOnLowestSide = max( map_get_surface_element_at(x - 1, y)->base_height, map_get_surface_element_at(x, y + 1)->base_height); break; case 2: highestOnLowestSide = max( map_get_surface_element_at(x - 1, y)->base_height, map_get_surface_element_at(x, y - 1)->base_height); break; case 3: highestOnLowestSide = max( map_get_surface_element_at(x + 1, y)->base_height, map_get_surface_element_at(x, y - 1)->base_height); break; } if (highestOnLowestSide > mapElement->base_height){ mapElement->base_height = mapElement->clearance_height = highestOnLowestSide; raisedLand = 1; canCompensate = 0; } } if (count == 1 && canCompensate) { if (mapElement->base_height < highest - 4) { mapElement->base_height = mapElement->clearance_height = highest - 4; raisedLand = 1; } if (cornerHeights[0] == highest && cornerHeights[2] <= cornerHeights[0] - 4) doubleCorner = 0; else if (cornerHeights[1] == highest && cornerHeights[3] <= cornerHeights[1] - 4) doubleCorner = 1; else if (cornerHeights[2] == highest && cornerHeights[0] <= cornerHeights[2] - 4) doubleCorner = 2; else if (cornerHeights[3] == highest && cornerHeights[1] <= cornerHeights[3] - 4) doubleCorner = 3; } else { if (mapElement->base_height < highest - 2) { mapElement->base_height = mapElement->clearance_height = highest - 2; raisedLand = 1; } } } if (doubleCorner != -1) { mapElement->properties.surface.slope |= 16; switch (doubleCorner) { case 0: mapElement->properties.surface.slope |= 2 | 4 | 8; break; case 1: mapElement->properties.surface.slope |= 1 | 2 | 4; break; case 2: mapElement->properties.surface.slope |= 1 | 2 | 8; break; case 3: mapElement->properties.surface.slope |= 1 | 4 | 8; break; } } else { // Corners mapElement2 = map_get_surface_element_at(x + 1, y + 1); if (mapElement2->base_height > mapElement->base_height) mapElement->properties.surface.slope |= 1; mapElement2 = map_get_surface_element_at(x - 1, y + 1); if (mapElement2->base_height > mapElement->base_height) mapElement->properties.surface.slope |= 8; mapElement2 = map_get_surface_element_at(x + 1, y - 1); if (mapElement2->base_height > mapElement->base_height) mapElement->properties.surface.slope |= 2; mapElement2 = map_get_surface_element_at(x - 1, y - 1); if (mapElement2->base_height > mapElement->base_height) mapElement->properties.surface.slope |= 4; // Sides mapElement2 = map_get_surface_element_at(x + 1, y + 0); if (mapElement2->base_height > mapElement->base_height) mapElement->properties.surface.slope |= 1 | 2; mapElement2 = map_get_surface_element_at(x - 1, y + 0); if (mapElement2->base_height > mapElement->base_height) mapElement->properties.surface.slope |= 4 | 8; mapElement2 = map_get_surface_element_at(x + 0, y - 1); if (mapElement2->base_height > mapElement->base_height) mapElement->properties.surface.slope |= 2 | 4; mapElement2 = map_get_surface_element_at(x + 0, y + 1); if (mapElement2->base_height > mapElement->base_height) mapElement->properties.surface.slope |= 1 | 8; // Raise if (mapElement->properties.surface.slope == (1 | 2 | 4 | 8)) { mapElement->properties.surface.slope &= ~0x1F; mapElement->base_height = mapElement->clearance_height += 2; } } } } return raisedLand; }
/** * There are non-smoothed tiles with this version, but diagonal land blocks end up being wavy. */ static sint32 map_smooth_wavy(sint32 l, sint32 t, sint32 r, sint32 b) { sint32 i, x, y, highest, count, cornerHeights[4], doubleCorner, raisedLand = 0; rct_map_element *mapElement; for (y = t; y < b; y++) { for (x = l; x < r; x++) { mapElement = map_get_surface_element_at(x, y); mapElement->properties.surface.slope &= ~0x1F; // Raise to edge height - 2 highest = mapElement->base_height; highest = max(highest, map_get_surface_element_at(x - 1, y + 0)->base_height); highest = max(highest, map_get_surface_element_at(x + 1, y + 0)->base_height); highest = max(highest, map_get_surface_element_at(x + 0, y - 1)->base_height); highest = max(highest, map_get_surface_element_at(x + 0, y + 1)->base_height); if (mapElement->base_height < highest - 2) { raisedLand = 1; mapElement->base_height = mapElement->clearance_height = highest - 2; } // Check corners doubleCorner = -1; cornerHeights[0] = max(map_get_corner_height(x - 1, y - 1, 0), max(map_get_corner_height(x + 1, y + 0, 1), map_get_corner_height(x + 0, y + 1, 2))); cornerHeights[1] = max(map_get_corner_height(x + 1, y - 1, 1), max(map_get_corner_height(x - 1, y + 0, 0), map_get_corner_height(x + 0, y + 1, 3))); cornerHeights[2] = max(map_get_corner_height(x + 1, y + 1, 3), max(map_get_corner_height(x + 1, y + 0, 3), map_get_corner_height(x + 0, y - 1, 0))); cornerHeights[3] = max(map_get_corner_height(x - 1, y + 1, 2), max(map_get_corner_height(x - 1, y + 0, 2), map_get_corner_height(x + 0, y - 1, 1))); highest = mapElement->base_height; for (i = 0; i < 4; i++) highest = max(highest, cornerHeights[i]); if (highest >= mapElement->base_height + 4) { count = 0; for (i = 0; i < 4; i++) if (cornerHeights[i] == highest) count++; if (count == 1) { if (mapElement->base_height < highest - 4) { mapElement->base_height = mapElement->clearance_height = highest - 4; raisedLand = 1; } if (cornerHeights[0] == highest && cornerHeights[2] <= cornerHeights[0] - 4) doubleCorner = 0; else if (cornerHeights[1] == highest && cornerHeights[3] <= cornerHeights[1] - 4) doubleCorner = 1; else if (cornerHeights[2] == highest && cornerHeights[0] <= cornerHeights[2] - 4) doubleCorner = 2; else if (cornerHeights[3] == highest && cornerHeights[1] <= cornerHeights[3] - 4) doubleCorner = 3; } else { if (mapElement->base_height < highest - 2) { mapElement->base_height = mapElement->clearance_height = highest - 2; raisedLand = 1; } } } if (doubleCorner != -1) { mapElement->properties.surface.slope |= 16; switch (doubleCorner) { case 0: mapElement->properties.surface.slope |= 2 | 4 | 8; break; case 1: mapElement->properties.surface.slope |= 1 | 2 | 4; break; case 2: mapElement->properties.surface.slope |= 1 | 2 | 8; break; case 3: mapElement->properties.surface.slope |= 1 | 4 | 8; break; } } else { // Corners if ( map_get_corner_height(x + 1, y + 1, 3) > mapElement->base_height || map_get_corner_height(x + 1, y + 0, 1) > mapElement->base_height || map_get_corner_height(x + 0, y + 1, 2) > mapElement->base_height ) mapElement->properties.surface.slope |= 1; if ( map_get_corner_height(x - 1, y + 1, 2) > mapElement->base_height || map_get_corner_height(x - 1, y + 0, 0) > mapElement->base_height || map_get_corner_height(x + 0, y + 1, 3) > mapElement->base_height ) mapElement->properties.surface.slope |= 8; if ( map_get_corner_height(x + 1, y - 1, 1) > mapElement->base_height || map_get_corner_height(x + 1, y + 0, 3) > mapElement->base_height || map_get_corner_height(x + 0, y - 1, 0) > mapElement->base_height ) mapElement->properties.surface.slope |= 2; if ( map_get_corner_height(x - 1, y - 1, 0) > mapElement->base_height || map_get_corner_height(x - 1, y + 0, 2) > mapElement->base_height || map_get_corner_height(x + 0, y - 1, 1) > mapElement->base_height ) mapElement->properties.surface.slope |= 4; // Raise if (mapElement->properties.surface.slope == (1 | 2 | 4 | 8)) { mapElement->properties.surface.slope &= ~0x1F; mapElement->base_height = mapElement->clearance_height += 2; } } } } return raisedLand; }
void mapgen_generate_from_heightmap(mapgen_settings *settings) { openrct2_assert(_heightMapData.width == _heightMapData.height, "Invalid height map size"); openrct2_assert(_heightMapData.mono_bitmap != NULL, "No height map loaded"); openrct2_assert(settings->simplex_high != settings->simplex_low, "Low and high setting cannot be the same"); // Make a copy of the original height map that we can edit uint8 *dest = (uint8*)malloc(_heightMapData.width * _heightMapData.height); memcpy(dest, _heightMapData.mono_bitmap, _heightMapData.width * _heightMapData.width); map_init(_heightMapData.width + 2); // + 2 for the black tiles around the map if (settings->smooth_height_map) { mapgen_smooth_heightmap(dest, settings->smooth_strength); } uint8 maxValue = 255; uint8 minValue = 0; if (settings->normalize_height) { // Get highest and lowest pixel value maxValue = 0; minValue = 0xff; for (uint32 y = 0; y < _heightMapData.height; y++) { for (uint32 x = 0; x < _heightMapData.width; x++) { uint8 value = dest[x + y * _heightMapData.width]; maxValue = max(maxValue, value); minValue = min(minValue, value); } } if (minValue == maxValue) { window_error_open(STR_HEIGHT_MAP_ERROR, STR_ERROR_CANNOT_NORMALIZE); free(dest); return; } } openrct2_assert(maxValue > minValue, "Input range is invalid"); openrct2_assert(settings->simplex_high > settings->simplex_low, "Output range is invalid"); const uint8 rangeIn = maxValue - minValue; const uint8 rangeOut = settings->simplex_high - settings->simplex_low; for (uint32 y = 0; y < _heightMapData.height; y++) { for (uint32 x = 0; x < _heightMapData.width; x++) { // The x and y axis are flipped in the world, so this uses y for x and x for y. rct_map_element *const surfaceElement = map_get_surface_element_at(y + 1, x + 1); // Read value from bitmap, and convert its range uint8 value = dest[x + y * _heightMapData.width]; value = (uint8)((float)(value - minValue) / rangeIn * rangeOut) + settings->simplex_low; surfaceElement->base_height = value; // Floor to even number surfaceElement->base_height /= 2; surfaceElement->base_height *= 2; surfaceElement->clearance_height = surfaceElement->base_height; // Set water level if (surfaceElement->base_height < settings->water_level) { surfaceElement->properties.surface.terrain |= settings->water_level / 2; } } } // Smooth map if (settings->smooth) { // Keep smoothing the entire map until no tiles are changed anymore while (true) { uint32 numTilesChanged = 0; for (uint32 y = 1; y <= _heightMapData.height; y++) { for (uint32 x = 1; x <= _heightMapData.width; x++) { numTilesChanged += tile_smooth(x, y); } } if (numTilesChanged == 0) break; } } // Clean up free(dest); }
/** * Raises the corners based on the height offset of neighbour tiles. * This does not change the base height, unless all corners have been raised. * @returns 0 if no edits were made, 1 otherwise */ sint32 tile_smooth(sint32 x, sint32 y) { rct_map_element *const surfaceElement = map_get_surface_element_at(x, y); // +-----+-----+-----+ // | NW | N | NE | // | 2 | 1 | 0 | // +-----+-----+-----+ // | W | _ | E | // | 4 | | 3 | // +-----+-----+-----+ // | SW | S | SE | // | 7 | 6 | 5 | // +-----+-----+-----+ union { sint32 baseheight[8]; struct { sint32 NE, N, NW, E, W, SE, S, SW; }; } neighbourHeightOffset = { 0 }; // Find the neighbour base heights for (sint32 index = 0, y_offset = -1; y_offset <= 1; y_offset++) { for (sint32 x_offset = -1; x_offset <= 1; x_offset++) { // Skip self if (y_offset == 0 && x_offset == 0) continue; // Get neighbour height. If the element is not valid (outside of map) assume the same height rct_map_element *neighbour_element = map_get_surface_element_at(x + x_offset, y + y_offset); neighbourHeightOffset.baseheight[index] = neighbour_element ? neighbour_element->base_height : surfaceElement->base_height; // Make the height relative to the current surface element neighbourHeightOffset.baseheight[index] -= surfaceElement->base_height; index++; } } // Count number from the three tiles that is currently higher sint8 thresholdNW = clamp(neighbourHeightOffset.W, 0, 1) + clamp(neighbourHeightOffset.NW, 0, 1) + clamp(neighbourHeightOffset.N, 0, 1); sint8 thresholdNE = clamp(neighbourHeightOffset.N, 0, 1) + clamp(neighbourHeightOffset.NE, 0, 1) + clamp(neighbourHeightOffset.E, 0, 1); sint8 thresholdSE = clamp(neighbourHeightOffset.E, 0, 1) + clamp(neighbourHeightOffset.SE, 0, 1) + clamp(neighbourHeightOffset.S, 0, 1); sint8 thresholdSW = clamp(neighbourHeightOffset.S, 0, 1) + clamp(neighbourHeightOffset.SW, 0, 1) + clamp(neighbourHeightOffset.W, 0, 1); uint8 slope = 0; slope |= (thresholdNW >= 1) ? (1 << 1) : 0; slope |= (thresholdNE >= 1) ? (1 << 2) : 0; slope |= (thresholdSE >= 1) ? (1 << 3) : 0; slope |= (thresholdSW >= 1) ? (1 << 0) : 0; // Set diagonal when three corners have been raised, and the middle one can be raised one more if ((slope == 0b0111 && neighbourHeightOffset.NW >= 4) || (slope == 0b1011 && neighbourHeightOffset.SW >= 4) || (slope == 0b1101 && neighbourHeightOffset.SE >= 4) || (slope == 0b1110 && neighbourHeightOffset.NE >= 4)) { slope |= 1 << 4; } // Check if the calculated slope is the same already uint8 currentSlope = surfaceElement->properties.surface.slope & 0x1F; if (currentSlope == slope) { return 0; } // Remove old slope value surfaceElement->properties.surface.slope &= ~0x1F; if ((slope & 0xF) == 0xF) { // All corners are raised, raise the entire tile instead. surfaceElement->base_height = (surfaceElement->clearance_height += 2); uint8 waterHeight = map_get_water_height(surfaceElement) * 2; if (waterHeight <= surfaceElement->base_height) { surfaceElement->properties.surface.terrain &= ~MAP_ELEMENT_WATER_HEIGHT_MASK; } } else { // Apply the slope to this tile surfaceElement->properties.surface.slope |= slope; // Set correct clearance height if (slope & 0x10) surfaceElement->clearance_height = surfaceElement->base_height + 4; else if (slope & 0x0F) surfaceElement->clearance_height = surfaceElement->base_height + 2; } return 1; }
static money32 ParkEntrancePlace(sint32 flags, sint16 x, sint16 y, uint8 z, uint8 direction) { if (!(gScreenFlags & SCREEN_FLAGS_EDITOR) && !gCheatsSandboxMode) { return MONEY32_UNDEFINED; } gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LAND_PURCHASE; gCommandPosition.x = x; gCommandPosition.y = y; gCommandPosition.z = z * 16; if (!map_check_free_elements_and_reorganise(3)) { return MONEY32_UNDEFINED; } if (x <= 32 || y <= 32 || x >= (gMapSizeUnits - 32) || y >= (gMapSizeUnits - 32)) { gGameCommandErrorText = STR_TOO_CLOSE_TO_EDGE_OF_MAP; return MONEY32_UNDEFINED; } sint8 entranceNum = -1; for (uint8 i = 0; i < MAX_PARK_ENTRANCES; ++i) { if (gParkEntrances[i].x == MAP_LOCATION_NULL) { entranceNum = i; break; } } if (entranceNum == -1) { gGameCommandErrorText = STR_ERR_TOO_MANY_PARK_ENTRANCES; return MONEY32_UNDEFINED; } if (flags & GAME_COMMAND_FLAG_APPLY) { gParkEntrances[entranceNum].x = x; gParkEntrances[entranceNum].y = y; gParkEntrances[entranceNum].z = z * 16; gParkEntrances[entranceNum].direction = direction; } sint8 zLow = z * 2; sint8 zHigh = zLow + 12; for (uint8 index = 0; index < 3; index++) { if (index == 1) { x += TileDirectionDelta[(direction - 1) & 0x3].x; y += TileDirectionDelta[(direction - 1) & 0x3].y; } else if (index == 2) { x += TileDirectionDelta[(direction + 1) & 0x3].x * 2; y += TileDirectionDelta[(direction + 1) & 0x3].y * 2; } if (!gCheatsDisableClearanceChecks) { if (!map_can_construct_at(x, y, zLow, zHigh, 0xF)) { return MONEY32_UNDEFINED; } } // Check that entrance element does not already exist at this location rct_map_element* entranceElement = map_get_park_entrance_element_at(x, y, zLow, false); if (entranceElement != NULL) { return MONEY32_UNDEFINED; } if (!(flags & GAME_COMMAND_FLAG_APPLY)) { continue; } if (!(flags & GAME_COMMAND_FLAG_GHOST)) { rct_map_element* surfaceElement = map_get_surface_element_at(x / 32, y / 32); surfaceElement->properties.surface.ownership = 0; } rct_map_element* newElement = map_element_insert(x / 32, y / 32, zLow, 0xF); assert(newElement != NULL); newElement->clearance_height = zHigh; if (flags & GAME_COMMAND_FLAG_GHOST) { newElement->flags |= MAP_ELEMENT_FLAG_GHOST; } newElement->type = MAP_ELEMENT_TYPE_ENTRANCE; newElement->type |= direction; newElement->properties.entrance.index = index; newElement->properties.entrance.type = ENTRANCE_TYPE_PARK_ENTRANCE; newElement->properties.entrance.path_type = gFootpathSelectedId; if (!(flags & GAME_COMMAND_FLAG_GHOST)) { footpath_connect_edges(x, y, newElement, 1); } update_park_fences(x, y); update_park_fences(x - 32, y); update_park_fences(x + 32, y); update_park_fences(x, y - 32); update_park_fences(x, y + 32); map_invalidate_tile(x, y, newElement->base_height * 8, newElement->clearance_height * 8); if (index == 0) { map_animation_create(MAP_ANIMATION_TYPE_PARK_ENTRANCE, x, y, zLow); } } return 0; }