1 | #include "layer_switch.h" |
2 | |
3 | #include <game/editor/editor.h> |
4 | |
5 | CLayerSwitch::CLayerSwitch(CEditor *pEditor, int w, int h) : |
6 | CLayerTiles(pEditor, w, h) |
7 | { |
8 | str_copy(dst&: m_aName, src: "Switch" ); |
9 | m_Switch = 1; |
10 | |
11 | m_pSwitchTile = new CSwitchTile[w * h]; |
12 | mem_zero(block: m_pSwitchTile, size: (size_t)w * h * sizeof(CSwitchTile)); |
13 | m_GotoSwitchLastPos = ivec2(-1, -1); |
14 | m_GotoSwitchOffset = 0; |
15 | } |
16 | |
17 | CLayerSwitch::CLayerSwitch(const CLayerSwitch &Other) : |
18 | CLayerTiles(Other) |
19 | { |
20 | str_copy(dst&: m_aName, src: "Switch copy" ); |
21 | m_Switch = 1; |
22 | |
23 | m_pSwitchTile = new CSwitchTile[m_Width * m_Height]; |
24 | mem_copy(dest: m_pSwitchTile, source: Other.m_pSwitchTile, size: (size_t)m_Width * m_Height * sizeof(CSwitchTile)); |
25 | } |
26 | |
27 | CLayerSwitch::~CLayerSwitch() |
28 | { |
29 | delete[] m_pSwitchTile; |
30 | } |
31 | |
32 | void CLayerSwitch::Resize(int NewW, int NewH) |
33 | { |
34 | // resize switch data |
35 | CSwitchTile *pNewSwitchData = new CSwitchTile[NewW * NewH]; |
36 | mem_zero(block: pNewSwitchData, size: (size_t)NewW * NewH * sizeof(CSwitchTile)); |
37 | |
38 | // copy old data |
39 | for(int y = 0; y < minimum(a: NewH, b: m_Height); y++) |
40 | mem_copy(dest: &pNewSwitchData[y * NewW], source: &m_pSwitchTile[y * m_Width], size: minimum(a: m_Width, b: NewW) * sizeof(CSwitchTile)); |
41 | |
42 | // replace old |
43 | delete[] m_pSwitchTile; |
44 | m_pSwitchTile = pNewSwitchData; |
45 | |
46 | // resize tile data |
47 | CLayerTiles::Resize(NewW, NewH); |
48 | |
49 | // resize gamelayer too |
50 | if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) |
51 | m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); |
52 | } |
53 | |
54 | void CLayerSwitch::Shift(int Direction) |
55 | { |
56 | CLayerTiles::Shift(Direction); |
57 | ShiftImpl(pTiles: m_pSwitchTile, Direction, ShiftBy: m_pEditor->m_ShiftBy); |
58 | } |
59 | |
60 | bool CLayerSwitch::IsEmpty(const std::shared_ptr<CLayerTiles> &pLayer) |
61 | { |
62 | for(int y = 0; y < pLayer->m_Height; y++) |
63 | for(int x = 0; x < pLayer->m_Width; x++) |
64 | if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidSwitchTile(Index: pLayer->GetTile(x, y).m_Index)) |
65 | return false; |
66 | |
67 | return true; |
68 | } |
69 | |
70 | void CLayerSwitch::BrushDraw(std::shared_ptr<CLayer> pBrush, float wx, float wy) |
71 | { |
72 | if(m_Readonly) |
73 | return; |
74 | |
75 | std::shared_ptr<CLayerSwitch> pSwitchLayer = std::static_pointer_cast<CLayerSwitch>(r: pBrush); |
76 | int sx = ConvertX(x: wx); |
77 | int sy = ConvertY(y: wy); |
78 | if(str_comp(a: pSwitchLayer->m_aFileName, b: m_pEditor->m_aFileName)) |
79 | { |
80 | m_pEditor->m_SwitchNum = pSwitchLayer->m_SwitchNumber; |
81 | m_pEditor->m_SwitchDelay = pSwitchLayer->m_SwitchDelay; |
82 | } |
83 | |
84 | bool Destructive = m_pEditor->m_BrushDrawDestructive || IsEmpty(pLayer: pSwitchLayer); |
85 | |
86 | for(int y = 0; y < pSwitchLayer->m_Height; y++) |
87 | for(int x = 0; x < pSwitchLayer->m_Width; x++) |
88 | { |
89 | int fx = x + sx; |
90 | int fy = y + sy; |
91 | |
92 | if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) |
93 | continue; |
94 | |
95 | if(!Destructive && GetTile(x: fx, y: fy).m_Index) |
96 | continue; |
97 | |
98 | int Index = fy * m_Width + fx; |
99 | SSwitchTileStateChange::SData Previous{ |
100 | .m_Number: m_pSwitchTile[Index].m_Number, |
101 | .m_Type: m_pSwitchTile[Index].m_Type, |
102 | .m_Flags: m_pSwitchTile[Index].m_Flags, |
103 | .m_Delay: m_pSwitchTile[Index].m_Delay, |
104 | .m_Index: m_pTiles[Index].m_Index}; |
105 | |
106 | if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSwitchTile(Index: pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) && pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index != TILE_AIR) |
107 | { |
108 | if(m_pEditor->m_SwitchNum != pSwitchLayer->m_SwitchNumber || m_pEditor->m_SwitchDelay != pSwitchLayer->m_SwitchDelay) |
109 | { |
110 | m_pSwitchTile[Index].m_Number = m_pEditor->m_SwitchNum; |
111 | m_pSwitchTile[Index].m_Delay = m_pEditor->m_SwitchDelay; |
112 | } |
113 | else if(pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number) |
114 | { |
115 | m_pSwitchTile[Index].m_Number = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number; |
116 | m_pSwitchTile[Index].m_Delay = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Delay; |
117 | } |
118 | else |
119 | { |
120 | m_pSwitchTile[Index].m_Number = m_pEditor->m_SwitchNum; |
121 | m_pSwitchTile[Index].m_Delay = m_pEditor->m_SwitchDelay; |
122 | } |
123 | |
124 | m_pSwitchTile[Index].m_Type = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; |
125 | m_pSwitchTile[Index].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; |
126 | m_pTiles[Index].m_Index = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; |
127 | m_pTiles[Index].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; |
128 | |
129 | if(!IsSwitchTileFlagsUsed(Index: pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) |
130 | { |
131 | m_pSwitchTile[Index].m_Flags = 0; |
132 | } |
133 | if(!IsSwitchTileNumberUsed(Index: pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) |
134 | { |
135 | m_pSwitchTile[Index].m_Number = 0; |
136 | } |
137 | if(!IsSwitchTileDelayUsed(Index: pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) |
138 | { |
139 | m_pSwitchTile[Index].m_Delay = 0; |
140 | } |
141 | } |
142 | else |
143 | { |
144 | m_pSwitchTile[Index].m_Number = 0; |
145 | m_pSwitchTile[Index].m_Type = 0; |
146 | m_pSwitchTile[Index].m_Flags = 0; |
147 | m_pSwitchTile[Index].m_Delay = 0; |
148 | m_pTiles[Index].m_Index = 0; |
149 | |
150 | if(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index != TILE_AIR) |
151 | ShowPreventUnusedTilesWarning(); |
152 | } |
153 | |
154 | SSwitchTileStateChange::SData Current{ |
155 | .m_Number: m_pSwitchTile[Index].m_Number, |
156 | .m_Type: m_pSwitchTile[Index].m_Type, |
157 | .m_Flags: m_pSwitchTile[Index].m_Flags, |
158 | .m_Delay: m_pSwitchTile[Index].m_Delay, |
159 | .m_Index: m_pTiles[Index].m_Index}; |
160 | |
161 | RecordStateChange(x: fx, y: fy, Previous, Current); |
162 | } |
163 | FlagModified(x: sx, y: sy, w: pSwitchLayer->m_Width, h: pSwitchLayer->m_Height); |
164 | } |
165 | |
166 | void CLayerSwitch::RecordStateChange(int x, int y, SSwitchTileStateChange::SData Previous, SSwitchTileStateChange::SData Current) |
167 | { |
168 | if(!m_History[y][x].m_Changed) |
169 | m_History[y][x] = SSwitchTileStateChange{.m_Changed: true, .m_Previous: Previous, .m_Current: Current}; |
170 | else |
171 | m_History[y][x].m_Current = Current; |
172 | } |
173 | |
174 | void CLayerSwitch::BrushFlipX() |
175 | { |
176 | CLayerTiles::BrushFlipX(); |
177 | BrushFlipXImpl(pTiles: m_pSwitchTile); |
178 | } |
179 | |
180 | void CLayerSwitch::BrushFlipY() |
181 | { |
182 | CLayerTiles::BrushFlipY(); |
183 | BrushFlipYImpl(pTiles: m_pSwitchTile); |
184 | } |
185 | |
186 | void CLayerSwitch::BrushRotate(float Amount) |
187 | { |
188 | int Rotation = (round_to_int(f: 360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270° |
189 | if(Rotation < 0) |
190 | Rotation += 4; |
191 | |
192 | if(Rotation == 1 || Rotation == 3) |
193 | { |
194 | // 90° rotation |
195 | CSwitchTile *pTempData1 = new CSwitchTile[m_Width * m_Height]; |
196 | CTile *pTempData2 = new CTile[m_Width * m_Height]; |
197 | mem_copy(dest: pTempData1, source: m_pSwitchTile, size: (size_t)m_Width * m_Height * sizeof(CSwitchTile)); |
198 | mem_copy(dest: pTempData2, source: m_pTiles, size: (size_t)m_Width * m_Height * sizeof(CTile)); |
199 | CSwitchTile *pDst1 = m_pSwitchTile; |
200 | CTile *pDst2 = m_pTiles; |
201 | for(int x = 0; x < m_Width; ++x) |
202 | for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2) |
203 | { |
204 | *pDst1 = pTempData1[y * m_Width + x]; |
205 | *pDst2 = pTempData2[y * m_Width + x]; |
206 | if(IsRotatableTile(Index: pDst2->m_Index)) |
207 | { |
208 | if(pDst2->m_Flags & TILEFLAG_ROTATE) |
209 | pDst2->m_Flags ^= (TILEFLAG_YFLIP | TILEFLAG_XFLIP); |
210 | pDst2->m_Flags ^= TILEFLAG_ROTATE; |
211 | } |
212 | } |
213 | |
214 | std::swap(a&: m_Width, b&: m_Height); |
215 | delete[] pTempData1; |
216 | delete[] pTempData2; |
217 | } |
218 | |
219 | if(Rotation == 2 || Rotation == 3) |
220 | { |
221 | BrushFlipX(); |
222 | BrushFlipY(); |
223 | } |
224 | } |
225 | |
226 | void CLayerSwitch::FillSelection(bool Empty, std::shared_ptr<CLayer> pBrush, CUIRect Rect) |
227 | { |
228 | if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES)) |
229 | return; |
230 | |
231 | Snap(pRect: &Rect); // corrects Rect; no need of <= |
232 | |
233 | Snap(pRect: &Rect); |
234 | |
235 | int sx = ConvertX(x: Rect.x); |
236 | int sy = ConvertY(y: Rect.y); |
237 | int w = ConvertX(x: Rect.w); |
238 | int h = ConvertY(y: Rect.h); |
239 | |
240 | std::shared_ptr<CLayerSwitch> pLt = std::static_pointer_cast<CLayerSwitch>(r: pBrush); |
241 | |
242 | bool Destructive = m_pEditor->m_BrushDrawDestructive || Empty || IsEmpty(pLayer: pLt); |
243 | |
244 | for(int y = 0; y < h; y++) |
245 | { |
246 | for(int x = 0; x < w; x++) |
247 | { |
248 | int fx = x + sx; |
249 | int fy = y + sy; |
250 | |
251 | if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) |
252 | continue; |
253 | |
254 | if(!Destructive && GetTile(x: fx, y: fy).m_Index) |
255 | continue; |
256 | |
257 | const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); |
258 | const int TgtIndex = fy * m_Width + fx; |
259 | |
260 | SSwitchTileStateChange::SData Previous{ |
261 | .m_Number: m_pSwitchTile[TgtIndex].m_Number, |
262 | .m_Type: m_pSwitchTile[TgtIndex].m_Type, |
263 | .m_Flags: m_pSwitchTile[TgtIndex].m_Flags, |
264 | .m_Delay: m_pSwitchTile[TgtIndex].m_Delay, |
265 | .m_Index: m_pTiles[TgtIndex].m_Index}; |
266 | |
267 | if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSwitchTile(Index: (pLt->m_pTiles[SrcIndex]).m_Index))) |
268 | { |
269 | m_pTiles[TgtIndex].m_Index = 0; |
270 | m_pSwitchTile[TgtIndex].m_Type = 0; |
271 | m_pSwitchTile[TgtIndex].m_Number = 0; |
272 | m_pSwitchTile[TgtIndex].m_Delay = 0; |
273 | |
274 | if(!Empty) |
275 | ShowPreventUnusedTilesWarning(); |
276 | } |
277 | else |
278 | { |
279 | m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex]; |
280 | m_pSwitchTile[TgtIndex].m_Type = m_pTiles[TgtIndex].m_Index; |
281 | if(pLt->m_Switch && m_pTiles[TgtIndex].m_Index > 0) |
282 | { |
283 | if(!IsSwitchTileNumberUsed(Index: m_pSwitchTile[TgtIndex].m_Type)) |
284 | m_pSwitchTile[TgtIndex].m_Number = 0; |
285 | else if(pLt->m_pSwitchTile[SrcIndex].m_Number == 0 || m_pEditor->m_SwitchNum != pLt->m_SwitchNumber) |
286 | m_pSwitchTile[TgtIndex].m_Number = m_pEditor->m_SwitchNum; |
287 | else |
288 | m_pSwitchTile[TgtIndex].m_Number = pLt->m_pSwitchTile[SrcIndex].m_Number; |
289 | |
290 | if(!IsSwitchTileDelayUsed(Index: m_pSwitchTile[TgtIndex].m_Type)) |
291 | m_pSwitchTile[TgtIndex].m_Delay = 0; |
292 | else if(pLt->m_pSwitchTile[SrcIndex].m_Delay == 0 || m_pEditor->m_SwitchDelay != pLt->m_SwitchDelay) |
293 | m_pSwitchTile[TgtIndex].m_Delay = m_pEditor->m_SwitchDelay; |
294 | else |
295 | m_pSwitchTile[TgtIndex].m_Delay = pLt->m_pSwitchTile[SrcIndex].m_Delay; |
296 | |
297 | if(!IsSwitchTileFlagsUsed(Index: m_pSwitchTile[TgtIndex].m_Type)) |
298 | m_pSwitchTile[TgtIndex].m_Flags = 0; |
299 | else |
300 | m_pSwitchTile[TgtIndex].m_Flags = pLt->m_pSwitchTile[SrcIndex].m_Flags; |
301 | } |
302 | } |
303 | |
304 | SSwitchTileStateChange::SData Current{ |
305 | .m_Number: m_pSwitchTile[TgtIndex].m_Number, |
306 | .m_Type: m_pSwitchTile[TgtIndex].m_Type, |
307 | .m_Flags: m_pSwitchTile[TgtIndex].m_Flags, |
308 | .m_Delay: m_pSwitchTile[TgtIndex].m_Delay, |
309 | .m_Index: m_pTiles[TgtIndex].m_Index}; |
310 | |
311 | RecordStateChange(x: fx, y: fy, Previous, Current); |
312 | } |
313 | } |
314 | FlagModified(x: sx, y: sy, w, h); |
315 | } |
316 | |
317 | bool CLayerSwitch::ContainsElementWithId(int Id) |
318 | { |
319 | for(int y = 0; y < m_Height; ++y) |
320 | { |
321 | for(int x = 0; x < m_Width; ++x) |
322 | { |
323 | if(IsSwitchTileNumberUsed(Index: m_pSwitchTile[y * m_Width + x].m_Type) && m_pSwitchTile[y * m_Width + x].m_Number == Id) |
324 | { |
325 | return true; |
326 | } |
327 | } |
328 | } |
329 | |
330 | return false; |
331 | } |
332 | |
333 | void CLayerSwitch::GetPos(int Number, int Offset, ivec2 &SwitchPos) |
334 | { |
335 | int Match = -1; |
336 | ivec2 MatchPos = ivec2(-1, -1); |
337 | SwitchPos = ivec2(-1, -1); |
338 | |
339 | auto FindTile = [this, &Match, &MatchPos, &Number, &Offset]() { |
340 | for(int x = 0; x < m_Width; x++) |
341 | { |
342 | for(int y = 0; y < m_Height; y++) |
343 | { |
344 | int i = y * m_Width + x; |
345 | int Switch = m_pSwitchTile[i].m_Number; |
346 | if(Number == Switch) |
347 | { |
348 | Match++; |
349 | if(Offset != -1) |
350 | { |
351 | if(Match == Offset) |
352 | { |
353 | MatchPos = ivec2(x, y); |
354 | m_GotoSwitchOffset = Match; |
355 | return; |
356 | } |
357 | continue; |
358 | } |
359 | MatchPos = ivec2(x, y); |
360 | if(m_GotoSwitchLastPos != ivec2(-1, -1)) |
361 | { |
362 | if(distance(a: m_GotoSwitchLastPos, b: MatchPos) < 10.0f) |
363 | { |
364 | m_GotoSwitchOffset++; |
365 | continue; |
366 | } |
367 | } |
368 | m_GotoSwitchLastPos = MatchPos; |
369 | if(Match == m_GotoSwitchOffset) |
370 | return; |
371 | } |
372 | } |
373 | } |
374 | }; |
375 | FindTile(); |
376 | |
377 | if(MatchPos == ivec2(-1, -1)) |
378 | return; |
379 | if(Match < m_GotoSwitchOffset) |
380 | m_GotoSwitchOffset = -1; |
381 | SwitchPos = MatchPos; |
382 | m_GotoSwitchOffset++; |
383 | } |
384 | |
385 | std::shared_ptr<CLayer> CLayerSwitch::Duplicate() const |
386 | { |
387 | return std::make_shared<CLayerSwitch>(args: *this); |
388 | } |
389 | |
390 | const char *CLayerSwitch::TypeName() const |
391 | { |
392 | return "switch" ; |
393 | } |
394 | |