1 | #include "layer_tele.h" |
2 | |
3 | #include <game/editor/editor.h> |
4 | |
5 | CLayerTele::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 | |
18 | CLayerTele::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 | |
28 | CLayerTele::~CLayerTele() |
29 | { |
30 | delete[] m_pTeleTile; |
31 | } |
32 | |
33 | void 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 | |
55 | void CLayerTele::Shift(int Direction) |
56 | { |
57 | CLayerTiles::Shift(Direction); |
58 | ShiftImpl(pTiles: m_pTeleTile, Direction, ShiftBy: m_pEditor->m_ShiftBy); |
59 | } |
60 | |
61 | bool 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 | |
71 | void 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 | |
168 | void 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 | |
178 | void CLayerTele::BrushFlipX() |
179 | { |
180 | CLayerTiles::BrushFlipX(); |
181 | BrushFlipXImpl(pTiles: m_pTeleTile); |
182 | } |
183 | |
184 | void CLayerTele::BrushFlipY() |
185 | { |
186 | CLayerTiles::BrushFlipY(); |
187 | BrushFlipYImpl(pTiles: m_pTeleTile); |
188 | } |
189 | |
190 | void 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 | |
224 | void 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 | |
306 | bool 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 | |
322 | void 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 | |
376 | std::shared_ptr<CLayer> CLayerTele::Duplicate() const |
377 | { |
378 | return std::make_shared<CLayerTele>(args: *this); |
379 | } |
380 | |
381 | const char *CLayerTele::TypeName() const |
382 | { |
383 | return "tele" ; |
384 | } |
385 | |