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