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