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