1#include "layer_switch.h"
2
3#include <game/editor/editor.h>
4
5CLayerSwitch::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
17CLayerSwitch::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
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(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
54void CLayerSwitch::Shift(int Direction)
55{
56 CLayerTiles::Shift(Direction);
57 ShiftImpl(pTiles: m_pSwitchTile, Direction, ShiftBy: m_pEditor->m_ShiftBy);
58}
59
60bool 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
70void 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
166void 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
174void CLayerSwitch::BrushFlipX()
175{
176 CLayerTiles::BrushFlipX();
177 BrushFlipXImpl(pTiles: m_pSwitchTile);
178}
179
180void CLayerSwitch::BrushFlipY()
181{
182 CLayerTiles::BrushFlipY();
183 BrushFlipYImpl(pTiles: m_pSwitchTile);
184}
185
186void 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
226void 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
317bool 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
333void 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
385std::shared_ptr<CLayer> CLayerSwitch::Duplicate() const
386{
387 return std::make_shared<CLayerSwitch>(args: *this);
388}
389
390const char *CLayerSwitch::TypeName() const
391{
392 return "switch";
393}
394