1#include "layer_speedup.h"
2
3#include <game/editor/editor.h>
4
5CLayerSpeedup::CLayerSpeedup(CEditorMap *pMap, int w, int h) :
6 CLayerTiles(pMap, w, h)
7{
8 str_copy(dst&: m_aName, src: "Speedup");
9 m_HasSpeedup = true;
10
11 m_pSpeedupTile = new CSpeedupTile[w * h];
12 mem_zero(block: m_pSpeedupTile, size: (size_t)w * h * sizeof(CSpeedupTile));
13}
14
15CLayerSpeedup::CLayerSpeedup(const CLayerSpeedup &Other) :
16 CLayerTiles(Other)
17{
18 str_copy(dst&: m_aName, src: "Speedup copy");
19 m_HasSpeedup = true;
20
21 m_pSpeedupTile = new CSpeedupTile[m_Width * m_Height];
22 mem_copy(dest: m_pSpeedupTile, source: Other.m_pSpeedupTile, size: (size_t)m_Width * m_Height * sizeof(CSpeedupTile));
23}
24
25CLayerSpeedup::~CLayerSpeedup()
26{
27 delete[] m_pSpeedupTile;
28}
29
30void CLayerSpeedup::Resize(int NewW, int NewH)
31{
32 // resize speedup data
33 CSpeedupTile *pNewSpeedupData = new CSpeedupTile[NewW * NewH];
34 mem_zero(block: pNewSpeedupData, size: (size_t)NewW * NewH * sizeof(CSpeedupTile));
35
36 // copy old data
37 for(int y = 0; y < minimum(a: NewH, b: m_Height); y++)
38 mem_copy(dest: &pNewSpeedupData[y * NewW], source: &m_pSpeedupTile[y * m_Width], size: minimum(a: m_Width, b: NewW) * sizeof(CSpeedupTile));
39
40 // replace old
41 delete[] m_pSpeedupTile;
42 m_pSpeedupTile = pNewSpeedupData;
43
44 // resize tile data
45 CLayerTiles::Resize(NewW, NewH);
46
47 // resize gamelayer too
48 if(Map()->m_pGameLayer->m_Width != NewW || Map()->m_pGameLayer->m_Height != NewH)
49 Map()->m_pGameLayer->Resize(NewW, NewH);
50}
51
52void CLayerSpeedup::Shift(EShiftDirection Direction)
53{
54 CLayerTiles::Shift(Direction);
55 ShiftImpl(pTiles: m_pSpeedupTile, Direction, ShiftBy: Editor()->m_ShiftBy);
56}
57
58bool CLayerSpeedup::IsEmpty() const
59{
60 for(int y = 0; y < m_Height; y++)
61 {
62 for(int x = 0; x < m_Width; x++)
63 {
64 const int Index = GetTile(x, y).m_Index;
65 if(Index == 0)
66 {
67 continue;
68 }
69 if(Editor()->IsAllowPlaceUnusedTiles() || IsValidSpeedupTile(Index))
70 {
71 return false;
72 }
73 }
74 }
75 return true;
76}
77
78void CLayerSpeedup::BrushDraw(CLayer *pBrush, vec2 WorldPos)
79{
80 if(m_Readonly)
81 return;
82
83 CLayerSpeedup *pSpeedupLayer = static_cast<CLayerSpeedup *>(pBrush);
84 int sx = ConvertX(x: WorldPos.x);
85 int sy = ConvertY(y: WorldPos.y);
86 if(str_comp(a: pSpeedupLayer->m_aFilename, b: Editor()->m_aFilename))
87 {
88 Editor()->m_SpeedupAngle = pSpeedupLayer->m_SpeedupAngle;
89 Editor()->m_SpeedupForce = pSpeedupLayer->m_SpeedupForce;
90 Editor()->m_SpeedupMaxSpeed = pSpeedupLayer->m_SpeedupMaxSpeed;
91 }
92
93 bool Destructive = Editor()->m_BrushDrawDestructive || pSpeedupLayer->IsEmpty();
94
95 for(int y = 0; y < pSpeedupLayer->m_Height; y++)
96 for(int x = 0; x < pSpeedupLayer->m_Width; x++)
97 {
98 int fx = x + sx;
99 int fy = y + sy;
100
101 if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height)
102 continue;
103
104 if(!Destructive && GetTile(x: fx, y: fy).m_Index)
105 continue;
106
107 const int SrcIndex = y * pSpeedupLayer->m_Width + x;
108 const int TgtIndex = fy * m_Width + fx;
109
110 SSpeedupTileStateChange::SData Previous{
111 .m_Force: m_pSpeedupTile[TgtIndex].m_Force,
112 .m_Angle: m_pSpeedupTile[TgtIndex].m_Angle,
113 .m_MaxSpeed: m_pSpeedupTile[TgtIndex].m_MaxSpeed,
114 .m_Type: m_pSpeedupTile[TgtIndex].m_Type,
115 .m_Index: m_pTiles[TgtIndex].m_Index};
116
117 if((Editor()->IsAllowPlaceUnusedTiles() || IsValidSpeedupTile(Index: pSpeedupLayer->m_pTiles[SrcIndex].m_Index)) && pSpeedupLayer->m_pTiles[SrcIndex].m_Index != TILE_AIR)
118 {
119 if(Editor()->m_SpeedupAngle != pSpeedupLayer->m_SpeedupAngle || Editor()->m_SpeedupForce != pSpeedupLayer->m_SpeedupForce || Editor()->m_SpeedupMaxSpeed != pSpeedupLayer->m_SpeedupMaxSpeed)
120 {
121 m_pSpeedupTile[TgtIndex].m_Force = Editor()->m_SpeedupForce;
122 m_pSpeedupTile[TgtIndex].m_MaxSpeed = Editor()->m_SpeedupMaxSpeed;
123 m_pSpeedupTile[TgtIndex].m_Angle = Editor()->m_SpeedupAngle;
124 m_pSpeedupTile[TgtIndex].m_Type = pSpeedupLayer->m_pTiles[SrcIndex].m_Index;
125 m_pTiles[TgtIndex].m_Index = pSpeedupLayer->m_pTiles[SrcIndex].m_Index;
126 }
127 else if(pSpeedupLayer->m_pSpeedupTile[SrcIndex].m_Force)
128 {
129 m_pSpeedupTile[TgtIndex].m_Force = pSpeedupLayer->m_pSpeedupTile[SrcIndex].m_Force;
130 m_pSpeedupTile[TgtIndex].m_Angle = pSpeedupLayer->m_pSpeedupTile[SrcIndex].m_Angle;
131 m_pSpeedupTile[TgtIndex].m_MaxSpeed = pSpeedupLayer->m_pSpeedupTile[SrcIndex].m_MaxSpeed;
132 m_pSpeedupTile[TgtIndex].m_Type = pSpeedupLayer->m_pTiles[SrcIndex].m_Index;
133 m_pTiles[TgtIndex].m_Index = pSpeedupLayer->m_pTiles[SrcIndex].m_Index;
134 }
135 else if(Editor()->m_SpeedupForce)
136 {
137 m_pSpeedupTile[TgtIndex].m_Force = Editor()->m_SpeedupForce;
138 m_pSpeedupTile[TgtIndex].m_MaxSpeed = Editor()->m_SpeedupMaxSpeed;
139 m_pSpeedupTile[TgtIndex].m_Angle = Editor()->m_SpeedupAngle;
140 m_pSpeedupTile[TgtIndex].m_Type = pSpeedupLayer->m_pTiles[SrcIndex].m_Index;
141 m_pTiles[TgtIndex].m_Index = pSpeedupLayer->m_pTiles[SrcIndex].m_Index;
142 }
143 else
144 {
145 m_pSpeedupTile[TgtIndex].m_Force = 0;
146 m_pSpeedupTile[TgtIndex].m_MaxSpeed = 0;
147 m_pSpeedupTile[TgtIndex].m_Angle = 0;
148 m_pSpeedupTile[TgtIndex].m_Type = 0;
149 m_pTiles[TgtIndex].m_Index = 0;
150 }
151 }
152 else
153 {
154 m_pSpeedupTile[TgtIndex].m_Force = 0;
155 m_pSpeedupTile[TgtIndex].m_MaxSpeed = 0;
156 m_pSpeedupTile[TgtIndex].m_Angle = 0;
157 m_pSpeedupTile[TgtIndex].m_Type = 0;
158 m_pTiles[TgtIndex].m_Index = 0;
159
160 if(pSpeedupLayer->m_pTiles[SrcIndex].m_Index != TILE_AIR)
161 ShowPreventUnusedTilesWarning();
162 }
163
164 SSpeedupTileStateChange::SData Current{
165 .m_Force: m_pSpeedupTile[TgtIndex].m_Force,
166 .m_Angle: m_pSpeedupTile[TgtIndex].m_Angle,
167 .m_MaxSpeed: m_pSpeedupTile[TgtIndex].m_MaxSpeed,
168 .m_Type: m_pSpeedupTile[TgtIndex].m_Type,
169 .m_Index: m_pTiles[TgtIndex].m_Index};
170
171 RecordStateChange(x: fx, y: fy, Previous, Current);
172 }
173 FlagModified(x: sx, y: sy, w: pSpeedupLayer->m_Width, h: pSpeedupLayer->m_Height);
174}
175
176void CLayerSpeedup::RecordStateChange(int x, int y, SSpeedupTileStateChange::SData Previous, SSpeedupTileStateChange::SData Current)
177{
178 if(!m_History[y][x].m_Changed)
179 m_History[y][x] = SSpeedupTileStateChange{.m_Changed: true, .m_Previous: Previous, .m_Current: Current};
180 else
181 m_History[y][x].m_Current = Current;
182}
183
184void CLayerSpeedup::BrushFlipX()
185{
186 CLayerTiles::BrushFlipX();
187 BrushFlipXImpl(pTiles: m_pSpeedupTile);
188
189 auto &&AngleFlipX = [](auto &Number) {
190 Number = (180 - Number % 360 + 360) % 360;
191 };
192
193 for(int y = 0; y < m_Height; y++)
194 for(int x = 0; x < m_Width; x++)
195 AngleFlipX(m_pSpeedupTile[y * m_Width + x].m_Angle);
196}
197
198void CLayerSpeedup::BrushFlipY()
199{
200 CLayerTiles::BrushFlipY();
201 BrushFlipYImpl(pTiles: m_pSpeedupTile);
202
203 auto &&AngleFlipY = [](auto &Number) {
204 Number = (360 - Number % 360 + 360) % 360;
205 };
206
207 for(int y = 0; y < m_Height; y++)
208 for(int x = 0; x < m_Width; x++)
209 AngleFlipY(m_pSpeedupTile[y * m_Width + x].m_Angle);
210}
211
212void CLayerSpeedup::BrushRotate(float Amount)
213{
214 int Rotation = (round_to_int(f: 360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270°
215 if(Rotation < 0)
216 Rotation += 4;
217
218 // 1 and 3 are both adjusted by 90, because for 3 the brush is also flipped
219 int Adjust = (Rotation == 0) ? 0 : ((Rotation == 2) ? 180 : 90);
220 auto &&AdjustAngle = [Adjust](auto &Number) {
221 Number = (Number + Adjust % 360 + 360) % 360;
222 };
223
224 if(Rotation == 1 || Rotation == 3)
225 {
226 // 90° rotation
227 CSpeedupTile *pTempData1 = new CSpeedupTile[m_Width * m_Height];
228 CTile *pTempData2 = new CTile[m_Width * m_Height];
229 mem_copy(dest: pTempData1, source: m_pSpeedupTile, size: (size_t)m_Width * m_Height * sizeof(CSpeedupTile));
230 mem_copy(dest: pTempData2, source: m_pTiles, size: (size_t)m_Width * m_Height * sizeof(CTile));
231 CSpeedupTile *pDst1 = m_pSpeedupTile;
232 CTile *pDst2 = m_pTiles;
233 for(int x = 0; x < m_Width; ++x)
234 for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2)
235 {
236 AdjustAngle(pTempData1[y * m_Width + x].m_Angle);
237 *pDst1 = pTempData1[y * m_Width + x];
238 *pDst2 = pTempData2[y * m_Width + x];
239 }
240
241 std::swap(a&: m_Width, b&: m_Height);
242 delete[] pTempData1;
243 delete[] pTempData2;
244 }
245
246 if(Rotation == 2 || Rotation == 3)
247 {
248 BrushFlipX();
249 BrushFlipY();
250 }
251}
252
253void CLayerSpeedup::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect)
254{
255 if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES))
256 return;
257
258 Snap(pRect: &Rect);
259
260 int sx = ConvertX(x: Rect.x);
261 int sy = ConvertY(y: Rect.y);
262 int w = ConvertX(x: Rect.w);
263 int h = ConvertY(y: Rect.h);
264
265 CLayerSpeedup *pLt = static_cast<CLayerSpeedup *>(pBrush);
266
267 bool Destructive = Editor()->m_BrushDrawDestructive || Empty || pLt->IsEmpty();
268
269 for(int y = 0; y < h; y++)
270 {
271 for(int x = 0; x < w; x++)
272 {
273 int fx = x + sx;
274 int fy = y + sy;
275
276 if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height)
277 continue;
278
279 if(!Destructive && GetTile(x: fx, y: fy).m_Index)
280 continue;
281
282 const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height);
283 const int TgtIndex = fy * m_Width + fx;
284
285 SSpeedupTileStateChange::SData Previous{
286 .m_Force: m_pSpeedupTile[TgtIndex].m_Force,
287 .m_Angle: m_pSpeedupTile[TgtIndex].m_Angle,
288 .m_MaxSpeed: m_pSpeedupTile[TgtIndex].m_MaxSpeed,
289 .m_Type: m_pSpeedupTile[TgtIndex].m_Type,
290 .m_Index: m_pTiles[TgtIndex].m_Index};
291
292 if(Empty || (!Editor()->IsAllowPlaceUnusedTiles() && !IsValidSpeedupTile(Index: (pLt->m_pTiles[SrcIndex]).m_Index))) // no speed up tile chosen: reset
293 {
294 m_pTiles[TgtIndex].m_Index = 0;
295 m_pSpeedupTile[TgtIndex].m_Force = 0;
296 m_pSpeedupTile[TgtIndex].m_Angle = 0;
297 m_pSpeedupTile[TgtIndex].m_MaxSpeed = 0;
298 m_pSpeedupTile[TgtIndex].m_Type = 0;
299
300 if(!Empty)
301 ShowPreventUnusedTilesWarning();
302 }
303 else
304 {
305 m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex];
306 if(pLt->m_HasSpeedup && m_pTiles[TgtIndex].m_Index > 0)
307 {
308 m_pSpeedupTile[TgtIndex].m_Type = m_pTiles[TgtIndex].m_Index;
309
310 if((pLt->m_pSpeedupTile[SrcIndex].m_Force == 0 && Editor()->m_SpeedupForce) || Editor()->m_SpeedupForce != pLt->m_SpeedupForce)
311 m_pSpeedupTile[TgtIndex].m_Force = Editor()->m_SpeedupForce;
312 else
313 m_pSpeedupTile[TgtIndex].m_Force = pLt->m_pSpeedupTile[SrcIndex].m_Force;
314
315 if((pLt->m_pSpeedupTile[SrcIndex].m_Angle == 0 && Editor()->m_SpeedupAngle) || Editor()->m_SpeedupAngle != pLt->m_SpeedupAngle)
316 m_pSpeedupTile[TgtIndex].m_Angle = Editor()->m_SpeedupAngle;
317 else
318 m_pSpeedupTile[TgtIndex].m_Angle = pLt->m_pSpeedupTile[SrcIndex].m_Angle;
319
320 if((pLt->m_pSpeedupTile[SrcIndex].m_MaxSpeed == 0 && Editor()->m_SpeedupMaxSpeed) || Editor()->m_SpeedupMaxSpeed != pLt->m_SpeedupMaxSpeed)
321 m_pSpeedupTile[TgtIndex].m_MaxSpeed = Editor()->m_SpeedupMaxSpeed;
322 else
323 m_pSpeedupTile[TgtIndex].m_MaxSpeed = pLt->m_pSpeedupTile[SrcIndex].m_MaxSpeed;
324 }
325 else
326 {
327 m_pTiles[TgtIndex].m_Index = 0;
328 m_pSpeedupTile[TgtIndex].m_Force = 0;
329 m_pSpeedupTile[TgtIndex].m_Angle = 0;
330 m_pSpeedupTile[TgtIndex].m_MaxSpeed = 0;
331 m_pSpeedupTile[TgtIndex].m_Type = 0;
332 }
333 }
334
335 SSpeedupTileStateChange::SData Current{
336 .m_Force: m_pSpeedupTile[TgtIndex].m_Force,
337 .m_Angle: m_pSpeedupTile[TgtIndex].m_Angle,
338 .m_MaxSpeed: m_pSpeedupTile[TgtIndex].m_MaxSpeed,
339 .m_Type: m_pSpeedupTile[TgtIndex].m_Type,
340 .m_Index: m_pTiles[TgtIndex].m_Index};
341
342 RecordStateChange(x: fx, y: fy, Previous, Current);
343 }
344 }
345 FlagModified(x: sx, y: sy, w, h);
346}
347
348std::shared_ptr<CLayer> CLayerSpeedup::Duplicate() const
349{
350 return std::make_shared<CLayerSpeedup>(args: *this);
351}
352
353const char *CLayerSpeedup::TypeName() const
354{
355 return "speedup";
356}
357