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
16static 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
31template<typename T, typename FCompare>
32static 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
67static 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
190int 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