From ca30cf711fe2c080d962c15805bf77da5916a30f Mon Sep 17 00:00:00 2001 From: Primekick Date: Wed, 27 Nov 2024 12:58:50 +0100 Subject: [PATCH] Implement recalculating autotiles D --- src/game_map.cpp | 15 ++----- src/game_map.h | 3 +- src/map_data.h | 11 +++++ src/spriteset_map.cpp | 2 - src/tilemap_layer.cpp | 95 ++++++++++++++++++++++++++++++++++++++++++- src/tilemap_layer.h | 6 +++ 6 files changed, 116 insertions(+), 16 deletions(-) diff --git a/src/game_map.cpp b/src/game_map.cpp index 8c24a75a5b..6efd76765f 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -1879,17 +1879,10 @@ int Game_Map::SubstituteUp(int old_id, int new_id) { return DoSubstitute(map_info.upper_tiles, old_id, new_id); } -static void DoReplaceAt(std::vector& layer, int x, int y, int new_id, int map_width) { - auto pos = x + y * map_width; - layer[pos] = static_cast(new_id); -} - -void Game_Map::ReplaceDownAt(int x, int y, int new_id) { - DoReplaceAt(map->lower_layer, x, y, new_id, map->width); -} - -void Game_Map::ReplaceUpAt(int x, int y, int new_id) { - DoReplaceAt(map->upper_layer, x, y, new_id, map->width); +void Game_Map::ReplaceTileAt(int x, int y, int new_id, int layer) { + auto pos = x + y * map->width; + auto& layer_vec = layer >= 1 ? map->upper_layer : map->lower_layer; + layer_vec[pos] = static_cast(new_id); } std::string Game_Map::ConstructMapName(int map_id, bool is_easyrpg) { diff --git a/src/game_map.h b/src/game_map.h index 609ee8bc52..95d6ce5389 100644 --- a/src/game_map.h +++ b/src/game_map.h @@ -641,8 +641,7 @@ namespace Game_Map { Game_Vehicle* GetVehicle(Game_Vehicle::Type which); int SubstituteDown(int old_id, int new_id); int SubstituteUp(int old_id, int new_id); - void ReplaceDownAt(int x, int y, int new_id); - void ReplaceUpAt(int x, int y, int new_id); + void ReplaceTileAt(int x, int y, int new_id, int layer); /** * Checks if its possible to step onto the tile at (x,y) diff --git a/src/map_data.h b/src/map_data.h index dee851bf25..0749d4b546 100644 --- a/src/map_data.h +++ b/src/map_data.h @@ -59,6 +59,17 @@ static constexpr int NUM_LOWER_TILES = BLOCK_F_INDEX; static constexpr int NUM_UPPER_TILES = BLOCK_F_TILES; static constexpr int NUM_TILES = NUM_LOWER_TILES + NUM_UPPER_TILES; +// Bit positions for neighbors +static constexpr uint8_t NEIGHBOR_NW = 0x80; // 0b10000000 +static constexpr uint8_t NEIGHBOR_N = 0x40; // 0b01000000 +static constexpr uint8_t NEIGHBOR_NE = 0x20; // 0b00100000 +static constexpr uint8_t NEIGHBOR_W = 0x10; // 0b00010000 +static constexpr uint8_t NEIGHBOR_E = 0x08; // 0b00001000 +static constexpr uint8_t NEIGHBOR_SW = 0x04; // 0b00000100 +static constexpr uint8_t NEIGHBOR_S = 0x02; // 0b00000010 +static constexpr uint8_t NEIGHBOR_SE = 0x01; // 0b00000001 + + /** Passability flags. */ namespace Passable { enum Passable { diff --git a/src/spriteset_map.cpp b/src/spriteset_map.cpp index 4d1a0331b6..370817e6cd 100644 --- a/src/spriteset_map.cpp +++ b/src/spriteset_map.cpp @@ -176,13 +176,11 @@ void Spriteset_Map::SubstituteUp(int old_id, int new_id) { void Spriteset_Map::ReplaceDownAt(int x, int y, int tile_index, bool disable_autotile) { auto tile_id = IndexToChipId(tile_index); - Game_Map::ReplaceDownAt(x, y, tile_id); tilemap->SetMapTileDataDownAt(x, y, tile_id, disable_autotile); } void Spriteset_Map::ReplaceUpAt(int x, int y, int tile_index) { auto tile_id = IndexToChipId(tile_index); - Game_Map::ReplaceUpAt(x, y, tile_id); tilemap->SetMapTileDataUpAt(x, y, tile_id); } diff --git a/src/tilemap_layer.cpp b/src/tilemap_layer.cpp index a871903058..c0f313a317 100644 --- a/src/tilemap_layer.cpp +++ b/src/tilemap_layer.cpp @@ -717,15 +717,108 @@ void TilemapLayer::SetMapData(std::vector nmap_data) { } void TilemapLayer::SetMapTileDataAt(int x, int y, int tile_id, bool disable_autotile) { + if(!IsInMapBounds(x, y)) + return; + substitutions = Game_Map::GetTilesLayer(layer); if (disable_autotile) { + map_data[x + y * width] = static_cast(tile_id); + Game_Map::ReplaceTileAt(x, y, tile_id, layer); CreateTileCacheAt(x, y, tile_id); } else { - // TODO: handle autotiles + // Recalculate the replaced tile itself + every neighboring tile + static constexpr struct { int dx; int dy; } adjacent[8] = { + {-1, -1}, { 0, -1}, { 1, -1}, + {-1, 0}, { 1, 0}, + {-1, 1}, { 0, 1}, { 1, 1} + }; + + RecalculateAutotile(x, y, tile_id); + for (const auto& adj : adjacent) { + auto nx = x + adj.dx; + auto ny = y + adj.dy; + if (IsInMapBounds(nx, ny)) { + RecalculateAutotile(nx, ny, GetDataCache(nx, ny).ID); + } + } } + + SetMapData(map_data); +} + +static inline bool IsAutotileAB(int tile_id) { + return tile_id >= BLOCK_A && tile_id < BLOCK_C; +} + +static inline bool IsAutotileD(int tile_id) { + return tile_id >= BLOCK_D && tile_id < BLOCK_E; } +static inline bool IsSameAutotileD(int current_tile_id, int neighbor_tile_id) { + return ChipIdToIndex(current_tile_id) == ChipIdToIndex(neighbor_tile_id); +} + +static inline void ApplyCornerFixups(uint8_t& neighbors) { + // Northwest corner + if ((neighbors & NEIGHBOR_NW) && (neighbors & (NEIGHBOR_N | NEIGHBOR_W)) != (NEIGHBOR_N | NEIGHBOR_W)) { + neighbors &= ~NEIGHBOR_NW; + } + + // Northeast corner + if ((neighbors & NEIGHBOR_NE) && (neighbors & (NEIGHBOR_N | NEIGHBOR_E)) != (NEIGHBOR_N | NEIGHBOR_E)) { + neighbors &= ~NEIGHBOR_NE; + } + + // Southwest corner + if ((neighbors & NEIGHBOR_SW) && (neighbors & (NEIGHBOR_S | NEIGHBOR_W)) != (NEIGHBOR_S | NEIGHBOR_W)) { + neighbors &= ~NEIGHBOR_SW; + } + + // Southeast corner + if ((neighbors & NEIGHBOR_SE) && (neighbors & (NEIGHBOR_S | NEIGHBOR_E)) != (NEIGHBOR_S | NEIGHBOR_E)) { + neighbors &= ~NEIGHBOR_SE; + } +} + +void TilemapLayer::RecalculateAutotile(int x, int y, int tile_id) { + // TODO: make it work for AB autotiles + if (IsAutotileAB(tile_id)) { + Output::Warning("Maniac Patch: Command RewriteMap is only partially supported."); + return; + } + + if (!IsAutotileD(tile_id)) { + return; + } + + const int block = (tile_id - BLOCK_D) / BLOCK_D_STRIDE; + uint8_t neighbors = 0; + + // Get all neighboring tiles in a single pass + static constexpr struct { int dx; int dy; uint8_t bit; } adjacent[8] = { + {-1, -1, NEIGHBOR_NW}, { 0, -1, NEIGHBOR_N}, { 1, -1, NEIGHBOR_NE}, + {-1, 0, NEIGHBOR_W }, { 1, 0, NEIGHBOR_E}, + {-1, 1, NEIGHBOR_SW}, { 0, 1, NEIGHBOR_S}, { 1, 1, NEIGHBOR_SE} + }; + + // Build the neighbors mask and fixup corners + for (const auto& adj : adjacent) { + auto nx = x + adj.dx; + auto ny = y + adj.dy; + auto adj_tile_id = IsInMapBounds(nx, ny) ? GetDataCache(nx, ny).ID : tile_id; + if (IsSameAutotileD(tile_id, adj_tile_id)) { + neighbors |= adj.bit; + } + } + ApplyCornerFixups(neighbors); + + // Recalculate tile id using the neighbors -> variant map + const int new_tile_id = BLOCK_D + block * BLOCK_D_STRIDE + AUTOTILE_VARIANTS_MAP.at(neighbors); + map_data[x + y * width] = static_cast(new_tile_id); + Game_Map::ReplaceTileAt(x, y, new_tile_id, layer); + CreateTileCacheAt(x, y, tile_id); +} void TilemapLayer::SetPassable(std::vector npassable) { passable = std::move(npassable); diff --git a/src/tilemap_layer.h b/src/tilemap_layer.h index 08a2601e4a..91a8adfc80 100644 --- a/src/tilemap_layer.h +++ b/src/tilemap_layer.h @@ -121,6 +121,7 @@ class TilemapLayer { void GenerateAutotileD(short ID); void DrawTile(Bitmap& dst, Bitmap& tile, Bitmap& tone_tile, int x, int y, int row, int col, uint32_t tone_hash, bool allow_fast_blit = true); void DrawTileImpl(Bitmap& dst, Bitmap& tile, Bitmap& tone_tile, int x, int y, int row, int col, uint32_t tone_hash, ImageOpacity op, bool allow_fast_blit); + void RecalculateAutotile(int x, int y, int tile_id); static const int TILES_PER_ROW = 64; @@ -163,6 +164,8 @@ class TilemapLayer { TilemapSubLayer upper_layer; Tone tone; + + bool IsInMapBounds(int x, int y) const; }; inline BitmapRef const& TilemapLayer::GetChipset() const { @@ -261,5 +264,8 @@ inline TilemapLayer::TileData& TilemapLayer::GetDataCache(int x, int y) { return data_cache_vec[x + y * width]; } +inline bool TilemapLayer::IsInMapBounds(int x, int y) const { + return x >= 0 && x < width && y >= 0 && y < height; +} #endif