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