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