From 1ffbec20904050d629ee8f2152df509f72f6fe7f Mon Sep 17 00:00:00 2001 From: Primekick Date: Tue, 26 Nov 2024 18:08:49 +0100 Subject: [PATCH 1/9] Add function for chip index -> chip id mapping --- src/map_data.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/map_data.h b/src/map_data.h index a4bede562e..dee851bf25 100644 --- a/src/map_data.h +++ b/src/map_data.h @@ -94,4 +94,26 @@ inline int ChipIdToIndex(int chip_id) { return 0; } +inline int IndexToChipId(int index) { + if (index >= BLOCK_A_INDEX && index < BLOCK_B_INDEX) { + return BLOCK_A + (index - BLOCK_A_INDEX) * BLOCK_A_STRIDE; + } + else if (index >= BLOCK_B_INDEX && index < BLOCK_C_INDEX) { + return BLOCK_B + (index - BLOCK_B_INDEX) * BLOCK_B_STRIDE; + } + else if (index >= BLOCK_C_INDEX && index < BLOCK_D_INDEX) { + return BLOCK_C + (index - BLOCK_C_INDEX) * BLOCK_C_STRIDE; + } + else if (index >= BLOCK_D_INDEX && index < BLOCK_E_INDEX) { + return BLOCK_D + (index - BLOCK_D_INDEX) * BLOCK_D_STRIDE; + } + else if (index >= BLOCK_E_INDEX && index < BLOCK_F_INDEX) { + return BLOCK_E + (index - BLOCK_E_INDEX) * BLOCK_E_STRIDE; + } + else if (index >= BLOCK_F_INDEX) { + return BLOCK_F + (index - BLOCK_F_INDEX) * BLOCK_F_STRIDE; + } + return 0; +} + #endif From a3be5eaa4e65ca00a4183916a6064cf28a51392b Mon Sep 17 00:00:00 2001 From: Primekick Date: Tue, 26 Nov 2024 20:02:24 +0100 Subject: [PATCH 2/9] Add functions to replace tile at given coordinates for both layers --- src/game_map.cpp | 13 +++++++++++++ src/game_map.h | 2 ++ 2 files changed, 15 insertions(+) diff --git a/src/game_map.cpp b/src/game_map.cpp index 46684779bf..8c24a75a5b 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -1879,6 +1879,19 @@ 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); +} + std::string Game_Map::ConstructMapName(int map_id, bool is_easyrpg) { std::stringstream ss; ss << "Map" << std::setfill('0') << std::setw(4) << map_id; diff --git a/src/game_map.h b/src/game_map.h index 9cea5c5ea2..609ee8bc52 100644 --- a/src/game_map.h +++ b/src/game_map.h @@ -641,6 +641,8 @@ 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); /** * Checks if its possible to step onto the tile at (x,y) From ad2557a684c88f8999adc18b9c02e04ac8e24602 Mon Sep 17 00:00:00 2001 From: Primekick Date: Tue, 26 Nov 2024 20:49:02 +0100 Subject: [PATCH 3/9] Partially implement Maniacs RewriteMap (without autotile fixup) --- src/game_interpreter.cpp | 44 +++++++++++++++++++++++-- src/spriteset_map.cpp | 13 ++++++++ src/spriteset_map.h | 10 ++++++ src/tilemap.h | 10 ++++++ src/tilemap_layer.cpp | 69 ++++++++++++++++++++++++---------------- src/tilemap_layer.h | 2 ++ 6 files changed, 118 insertions(+), 30 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index d903697b8f..cb829dc6f0 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -4547,12 +4547,52 @@ bool Game_Interpreter::CommandManiacKeyInputProcEx(lcf::rpg::EventCommand const& return true; } -bool Game_Interpreter::CommandManiacRewriteMap(lcf::rpg::EventCommand const&) { +bool Game_Interpreter::CommandManiacRewriteMap(lcf::rpg::EventCommand const& com) { if (!Player::IsPatchManiac()) { return true; } - Output::Warning("Maniac Patch: Command RewriteMap not supported"); + int mode = com.parameters[0]; + bool is_replace_range = com.parameters[1] != 0; + bool is_upper_layer = com.parameters[2] != 0; + + int tile_index = ValueOrVariableBitfield(mode, 0, com.parameters[3]); + int x_start = ValueOrVariableBitfield(mode, 1, com.parameters[4]); + int y_start = ValueOrVariableBitfield(mode, 2, com.parameters[5]); + int width = ValueOrVariableBitfield(mode, 3, com.parameters[6]); + int height = ValueOrVariableBitfield(mode, 4, com.parameters[7]); + + bool disable_autotile = com.parameters[8] != 0; + + Output::Debug("RewriteMap | Mode: {}, SingleRange: {}, Layer: {}, IdOrArrayStart: {}, LeftEnd: {}, TopValue: {}, Width: {}, Height: {}, Autotile: {}", + mode, + is_replace_range, + is_upper_layer, + tile_index, + x_start, + y_start, + width, + height, + disable_autotile); + + Scene_Map* scene = (Scene_Map*)Scene::Find(Scene::Map).get(); + if (!scene) + return true; + + if (is_upper_layer) { + for (auto y = y_start; y < y_start + height; ++y) { + for (auto x = x_start; x < x_start + width; ++x) { + scene->spriteset->ReplaceUpAt(x, y, tile_index); + } + } + } else { + for (auto y = y_start; y < y_start + height; ++y) { + for (auto x = x_start; x < x_start + width; ++x) { + scene->spriteset->ReplaceDownAt(x, y, tile_index, disable_autotile); + } + } + } + return true; } diff --git a/src/spriteset_map.cpp b/src/spriteset_map.cpp index c8be227b4e..4d1a0331b6 100644 --- a/src/spriteset_map.cpp +++ b/src/spriteset_map.cpp @@ -30,6 +30,7 @@ #include "bitmap.h" #include "player.h" #include "drawable_list.h" +#include "map_data.h" Spriteset_Map::Spriteset_Map() { panorama = std::make_unique(); @@ -173,6 +174,18 @@ 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); +} + bool Spriteset_Map::RequireClear(DrawableList& drawable_list) { if (drawable_list.empty()) { return true; diff --git a/src/spriteset_map.h b/src/spriteset_map.h index b9dddc31ec..dc176cf7a0 100644 --- a/src/spriteset_map.h +++ b/src/spriteset_map.h @@ -71,6 +71,16 @@ class Spriteset_Map { */ void SubstituteUp(int old_id, int new_id); + /** + * Replaces a single tile in lower layer at the given coordinates. + */ + void ReplaceDownAt(int x, int y, int tile_index, bool disable_autotile); + + /** + * Replaces a single tile in upper layer at the given coordinates. + */ + void ReplaceUpAt(int x, int y, int tile_index); + /** * @return true if we should clear the screen before drawing the map */ diff --git a/src/tilemap.h b/src/tilemap.h index ee5a4c78f6..0a79129595 100644 --- a/src/tilemap.h +++ b/src/tilemap.h @@ -36,9 +36,11 @@ class Tilemap { const std::vector& GetMapDataDown() const; void SetMapDataDown(std::vector down); + void SetMapTileDataDownAt(int x, int y, int tile_id, bool disable_autotile); const std::vector& GetMapDataUp() const; void SetMapDataUp(std::vector up); + void SetMapTileDataUpAt(int x, int y, int tile_id); const std::vector& GetPassableUp() const; void SetPassableUp(std::vector up); @@ -81,6 +83,10 @@ inline void Tilemap::SetMapDataDown(std::vector down) { layer_down.SetMapData(std::move(down)); } +inline void Tilemap::SetMapTileDataDownAt(int x, int y, int tile_id, bool disable_autotile) { + layer_down.SetMapTileDataAt(x, y, tile_id, disable_autotile); +} + inline const std::vector& Tilemap::GetMapDataUp() const { return layer_up.GetMapData(); } @@ -89,6 +95,10 @@ inline void Tilemap::SetMapDataUp(std::vector up) { layer_up.SetMapData(std::move(up)); } +inline void Tilemap::SetMapTileDataUpAt(int x, int y, int tile_id) { + layer_down.SetMapTileDataAt(x, y, tile_id, true); +} + inline const std::vector& Tilemap::GetPassableDown() const { return layer_down.GetPassable(); } diff --git a/src/tilemap_layer.cpp b/src/tilemap_layer.cpp index df8c62c436..18cc53c671 100644 --- a/src/tilemap_layer.cpp +++ b/src/tilemap_layer.cpp @@ -376,37 +376,39 @@ void TilemapLayer::CreateTileCache(const std::vector& nmap_data) { data_cache_vec.resize(width * height); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - TileData tile; - - // Get the tile ID - tile.ID = nmap_data[x + y * width]; - - tile.z = TileBelow; - - // Calculate the tile Z - if (!passable.empty()) { - if (tile.ID >= BLOCK_F) { // Upper layer - if ((passable[substitutions[tile.ID - BLOCK_F]] & Passable::Above) != 0) - tile.z = TileAbove + 1; // Upper sublayer - else - tile.z = TileBelow + 1; // Lower sublayer - - } else { // Lower layer - int chip_index = - tile.ID >= BLOCK_E ? substitutions[tile.ID - BLOCK_E] + 18 : - tile.ID >= BLOCK_D ? (tile.ID - BLOCK_D) / 50 + 6 : - tile.ID >= BLOCK_C ? (tile.ID - BLOCK_C) / 50 + 3 : - tile.ID / 1000; - if ((passable[chip_index] & (Passable::Wall | Passable::Above)) != 0) - tile.z = TileAbove; // Upper sublayer - else - tile.z = TileBelow; // Lower sublayer + auto tile_id = nmap_data[x + y * width]; + CreateTileCacheAt(x, y, tile_id); + } + } +} + +void TilemapLayer::CreateTileCacheAt(int x, int y, int tile_id) { + TileData tile; + tile.ID = static_cast(tile_id); + tile.z = TileBelow; + + // Calculate the tile Z + if (!passable.empty()) { + if (tile.ID >= BLOCK_F) { // Upper layer + if ((passable[substitutions[tile.ID - BLOCK_F]] & Passable::Above) != 0) + tile.z = TileAbove + 1; // Upper sublayer + else + tile.z = TileBelow + 1; // Lower sublayer + + } else { // Lower layer + int chip_index = + tile.ID >= BLOCK_E ? substitutions[tile.ID - BLOCK_E] + 18 : + tile.ID >= BLOCK_D ? (tile.ID - BLOCK_D) / 50 + 6 : + tile.ID >= BLOCK_C ? (tile.ID - BLOCK_C) / 50 + 3 : + tile.ID / 1000; + if ((passable[chip_index] & (Passable::Wall | Passable::Above)) != 0) + tile.z = TileAbove; // Upper sublayer + else + tile.z = TileBelow; // Lower sublayer - } - } - GetDataCache(x, y) = tile; } } + GetDataCache(x, y) = tile; } void TilemapLayer::GenerateAutotileAB(short ID, short animID) { @@ -661,6 +663,17 @@ void TilemapLayer::SetMapData(std::vector nmap_data) { map_data = std::move(nmap_data); } +void TilemapLayer::SetMapTileDataAt(int x, int y, int tile_id, bool disable_autotile) { + substitutions = Game_Map::GetTilesLayer(layer); + + if (disable_autotile) { + CreateTileCacheAt(x, y, tile_id); + } else { + // TODO: handle autotiles + } +} + + void TilemapLayer::SetPassable(std::vector npassable) { passable = std::move(npassable); diff --git a/src/tilemap_layer.h b/src/tilemap_layer.h index f4cf47a599..08a2601e4a 100644 --- a/src/tilemap_layer.h +++ b/src/tilemap_layer.h @@ -66,6 +66,7 @@ class TilemapLayer { void SetChipset(BitmapRef const& nchipset); const std::vector& GetMapData() const; void SetMapData(std::vector nmap_data); + void SetMapTileDataAt(int x, int y, int tile_id, bool disable_autotile); const std::vector& GetPassable() const; void SetPassable(std::vector npassable); bool IsVisible() const; @@ -115,6 +116,7 @@ class TilemapLayer { bool fast_blit = false; void CreateTileCache(const std::vector& nmap_data); + void CreateTileCacheAt(int x, int y, int tile_id); void GenerateAutotileAB(short ID, short animID); 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); From 262883de0e9c1054ef0cc1fbb935c3490e946121 Mon Sep 17 00:00:00 2001 From: Primekick Date: Tue, 26 Nov 2024 21:43:56 +0100 Subject: [PATCH 4/9] Add neighbouring autotiles -> autotile variant mapping --- src/tilemap_layer.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/tilemap_layer.cpp b/src/tilemap_layer.cpp index 18cc53c671..a871903058 100644 --- a/src/tilemap_layer.cpp +++ b/src/tilemap_layer.cpp @@ -140,6 +140,59 @@ static constexpr uint8_t BlockD_Subtiles_IDS[50][2][2][2] = { {{{0, 0}, {0, 0}}, {{0, 0}, {0, 0}}} }; +// Set of neighboring autotiles -> autotile variant +// Each neighbor is represented by a single bit (1 - same autotile, 0 - any other case) +// The bits are ordered as follows (from most to least significant bit): NW N NE W E SW S SE +static const std::unordered_map AUTOTILE_VARIANTS_MAP = { + {0b11111111, 0}, + {0b01111111, 1}, + {0b11011111, 2}, + {0b01011111, 3}, + {0b11111110, 4}, + {0b01111110, 5}, + {0b11011110, 6}, + {0b01011110, 7}, + {0b11111011, 8}, + {0b01111011, 9}, + {0b11011011, 10}, + {0b01011011, 11}, + {0b11111010, 12}, + {0b01111010, 13}, + {0b11011010, 14}, + {0b01011010, 15}, + {0b01101011, 16}, + {0b01001011, 17}, + {0b01101010, 18}, + {0b01001010, 19}, + {0b00011111, 20}, + {0b00011110, 21}, + {0b00011011, 22}, + {0b00011010, 23}, + {0b11010110, 24}, + {0b11010010, 25}, + {0b01010110, 26}, + {0b01010010, 27}, + {0b11111000, 28}, + {0b01111000, 29}, + {0b11011000, 30}, + {0b01011000, 31}, + {0b01000010, 32}, + {0b00011000, 33}, + {0b00001011, 34}, + {0b00001010, 35}, + {0b00010110, 36}, + {0b00010010, 37}, + {0b11010000, 38}, + {0b01010000, 39}, + {0b01101000, 40}, + {0b01001000, 41}, + {0b00000010, 42}, + {0b00001000, 43}, + {0b01000000, 44}, + {0b00010000, 45}, + {0b00000000, 46} +}; + TilemapLayer::TilemapLayer(int ilayer) : substitutions(Game_Map::GetTilesLayer(ilayer)), layer(ilayer), From ca30cf711fe2c080d962c15805bf77da5916a30f Mon Sep 17 00:00:00 2001 From: Primekick Date: Wed, 27 Nov 2024 12:58:50 +0100 Subject: [PATCH 5/9] 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 From b41a0cc42babc368e62e76ae0836c492608c9a11 Mon Sep 17 00:00:00 2001 From: Primekick Date: Wed, 27 Nov 2024 12:59:47 +0100 Subject: [PATCH 6/9] Rename autotile variants map --- src/tilemap_layer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tilemap_layer.cpp b/src/tilemap_layer.cpp index c0f313a317..82b49fd4b0 100644 --- a/src/tilemap_layer.cpp +++ b/src/tilemap_layer.cpp @@ -143,7 +143,7 @@ static constexpr uint8_t BlockD_Subtiles_IDS[50][2][2][2] = { // Set of neighboring autotiles -> autotile variant // Each neighbor is represented by a single bit (1 - same autotile, 0 - any other case) // The bits are ordered as follows (from most to least significant bit): NW N NE W E SW S SE -static const std::unordered_map AUTOTILE_VARIANTS_MAP = { +static const std::unordered_map AUTOTILE_D_VARIANTS_MAP = { {0b11111111, 0}, {0b01111111, 1}, {0b11011111, 2}, @@ -814,7 +814,7 @@ void TilemapLayer::RecalculateAutotile(int x, int y, int tile_id) { 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); + const int new_tile_id = BLOCK_D + block * BLOCK_D_STRIDE + AUTOTILE_D_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); From 9b1f154e5be860ae3078e1661e2aac00f4ec8a6b Mon Sep 17 00:00:00 2001 From: Primekick Date: Wed, 27 Nov 2024 14:50:57 +0100 Subject: [PATCH 7/9] Extract tile data recreation method --- src/tilemap_layer.cpp | 22 ++++++++++++---------- src/tilemap_layer.h | 1 + 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/tilemap_layer.cpp b/src/tilemap_layer.cpp index 82b49fd4b0..f4e1dff8c3 100644 --- a/src/tilemap_layer.cpp +++ b/src/tilemap_layer.cpp @@ -464,6 +464,12 @@ void TilemapLayer::CreateTileCacheAt(int x, int y, int tile_id) { GetDataCache(x, y) = tile; } +void TilemapLayer::RecreateTileDataAt(int x, int y, int tile_id) { + map_data[x + y * width] = static_cast(tile_id); + Game_Map::ReplaceTileAt(x, y, tile_id, layer); + CreateTileCacheAt(x, y, tile_id); +} + void TilemapLayer::GenerateAutotileAB(short ID, short animID) { // Calculate the block to use // 1: A1 + Upper B (Grass + Coast) @@ -716,6 +722,10 @@ void TilemapLayer::SetMapData(std::vector nmap_data) { map_data = std::move(nmap_data); } +static inline bool IsAutotileAB(int tile_id) { + return tile_id >= BLOCK_A && tile_id < BLOCK_C; +} + void TilemapLayer::SetMapTileDataAt(int x, int y, int tile_id, bool disable_autotile) { if(!IsInMapBounds(x, y)) return; @@ -723,9 +733,7 @@ void TilemapLayer::SetMapTileDataAt(int x, int y, int tile_id, bool disable_auto 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); + RecreateTileDataAt(x, y, tile_id); } else { // Recalculate the replaced tile itself + every neighboring tile static constexpr struct { int dx; int dy; } adjacent[8] = { @@ -747,10 +755,6 @@ void TilemapLayer::SetMapTileDataAt(int x, int y, int tile_id, bool disable_auto 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; } @@ -815,9 +819,7 @@ void TilemapLayer::RecalculateAutotile(int x, int y, int tile_id) { // Recalculate tile id using the neighbors -> variant map const int new_tile_id = BLOCK_D + block * BLOCK_D_STRIDE + AUTOTILE_D_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); + RecreateTileDataAt(x, y, new_tile_id); } void TilemapLayer::SetPassable(std::vector npassable) { diff --git a/src/tilemap_layer.h b/src/tilemap_layer.h index 91a8adfc80..1cf2c88c43 100644 --- a/src/tilemap_layer.h +++ b/src/tilemap_layer.h @@ -117,6 +117,7 @@ class TilemapLayer { void CreateTileCache(const std::vector& nmap_data); void CreateTileCacheAt(int x, int y, int tile_id); + void RecreateTileDataAt(int x, int y, int tile_id); void GenerateAutotileAB(short ID, short animID); 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); From 3ad40c3ec240303885219fecf789c649d66de502 Mon Sep 17 00:00:00 2001 From: Primekick Date: Wed, 27 Nov 2024 14:51:57 +0100 Subject: [PATCH 8/9] Add partial AB autotile rewriting --- src/tilemap_layer.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tilemap_layer.cpp b/src/tilemap_layer.cpp index f4e1dff8c3..e2ea3e560b 100644 --- a/src/tilemap_layer.cpp +++ b/src/tilemap_layer.cpp @@ -742,7 +742,14 @@ void TilemapLayer::SetMapTileDataAt(int x, int y, int tile_id, bool disable_auto {-1, 1}, { 0, 1}, { 1, 1} }; - RecalculateAutotile(x, y, tile_id); + // TODO: make it work for AB autotiles + if (IsAutotileAB(tile_id)) { + RecreateTileDataAt(x, y, tile_id); + Output::Warning("Maniac Patch: Command RewriteMap is only partially supported."); + } else { + RecalculateAutotile(x, y, tile_id); + } + for (const auto& adj : adjacent) { auto nx = x + adj.dx; auto ny = y + adj.dy; From a5c3aed60141f574dd9f68bee6d632019af3e6e9 Mon Sep 17 00:00:00 2001 From: Primekick Date: Sat, 30 Nov 2024 19:08:40 +0100 Subject: [PATCH 9/9] Remove debug log, reword warning logs --- src/game_interpreter.cpp | 11 ----------- src/tilemap_layer.cpp | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index cb829dc6f0..345a092a3c 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -4564,17 +4564,6 @@ bool Game_Interpreter::CommandManiacRewriteMap(lcf::rpg::EventCommand const& com bool disable_autotile = com.parameters[8] != 0; - Output::Debug("RewriteMap | Mode: {}, SingleRange: {}, Layer: {}, IdOrArrayStart: {}, LeftEnd: {}, TopValue: {}, Width: {}, Height: {}, Autotile: {}", - mode, - is_replace_range, - is_upper_layer, - tile_index, - x_start, - y_start, - width, - height, - disable_autotile); - Scene_Map* scene = (Scene_Map*)Scene::Find(Scene::Map).get(); if (!scene) return true; diff --git a/src/tilemap_layer.cpp b/src/tilemap_layer.cpp index e2ea3e560b..5c457705d0 100644 --- a/src/tilemap_layer.cpp +++ b/src/tilemap_layer.cpp @@ -745,7 +745,7 @@ void TilemapLayer::SetMapTileDataAt(int x, int y, int tile_id, bool disable_auto // TODO: make it work for AB autotiles if (IsAutotileAB(tile_id)) { RecreateTileDataAt(x, y, tile_id); - Output::Warning("Maniac Patch: Command RewriteMap is only partially supported."); + Output::Warning("Maniac Patch: Autotiles A and B in RewriteMap are only partially supported."); } else { RecalculateAutotile(x, y, tile_id); } @@ -795,7 +795,7 @@ static inline void ApplyCornerFixups(uint8_t& neighbors) { 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."); + Output::Warning("Maniac Patch: Autotiles A and B in RewriteMap are only partially supported."); return; }