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 < minimum(a: NewH, b: m_Height); y++)
41 mem_copy(dest: &pNewTuneData[y * NewW], source: &m_pTuneTile[y * m_Width], size: minimum(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: Editor()->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: Editor()->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 continue;
132 }
133 else
134 m_pTuneTile[TgtIndex].m_Number = Editor()->m_TuningNumber;
135 }
136
137 m_pTuneTile[TgtIndex].m_Type = pTuneLayer->m_pTiles[SrcIndex].m_Index;
138 m_pTiles[TgtIndex].m_Index = pTuneLayer->m_pTiles[SrcIndex].m_Index;
139 }
140 else
141 {
142 m_pTuneTile[TgtIndex].m_Number = 0;
143 m_pTuneTile[TgtIndex].m_Type = 0;
144 m_pTiles[TgtIndex].m_Index = 0;
145
146 if(pTuneLayer->m_pTiles[SrcIndex].m_Index != TILE_AIR)
147 ShowPreventUnusedTilesWarning();
148 }
149
150 STuneTileStateChange::SData Current{
151 .m_Number: m_pTuneTile[TgtIndex].m_Number,
152 .m_Type: m_pTuneTile[TgtIndex].m_Type,
153 .m_Index: m_pTiles[TgtIndex].m_Index};
154
155 RecordStateChange(x: fx, y: fy, Previous, Current);
156 }
157 FlagModified(x: sx, y: sy, w: pTuneLayer->m_Width, h: pTuneLayer->m_Height);
158}
159
160void CLayerTune::RecordStateChange(int x, int y, STuneTileStateChange::SData Previous, STuneTileStateChange::SData Current)
161{
162 if(!m_History[y][x].m_Changed)
163 m_History[y][x] = STuneTileStateChange{.m_Changed: true, .m_Previous: Previous, .m_Current: Current};
164 else
165 m_History[y][x].m_Current = Current;
166}
167
168void CLayerTune::BrushFlipX()
169{
170 CLayerTiles::BrushFlipX();
171 BrushFlipXImpl(pTiles: m_pTuneTile);
172}
173
174void CLayerTune::BrushFlipY()
175{
176 CLayerTiles::BrushFlipY();
177 BrushFlipYImpl(pTiles: m_pTuneTile);
178}
179
180void CLayerTune::BrushRotate(float Amount)
181{
182 int Rotation = (round_to_int(f: 360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270°
183 if(Rotation < 0)
184 Rotation += 4;
185
186 if(Rotation == 1 || Rotation == 3)
187 {
188 // 90° rotation
189 CTuneTile *pTempData1 = new CTuneTile[m_Width * m_Height];
190 CTile *pTempData2 = new CTile[m_Width * m_Height];
191 mem_copy(dest: pTempData1, source: m_pTuneTile, size: (size_t)m_Width * m_Height * sizeof(CTuneTile));
192 mem_copy(dest: pTempData2, source: m_pTiles, size: (size_t)m_Width * m_Height * sizeof(CTile));
193 CTuneTile *pDst1 = m_pTuneTile;
194 CTile *pDst2 = m_pTiles;
195 for(int x = 0; x < m_Width; ++x)
196 for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2)
197 {
198 *pDst1 = pTempData1[y * m_Width + x];
199 *pDst2 = pTempData2[y * m_Width + x];
200 }
201
202 std::swap(a&: m_Width, b&: m_Height);
203 delete[] pTempData1;
204 delete[] pTempData2;
205 }
206
207 if(Rotation == 2 || Rotation == 3)
208 {
209 BrushFlipX();
210 BrushFlipY();
211 }
212}
213
214void CLayerTune::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect)
215{
216 if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES))
217 return;
218
219 Snap(pRect: &Rect);
220
221 int sx = ConvertX(x: Rect.x);
222 int sy = ConvertY(y: Rect.y);
223 int w = ConvertX(x: Rect.w);
224 int h = ConvertY(y: Rect.h);
225
226 CLayerTune *pLt = static_cast<CLayerTune *>(pBrush);
227
228 bool Destructive = Editor()->m_BrushDrawDestructive || Empty || pLt->IsEmpty();
229
230 for(int y = 0; y < h; y++)
231 {
232 for(int x = 0; x < w; x++)
233 {
234 int fx = x + sx;
235 int fy = y + sy;
236
237 if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height)
238 continue;
239
240 if(!Destructive && GetTile(x: fx, y: fy).m_Index)
241 continue;
242
243 const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height);
244 const int TgtIndex = fy * m_Width + fx;
245
246 STuneTileStateChange::SData Previous{
247 .m_Number: m_pTuneTile[TgtIndex].m_Number,
248 .m_Type: m_pTuneTile[TgtIndex].m_Type,
249 .m_Index: m_pTiles[TgtIndex].m_Index};
250
251 if(Empty || (!Editor()->IsAllowPlaceUnusedTiles() && !IsValidTuneTile(Index: (pLt->m_pTiles[SrcIndex]).m_Index)))
252 {
253 m_pTiles[TgtIndex].m_Index = 0;
254 m_pTuneTile[TgtIndex].m_Type = 0;
255 m_pTuneTile[TgtIndex].m_Number = 0;
256
257 if(!Empty)
258 ShowPreventUnusedTilesWarning();
259 }
260 else
261 {
262 m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex];
263 if(pLt->m_HasTune && m_pTiles[TgtIndex].m_Index > 0)
264 {
265 m_pTuneTile[TgtIndex].m_Type = m_pTiles[fy * m_Width + fx].m_Index;
266
267 if((pLt->m_pTuneTile[SrcIndex].m_Number == 0 && Editor()->m_TuningNumber) || Editor()->m_TuningNumber != pLt->m_TuningNumber)
268 m_pTuneTile[TgtIndex].m_Number = Editor()->m_TuningNumber;
269 else
270 m_pTuneTile[TgtIndex].m_Number = pLt->m_pTuneTile[SrcIndex].m_Number;
271 }
272 else
273 {
274 m_pTiles[TgtIndex].m_Index = 0;
275 m_pTuneTile[TgtIndex].m_Type = 0;
276 m_pTuneTile[TgtIndex].m_Number = 0;
277 }
278 }
279
280 STuneTileStateChange::SData Current{
281 .m_Number: m_pTuneTile[TgtIndex].m_Number,
282 .m_Type: m_pTuneTile[TgtIndex].m_Type,
283 .m_Index: m_pTiles[TgtIndex].m_Index};
284
285 RecordStateChange(x: fx, y: fy, Previous, Current);
286 }
287 }
288
289 FlagModified(x: sx, y: sy, w, h);
290}
291
292int CLayerTune::FindNextFreeNumber() const
293{
294 for(int i = 1; i <= 255; i++)
295 {
296 if(!ContainsElementWithId(Id: i))
297 {
298 return i;
299 }
300 }
301 return -1;
302}
303
304bool CLayerTune::ContainsElementWithId(int Id) const
305{
306 for(int y = 0; y < m_Height; ++y)
307 {
308 for(int x = 0; x < m_Width; ++x)
309 {
310 if(IsValidTuneTile(Index: m_pTuneTile[y * m_Width + x].m_Type) && m_pTuneTile[y * m_Width + x].m_Number == Id)
311 {
312 return true;
313 }
314 }
315 }
316
317 return false;
318}
319
320void CLayerTune::GetPos(int Number, int Offset, ivec2 &Pos)
321{
322 int Match = -1;
323 ivec2 MatchPos = ivec2(-1, -1);
324 Pos = ivec2(-1, -1);
325
326 auto FindTile = [this, &Match, &MatchPos, &Number, &Offset]() {
327 for(int x = 0; x < m_Width; x++)
328 {
329 for(int y = 0; y < m_Height; y++)
330 {
331 int i = y * m_Width + x;
332 int Tune = m_pTuneTile[i].m_Number;
333 if(Number == Tune)
334 {
335 Match++;
336 if(Offset != -1)
337 {
338 if(Match == Offset)
339 {
340 MatchPos = ivec2(x, y);
341 m_GotoTuneOffset = Match;
342 return;
343 }
344 continue;
345 }
346 MatchPos = ivec2(x, y);
347 if(m_GotoTuneLastPos != ivec2(-1, -1))
348 {
349 if(distance(a: m_GotoTuneLastPos, b: MatchPos) < 10.0f)
350 {
351 m_GotoTuneOffset++;
352 continue;
353 }
354 }
355 m_GotoTuneLastPos = MatchPos;
356 if(Match == m_GotoTuneOffset)
357 return;
358 }
359 }
360 }
361 };
362 FindTile();
363
364 if(MatchPos == ivec2(-1, -1))
365 return;
366 if(Match < m_GotoTuneOffset)
367 m_GotoTuneOffset = -1;
368 Pos = MatchPos;
369 m_GotoTuneOffset++;
370}
371
372std::shared_ptr<CLayer> CLayerTune::Duplicate() const
373{
374 return std::make_shared<CLayerTune>(args: *this);
375}
376
377const char *CLayerTune::TypeName() const
378{
379 return "tune";
380}
381