| 1 | #include <base/dbg.h> |
| 2 | #include <base/io.h> |
| 3 | #include <base/logger.h> |
| 4 | #include <base/os.h> |
| 5 | #include <base/str.h> |
| 6 | |
| 7 | #include <engine/map.h> |
| 8 | #include <engine/storage.h> |
| 9 | |
| 10 | #include <game/gamecore.h> |
| 11 | #include <game/mapitems.h> |
| 12 | |
| 13 | #include <cstdlib> |
| 14 | #include <memory> |
| 15 | |
| 16 | static int TileDataIndex(const CMapItemLayerTilemap *pTilemap, int PhysicsLayerFlags) |
| 17 | { |
| 18 | if(PhysicsLayerFlags & TILESLAYERFLAG_TELE) |
| 19 | return pTilemap->m_Tele; |
| 20 | if(PhysicsLayerFlags & TILESLAYERFLAG_SPEEDUP) |
| 21 | return pTilemap->m_Speedup; |
| 22 | if(PhysicsLayerFlags & TILESLAYERFLAG_FRONT) |
| 23 | return pTilemap->m_Front; |
| 24 | if(PhysicsLayerFlags & TILESLAYERFLAG_SWITCH) |
| 25 | return pTilemap->m_Switch; |
| 26 | if(PhysicsLayerFlags & TILESLAYERFLAG_TUNE) |
| 27 | return pTilemap->m_Tune; |
| 28 | return pTilemap->m_Data; |
| 29 | } |
| 30 | |
| 31 | template<typename T, typename FCompare> |
| 32 | static bool DiffTileLayer(std::shared_ptr<IMap> apMaps[2], const char *apMapNames[2], const int aData[2], int Width, int Height, FCompare pfnCompare) |
| 33 | { |
| 34 | const T *apTile[2]; |
| 35 | for(int i = 0; i < 2; ++i) |
| 36 | { |
| 37 | apTile[i] = (const T *)apMaps[i]->GetData(Index: aData[i]); |
| 38 | } |
| 39 | |
| 40 | if(apTile[0] == nullptr || apTile[1] == nullptr) |
| 41 | { |
| 42 | for(int i = 0; i < 2; ++i) |
| 43 | { |
| 44 | if(apTile[i] == nullptr) |
| 45 | dbg_msg(sys: "map_diff" , fmt: "invalid tile layer data in \"%s\"" , apMapNames[i]); |
| 46 | } |
| 47 | for(int i = 0; i < 2; ++i) |
| 48 | apMaps[i]->UnloadData(Index: aData[i]); |
| 49 | // Layers that are invalid in both maps are ignored |
| 50 | return apTile[0] == apTile[1]; |
| 51 | } |
| 52 | |
| 53 | for(int y = 0; y < Height; y++) |
| 54 | { |
| 55 | for(int x = 0; x < Width; x++) |
| 56 | { |
| 57 | const int Pos = y * Width + x; |
| 58 | pfnCompare(apTile[0][Pos], apTile[1][Pos], x, y); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | for(int i = 0; i < 2; ++i) |
| 63 | apMaps[i]->UnloadData(Index: aData[i]); |
| 64 | return true; |
| 65 | } |
| 66 | |
| 67 | static bool Process(IStorage *pStorage, const char *apMapNames[2]) |
| 68 | { |
| 69 | std::shared_ptr<IMap> apMaps[2]; |
| 70 | |
| 71 | for(int i = 0; i < 2; ++i) |
| 72 | { |
| 73 | apMaps[i] = CreateMap(); |
| 74 | |
| 75 | if(!apMaps[i]->Load(pStorage, pPath: apMapNames[i], StorageType: IStorage::TYPE_ABSOLUTE)) |
| 76 | { |
| 77 | dbg_msg(sys: "map_diff" , fmt: "error opening map '%s'" , apMapNames[i]); |
| 78 | return false; |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | int aStart[2], aLayersNum[2]; |
| 83 | for(int i = 0; i < 2; ++i) |
| 84 | apMaps[i]->GetType(Type: MAPITEMTYPE_LAYER, pStart: &aStart[i], pNum: &aLayersNum[i]); |
| 85 | |
| 86 | // ensure basic layout |
| 87 | if(aLayersNum[0] != aLayersNum[1]) |
| 88 | { |
| 89 | dbg_msg(sys: "map_diff" , fmt: "different layer numbers:" ); |
| 90 | for(int i = 0; i < 2; ++i) |
| 91 | dbg_msg(sys: "map_diff" , fmt: " \"%s\": %d layers" , apMapNames[i], aLayersNum[i]); |
| 92 | return false; |
| 93 | } |
| 94 | |
| 95 | // compare |
| 96 | for(int j = 0; j < aLayersNum[0]; ++j) |
| 97 | { |
| 98 | CMapItemLayer *apItem[2]; |
| 99 | for(int i = 0; i < 2; ++i) |
| 100 | apItem[i] = (CMapItemLayer *)apMaps[i]->GetItem(Index: aStart[i] + j); |
| 101 | |
| 102 | if(apItem[0]->m_Type != LAYERTYPE_TILES || apItem[1]->m_Type != LAYERTYPE_TILES) |
| 103 | continue; |
| 104 | |
| 105 | CMapItemLayerTilemap *apTilemap[2]; |
| 106 | char aaName[2][sizeof(CMapItemLayerTilemap{}.m_aName)]; |
| 107 | |
| 108 | for(int i = 0; i < 2; ++i) |
| 109 | { |
| 110 | apTilemap[i] = (CMapItemLayerTilemap *)apItem[i]; |
| 111 | IntsToStr(pInts: apTilemap[i]->m_aName, NumInts: std::size(apTilemap[i]->m_aName), pStr: aaName[i], StrSize: std::size(aaName[i])); |
| 112 | } |
| 113 | |
| 114 | const int PhysicsLayerFlags = TILESLAYERFLAG_TELE | TILESLAYERFLAG_SPEEDUP | TILESLAYERFLAG_FRONT | TILESLAYERFLAG_SWITCH | TILESLAYERFLAG_TUNE; |
| 115 | if(str_comp(a: aaName[0], b: aaName[1]) != 0 || |
| 116 | apTilemap[0]->m_Width != apTilemap[1]->m_Width || |
| 117 | apTilemap[0]->m_Height != apTilemap[1]->m_Height || |
| 118 | (apTilemap[0]->m_Flags & PhysicsLayerFlags) != (apTilemap[1]->m_Flags & PhysicsLayerFlags)) |
| 119 | { |
| 120 | dbg_msg(sys: "map_diff" , fmt: "different tile layers:" ); |
| 121 | for(int i = 0; i < 2; ++i) |
| 122 | dbg_msg(sys: "map_diff" , fmt: " [%d:%s] (%dx%d, flags: %d)" , j, aaName[i], apTilemap[i]->m_Width, apTilemap[i]->m_Height, apTilemap[i]->m_Flags); |
| 123 | return false; |
| 124 | } |
| 125 | |
| 126 | const int Width = apTilemap[0]->m_Width; |
| 127 | const int Height = apTilemap[0]->m_Height; |
| 128 | const int Flags = apTilemap[0]->m_Flags & PhysicsLayerFlags; |
| 129 | const int aData[2] = {TileDataIndex(pTilemap: apTilemap[0], PhysicsLayerFlags: Flags), TileDataIndex(pTilemap: apTilemap[1], PhysicsLayerFlags: Flags)}; |
| 130 | bool Ok; |
| 131 | if(Flags & TILESLAYERFLAG_TELE) |
| 132 | { |
| 133 | Ok = DiffTileLayer<CTeleTile>(apMaps, apMapNames, aData, Width, Height, pfnCompare: [&](const CTeleTile &Tile0, const CTeleTile &Tile1, int x, int y) { |
| 134 | if(Tile0.m_Number != Tile1.m_Number || Tile0.m_Type != Tile1.m_Type) |
| 135 | { |
| 136 | dbg_msg(sys: "map_diff" , fmt: "[%d:%s] %dx%d: (number: %d, type: %d) != (number: %d, type: %d)" , |
| 137 | aLayersNum[0], aaName[0], x, y, Tile0.m_Number, Tile0.m_Type, Tile1.m_Number, Tile1.m_Type); |
| 138 | } |
| 139 | }); |
| 140 | } |
| 141 | else if(Flags & TILESLAYERFLAG_SPEEDUP) |
| 142 | { |
| 143 | Ok = DiffTileLayer<CSpeedupTile>(apMaps, apMapNames, aData, Width, Height, pfnCompare: [&](const CSpeedupTile &Tile0, const CSpeedupTile &Tile1, int x, int y) { |
| 144 | if(Tile0.m_Force != Tile1.m_Force || Tile0.m_MaxSpeed != Tile1.m_MaxSpeed || Tile0.m_Type != Tile1.m_Type || Tile0.m_Angle != Tile1.m_Angle) |
| 145 | { |
| 146 | dbg_msg(sys: "map_diff" , fmt: "[%d:%s] %dx%d: (force: %d, maxspeed: %d, angle: %d, type: %d) != (force: %d, maxspeed: %d, angle: %d, type: %d)" , |
| 147 | aLayersNum[0], aaName[0], x, y, Tile0.m_Force, Tile0.m_MaxSpeed, Tile0.m_Angle, Tile0.m_Type, Tile1.m_Force, Tile1.m_MaxSpeed, Tile1.m_Angle, Tile1.m_Type); |
| 148 | } |
| 149 | }); |
| 150 | } |
| 151 | else if(Flags & TILESLAYERFLAG_SWITCH) |
| 152 | { |
| 153 | Ok = DiffTileLayer<CSwitchTile>(apMaps, apMapNames, aData, Width, Height, pfnCompare: [&](const CSwitchTile &Tile0, const CSwitchTile &Tile1, int x, int y) { |
| 154 | if(Tile0.m_Number != Tile1.m_Number || Tile0.m_Type != Tile1.m_Type || Tile0.m_Flags != Tile1.m_Flags || Tile0.m_Delay != Tile1.m_Delay) |
| 155 | { |
| 156 | dbg_msg(sys: "map_diff" , fmt: "[%d:%s] %dx%d: (number: %d, type: %d, flags: %d, delay: %d) != (number: %d, type: %d, flags: %d, delay: %d)" , |
| 157 | aLayersNum[0], aaName[0], x, y, Tile0.m_Number, Tile0.m_Type, Tile0.m_Flags, Tile0.m_Delay, Tile1.m_Number, Tile1.m_Type, Tile1.m_Flags, Tile1.m_Delay); |
| 158 | } |
| 159 | }); |
| 160 | } |
| 161 | else if(Flags & TILESLAYERFLAG_TUNE) |
| 162 | { |
| 163 | Ok = DiffTileLayer<CTuneTile>(apMaps, apMapNames, aData, Width, Height, pfnCompare: [&](const CTuneTile &Tile0, const CTuneTile &Tile1, int x, int y) { |
| 164 | if(Tile0.m_Number != Tile1.m_Number || Tile0.m_Type != Tile1.m_Type) |
| 165 | { |
| 166 | dbg_msg(sys: "map_diff" , fmt: "[%d:%s] %dx%d: (number: %d, type: %d) != (number: %d, type: %d)" , |
| 167 | aLayersNum[0], aaName[0], x, y, Tile0.m_Number, Tile0.m_Type, Tile1.m_Number, Tile1.m_Type); |
| 168 | } |
| 169 | }); |
| 170 | } |
| 171 | else |
| 172 | { |
| 173 | // Regular, game and front layers all store CTile |
| 174 | Ok = DiffTileLayer<CTile>(apMaps, apMapNames, aData, Width, Height, pfnCompare: [&](const CTile &Tile0, const CTile &Tile1, int x, int y) { |
| 175 | if(Tile0.m_Index != Tile1.m_Index || Tile0.m_Flags != Tile1.m_Flags) |
| 176 | { |
| 177 | dbg_msg(sys: "map_diff" , fmt: "[%d:%s] %dx%d: (index: %d, flags: %d) != (index: %d, flags: %d)" , |
| 178 | aLayersNum[0], aaName[0], x, y, Tile0.m_Index, Tile0.m_Flags, Tile1.m_Index, Tile1.m_Flags); |
| 179 | } |
| 180 | }); |
| 181 | } |
| 182 | |
| 183 | if(!Ok) |
| 184 | return false; |
| 185 | } |
| 186 | |
| 187 | return true; |
| 188 | } |
| 189 | |
| 190 | int main(int argc, const char *argv[]) |
| 191 | { |
| 192 | CCmdlineFix CmdlineFix(&argc, &argv); |
| 193 | std::vector<std::shared_ptr<ILogger>> vpLoggers; |
| 194 | std::shared_ptr<ILogger> pStdoutLogger = std::shared_ptr<ILogger>(log_logger_stdout()); |
| 195 | if(pStdoutLogger) |
| 196 | { |
| 197 | vpLoggers.push_back(x: pStdoutLogger); |
| 198 | } |
| 199 | IOHANDLE LogFile = io_open(filename: "map_diff.txt" , flags: IOFLAG_WRITE); |
| 200 | if(LogFile) |
| 201 | { |
| 202 | vpLoggers.push_back(x: std::shared_ptr<ILogger>(log_logger_file(file: LogFile))); |
| 203 | } |
| 204 | log_set_global_logger(logger: log_logger_collection(vpLoggers: std::move(vpLoggers)).release()); |
| 205 | |
| 206 | if(argc != 3) |
| 207 | { |
| 208 | dbg_msg(sys: "usage" , fmt: "%s map1 map2" , argv[0]); |
| 209 | return -1; |
| 210 | } |
| 211 | |
| 212 | std::unique_ptr<IStorage> pStorage = CreateLocalStorage(); |
| 213 | if(!pStorage) |
| 214 | { |
| 215 | log_error("map_diff" , "Error creating local storage" ); |
| 216 | return -1; |
| 217 | } |
| 218 | |
| 219 | const char *apMapNames[] = {argv[1], argv[2]}; |
| 220 | return Process(pStorage: pStorage.get(), apMapNames) ? 0 : 1; |
| 221 | } |
| 222 | |