1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "layer_tiles.h"
4
5#include "image.h"
6
7#include <engine/keys.h>
8#include <engine/shared/config.h>
9#include <engine/shared/map.h>
10
11#include <game/editor/editor.h>
12#include <game/editor/editor_actions.h>
13#include <game/editor/enums.h>
14
15#include <iterator>
16#include <numeric>
17
18CLayerTiles::CLayerTiles(CEditorMap *pMap, int w, int h) :
19 CLayer(pMap, LAYERTYPE_TILES)
20{
21 m_aName[0] = '\0';
22 m_Width = w;
23 m_Height = h;
24 m_Image = -1;
25 m_HasGame = false;
26 m_Color.r = 255;
27 m_Color.g = 255;
28 m_Color.b = 255;
29 m_Color.a = 255;
30 m_ColorEnv = -1;
31 m_ColorEnvOffset = 0;
32
33 m_HasTele = false;
34 m_HasSpeedup = false;
35 m_HasFront = false;
36 m_HasSwitch = false;
37 m_HasTune = false;
38 m_AutoMapperConfig = -1;
39 m_AutoMapperReference = -1;
40 m_Seed = 0;
41 m_AutoAutoMap = false;
42
43 m_pTiles = new CTile[m_Width * m_Height];
44 mem_zero(block: m_pTiles, size: (size_t)m_Width * m_Height * sizeof(CTile));
45}
46
47CLayerTiles::CLayerTiles(const CLayerTiles &Other) :
48 CLayer(Other)
49{
50 m_Width = Other.m_Width;
51 m_Height = Other.m_Height;
52 m_pTiles = new CTile[m_Width * m_Height];
53 mem_copy(dest: m_pTiles, source: Other.m_pTiles, size: (size_t)m_Width * m_Height * sizeof(CTile));
54
55 m_Image = Other.m_Image;
56 m_HasGame = Other.m_HasGame;
57 m_Color = Other.m_Color;
58 m_ColorEnv = Other.m_ColorEnv;
59 m_ColorEnvOffset = Other.m_ColorEnvOffset;
60
61 m_AutoMapperConfig = Other.m_AutoMapperConfig;
62 m_AutoMapperReference = Other.m_AutoMapperReference;
63 m_Seed = Other.m_Seed;
64 m_AutoAutoMap = Other.m_AutoAutoMap;
65 m_HasTele = Other.m_HasTele;
66 m_HasSpeedup = Other.m_HasSpeedup;
67 m_HasFront = Other.m_HasFront;
68 m_HasSwitch = Other.m_HasSwitch;
69 m_HasTune = Other.m_HasTune;
70
71 str_copy(dst&: m_aFilename, src: Other.m_aFilename);
72}
73
74CLayerTiles::~CLayerTiles()
75{
76 delete[] m_pTiles;
77}
78
79CTile CLayerTiles::GetTile(int x, int y) const
80{
81 return m_pTiles[y * m_Width + x];
82}
83
84void CLayerTiles::SetTile(int x, int y, CTile Tile)
85{
86 auto CurrentTile = m_pTiles[y * m_Width + x];
87 SetTileIgnoreHistory(x, y, Tile);
88 RecordStateChange(x, y, Previous: CurrentTile, Tile);
89
90 if(m_FillGameTile != -1 && m_LiveGameTiles)
91 {
92 std::shared_ptr<CLayerTiles> pLayer = Map()->m_pGameLayer;
93 if(m_FillGameTile == TILE_TELECHECKIN || m_FillGameTile == TILE_TELECHECKINEVIL)
94 {
95 if(!Map()->m_pTeleLayer)
96 {
97 std::shared_ptr<CLayerTele> pLayerTele = std::make_shared<CLayerTele>(args: Map(), args&: m_Width, args&: m_Height);
98 Map()->MakeTeleLayer(pLayer: pLayerTele);
99 Map()->m_pGameGroup->AddLayer(pLayer: pLayerTele);
100 int GameGroupIndex = std::find(first: Map()->m_vpGroups.begin(), last: Map()->m_vpGroups.end(), val: Map()->m_pGameGroup) - Map()->m_vpGroups.begin();
101 int LayerIndex = Map()->m_vpGroups[GameGroupIndex]->m_vpLayers.size() - 1;
102 Map()->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAddLayer>(args: Map(), args&: GameGroupIndex, args&: LayerIndex));
103 }
104
105 pLayer = Map()->m_pTeleLayer;
106 }
107
108 bool HasTile = Tile.m_Index != 0;
109 pLayer->SetTile(x, y, Tile: CTile{.m_Index: (unsigned char)(HasTile ? m_FillGameTile : TILE_AIR)});
110 }
111}
112
113void CLayerTiles::SetTileIgnoreHistory(int x, int y, CTile Tile) const
114{
115 m_pTiles[y * m_Width + x] = Tile;
116}
117
118void CLayerTiles::RecordStateChange(int x, int y, CTile Previous, CTile Tile)
119{
120 if(!m_TilesHistory[y][x].m_Changed)
121 m_TilesHistory[y][x] = STileStateChange{.m_Changed: true, .m_Previous: Previous, .m_Current: Tile};
122 else
123 m_TilesHistory[y][x].m_Current = Tile;
124}
125
126void CLayerTiles::PrepareForSave()
127{
128 for(int y = 0; y < m_Height; y++)
129 for(int x = 0; x < m_Width; x++)
130 m_pTiles[y * m_Width + x].m_Flags &= TILEFLAG_XFLIP | TILEFLAG_YFLIP | TILEFLAG_ROTATE;
131
132 if(m_Image != -1 && m_Color.a == 255)
133 {
134 for(int y = 0; y < m_Height; y++)
135 for(int x = 0; x < m_Width; x++)
136 m_pTiles[y * m_Width + x].m_Flags |= Map()->m_vpImages[m_Image]->m_aTileFlags[m_pTiles[y * m_Width + x].m_Index];
137 }
138}
139
140void CLayerTiles::ExtractTiles(int TilemapItemVersion, const CTile *pSavedTiles, size_t SavedTilesSize) const
141{
142 const size_t DestSize = (size_t)m_Width * m_Height;
143 if(TilemapItemVersion >= CMapItemLayerTilemap::VERSION_TEEWORLDS_TILESKIP)
144 {
145 CMap::ExtractTiles(pDest: m_pTiles, DestSize, pSrc: pSavedTiles, SrcSize: SavedTilesSize);
146 }
147 else if(SavedTilesSize >= DestSize)
148 {
149 mem_copy(dest: m_pTiles, source: pSavedTiles, size: DestSize * sizeof(CTile));
150 for(size_t TileIndex = 0; TileIndex < DestSize; ++TileIndex)
151 {
152 m_pTiles[TileIndex].m_Skip = 0;
153 m_pTiles[TileIndex].m_Reserved = 0;
154 }
155 }
156}
157
158void CLayerTiles::MakePalette() const
159{
160 for(int y = 0; y < m_Height; y++)
161 for(int x = 0; x < m_Width; x++)
162 m_pTiles[y * m_Width + x].m_Index = y * 16 + x;
163}
164
165void CLayerTiles::Render(bool Tileset)
166{
167 IGraphics::CTextureHandle Texture;
168 if(m_Image >= 0 && (size_t)m_Image < Map()->m_vpImages.size())
169 Texture = Map()->m_vpImages[m_Image]->m_Texture;
170 else if(m_HasGame)
171 Texture = Editor()->GetEntitiesTexture();
172 else if(m_HasFront)
173 Texture = Editor()->GetFrontTexture();
174 else if(m_HasTele)
175 Texture = Editor()->GetTeleTexture();
176 else if(m_HasSpeedup)
177 Texture = Editor()->GetSpeedupTexture();
178 else if(m_HasSwitch)
179 Texture = Editor()->GetSwitchTexture();
180 else if(m_HasTune)
181 Texture = Editor()->GetTuneTexture();
182 Graphics()->TextureSet(Texture);
183
184 ColorRGBA ColorEnv = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
185 Editor()->EnvelopeEval(TimeOffsetMillis: m_ColorEnvOffset, EnvelopeIndex: m_ColorEnv, Result&: ColorEnv, Channels: 4);
186 const ColorRGBA Color = ColorRGBA(m_Color.r / 255.0f, m_Color.g / 255.0f, m_Color.b / 255.0f, m_Color.a / 255.0f).Multiply(Other: ColorEnv);
187
188 Graphics()->BlendNone();
189 Editor()->RenderMap()->RenderTilemap(pTiles: m_pTiles, w: m_Width, h: m_Height, Scale: 32.0f, Color, RenderFlags: LAYERRENDERFLAG_OPAQUE);
190 Graphics()->BlendNormal();
191 Editor()->RenderMap()->RenderTilemap(pTiles: m_pTiles, w: m_Width, h: m_Height, Scale: 32.0f, Color, RenderFlags: LAYERRENDERFLAG_TRANSPARENT);
192
193 // Render DDRace Layers
194 if(!Tileset)
195 {
196 int OverlayRenderFlags = (g_Config.m_ClTextEntitiesEditor ? OVERLAYRENDERFLAG_TEXT : 0) | OVERLAYRENDERFLAG_EDITOR;
197 if(m_HasTele)
198 Editor()->RenderMap()->RenderTeleOverlay(pTele: static_cast<CLayerTele *>(this)->m_pTeleTile, w: m_Width, h: m_Height, Scale: 32.0f, OverlayRenderFlags);
199 if(m_HasSpeedup)
200 Editor()->RenderMap()->RenderSpeedupOverlay(pSpeedup: static_cast<CLayerSpeedup *>(this)->m_pSpeedupTile, w: m_Width, h: m_Height, Scale: 32.0f, OverlayRenderFlags);
201 if(m_HasSwitch)
202 Editor()->RenderMap()->RenderSwitchOverlay(pSwitch: static_cast<CLayerSwitch *>(this)->m_pSwitchTile, w: m_Width, h: m_Height, Scale: 32.0f, OverlayRenderFlags);
203 if(m_HasTune)
204 Editor()->RenderMap()->RenderTuneOverlay(pTune: static_cast<CLayerTune *>(this)->m_pTuneTile, w: m_Width, h: m_Height, Scale: 32.0f, OverlayRenderFlags);
205 }
206}
207
208int CLayerTiles::ConvertX(float x) const { return (int)(x / 32.0f); }
209int CLayerTiles::ConvertY(float y) const { return (int)(y / 32.0f); }
210
211void CLayerTiles::Convert(CUIRect Rect, CIntRect *pOut) const
212{
213 pOut->x = ConvertX(x: Rect.x);
214 pOut->y = ConvertY(y: Rect.y);
215 pOut->w = ConvertX(x: Rect.x + Rect.w + 31) - pOut->x;
216 pOut->h = ConvertY(y: Rect.y + Rect.h + 31) - pOut->y;
217}
218
219void CLayerTiles::Snap(CUIRect *pRect) const
220{
221 CIntRect Out;
222 Convert(Rect: *pRect, pOut: &Out);
223 pRect->x = Out.x * 32.0f;
224 pRect->y = Out.y * 32.0f;
225 pRect->w = Out.w * 32.0f;
226 pRect->h = Out.h * 32.0f;
227}
228
229void CLayerTiles::Clamp(CIntRect *pRect) const
230{
231 if(pRect->x < 0)
232 {
233 pRect->w += pRect->x;
234 pRect->x = 0;
235 }
236
237 if(pRect->y < 0)
238 {
239 pRect->h += pRect->y;
240 pRect->y = 0;
241 }
242
243 if(pRect->x + pRect->w > m_Width)
244 pRect->w = m_Width - pRect->x;
245
246 if(pRect->y + pRect->h > m_Height)
247 pRect->h = m_Height - pRect->y;
248
249 if(pRect->h < 0)
250 pRect->h = 0;
251 if(pRect->w < 0)
252 pRect->w = 0;
253}
254
255bool CLayerTiles::IsEntitiesLayer() const
256{
257 return Map()->m_pGameLayer.get() == this || Map()->m_pTeleLayer.get() == this || Map()->m_pSpeedupLayer.get() == this || Map()->m_pFrontLayer.get() == this || Map()->m_pSwitchLayer.get() == this || Map()->m_pTuneLayer.get() == this;
258}
259
260bool CLayerTiles::IsEmpty() const
261{
262 for(int y = 0; y < m_Height; y++)
263 {
264 for(int x = 0; x < m_Width; x++)
265 {
266 if(GetTile(x, y).m_Index != 0)
267 {
268 return false;
269 }
270 }
271 }
272 return true;
273}
274
275void CLayerTiles::BrushSelecting(CUIRect Rect)
276{
277 Graphics()->TextureClear();
278 Graphics()->QuadsBegin();
279 Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 0.4f);
280 Snap(pRect: &Rect);
281 IGraphics::CQuadItem QuadItem(Rect.x, Rect.y, Rect.w, Rect.h);
282 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
283 Graphics()->QuadsEnd();
284 char aBuf[16];
285 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d⨯%d", ConvertX(x: Rect.w), ConvertY(y: Rect.h));
286 TextRender()->Text(x: Rect.x + 3.0f, y: Rect.y + 3.0f, Size: Editor()->m_ShowPicker ? 15.0f : Editor()->MapView()->ScaleLength(Value: 15.0f), pText: aBuf, LineWidth: -1.0f);
287}
288
289template<typename T>
290static void InitGrabbedLayer(std::shared_ptr<T> &pLayer, CLayerTiles *pThisLayer)
291{
292 pLayer->m_Image = pThisLayer->m_Image;
293 pLayer->m_HasGame = pThisLayer->m_HasGame;
294 pLayer->m_HasFront = pThisLayer->m_HasFront;
295 pLayer->m_HasTele = pThisLayer->m_HasTele;
296 pLayer->m_HasSpeedup = pThisLayer->m_HasSpeedup;
297 pLayer->m_HasSwitch = pThisLayer->m_HasSwitch;
298 pLayer->m_HasTune = pThisLayer->m_HasTune;
299 if(pThisLayer->Editor()->m_BrushColorEnabled)
300 {
301 pLayer->m_Color = pThisLayer->m_Color;
302 pLayer->m_Color.a = 255;
303 }
304}
305
306int CLayerTiles::BrushGrab(CLayerGroup *pBrush, CUIRect Rect)
307{
308 CIntRect r;
309 Convert(Rect, pOut: &r);
310 Clamp(pRect: &r);
311
312 if(!r.w || !r.h)
313 return 0;
314
315 // create new layers
316 if(m_HasTele)
317 {
318 std::shared_ptr<CLayerTele> pGrabbed = std::make_shared<CLayerTele>(args: pBrush->Map(), args&: r.w, args&: r.h);
319 InitGrabbedLayer(pLayer&: pGrabbed, pThisLayer: this);
320
321 pBrush->AddLayer(pLayer: pGrabbed);
322
323 for(int y = 0; y < r.h; y++)
324 {
325 for(int x = 0; x < r.w; x++)
326 {
327 // copy the tiles
328 pGrabbed->m_pTiles[y * pGrabbed->m_Width + x] = GetTile(x: r.x + x, y: r.y + y);
329
330 // copy the tele data
331 if(!Editor()->Input()->KeyIsPressed(Key: KEY_SPACE))
332 {
333 pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x] = static_cast<CLayerTele *>(this)->m_pTeleTile[(r.y + y) * m_Width + (r.x + x)];
334 unsigned char TgtIndex = pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Type;
335 if(IsValidTeleTile(Index: TgtIndex))
336 {
337 if(IsTeleTileNumberUsed(Index: TgtIndex, Checkpoint: false))
338 Editor()->m_TeleNumber = pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Number;
339 else if(IsTeleTileNumberUsed(Index: TgtIndex, Checkpoint: true))
340 Editor()->m_TeleCheckpointNumber = pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Number;
341 }
342 }
343 else
344 {
345 const CTile &Tile = pGrabbed->m_pTiles[y * pGrabbed->m_Width + x];
346 if(IsValidTeleTile(Index: Tile.m_Index) && IsTeleTileNumberUsedAny(Index: Tile.m_Index))
347 {
348 pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Type = Tile.m_Index;
349 pGrabbed->m_pTeleTile[y * pGrabbed->m_Width + x].m_Number = IsTeleTileCheckpoint(Index: Tile.m_Index) ? Editor()->m_TeleCheckpointNumber : Editor()->m_TeleNumber;
350 }
351 }
352 }
353 }
354
355 pGrabbed->m_TeleNumber = Editor()->m_TeleNumber;
356 pGrabbed->m_TeleCheckpointNumber = Editor()->m_TeleCheckpointNumber;
357
358 str_copy(dst&: pGrabbed->m_aFilename, src: pGrabbed->Map()->m_aFilename);
359 }
360 else if(m_HasSpeedup)
361 {
362 std::shared_ptr<CLayerSpeedup> pGrabbed = std::make_shared<CLayerSpeedup>(args: pBrush->Map(), args&: r.w, args&: r.h);
363 InitGrabbedLayer(pLayer&: pGrabbed, pThisLayer: this);
364
365 pBrush->AddLayer(pLayer: pGrabbed);
366
367 for(int y = 0; y < r.h; y++)
368 {
369 for(int x = 0; x < r.w; x++)
370 {
371 // copy the tiles
372 pGrabbed->m_pTiles[y * pGrabbed->m_Width + x] = GetTile(x: r.x + x, y: r.y + y);
373
374 // copy the speedup data
375 if(!Editor()->Input()->KeyIsPressed(Key: KEY_SPACE))
376 {
377 pGrabbed->m_pSpeedupTile[y * pGrabbed->m_Width + x] = static_cast<CLayerSpeedup *>(this)->m_pSpeedupTile[(r.y + y) * m_Width + (r.x + x)];
378 if(IsValidSpeedupTile(Index: pGrabbed->m_pSpeedupTile[y * pGrabbed->m_Width + x].m_Type))
379 {
380 Editor()->m_SpeedupAngle = pGrabbed->m_pSpeedupTile[y * pGrabbed->m_Width + x].m_Angle;
381 Editor()->m_SpeedupForce = pGrabbed->m_pSpeedupTile[y * pGrabbed->m_Width + x].m_Force;
382 Editor()->m_SpeedupMaxSpeed = pGrabbed->m_pSpeedupTile[y * pGrabbed->m_Width + x].m_MaxSpeed;
383 }
384 }
385 else
386 {
387 const CTile &Tile = pGrabbed->m_pTiles[y * pGrabbed->m_Width + x];
388 if(IsValidSpeedupTile(Index: Tile.m_Index))
389 {
390 pGrabbed->m_pSpeedupTile[y * pGrabbed->m_Width + x].m_Type = Tile.m_Index;
391 pGrabbed->m_pSpeedupTile[y * pGrabbed->m_Width + x].m_Angle = Editor()->m_SpeedupAngle;
392 pGrabbed->m_pSpeedupTile[y * pGrabbed->m_Width + x].m_Force = Editor()->m_SpeedupForce;
393 pGrabbed->m_pSpeedupTile[y * pGrabbed->m_Width + x].m_MaxSpeed = Editor()->m_SpeedupMaxSpeed;
394 }
395 }
396 }
397 }
398
399 pGrabbed->m_SpeedupForce = Editor()->m_SpeedupForce;
400 pGrabbed->m_SpeedupMaxSpeed = Editor()->m_SpeedupMaxSpeed;
401 pGrabbed->m_SpeedupAngle = Editor()->m_SpeedupAngle;
402 str_copy(dst&: pGrabbed->m_aFilename, src: pGrabbed->Map()->m_aFilename);
403 }
404 else if(m_HasSwitch)
405 {
406 std::shared_ptr<CLayerSwitch> pGrabbed = std::make_shared<CLayerSwitch>(args: pBrush->Map(), args&: r.w, args&: r.h);
407 InitGrabbedLayer(pLayer&: pGrabbed, pThisLayer: this);
408
409 pBrush->AddLayer(pLayer: pGrabbed);
410
411 for(int y = 0; y < r.h; y++)
412 {
413 for(int x = 0; x < r.w; x++)
414 {
415 // copy the tiles
416 pGrabbed->m_pTiles[y * pGrabbed->m_Width + x] = GetTile(x: r.x + x, y: r.y + y);
417
418 // copy the switch data
419 if(!Editor()->Input()->KeyIsPressed(Key: KEY_SPACE))
420 {
421 pGrabbed->m_pSwitchTile[y * pGrabbed->m_Width + x] = static_cast<CLayerSwitch *>(this)->m_pSwitchTile[(r.y + y) * m_Width + (r.x + x)];
422 if(IsValidSwitchTile(Index: pGrabbed->m_pSwitchTile[y * pGrabbed->m_Width + x].m_Type))
423 {
424 Editor()->m_SwitchNumber = pGrabbed->m_pSwitchTile[y * pGrabbed->m_Width + x].m_Number;
425 Editor()->m_SwitchDelay = pGrabbed->m_pSwitchTile[y * pGrabbed->m_Width + x].m_Delay;
426 }
427 }
428 else
429 {
430 const CTile &Tile = pGrabbed->m_pTiles[y * pGrabbed->m_Width + x];
431 if(IsValidSwitchTile(Index: Tile.m_Index))
432 {
433 pGrabbed->m_pSwitchTile[y * pGrabbed->m_Width + x].m_Type = Tile.m_Index;
434 pGrabbed->m_pSwitchTile[y * pGrabbed->m_Width + x].m_Number = Editor()->m_SwitchNumber;
435 pGrabbed->m_pSwitchTile[y * pGrabbed->m_Width + x].m_Delay = Editor()->m_SwitchDelay;
436 pGrabbed->m_pSwitchTile[y * pGrabbed->m_Width + x].m_Flags = Tile.m_Flags;
437 }
438 }
439 }
440 }
441
442 pGrabbed->m_SwitchNumber = Editor()->m_SwitchNumber;
443 pGrabbed->m_SwitchDelay = Editor()->m_SwitchDelay;
444 str_copy(dst&: pGrabbed->m_aFilename, src: pGrabbed->Map()->m_aFilename);
445 }
446
447 else if(m_HasTune)
448 {
449 std::shared_ptr<CLayerTune> pGrabbed = std::make_shared<CLayerTune>(args: pBrush->Map(), args&: r.w, args&: r.h);
450 InitGrabbedLayer(pLayer&: pGrabbed, pThisLayer: this);
451
452 pBrush->AddLayer(pLayer: pGrabbed);
453
454 // copy the tiles
455 for(int y = 0; y < r.h; y++)
456 {
457 for(int x = 0; x < r.w; x++)
458 {
459 pGrabbed->m_pTiles[y * pGrabbed->m_Width + x] = GetTile(x: r.x + x, y: r.y + y);
460
461 if(!Editor()->Input()->KeyIsPressed(Key: KEY_SPACE))
462 {
463 pGrabbed->m_pTuneTile[y * pGrabbed->m_Width + x] = static_cast<CLayerTune *>(this)->m_pTuneTile[(r.y + y) * m_Width + (r.x + x)];
464 if(IsValidTuneTile(Index: pGrabbed->m_pTuneTile[y * pGrabbed->m_Width + x].m_Type))
465 {
466 Editor()->m_TuningNumber = pGrabbed->m_pTuneTile[y * pGrabbed->m_Width + x].m_Number;
467 }
468 }
469 else
470 {
471 const CTile &Tile = pGrabbed->m_pTiles[y * pGrabbed->m_Width + x];
472 if(IsValidTuneTile(Index: Tile.m_Index))
473 {
474 pGrabbed->m_pTuneTile[y * pGrabbed->m_Width + x].m_Type = Tile.m_Index;
475 pGrabbed->m_pTuneTile[y * pGrabbed->m_Width + x].m_Number = Editor()->m_TuningNumber;
476 }
477 }
478 }
479 }
480
481 pGrabbed->m_TuningNumber = Editor()->m_TuningNumber;
482 str_copy(dst&: pGrabbed->m_aFilename, src: pGrabbed->Map()->m_aFilename);
483 }
484 else // game, front and tiles layers
485 {
486 std::shared_ptr<CLayerTiles> pGrabbed;
487 if(m_HasGame)
488 {
489 pGrabbed = std::make_shared<CLayerGame>(args: pBrush->Map(), args&: r.w, args&: r.h);
490 }
491 else if(m_HasFront)
492 {
493 pGrabbed = std::make_shared<CLayerFront>(args: pBrush->Map(), args&: r.w, args&: r.h);
494 }
495 else
496 {
497 pGrabbed = std::make_shared<CLayerTiles>(args: pBrush->Map(), args&: r.w, args&: r.h);
498 }
499 InitGrabbedLayer(pLayer&: pGrabbed, pThisLayer: this);
500
501 pBrush->AddLayer(pLayer: pGrabbed);
502
503 // copy the tiles
504 for(int y = 0; y < r.h; y++)
505 for(int x = 0; x < r.w; x++)
506 pGrabbed->m_pTiles[y * pGrabbed->m_Width + x] = GetTile(x: r.x + x, y: r.y + y);
507 str_copy(dst&: pGrabbed->m_aFilename, src: pGrabbed->Map()->m_aFilename);
508 }
509
510 return 1;
511}
512
513void CLayerTiles::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect)
514{
515 if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES))
516 return;
517
518 Snap(pRect: &Rect);
519
520 int sx = ConvertX(x: Rect.x);
521 int sy = ConvertY(y: Rect.y);
522 int w = ConvertX(x: Rect.w);
523 int h = ConvertY(y: Rect.h);
524
525 CLayerTiles *pLt = static_cast<CLayerTiles *>(pBrush);
526
527 bool Destructive = Editor()->m_BrushDrawDestructive || Empty || pLt->IsEmpty();
528
529 for(int y = 0; y < h; y++)
530 {
531 for(int x = 0; x < w; x++)
532 {
533 int fx = x + sx;
534 int fy = y + sy;
535
536 if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height)
537 continue;
538
539 bool HasTile = GetTile(x: fx, y: fy).m_Index;
540 if(!Empty && pLt->GetTile(x: x % pLt->m_Width, y: y % pLt->m_Height).m_Index == TILE_THROUGH_CUT)
541 {
542 if(m_HasGame && Map()->m_pFrontLayer)
543 {
544 HasTile = HasTile || Map()->m_pFrontLayer->GetTile(x: fx, y: fy).m_Index;
545 }
546 else if(m_HasFront)
547 {
548 HasTile = HasTile || Map()->m_pGameLayer->GetTile(x: fx, y: fy).m_Index;
549 }
550 }
551
552 if(!Destructive && HasTile)
553 continue;
554
555 SetTile(x: fx, y: fy, Tile: Empty ? CTile{.m_Index: TILE_AIR} : pLt->m_pTiles[(y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height)]);
556 }
557 }
558 FlagModified(x: sx, y: sy, w, h);
559}
560
561void CLayerTiles::BrushDraw(CLayer *pBrush, vec2 WorldPos)
562{
563 if(m_Readonly)
564 return;
565
566 CLayerTiles *pTileLayer = static_cast<CLayerTiles *>(pBrush);
567 int sx = ConvertX(x: WorldPos.x);
568 int sy = ConvertY(y: WorldPos.y);
569
570 bool Destructive = Editor()->m_BrushDrawDestructive || pTileLayer->IsEmpty();
571
572 for(int y = 0; y < pTileLayer->m_Height; y++)
573 for(int x = 0; x < pTileLayer->m_Width; x++)
574 {
575 int fx = x + sx;
576 int fy = y + sy;
577
578 if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height)
579 continue;
580
581 bool HasTile = GetTile(x: fx, y: fy).m_Index;
582 if(pTileLayer->GetTile(x, y).m_Index == TILE_THROUGH_CUT)
583 {
584 if(m_HasGame && Map()->m_pFrontLayer)
585 {
586 HasTile = HasTile || Map()->m_pFrontLayer->GetTile(x: fx, y: fy).m_Index;
587 }
588 else if(m_HasFront)
589 {
590 HasTile = HasTile || Map()->m_pGameLayer->GetTile(x: fx, y: fy).m_Index;
591 }
592 }
593
594 if(!Destructive && HasTile)
595 continue;
596
597 SetTile(x: fx, y: fy, Tile: pTileLayer->GetTile(x, y));
598 }
599
600 FlagModified(x: sx, y: sy, w: pTileLayer->m_Width, h: pTileLayer->m_Height);
601}
602
603void CLayerTiles::BrushFlipX()
604{
605 BrushFlipXImpl(pTiles: m_pTiles);
606
607 if(m_HasTele || m_HasSpeedup || m_HasTune)
608 return;
609
610 bool Rotate = !(m_HasGame || m_HasFront || m_HasSwitch) || Editor()->IsAllowPlaceUnusedTiles();
611 for(int y = 0; y < m_Height; y++)
612 for(int x = 0; x < m_Width; x++)
613 if(!Rotate && !IsRotatableTile(Index: m_pTiles[y * m_Width + x].m_Index))
614 m_pTiles[y * m_Width + x].m_Flags = 0;
615 else
616 m_pTiles[y * m_Width + x].m_Flags ^= (m_pTiles[y * m_Width + x].m_Flags & TILEFLAG_ROTATE) ? TILEFLAG_YFLIP : TILEFLAG_XFLIP;
617}
618
619void CLayerTiles::BrushFlipY()
620{
621 BrushFlipYImpl(pTiles: m_pTiles);
622
623 if(m_HasTele || m_HasSpeedup || m_HasTune)
624 return;
625
626 bool Rotate = !(m_HasGame || m_HasFront || m_HasSwitch) || Editor()->IsAllowPlaceUnusedTiles();
627 for(int y = 0; y < m_Height; y++)
628 for(int x = 0; x < m_Width; x++)
629 if(!Rotate && !IsRotatableTile(Index: m_pTiles[y * m_Width + x].m_Index))
630 m_pTiles[y * m_Width + x].m_Flags = 0;
631 else
632 m_pTiles[y * m_Width + x].m_Flags ^= (m_pTiles[y * m_Width + x].m_Flags & TILEFLAG_ROTATE) ? TILEFLAG_XFLIP : TILEFLAG_YFLIP;
633}
634
635void CLayerTiles::BrushRotate(float Amount)
636{
637 int Rotation = (round_to_int(f: 360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270°
638 if(Rotation < 0)
639 Rotation += 4;
640
641 if(Rotation == 1 || Rotation == 3)
642 {
643 // 90° rotation
644 CTile *pTempData = new CTile[m_Width * m_Height];
645 mem_copy(dest: pTempData, source: m_pTiles, size: (size_t)m_Width * m_Height * sizeof(CTile));
646 CTile *pDst = m_pTiles;
647 bool Rotate = !(m_HasGame || m_HasFront) || Editor()->IsAllowPlaceUnusedTiles();
648 for(int x = 0; x < m_Width; ++x)
649 for(int y = m_Height - 1; y >= 0; --y, ++pDst)
650 {
651 *pDst = pTempData[y * m_Width + x];
652 if(!Rotate && !IsRotatableTile(Index: pDst->m_Index))
653 pDst->m_Flags = 0;
654 else
655 {
656 if(pDst->m_Flags & TILEFLAG_ROTATE)
657 pDst->m_Flags ^= (TILEFLAG_YFLIP | TILEFLAG_XFLIP);
658 pDst->m_Flags ^= TILEFLAG_ROTATE;
659 }
660 }
661
662 std::swap(a&: m_Width, b&: m_Height);
663 delete[] pTempData;
664 }
665
666 if(Rotation == 2 || Rotation == 3)
667 {
668 BrushFlipX();
669 BrushFlipY();
670 }
671}
672
673std::shared_ptr<CLayer> CLayerTiles::Duplicate() const
674{
675 return std::make_shared<CLayerTiles>(args: *this);
676}
677
678const char *CLayerTiles::TypeName() const
679{
680 return "tiles";
681}
682
683void CLayerTiles::Resize(int NewW, int NewH)
684{
685 CTile *pNewData = new CTile[NewW * NewH];
686 mem_zero(block: pNewData, size: (size_t)NewW * NewH * sizeof(CTile));
687
688 // copy old data
689 for(int y = 0; y < minimum(a: NewH, b: m_Height); y++)
690 mem_copy(dest: &pNewData[y * NewW], source: &m_pTiles[y * m_Width], size: minimum(a: m_Width, b: NewW) * sizeof(CTile));
691
692 // replace old
693 delete[] m_pTiles;
694 m_pTiles = pNewData;
695 m_Width = NewW;
696 m_Height = NewH;
697
698 // resize tele layer if available
699 if(m_HasGame && Map()->m_pTeleLayer && (Map()->m_pTeleLayer->m_Width != NewW || Map()->m_pTeleLayer->m_Height != NewH))
700 Map()->m_pTeleLayer->Resize(NewW, NewH);
701
702 // resize speedup layer if available
703 if(m_HasGame && Map()->m_pSpeedupLayer && (Map()->m_pSpeedupLayer->m_Width != NewW || Map()->m_pSpeedupLayer->m_Height != NewH))
704 Map()->m_pSpeedupLayer->Resize(NewW, NewH);
705
706 // resize front layer
707 if(m_HasGame && Map()->m_pFrontLayer && (Map()->m_pFrontLayer->m_Width != NewW || Map()->m_pFrontLayer->m_Height != NewH))
708 Map()->m_pFrontLayer->Resize(NewW, NewH);
709
710 // resize switch layer if available
711 if(m_HasGame && Map()->m_pSwitchLayer && (Map()->m_pSwitchLayer->m_Width != NewW || Map()->m_pSwitchLayer->m_Height != NewH))
712 Map()->m_pSwitchLayer->Resize(NewW, NewH);
713
714 // resize tune layer if available
715 if(m_HasGame && Map()->m_pTuneLayer && (Map()->m_pTuneLayer->m_Width != NewW || Map()->m_pTuneLayer->m_Height != NewH))
716 Map()->m_pTuneLayer->Resize(NewW, NewH);
717}
718
719void CLayerTiles::Shift(EShiftDirection Direction)
720{
721 ShiftImpl(pTiles: m_pTiles, Direction, ShiftBy: Map()->m_ShiftBy);
722}
723
724void CLayerTiles::ShowInfo()
725{
726 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
727 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
728 Graphics()->TextureSet(Texture: Editor()->Client()->GetDebugFont());
729 Graphics()->QuadsBegin();
730
731 int StartY = maximum(a: 0, b: (int)(ScreenY0 / 32.0f) - 1);
732 int StartX = maximum(a: 0, b: (int)(ScreenX0 / 32.0f) - 1);
733 int EndY = minimum(a: (int)(ScreenY1 / 32.0f) + 1, b: m_Height);
734 int EndX = minimum(a: (int)(ScreenX1 / 32.0f) + 1, b: m_Width);
735
736 for(int y = StartY; y < EndY; y++)
737 for(int x = StartX; x < EndX; x++)
738 {
739 int c = x + y * m_Width;
740 if(m_pTiles[c].m_Index)
741 {
742 char aBuf[4];
743 if(Editor()->m_ShowTileInfo == CEditor::SHOW_TILE_HEXADECIMAL)
744 {
745 str_hex(dst: aBuf, dst_size: sizeof(aBuf), data: &m_pTiles[c].m_Index, data_size: 1);
746 aBuf[2] = '\0'; // would otherwise be a space
747 }
748 else
749 {
750 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", m_pTiles[c].m_Index);
751 }
752 Graphics()->QuadsText(x: x * 32, y: y * 32, Size: 16.0f, pText: aBuf);
753
754 char aFlags[4] = {m_pTiles[c].m_Flags & TILEFLAG_XFLIP ? 'X' : ' ',
755 m_pTiles[c].m_Flags & TILEFLAG_YFLIP ? 'Y' : ' ',
756 m_pTiles[c].m_Flags & TILEFLAG_ROTATE ? 'R' : ' ',
757 0};
758 Graphics()->QuadsText(x: x * 32, y: y * 32 + 16, Size: 16.0f, pText: aFlags);
759 }
760 x += m_pTiles[c].m_Skip;
761 }
762
763 Graphics()->QuadsEnd();
764 Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
765}
766
767void CLayerTiles::FillGameTiles(EGameTileOp Fill)
768{
769 if(!CanFillGameTiles())
770 return;
771
772 auto GameTileOpToIndex = [](EGameTileOp Op) -> int {
773 switch(Op)
774 {
775 case EGameTileOp::AIR: return TILE_AIR;
776 case EGameTileOp::HOOKABLE: return TILE_SOLID;
777 case EGameTileOp::DEATH: return TILE_DEATH;
778 case EGameTileOp::UNHOOKABLE: return TILE_NOHOOK;
779 case EGameTileOp::HOOKTHROUGH: return TILE_THROUGH_CUT;
780 case EGameTileOp::FREEZE: return TILE_FREEZE;
781 case EGameTileOp::UNFREEZE: return TILE_UNFREEZE;
782 case EGameTileOp::DEEP_FREEZE: return TILE_DFREEZE;
783 case EGameTileOp::DEEP_UNFREEZE: return TILE_DUNFREEZE;
784 case EGameTileOp::BLUE_CHECK_TELE: return TILE_TELECHECKIN;
785 case EGameTileOp::RED_CHECK_TELE: return TILE_TELECHECKINEVIL;
786 case EGameTileOp::LIVE_FREEZE: return TILE_LFREEZE;
787 case EGameTileOp::LIVE_UNFREEZE: return TILE_LUNFREEZE;
788 default: return -1;
789 }
790 };
791
792 int Result = GameTileOpToIndex(Fill);
793 if(Result > -1)
794 {
795 std::shared_ptr<CLayerGroup> pGroup = Map()->m_vpGroups[Map()->m_SelectedGroup];
796 m_FillGameTile = Result;
797 const int OffsetX = -pGroup->m_OffsetX / 32;
798 const int OffsetY = -pGroup->m_OffsetY / 32;
799
800 std::vector<std::shared_ptr<IEditorAction>> vpActions;
801 std::shared_ptr<CLayerTiles> pGLayer = Map()->m_pGameLayer;
802 int GameLayerIndex = std::find(first: Map()->m_pGameGroup->m_vpLayers.begin(), last: Map()->m_pGameGroup->m_vpLayers.end(), val: pGLayer) - Map()->m_pGameGroup->m_vpLayers.begin();
803 int GameGroupIndex = std::find(first: Map()->m_vpGroups.begin(), last: Map()->m_vpGroups.end(), val: Map()->m_pGameGroup) - Map()->m_vpGroups.begin();
804
805 if(Result != TILE_TELECHECKIN && Result != TILE_TELECHECKINEVIL)
806 {
807 if(pGLayer->m_Width < m_Width + OffsetX || pGLayer->m_Height < m_Height + OffsetY)
808 {
809 std::map<int, std::shared_ptr<CLayer>> SavedLayers;
810 SavedLayers[LAYERTYPE_TILES] = pGLayer->Duplicate();
811 SavedLayers[LAYERTYPE_GAME] = SavedLayers[LAYERTYPE_TILES];
812
813 int PrevW = pGLayer->m_Width;
814 int PrevH = pGLayer->m_Height;
815 const int NewW = pGLayer->m_Width < m_Width + OffsetX ? m_Width + OffsetX : pGLayer->m_Width;
816 const int NewH = pGLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pGLayer->m_Height;
817 pGLayer->Resize(NewW, NewH);
818 vpActions.push_back(x: std::make_shared<CEditorActionEditLayerTilesProp>(args: Map(), args&: GameGroupIndex, args&: GameLayerIndex, args: ETilesProp::PROP_WIDTH, args&: PrevW, args: NewW));
819 const std::shared_ptr<CEditorActionEditLayerTilesProp> &Action1 = std::static_pointer_cast<CEditorActionEditLayerTilesProp>(r: vpActions[vpActions.size() - 1]);
820 vpActions.push_back(x: std::make_shared<CEditorActionEditLayerTilesProp>(args: Map(), args&: GameGroupIndex, args&: GameLayerIndex, args: ETilesProp::PROP_HEIGHT, args&: PrevH, args: NewH));
821 const std::shared_ptr<CEditorActionEditLayerTilesProp> &Action2 = std::static_pointer_cast<CEditorActionEditLayerTilesProp>(r: vpActions[vpActions.size() - 1]);
822
823 Action1->SetSavedLayers(SavedLayers);
824 Action2->SetSavedLayers(SavedLayers);
825 }
826
827 int Changes = 0;
828 for(int y = OffsetY < 0 ? -OffsetY : 0; y < m_Height; y++)
829 {
830 for(int x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++)
831 {
832 if(GetTile(x, y).m_Index)
833 {
834 pGLayer->SetTile(x: x + OffsetX, y: y + OffsetY, Tile: CTile{.m_Index: (unsigned char)Result});
835 Changes++;
836 }
837 }
838 }
839
840 vpActions.push_back(x: std::make_shared<CEditorBrushDrawAction>(args: Map(), args&: GameGroupIndex));
841 char aDisplay[256];
842 str_format(buffer: aDisplay, buffer_size: sizeof(aDisplay), format: "Construct '%s' game tiles (x%d)", GAME_TILE_OP_NAMES[(int)Fill], Changes);
843 Map()->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionBulk>(args: Map(), args&: vpActions, args&: aDisplay, args: true));
844 }
845 else
846 {
847 if(!Map()->m_pTeleLayer)
848 {
849 std::shared_ptr<CLayerTele> pLayer = std::make_shared<CLayerTele>(args: Map(), args&: m_Width, args&: m_Height);
850 Map()->MakeTeleLayer(pLayer);
851 Map()->m_pGameGroup->AddLayer(pLayer);
852
853 vpActions.push_back(x: std::make_shared<CEditorActionAddLayer>(args: Map(), args&: GameGroupIndex, args: Map()->m_pGameGroup->m_vpLayers.size() - 1));
854
855 if(m_Width != pGLayer->m_Width || m_Height > pGLayer->m_Height)
856 {
857 std::map<int, std::shared_ptr<CLayer>> SavedLayers;
858 SavedLayers[LAYERTYPE_TILES] = pGLayer->Duplicate();
859 SavedLayers[LAYERTYPE_GAME] = SavedLayers[LAYERTYPE_TILES];
860
861 int NewW = pGLayer->m_Width;
862 int NewH = pGLayer->m_Height;
863 if(m_Width > pGLayer->m_Width)
864 {
865 NewW = m_Width;
866 }
867 if(m_Height > pGLayer->m_Height)
868 {
869 NewH = m_Height;
870 }
871
872 int PrevW = pGLayer->m_Width;
873 int PrevH = pGLayer->m_Height;
874 pLayer->Resize(NewW, NewH);
875 vpActions.push_back(x: std::make_shared<CEditorActionEditLayerTilesProp>(args: Map(), args&: GameGroupIndex, args&: GameLayerIndex, args: ETilesProp::PROP_WIDTH, args&: PrevW, args&: NewW));
876 const std::shared_ptr<CEditorActionEditLayerTilesProp> &Action1 = std::static_pointer_cast<CEditorActionEditLayerTilesProp>(r: vpActions[vpActions.size() - 1]);
877 vpActions.push_back(x: std::make_shared<CEditorActionEditLayerTilesProp>(args: Map(), args&: GameGroupIndex, args&: GameLayerIndex, args: ETilesProp::PROP_HEIGHT, args&: PrevH, args&: NewH));
878 const std::shared_ptr<CEditorActionEditLayerTilesProp> &Action2 = std::static_pointer_cast<CEditorActionEditLayerTilesProp>(r: vpActions[vpActions.size() - 1]);
879
880 Action1->SetSavedLayers(SavedLayers);
881 Action2->SetSavedLayers(SavedLayers);
882 }
883 }
884
885 std::shared_ptr<CLayerTele> pTLayer = Map()->m_pTeleLayer;
886 int TeleLayerIndex = std::find(first: Map()->m_pGameGroup->m_vpLayers.begin(), last: Map()->m_pGameGroup->m_vpLayers.end(), val: pTLayer) - Map()->m_pGameGroup->m_vpLayers.begin();
887
888 if(pTLayer->m_Width < m_Width + OffsetX || pTLayer->m_Height < m_Height + OffsetY)
889 {
890 std::map<int, std::shared_ptr<CLayer>> SavedLayers;
891 SavedLayers[LAYERTYPE_TILES] = pTLayer->Duplicate();
892 SavedLayers[LAYERTYPE_TELE] = SavedLayers[LAYERTYPE_TILES];
893
894 int PrevW = pTLayer->m_Width;
895 int PrevH = pTLayer->m_Height;
896 int NewW = pTLayer->m_Width < m_Width + OffsetX ? m_Width + OffsetX : pTLayer->m_Width;
897 int NewH = pTLayer->m_Height < m_Height + OffsetY ? m_Height + OffsetY : pTLayer->m_Height;
898 pTLayer->Resize(NewW, NewH);
899 std::shared_ptr<CEditorActionEditLayerTilesProp> Action1, Action2;
900 vpActions.push_back(x: Action1 = std::make_shared<CEditorActionEditLayerTilesProp>(args: Map(), args&: GameGroupIndex, args&: TeleLayerIndex, args: ETilesProp::PROP_WIDTH, args&: PrevW, args&: NewW));
901 vpActions.push_back(x: Action2 = std::make_shared<CEditorActionEditLayerTilesProp>(args: Map(), args&: GameGroupIndex, args&: TeleLayerIndex, args: ETilesProp::PROP_HEIGHT, args&: PrevH, args&: NewH));
902
903 Action1->SetSavedLayers(SavedLayers);
904 Action2->SetSavedLayers(SavedLayers);
905 }
906
907 int Changes = 0;
908 for(int y = OffsetY < 0 ? -OffsetY : 0; y < m_Height; y++)
909 {
910 for(int x = OffsetX < 0 ? -OffsetX : 0; x < m_Width; x++)
911 {
912 if(GetTile(x, y).m_Index)
913 {
914 auto TileIndex = (y + OffsetY) * pTLayer->m_Width + x + OffsetX;
915 Changes++;
916
917 STeleTileStateChange::SData Previous{
918 .m_Number: pTLayer->m_pTeleTile[TileIndex].m_Number,
919 .m_Type: pTLayer->m_pTeleTile[TileIndex].m_Type,
920 .m_Index: pTLayer->m_pTiles[TileIndex].m_Index};
921
922 pTLayer->m_pTiles[TileIndex].m_Index = TILE_AIR + Result;
923 pTLayer->m_pTeleTile[TileIndex].m_Number = 1;
924 pTLayer->m_pTeleTile[TileIndex].m_Type = TILE_AIR + Result;
925
926 STeleTileStateChange::SData Current{
927 .m_Number: pTLayer->m_pTeleTile[TileIndex].m_Number,
928 .m_Type: pTLayer->m_pTeleTile[TileIndex].m_Type,
929 .m_Index: pTLayer->m_pTiles[TileIndex].m_Index};
930
931 pTLayer->RecordStateChange(x, y, Previous, Current);
932 }
933 }
934 }
935
936 vpActions.push_back(x: std::make_shared<CEditorBrushDrawAction>(args: Map(), args&: GameGroupIndex));
937 char aDisplay[256];
938 str_format(buffer: aDisplay, buffer_size: sizeof(aDisplay), format: "Construct 'tele' game tiles (x%d)", Changes);
939 Map()->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionBulk>(args: Map(), args&: vpActions, args&: aDisplay, args: true));
940 }
941 }
942}
943
944bool CLayerTiles::CanFillGameTiles() const
945{
946 const bool EntitiesLayer = IsEntitiesLayer();
947 if(EntitiesLayer)
948 return false;
949
950 std::shared_ptr<CLayerGroup> pGroup = Map()->m_vpGroups[Map()->m_SelectedGroup];
951
952 // Game tiles can only be constructed if the layer is relative to the game layer
953 return !(pGroup->m_OffsetX % 32) && !(pGroup->m_OffsetY % 32) && pGroup->m_ParallaxX == 100 && pGroup->m_ParallaxY == 100;
954}
955
956CUi::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
957{
958 CUIRect Button;
959
960 const bool EntitiesLayer = IsEntitiesLayer();
961
962 if(CanFillGameTiles())
963 {
964 pToolBox->HSplitBottom(Cut: 12.0f, pTop: pToolBox, pBottom: &Button);
965 static int s_GameTilesButton = 0;
966
967 auto GameTileToOp = [](int TileIndex) -> EGameTileOp {
968 switch(TileIndex)
969 {
970 case TILE_AIR: return EGameTileOp::AIR;
971 case TILE_SOLID: return EGameTileOp::HOOKABLE;
972 case TILE_DEATH: return EGameTileOp::DEATH;
973 case TILE_NOHOOK: return EGameTileOp::UNHOOKABLE;
974 case TILE_THROUGH_CUT: return EGameTileOp::HOOKTHROUGH;
975 case TILE_FREEZE: return EGameTileOp::FREEZE;
976 case TILE_UNFREEZE: return EGameTileOp::UNFREEZE;
977 case TILE_DFREEZE: return EGameTileOp::DEEP_FREEZE;
978 case TILE_DUNFREEZE: return EGameTileOp::DEEP_UNFREEZE;
979 case TILE_TELECHECKIN: return EGameTileOp::BLUE_CHECK_TELE;
980 case TILE_TELECHECKINEVIL: return EGameTileOp::RED_CHECK_TELE;
981 case TILE_LFREEZE: return EGameTileOp::LIVE_FREEZE;
982 case TILE_LUNFREEZE: return EGameTileOp::LIVE_UNFREEZE;
983 default: return EGameTileOp::AIR;
984 }
985 };
986
987 char aBuf[128] = "Game tiles";
988 if(m_LiveGameTiles)
989 {
990 auto TileOp = GameTileToOp(m_FillGameTile);
991 if(TileOp != EGameTileOp::AIR)
992 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Game tiles: %s", GAME_TILE_OP_NAMES[(size_t)TileOp]);
993 }
994 if(Editor()->DoButton_Editor(pId: &s_GameTilesButton, pText: aBuf, Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pToolTip: "Construct game tiles from this layer."))
995 Editor()->PopupSelectGametileOpInvoke(x: Editor()->Ui()->MouseX(), y: Editor()->Ui()->MouseY());
996 const int Selected = Editor()->PopupSelectGameTileOpResult();
997 FillGameTiles(Fill: (EGameTileOp)Selected);
998 }
999
1000 if(Map()->m_pGameLayer.get() != this)
1001 {
1002 if(m_Image >= 0 && (size_t)m_Image < Map()->m_vpImages.size() && Map()->m_vpImages[m_Image]->m_AutoMapper.IsLoaded() && m_AutoMapperConfig != -1)
1003 {
1004 pToolBox->HSplitBottom(Cut: 2.0f, pTop: pToolBox, pBottom: nullptr);
1005 pToolBox->HSplitBottom(Cut: 12.0f, pTop: pToolBox, pBottom: &Button);
1006 if(m_Seed != 0)
1007 {
1008 CUIRect ButtonAuto;
1009 Button.VSplitRight(Cut: 16.0f, pLeft: &Button, pRight: &ButtonAuto);
1010 Button.VSplitRight(Cut: 2.0f, pLeft: &Button, pRight: nullptr);
1011 static int s_AutoMapperButtonAuto = 0;
1012 if(Editor()->DoButton_Editor(pId: &s_AutoMapperButtonAuto, pText: "A", Checked: m_AutoAutoMap, pRect: &ButtonAuto, Flags: BUTTONFLAG_LEFT, pToolTip: "Automatically run the automapper after modifications."))
1013 {
1014 m_AutoAutoMap = !m_AutoAutoMap;
1015 FlagModified(x: 0, y: 0, w: m_Width, h: m_Height);
1016 if(!m_TilesHistory.empty()) // Sometimes pressing that button causes the automap to run so we should be able to undo that
1017 {
1018 // record undo
1019 Map()->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionTileChanges>(args: Map(), args&: Map()->m_SelectedGroup, args&: Map()->m_vSelectedLayers[0], args: "Auto map", args&: m_TilesHistory));
1020 ClearHistory();
1021 }
1022 }
1023 }
1024
1025 static int s_AutoMapperButton = 0;
1026 if(Editor()->DoButton_Editor(pId: &s_AutoMapperButton, pText: "Automap", Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT, pToolTip: "Run the automapper."))
1027 {
1028 Map()->m_vpImages[m_Image]->m_AutoMapper.Proceed(pLayer: this, pGameLayer: Map()->m_pGameLayer.get(), ReferenceId: m_AutoMapperReference, ConfigId: m_AutoMapperConfig, Seed: m_Seed);
1029 // record undo
1030 Map()->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionTileChanges>(args: Map(), args&: Map()->m_SelectedGroup, args&: Map()->m_vSelectedLayers[0], args: "Auto map", args&: m_TilesHistory));
1031 ClearHistory();
1032 return CUi::POPUP_CLOSE_CURRENT;
1033 }
1034 }
1035 }
1036
1037 CProperty aProps[] = {
1038 {"Width", m_Width, PROPTYPE_INT, 2, 100000},
1039 {"Height", m_Height, PROPTYPE_INT, 2, 100000},
1040 {"Shift", 0, PROPTYPE_SHIFT, 0, 0},
1041 {"Shift by", Map()->m_ShiftBy, PROPTYPE_INT, 1, 100000},
1042 {"Image", m_Image, PROPTYPE_IMAGE, 0, 0},
1043 {"Color", PackColor(Color: m_Color), PROPTYPE_COLOR, 0, 0},
1044 {"Color Env", m_ColorEnv + 1, PROPTYPE_ENVELOPE, 0, 0},
1045 {"Color TO", m_ColorEnvOffset, PROPTYPE_INT, -1000000, 1000000},
1046 {"Auto Rule", m_AutoMapperConfig, PROPTYPE_AUTOMAPPER, m_Image, 0},
1047 {"Reference", m_AutoMapperReference, PROPTYPE_AUTOMAPPER_REFERENCE, 0, 0},
1048 {"Live Gametiles", m_LiveGameTiles, PROPTYPE_BOOL, 0, 1},
1049 {"Seed", m_Seed, PROPTYPE_INT, 0, 1000000000},
1050 {nullptr},
1051 };
1052
1053 if(EntitiesLayer) // remove the image and color properties if this is a game layer
1054 {
1055 aProps[(int)ETilesProp::PROP_IMAGE].m_pName = nullptr;
1056 aProps[(int)ETilesProp::PROP_COLOR].m_pName = nullptr;
1057 aProps[(int)ETilesProp::PROP_AUTOMAPPER].m_pName = nullptr;
1058 aProps[(int)ETilesProp::PROP_AUTOMAPPER_REFERENCE].m_pName = nullptr;
1059 }
1060 if(m_Image == -1)
1061 {
1062 aProps[(int)ETilesProp::PROP_AUTOMAPPER].m_pName = nullptr;
1063 aProps[(int)ETilesProp::PROP_AUTOMAPPER_REFERENCE].m_pName = nullptr;
1064 aProps[(int)ETilesProp::PROP_SEED].m_pName = nullptr;
1065 }
1066
1067 static int s_aIds[(int)ETilesProp::NUM_PROPS] = {0};
1068 int NewVal = 0;
1069 auto [State, Prop] = Editor()->DoPropertiesWithState<ETilesProp>(pToolbox: pToolBox, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
1070
1071 Map()->m_LayerTilesPropTracker.Begin(pObject: this, Prop, State);
1072 Map()->m_EditorHistory.BeginBulk();
1073
1074 if(Prop == ETilesProp::PROP_WIDTH)
1075 {
1076 if(NewVal > 1000 && !Editor()->m_LargeLayerWasWarned)
1077 {
1078 Editor()->m_PopupEventType = CEditor::POPEVENT_LARGELAYER;
1079 Editor()->m_PopupEventActivated = true;
1080 Editor()->m_LargeLayerWasWarned = true;
1081 }
1082 Resize(NewW: NewVal, NewH: m_Height);
1083 }
1084 else if(Prop == ETilesProp::PROP_HEIGHT)
1085 {
1086 if(NewVal > 1000 && !Editor()->m_LargeLayerWasWarned)
1087 {
1088 Editor()->m_PopupEventType = CEditor::POPEVENT_LARGELAYER;
1089 Editor()->m_PopupEventActivated = true;
1090 Editor()->m_LargeLayerWasWarned = true;
1091 }
1092 Resize(NewW: m_Width, NewH: NewVal);
1093 }
1094 else if(Prop == ETilesProp::PROP_SHIFT)
1095 {
1096 Shift(Direction: (EShiftDirection)NewVal);
1097 }
1098 else if(Prop == ETilesProp::PROP_SHIFT_BY)
1099 {
1100 Map()->m_ShiftBy = NewVal;
1101 }
1102 else if(Prop == ETilesProp::PROP_IMAGE)
1103 {
1104 m_Image = NewVal;
1105 if(NewVal == -1)
1106 {
1107 m_Image = -1;
1108 }
1109 else
1110 {
1111 m_Image = NewVal % Map()->m_vpImages.size();
1112 m_AutoMapperConfig = -1;
1113
1114 if(Map()->m_vpImages[m_Image]->m_Width % 16 != 0 || Map()->m_vpImages[m_Image]->m_Height % 16 != 0)
1115 {
1116 Editor()->m_PopupEventType = CEditor::POPEVENT_IMAGEDIV16;
1117 Editor()->m_PopupEventActivated = true;
1118 m_Image = -1;
1119 }
1120 }
1121 }
1122 else if(Prop == ETilesProp::PROP_COLOR)
1123 {
1124 m_Color = UnpackColor(PackedColor: NewVal);
1125 }
1126 else if(Prop == ETilesProp::PROP_COLOR_ENV)
1127 {
1128 int Index = std::clamp(val: NewVal - 1, lo: -1, hi: (int)Map()->m_vpEnvelopes.size() - 1);
1129 const int Step = (Index - m_ColorEnv) % 2;
1130 if(Step != 0)
1131 {
1132 for(; Index >= -1 && Index < (int)Map()->m_vpEnvelopes.size(); Index += Step)
1133 {
1134 if(Index == -1 || Map()->m_vpEnvelopes[Index]->GetChannels() == 4)
1135 {
1136 m_ColorEnv = Index;
1137 break;
1138 }
1139 }
1140 }
1141 }
1142 else if(Prop == ETilesProp::PROP_COLOR_ENV_OFFSET)
1143 {
1144 m_ColorEnvOffset = NewVal;
1145 }
1146 else if(Prop == ETilesProp::PROP_SEED)
1147 {
1148 m_Seed = NewVal;
1149 }
1150 else if(Prop == ETilesProp::PROP_AUTOMAPPER)
1151 {
1152 if(m_Image >= 0 && Map()->m_vpImages[m_Image]->m_AutoMapper.ConfigNamesNum() > 0 && NewVal >= 0)
1153 m_AutoMapperConfig = NewVal % Map()->m_vpImages[m_Image]->m_AutoMapper.ConfigNamesNum();
1154 else
1155 m_AutoMapperConfig = -1;
1156 }
1157 else if(Prop == ETilesProp::PROP_AUTOMAPPER_REFERENCE)
1158 {
1159 m_AutoMapperReference = NewVal;
1160 }
1161 else if(Prop == ETilesProp::PROP_LIVE_GAMETILES)
1162 {
1163 m_LiveGameTiles = NewVal != 0;
1164 }
1165
1166 Map()->m_LayerTilesPropTracker.End(Prop, State);
1167
1168 // Check if modified property could have an effect on automapper
1169 if((State == EEditState::END || State == EEditState::ONE_GO) && HasAutomapEffect(Prop))
1170 {
1171 FlagModified(x: 0, y: 0, w: m_Width, h: m_Height);
1172
1173 // Record undo if automapper was ran
1174 if(m_AutoAutoMap && !m_TilesHistory.empty())
1175 {
1176 Map()->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionTileChanges>(args: Map(), args&: Map()->m_SelectedGroup, args&: Map()->m_vSelectedLayers[0], args: "Auto map", args&: m_TilesHistory));
1177 ClearHistory();
1178 }
1179 }
1180
1181 // End undo bulk, taking the first action display as the displayed text in the history
1182 // This is usually the resulting text of the edit layer tiles prop action
1183 // Since we may also squeeze a tile changes action, we want both to appear as one, thus using a bulk
1184 Map()->m_EditorHistory.EndBulk(DisplayToUse: 0);
1185
1186 return CUi::POPUP_KEEP_OPEN;
1187}
1188
1189CUi::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropState &State, CEditorMap *pEditorMap, CUIRect *pToolbox, std::vector<std::shared_ptr<CLayerTiles>> &vpLayers, std::vector<int> &vLayerIndices)
1190{
1191 CEditor *pEditor = pEditorMap->Editor();
1192 if(State.m_Modified)
1193 {
1194 CUIRect Commit;
1195 pToolbox->HSplitBottom(Cut: 20.0f, pTop: pToolbox, pBottom: &Commit);
1196 static int s_CommitButton = 0;
1197 if(pEditor->DoButton_Editor(pId: &s_CommitButton, pText: "Commit", Checked: 0, pRect: &Commit, Flags: BUTTONFLAG_LEFT, pToolTip: "Apply the changes."))
1198 {
1199 bool HasModifiedSize = (State.m_Modified & SCommonPropState::MODIFIED_SIZE) != 0;
1200 bool HasModifiedColor = (State.m_Modified & SCommonPropState::MODIFIED_COLOR) != 0;
1201
1202 std::vector<std::shared_ptr<IEditorAction>> vpActions;
1203 int j = 0;
1204 int GroupIndex = pEditorMap->m_SelectedGroup;
1205 for(auto &pLayer : vpLayers)
1206 {
1207 int LayerIndex = vLayerIndices[j++];
1208 if(HasModifiedSize)
1209 {
1210 std::map<int, std::shared_ptr<CLayer>> SavedLayers;
1211 SavedLayers[LAYERTYPE_TILES] = pLayer->Duplicate();
1212 if(pLayer->m_HasGame || pLayer->m_HasFront || pLayer->m_HasSwitch || pLayer->m_HasSpeedup || pLayer->m_HasTune || pLayer->m_HasTele)
1213 { // Need to save all entities layers when any entity layer
1214 if(pEditorMap->m_pFrontLayer && !pLayer->m_HasFront)
1215 SavedLayers[LAYERTYPE_FRONT] = pEditorMap->m_pFrontLayer->Duplicate();
1216 if(pEditorMap->m_pTeleLayer && !pLayer->m_HasTele)
1217 SavedLayers[LAYERTYPE_TELE] = pEditorMap->m_pTeleLayer->Duplicate();
1218 if(pEditorMap->m_pSwitchLayer && !pLayer->m_HasSwitch)
1219 SavedLayers[LAYERTYPE_SWITCH] = pEditorMap->m_pSwitchLayer->Duplicate();
1220 if(pEditorMap->m_pSpeedupLayer && !pLayer->m_HasSpeedup)
1221 SavedLayers[LAYERTYPE_SPEEDUP] = pEditorMap->m_pSpeedupLayer->Duplicate();
1222 if(pEditorMap->m_pTuneLayer && !pLayer->m_HasTune)
1223 SavedLayers[LAYERTYPE_TUNE] = pEditorMap->m_pTuneLayer->Duplicate();
1224 if(!pLayer->m_HasGame)
1225 SavedLayers[LAYERTYPE_GAME] = pEditorMap->m_pGameLayer->Duplicate();
1226 }
1227
1228 int PrevW = pLayer->m_Width;
1229 int PrevH = pLayer->m_Height;
1230 pLayer->Resize(NewW: State.m_Width, NewH: State.m_Height);
1231
1232 if(PrevW != State.m_Width)
1233 {
1234 std::shared_ptr<CEditorActionEditLayerTilesProp> pAction;
1235 vpActions.push_back(x: pAction = std::make_shared<CEditorActionEditLayerTilesProp>(args&: pEditorMap, args&: GroupIndex, args&: LayerIndex, args: ETilesProp::PROP_WIDTH, args&: PrevW, args&: State.m_Width));
1236 pAction->SetSavedLayers(SavedLayers);
1237 }
1238
1239 if(PrevH != State.m_Height)
1240 {
1241 std::shared_ptr<CEditorActionEditLayerTilesProp> pAction;
1242 vpActions.push_back(x: pAction = std::make_shared<CEditorActionEditLayerTilesProp>(args&: pEditorMap, args&: GroupIndex, args&: LayerIndex, args: ETilesProp::PROP_HEIGHT, args&: PrevH, args&: State.m_Height));
1243 pAction->SetSavedLayers(SavedLayers);
1244 }
1245 }
1246
1247 if(HasModifiedColor && !pLayer->IsEntitiesLayer())
1248 {
1249 const int PackedColor = PackColor(Color: pLayer->m_Color);
1250 pLayer->m_Color = UnpackColor(PackedColor: State.m_Color);
1251 vpActions.push_back(x: std::make_shared<CEditorActionEditLayerTilesProp>(args&: pEditorMap, args&: GroupIndex, args&: LayerIndex, args: ETilesProp::PROP_COLOR, args: PackedColor, args&: State.m_Color));
1252 }
1253
1254 pLayer->FlagModified(x: 0, y: 0, w: pLayer->m_Width, h: pLayer->m_Height);
1255 }
1256 State.m_Modified = 0;
1257
1258 char aDisplay[256];
1259 str_format(buffer: aDisplay, buffer_size: sizeof(aDisplay), format: "Edit %d layers common properties: %s", (int)vpLayers.size(), HasModifiedColor && HasModifiedSize ? "color, size" : (HasModifiedColor ? "color" : "size"));
1260 pEditorMap->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionBulk>(args&: pEditorMap, args&: vpActions, args&: aDisplay));
1261 }
1262 }
1263 else
1264 {
1265 for(auto &pLayer : vpLayers)
1266 {
1267 if(pLayer->m_Width > State.m_Width)
1268 State.m_Width = pLayer->m_Width;
1269 if(pLayer->m_Height > State.m_Height)
1270 State.m_Height = pLayer->m_Height;
1271 }
1272
1273 State.m_Color = PackColor(Color: vpLayers[0]->m_Color);
1274 }
1275
1276 {
1277 CUIRect Warning;
1278 pToolbox->HSplitTop(Cut: 13.0f, pTop: &Warning, pBottom: pToolbox);
1279 Warning.HMargin(Cut: 0.5f, pOtherRect: &Warning);
1280
1281 pEditor->TextRender()->TextColor(Color: ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f));
1282 SLabelProperties Props;
1283 Props.m_MaxWidth = Warning.w;
1284 pEditor->Ui()->DoLabel(pRect: &Warning, pText: "Editing multiple layers", Size: 9.0f, Align: TEXTALIGN_ML, LabelProps: Props);
1285 pEditor->TextRender()->TextColor(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
1286 pToolbox->HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: pToolbox);
1287 }
1288
1289 CProperty aProps[] = {
1290 {"Width", State.m_Width, PROPTYPE_INT, 2, 100000},
1291 {"Height", State.m_Height, PROPTYPE_INT, 2, 100000},
1292 {"Shift", 0, PROPTYPE_SHIFT, 0, 0},
1293 {"Shift by", pEditorMap->m_ShiftBy, PROPTYPE_INT, 1, 100000},
1294 {"Color", State.m_Color, PROPTYPE_COLOR, 0, 0},
1295 {nullptr},
1296 };
1297
1298 static int s_aIds[(int)ETilesCommonProp::NUM_PROPS] = {0};
1299 int NewVal = 0;
1300 auto [PropState, Prop] = pEditor->DoPropertiesWithState<ETilesCommonProp>(pToolbox, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
1301
1302 pEditorMap->m_LayerTilesCommonPropTracker.m_vpLayers = vpLayers;
1303 pEditorMap->m_LayerTilesCommonPropTracker.m_vLayerIndices = vLayerIndices;
1304
1305 pEditorMap->m_LayerTilesCommonPropTracker.Begin(pObject: nullptr, Prop, State: PropState);
1306
1307 if(Prop == ETilesCommonProp::PROP_WIDTH)
1308 {
1309 if(NewVal > 1000 && !pEditor->m_LargeLayerWasWarned)
1310 {
1311 pEditor->m_PopupEventType = CEditor::POPEVENT_LARGELAYER;
1312 pEditor->m_PopupEventActivated = true;
1313 pEditor->m_LargeLayerWasWarned = true;
1314 }
1315 State.m_Width = NewVal;
1316 }
1317 else if(Prop == ETilesCommonProp::PROP_HEIGHT)
1318 {
1319 if(NewVal > 1000 && !pEditor->m_LargeLayerWasWarned)
1320 {
1321 pEditor->m_PopupEventType = CEditor::POPEVENT_LARGELAYER;
1322 pEditor->m_PopupEventActivated = true;
1323 pEditor->m_LargeLayerWasWarned = true;
1324 }
1325 State.m_Height = NewVal;
1326 }
1327 else if(Prop == ETilesCommonProp::PROP_SHIFT)
1328 {
1329 for(auto &pLayer : vpLayers)
1330 pLayer->Shift(Direction: (EShiftDirection)NewVal);
1331 }
1332 else if(Prop == ETilesCommonProp::PROP_SHIFT_BY)
1333 {
1334 pEditorMap->m_ShiftBy = NewVal;
1335 }
1336 else if(Prop == ETilesCommonProp::PROP_COLOR)
1337 {
1338 State.m_Color = NewVal;
1339 }
1340
1341 pEditorMap->m_LayerTilesCommonPropTracker.End(Prop, State: PropState);
1342
1343 if(PropState == EEditState::END || PropState == EEditState::ONE_GO)
1344 {
1345 if(Prop == ETilesCommonProp::PROP_WIDTH || Prop == ETilesCommonProp::PROP_HEIGHT)
1346 {
1347 State.m_Modified |= SCommonPropState::MODIFIED_SIZE;
1348 }
1349 else if(Prop == ETilesCommonProp::PROP_COLOR)
1350 {
1351 State.m_Modified |= SCommonPropState::MODIFIED_COLOR;
1352 }
1353 }
1354
1355 return CUi::POPUP_KEEP_OPEN;
1356}
1357
1358void CLayerTiles::FlagModified(int x, int y, int w, int h)
1359{
1360 Map()->OnModify();
1361 if(m_Seed != 0 && m_AutoMapperConfig != -1 && m_AutoAutoMap && m_Image >= 0)
1362 {
1363 Map()->m_vpImages[m_Image]->m_AutoMapper.ProceedLocalized(pLayer: this, pGameLayer: Map()->m_pGameLayer.get(), ReferenceId: m_AutoMapperReference, ConfigId: m_AutoMapperConfig, Seed: m_Seed, X: x, Y: y, Width: w, Height: h);
1364 }
1365}
1366
1367void CLayerTiles::ModifyImageIndex(const FIndexModifyFunction &IndexModifyFunction)
1368{
1369 IndexModifyFunction(&m_Image);
1370}
1371
1372void CLayerTiles::ModifyEnvelopeIndex(const FIndexModifyFunction &IndexModifyFunction)
1373{
1374 IndexModifyFunction(&m_ColorEnv);
1375}
1376
1377void CLayerTiles::ShowPreventUnusedTilesWarning()
1378{
1379 if(!Editor()->m_PreventUnusedTilesWasWarned)
1380 {
1381 Editor()->m_PopupEventType = CEditor::POPEVENT_PREVENTUNUSEDTILES;
1382 Editor()->m_PopupEventActivated = true;
1383 Editor()->m_PreventUnusedTilesWasWarned = true;
1384 }
1385}
1386
1387bool CLayerTiles::HasAutomapEffect(ETilesProp Prop)
1388{
1389 switch(Prop)
1390 {
1391 case ETilesProp::PROP_WIDTH:
1392 case ETilesProp::PROP_HEIGHT:
1393 case ETilesProp::PROP_SHIFT:
1394 case ETilesProp::PROP_IMAGE:
1395 case ETilesProp::PROP_AUTOMAPPER:
1396 case ETilesProp::PROP_SEED:
1397 return true;
1398 default:
1399 return false;
1400 }
1401 return false;
1402}
1403