1#include "editor_actions.h"
2
3#include <game/editor/editor.h>
4#include <game/editor/mapitems.h>
5#include <game/editor/mapitems/image.h>
6#include <game/editor/mapitems/layer.h>
7#include <game/editor/mapitems/layer_front.h>
8#include <game/editor/mapitems/layer_group.h>
9#include <game/editor/mapitems/layer_quads.h>
10#include <game/editor/mapitems/layer_sounds.h>
11#include <game/editor/mapitems/map.h>
12
13CEditorBrushDrawAction::CEditorBrushDrawAction(CEditorMap *pMap, int Group) :
14 IEditorAction(pMap), m_Group(Group)
15{
16 for(size_t k = 0; k < Map()->m_vpGroups[Group]->m_vpLayers.size(); k++)
17 {
18 auto pLayer = Map()->m_vpGroups[Group]->m_vpLayers[k];
19
20 if(pLayer->m_Type == LAYERTYPE_TILES)
21 {
22 auto pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer);
23
24 if(pLayer == Map()->m_pTeleLayer)
25 {
26 if(!Map()->m_pTeleLayer->m_History.empty())
27 {
28 m_TeleTileChanges = std::map(Map()->m_pTeleLayer->m_History);
29 Map()->m_pTeleLayer->ClearHistory();
30 }
31 }
32 else if(pLayer == Map()->m_pTuneLayer)
33 {
34 if(!Map()->m_pTuneLayer->m_History.empty())
35 {
36 m_TuneTileChanges = std::map(Map()->m_pTuneLayer->m_History);
37 Map()->m_pTuneLayer->ClearHistory();
38 }
39 }
40 else if(pLayer == Map()->m_pSwitchLayer)
41 {
42 if(!Map()->m_pSwitchLayer->m_History.empty())
43 {
44 m_SwitchTileChanges = std::map(Map()->m_pSwitchLayer->m_History);
45 Map()->m_pSwitchLayer->ClearHistory();
46 }
47 }
48 else if(pLayer == Map()->m_pSpeedupLayer)
49 {
50 if(!Map()->m_pSpeedupLayer->m_History.empty())
51 {
52 m_SpeedupTileChanges = std::map(Map()->m_pSpeedupLayer->m_History);
53 Map()->m_pSpeedupLayer->ClearHistory();
54 }
55 }
56
57 if(!pLayerTiles->m_TilesHistory.empty())
58 {
59 m_vTileChanges.emplace_back(args&: k, args: std::map(pLayerTiles->m_TilesHistory));
60 pLayerTiles->ClearHistory();
61 }
62 }
63 }
64
65 SetInfos();
66 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Brush draw (x%d) on %d layers", m_TotalTilesDrawn, m_TotalLayers);
67}
68
69void CEditorBrushDrawAction::SetInfos()
70{
71 m_TotalTilesDrawn = 0;
72 m_TotalLayers = 0;
73
74 // Process normal tiles
75 for(auto const &Pair : m_vTileChanges)
76 {
77 int Layer = Pair.first;
78 std::shared_ptr<CLayer> pLayer = Map()->m_vpGroups[m_Group]->m_vpLayers[Layer];
79 m_TotalLayers++;
80
81 if(pLayer->m_Type == LAYERTYPE_TILES)
82 {
83 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer);
84 auto Changes = Pair.second;
85 for(auto &Change : Changes)
86 {
87 m_TotalTilesDrawn += Change.second.size();
88 }
89 }
90 }
91
92 // Process speedup tiles
93 for(auto const &SpeedupChange : m_SpeedupTileChanges)
94 {
95 m_TotalTilesDrawn += SpeedupChange.second.size();
96 }
97
98 // Process tele tiles
99 for(auto const &TeleChange : m_TeleTileChanges)
100 {
101 m_TotalTilesDrawn += TeleChange.second.size();
102 }
103
104 // Process switch tiles
105 for(auto const &SwitchChange : m_SwitchTileChanges)
106 {
107 m_TotalTilesDrawn += SwitchChange.second.size();
108 }
109
110 // Process tune tiles
111 for(auto const &TuneChange : m_TuneTileChanges)
112 {
113 m_TotalTilesDrawn += TuneChange.second.size();
114 }
115
116 m_TotalLayers += !m_SpeedupTileChanges.empty();
117 m_TotalLayers += !m_SwitchTileChanges.empty();
118 m_TotalLayers += !m_TeleTileChanges.empty();
119 m_TotalLayers += !m_TuneTileChanges.empty();
120}
121
122bool CEditorBrushDrawAction::IsEmpty()
123{
124 return m_vTileChanges.empty() && m_SpeedupTileChanges.empty() && m_SwitchTileChanges.empty() && m_TeleTileChanges.empty() && m_TuneTileChanges.empty();
125}
126
127void CEditorBrushDrawAction::Undo()
128{
129 Apply(Undo: true);
130}
131
132void CEditorBrushDrawAction::Redo()
133{
134 Apply(Undo: false);
135}
136
137void CEditorBrushDrawAction::Apply(bool Undo)
138{
139 // Process normal tiles
140 for(auto const &Pair : m_vTileChanges)
141 {
142 int Layer = Pair.first;
143 std::shared_ptr<CLayer> pLayer = Map()->m_vpGroups[m_Group]->m_vpLayers[Layer];
144
145 if(pLayer->m_Type == LAYERTYPE_TILES)
146 {
147 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer);
148 auto Changes = Pair.second;
149 for(auto &Change : Changes)
150 {
151 int y = Change.first;
152 auto Line = Change.second;
153 for(auto &Tile : Line)
154 {
155 int x = Tile.first;
156 STileStateChange State = Tile.second;
157 pLayerTiles->SetTileIgnoreHistory(x, y, Tile: Undo ? State.m_Previous : State.m_Current);
158 }
159 }
160 }
161 }
162
163 // Process speedup tiles
164 for(auto const &SpeedupChange : m_SpeedupTileChanges)
165 {
166 int y = SpeedupChange.first;
167 auto Line = SpeedupChange.second;
168 for(auto &Tile : Line)
169 {
170 int x = Tile.first;
171 int Index = y * Map()->m_pSpeedupLayer->m_Width + x;
172 SSpeedupTileStateChange State = Tile.second;
173 SSpeedupTileStateChange::SData Data = Undo ? State.m_Previous : State.m_Current;
174
175 Map()->m_pSpeedupLayer->m_pSpeedupTile[Index].m_Force = Data.m_Force;
176 Map()->m_pSpeedupLayer->m_pSpeedupTile[Index].m_MaxSpeed = Data.m_MaxSpeed;
177 Map()->m_pSpeedupLayer->m_pSpeedupTile[Index].m_Angle = Data.m_Angle;
178 Map()->m_pSpeedupLayer->m_pSpeedupTile[Index].m_Type = Data.m_Type;
179 Map()->m_pSpeedupLayer->m_pTiles[Index].m_Index = Data.m_Index;
180 }
181 }
182
183 // Process tele tiles
184 for(auto const &TeleChange : m_TeleTileChanges)
185 {
186 int y = TeleChange.first;
187 auto Line = TeleChange.second;
188 for(auto &Tile : Line)
189 {
190 int x = Tile.first;
191 int Index = y * Map()->m_pTeleLayer->m_Width + x;
192 STeleTileStateChange State = Tile.second;
193 STeleTileStateChange::SData Data = Undo ? State.m_Previous : State.m_Current;
194
195 Map()->m_pTeleLayer->m_pTeleTile[Index].m_Number = Data.m_Number;
196 Map()->m_pTeleLayer->m_pTeleTile[Index].m_Type = Data.m_Type;
197 Map()->m_pTeleLayer->m_pTiles[Index].m_Index = Data.m_Index;
198 }
199 }
200
201 // Process switch tiles
202 for(auto const &SwitchChange : m_SwitchTileChanges)
203 {
204 int y = SwitchChange.first;
205 auto Line = SwitchChange.second;
206 for(auto &Tile : Line)
207 {
208 int x = Tile.first;
209 int Index = y * Map()->m_pSwitchLayer->m_Width + x;
210 SSwitchTileStateChange State = Tile.second;
211 SSwitchTileStateChange::SData Data = Undo ? State.m_Previous : State.m_Current;
212
213 Map()->m_pSwitchLayer->m_pSwitchTile[Index].m_Number = Data.m_Number;
214 Map()->m_pSwitchLayer->m_pSwitchTile[Index].m_Type = Data.m_Type;
215 Map()->m_pSwitchLayer->m_pSwitchTile[Index].m_Flags = Data.m_Flags;
216 Map()->m_pSwitchLayer->m_pSwitchTile[Index].m_Delay = Data.m_Delay;
217 Map()->m_pSwitchLayer->m_pTiles[Index].m_Index = Data.m_Index;
218 }
219 }
220
221 // Process tune tiles
222 for(auto const &TuneChange : m_TuneTileChanges)
223 {
224 int y = TuneChange.first;
225 auto Line = TuneChange.second;
226 for(auto &Tile : Line)
227 {
228 int x = Tile.first;
229 int Index = y * Map()->m_pTuneLayer->m_Width + x;
230 STuneTileStateChange State = Tile.second;
231 STuneTileStateChange::SData Data = Undo ? State.m_Previous : State.m_Current;
232
233 Map()->m_pTuneLayer->m_pTuneTile[Index].m_Number = Data.m_Number;
234 Map()->m_pTuneLayer->m_pTuneTile[Index].m_Type = Data.m_Type;
235 Map()->m_pTuneLayer->m_pTiles[Index].m_Index = Data.m_Index;
236 }
237 }
238}
239
240// -------------------------------------------
241
242CEditorActionQuadPlace::CEditorActionQuadPlace(CEditorMap *pMap, int GroupIndex, int LayerIndex, std::vector<CQuad> &vBrush) :
243 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_vBrush(vBrush)
244{
245 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Quad place (x%d)", (int)m_vBrush.size());
246}
247
248void CEditorActionQuadPlace::Undo()
249{
250 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
251 for(size_t k = 0; k < m_vBrush.size(); k++)
252 pLayerQuads->m_vQuads.pop_back();
253
254 Map()->OnModify();
255}
256void CEditorActionQuadPlace::Redo()
257{
258 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
259 for(auto &Brush : m_vBrush)
260 pLayerQuads->m_vQuads.push_back(x: Brush);
261
262 Map()->OnModify();
263}
264
265CEditorActionSoundPlace::CEditorActionSoundPlace(CEditorMap *pMap, int GroupIndex, int LayerIndex, std::vector<CSoundSource> &vBrush) :
266 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_vBrush(vBrush)
267{
268 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Sound place (x%d)", (int)m_vBrush.size());
269}
270
271void CEditorActionSoundPlace::Undo()
272{
273 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
274 for(size_t k = 0; k < m_vBrush.size(); k++)
275 pLayerSounds->m_vSources.pop_back();
276
277 Map()->OnModify();
278}
279
280void CEditorActionSoundPlace::Redo()
281{
282 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
283 for(auto &Brush : m_vBrush)
284 pLayerSounds->m_vSources.push_back(x: Brush);
285
286 Map()->OnModify();
287}
288
289// ---------------------------------------------------------------------------------------
290
291CEditorActionDeleteQuad::CEditorActionDeleteQuad(CEditorMap *pMap, int GroupIndex, int LayerIndex, std::vector<int> const &vQuadsIndices, std::vector<CQuad> const &vDeletedQuads) :
292 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_vQuadsIndices(vQuadsIndices), m_vDeletedQuads(vDeletedQuads)
293{
294 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Delete quad (x%d)", (int)m_vDeletedQuads.size());
295}
296
297void CEditorActionDeleteQuad::Undo()
298{
299 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
300 for(size_t k = 0; k < m_vQuadsIndices.size(); k++)
301 {
302 pLayerQuads->m_vQuads.insert(position: pLayerQuads->m_vQuads.begin() + m_vQuadsIndices[k], x: m_vDeletedQuads[k]);
303 }
304}
305
306void CEditorActionDeleteQuad::Redo()
307{
308 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
309 std::vector<int> vQuads(m_vQuadsIndices);
310
311 for(int i = 0; i < (int)vQuads.size(); ++i)
312 {
313 pLayerQuads->m_vQuads.erase(position: pLayerQuads->m_vQuads.begin() + vQuads[i]);
314 for(int j = i + 1; j < (int)vQuads.size(); ++j)
315 if(vQuads[j] > vQuads[i])
316 vQuads[j]--;
317
318 vQuads.erase(position: vQuads.begin() + i);
319
320 i--;
321 }
322}
323
324// ---------------------------------------------------------------------------------------
325
326CEditorActionEditQuadPoint::CEditorActionEditQuadPoint(CEditorMap *pMap, int GroupIndex, int LayerIndex, int QuadIndex, std::vector<CPoint> const &vPreviousPoints, std::vector<CPoint> const &vCurrentPoints) :
327 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_QuadIndex(QuadIndex), m_vPreviousPoints(vPreviousPoints), m_vCurrentPoints(vCurrentPoints)
328{
329 str_copy(dst&: m_aDisplayText, src: "Edit quad points");
330}
331
332void CEditorActionEditQuadPoint::Undo()
333{
334 Apply(vValue: m_vPreviousPoints);
335}
336
337void CEditorActionEditQuadPoint::Redo()
338{
339 Apply(vValue: m_vCurrentPoints);
340}
341
342void CEditorActionEditQuadPoint::Apply(const std::vector<CPoint> &vValue)
343{
344 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
345 CQuad &Quad = pLayerQuads->m_vQuads[m_QuadIndex];
346 dbg_assert(std::size(Quad.m_aPoints) == vValue.size(), "Expected %d values, got %d", (int)std::size(Quad.m_aPoints), (int)vValue.size());
347 std::copy_n(first: vValue.begin(), n: std::size(Quad.m_aPoints), result: Quad.m_aPoints);
348}
349
350CEditorActionEditQuadColor::CEditorActionEditQuadColor(CEditorMap *pMap, int GroupIndex, int LayerIndex, int QuadIndex, std::vector<CColor> const &vPreviousColors, std::vector<CColor> const &vCurrentColors) :
351 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_QuadIndex(QuadIndex), m_vPreviousColors(vPreviousColors), m_vCurrentColors(vCurrentColors)
352{
353 str_copy(dst&: m_aDisplayText, src: "Edit quad point colors");
354}
355
356void CEditorActionEditQuadColor::Undo()
357{
358 Apply(vValue&: m_vPreviousColors);
359}
360
361void CEditorActionEditQuadColor::Redo()
362{
363 Apply(vValue&: m_vCurrentColors);
364}
365
366void CEditorActionEditQuadColor::Apply(std::vector<CColor> &vValue)
367{
368 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
369 CQuad &Quad = pLayerQuads->m_vQuads[m_QuadIndex];
370 dbg_assert(std::size(Quad.m_aColors) == vValue.size(), "Expected %d values, got %d", (int)std::size(Quad.m_aColors), (int)vValue.size());
371 std::copy_n(first: vValue.begin(), n: std::size(Quad.m_aColors), result: Quad.m_aColors);
372}
373
374CEditorActionEditQuadProp::CEditorActionEditQuadProp(CEditorMap *pMap, int GroupIndex, int LayerIndex, int QuadIndex, EQuadProp Prop, int Previous, int Current) :
375 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_QuadIndex(QuadIndex), m_Prop(Prop), m_Previous(Previous), m_Current(Current)
376{
377 static const char *s_apNames[] = {
378 "order",
379 "pos X",
380 "pos Y",
381 "pos env",
382 "pos env offset",
383 nullptr, // color
384 "color env",
385 "color env offset"};
386 static_assert(std::size(s_apNames) == (size_t)EQuadProp::NUM_PROPS);
387 dbg_assert(Prop != EQuadProp::PROP_COLOR, "Color prop implemented by CEditorActionEditQuadPointProp");
388 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit quad %s property in layer %d of group %d", s_apNames[(int)m_Prop], m_LayerIndex, m_GroupIndex);
389}
390
391void CEditorActionEditQuadProp::Undo()
392{
393 Apply(Value: m_Previous);
394}
395
396void CEditorActionEditQuadProp::Redo()
397{
398 Apply(Value: m_Current);
399}
400
401void CEditorActionEditQuadProp::Apply(int Value)
402{
403 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
404 CQuad &Quad = pLayerQuads->m_vQuads[m_QuadIndex];
405 if(m_Prop == EQuadProp::PROP_POS_ENV)
406 Quad.m_PosEnv = Value;
407 else if(m_Prop == EQuadProp::PROP_POS_ENV_OFFSET)
408 Quad.m_PosEnvOffset = Value;
409 else if(m_Prop == EQuadProp::PROP_COLOR_ENV)
410 Quad.m_ColorEnv = Value;
411 else if(m_Prop == EQuadProp::PROP_COLOR_ENV_OFFSET)
412 Quad.m_ColorEnvOffset = Value;
413}
414
415CEditorActionEditQuadPointProp::CEditorActionEditQuadPointProp(CEditorMap *pMap, int GroupIndex, int LayerIndex, int QuadIndex, int PointIndex, EQuadPointProp Prop, int Previous, int Current) :
416 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_QuadIndex(QuadIndex), m_PointIndex(PointIndex), m_Prop(Prop), m_Previous(Previous), m_Current(Current)
417{
418 static const char *s_apNames[] = {
419 "pos X",
420 "pos Y",
421 "color",
422 "tex U",
423 "tex V"};
424 static_assert(std::size(s_apNames) == (size_t)EQuadPointProp::NUM_PROPS);
425 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit quad point %s property in layer %d of group %d", s_apNames[(int)m_Prop], m_LayerIndex, m_GroupIndex);
426}
427
428void CEditorActionEditQuadPointProp::Undo()
429{
430 Apply(Value: m_Previous);
431}
432
433void CEditorActionEditQuadPointProp::Redo()
434{
435 Apply(Value: m_Current);
436}
437
438void CEditorActionEditQuadPointProp::Apply(int Value)
439{
440 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
441 CQuad &Quad = pLayerQuads->m_vQuads[m_QuadIndex];
442
443 if(m_Prop == EQuadPointProp::PROP_COLOR)
444 {
445 const ColorRGBA ColorPick = ColorRGBA::UnpackAlphaLast<ColorRGBA>(Color: Value);
446
447 Quad.m_aColors[m_PointIndex].r = ColorPick.r * 255.0f;
448 Quad.m_aColors[m_PointIndex].g = ColorPick.g * 255.0f;
449 Quad.m_aColors[m_PointIndex].b = ColorPick.b * 255.0f;
450 Quad.m_aColors[m_PointIndex].a = ColorPick.a * 255.0f;
451
452 Editor()->m_ColorPickerPopupContext.m_RgbaColor = ColorPick;
453 Editor()->m_ColorPickerPopupContext.m_HslaColor = color_cast<ColorHSLA>(rgb: ColorPick);
454 Editor()->m_ColorPickerPopupContext.m_HsvaColor = color_cast<ColorHSVA>(hsl: Editor()->m_ColorPickerPopupContext.m_HslaColor);
455 }
456 else if(m_Prop == EQuadPointProp::PROP_TEX_U)
457 {
458 Quad.m_aTexcoords[m_PointIndex].x = Value;
459 }
460 else if(m_Prop == EQuadPointProp::PROP_TEX_V)
461 {
462 Quad.m_aTexcoords[m_PointIndex].y = Value;
463 }
464}
465
466// ---------------------------------------------------------------------------------------
467
468CEditorActionBulk::CEditorActionBulk(CEditorMap *pMap, const std::vector<std::shared_ptr<IEditorAction>> &vpActions, const char *pDisplay, bool Reverse) :
469 IEditorAction(pMap), m_vpActions(vpActions), m_Reverse(Reverse)
470{
471 // Assuming we only use bulk for actions of same type, if no display was provided
472 if(!pDisplay)
473 {
474 const char *pBaseDisplay = m_vpActions[0]->DisplayText();
475 if(m_vpActions.size() == 1)
476 str_copy(dst&: m_aDisplayText, src: pBaseDisplay);
477 else
478 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "%s (x%d)", pBaseDisplay, (int)m_vpActions.size());
479 }
480 else
481 {
482 str_copy(dst&: m_aDisplayText, src: pDisplay);
483 }
484}
485
486void CEditorActionBulk::Undo()
487{
488 if(m_Reverse)
489 {
490 // reverse_view is not supported in gcc 10
491 for(auto pIt = m_vpActions.rbegin(); pIt != m_vpActions.rend(); pIt++) // NOLINT: modernize-loop-convert
492 {
493 auto &pAction = *pIt;
494 pAction->Undo();
495 }
496 }
497 else
498 {
499 for(auto &pAction : m_vpActions)
500 {
501 pAction->Undo();
502 }
503 }
504}
505
506void CEditorActionBulk::Redo()
507{
508 for(auto &pAction : m_vpActions)
509 {
510 pAction->Redo();
511 }
512}
513
514// ---------
515
516CEditorActionTileChanges::CEditorActionTileChanges(CEditorMap *pMap, int GroupIndex, int LayerIndex, const char *pAction, const EditorTileStateChangeHistory<STileStateChange> &Changes) :
517 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_Changes(Changes)
518{
519 ComputeInfos();
520 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "%s (x%d)", pAction, m_TotalChanges);
521}
522
523void CEditorActionTileChanges::Undo()
524{
525 Apply(Undo: true);
526}
527
528void CEditorActionTileChanges::Redo()
529{
530 Apply(Undo: false);
531}
532
533void CEditorActionTileChanges::Apply(bool Undo)
534{
535 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: m_pLayer);
536 for(auto &Change : m_Changes)
537 {
538 int y = Change.first;
539 auto Line = Change.second;
540 for(auto &Tile : Line)
541 {
542 int x = Tile.first;
543 STileStateChange State = Tile.second;
544 pLayerTiles->SetTileIgnoreHistory(x, y, Tile: Undo ? State.m_Previous : State.m_Current);
545 }
546 }
547
548 Map()->OnModify();
549}
550
551void CEditorActionTileChanges::ComputeInfos()
552{
553 m_TotalChanges = 0;
554 for(auto &Line : m_Changes)
555 m_TotalChanges += Line.second.size();
556}
557
558// ---------
559
560CEditorActionLayerBase::CEditorActionLayerBase(CEditorMap *pMap, int GroupIndex, int LayerIndex) :
561 IEditorAction(pMap), m_GroupIndex(GroupIndex), m_LayerIndex(LayerIndex)
562{
563 m_pLayer = Map()->m_vpGroups[GroupIndex]->m_vpLayers[LayerIndex];
564}
565
566// ----------
567
568CEditorActionAddLayer::CEditorActionAddLayer(CEditorMap *pMap, int GroupIndex, int LayerIndex, bool Duplicate) :
569 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_Duplicate(Duplicate)
570{
571 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "%s %s layer in group %d", m_Duplicate ? "Duplicate" : "New", m_pLayer->TypeName(), m_GroupIndex);
572}
573
574void CEditorActionAddLayer::Undo()
575{
576 // Undo: remove layer from vector but keep it in case we want to add it back
577 auto &vLayers = Map()->m_vpGroups[m_GroupIndex]->m_vpLayers;
578
579 if(m_pLayer->m_Type == LAYERTYPE_TILES)
580 {
581 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: m_pLayer);
582 if(pLayerTiles->m_HasFront)
583 Map()->m_pFrontLayer = nullptr;
584 else if(pLayerTiles->m_HasTele)
585 Map()->m_pTeleLayer = nullptr;
586 else if(pLayerTiles->m_HasSpeedup)
587 Map()->m_pSpeedupLayer = nullptr;
588 else if(pLayerTiles->m_HasSwitch)
589 Map()->m_pSwitchLayer = nullptr;
590 else if(pLayerTiles->m_HasTune)
591 Map()->m_pTuneLayer = nullptr;
592 }
593
594 vLayers.erase(position: vLayers.begin() + m_LayerIndex);
595
596 Map()->m_vpGroups[m_GroupIndex]->m_Collapse = false;
597 if(m_LayerIndex >= (int)vLayers.size())
598 Editor()->SelectLayer(LayerIndex: vLayers.size() - 1, GroupIndex: m_GroupIndex);
599
600 Map()->OnModify();
601}
602
603void CEditorActionAddLayer::Redo()
604{
605 // Redo: add back the removed layer contained in this class
606 auto &vLayers = Map()->m_vpGroups[m_GroupIndex]->m_vpLayers;
607
608 if(m_pLayer->m_Type == LAYERTYPE_TILES)
609 {
610 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: m_pLayer);
611 if(pLayerTiles->m_HasFront)
612 Map()->m_pFrontLayer = std::static_pointer_cast<CLayerFront>(r: m_pLayer);
613 else if(pLayerTiles->m_HasTele)
614 Map()->m_pTeleLayer = std::static_pointer_cast<CLayerTele>(r: m_pLayer);
615 else if(pLayerTiles->m_HasSpeedup)
616 Map()->m_pSpeedupLayer = std::static_pointer_cast<CLayerSpeedup>(r: m_pLayer);
617 else if(pLayerTiles->m_HasSwitch)
618 Map()->m_pSwitchLayer = std::static_pointer_cast<CLayerSwitch>(r: m_pLayer);
619 else if(pLayerTiles->m_HasTune)
620 Map()->m_pTuneLayer = std::static_pointer_cast<CLayerTune>(r: m_pLayer);
621 }
622
623 vLayers.insert(position: vLayers.begin() + m_LayerIndex, x: m_pLayer);
624
625 Map()->m_vpGroups[m_GroupIndex]->m_Collapse = false;
626 Editor()->SelectLayer(LayerIndex: m_LayerIndex, GroupIndex: m_GroupIndex);
627 Map()->OnModify();
628}
629
630CEditorActionDeleteLayer::CEditorActionDeleteLayer(CEditorMap *pMap, int GroupIndex, int LayerIndex) :
631 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex)
632{
633 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Delete %s layer of group %d", m_pLayer->TypeName(), m_GroupIndex);
634}
635
636void CEditorActionDeleteLayer::Redo()
637{
638 // Redo: remove layer from vector but keep it in case we want to add it back
639 auto &vLayers = Map()->m_vpGroups[m_GroupIndex]->m_vpLayers;
640
641 if(m_pLayer->m_Type == LAYERTYPE_TILES)
642 {
643 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: m_pLayer);
644 if(pLayerTiles->m_HasFront)
645 Map()->m_pFrontLayer = nullptr;
646 else if(pLayerTiles->m_HasTele)
647 Map()->m_pTeleLayer = nullptr;
648 else if(pLayerTiles->m_HasSpeedup)
649 Map()->m_pSpeedupLayer = nullptr;
650 else if(pLayerTiles->m_HasSwitch)
651 Map()->m_pSwitchLayer = nullptr;
652 else if(pLayerTiles->m_HasTune)
653 Map()->m_pTuneLayer = nullptr;
654 }
655
656 Map()->m_vpGroups[m_GroupIndex]->DeleteLayer(Index: m_LayerIndex);
657
658 Map()->m_vpGroups[m_GroupIndex]->m_Collapse = false;
659 if(m_LayerIndex >= (int)vLayers.size())
660 Editor()->SelectLayer(LayerIndex: vLayers.size() - 1, GroupIndex: m_GroupIndex);
661
662 Map()->OnModify();
663}
664
665void CEditorActionDeleteLayer::Undo()
666{
667 // Undo: add back the removed layer contained in this class
668 auto &vLayers = Map()->m_vpGroups[m_GroupIndex]->m_vpLayers;
669
670 if(m_pLayer->m_Type == LAYERTYPE_TILES)
671 {
672 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: m_pLayer);
673 if(pLayerTiles->m_HasFront)
674 Map()->m_pFrontLayer = std::static_pointer_cast<CLayerFront>(r: m_pLayer);
675 else if(pLayerTiles->m_HasTele)
676 Map()->m_pTeleLayer = std::static_pointer_cast<CLayerTele>(r: m_pLayer);
677 else if(pLayerTiles->m_HasSpeedup)
678 Map()->m_pSpeedupLayer = std::static_pointer_cast<CLayerSpeedup>(r: m_pLayer);
679 else if(pLayerTiles->m_HasSwitch)
680 Map()->m_pSwitchLayer = std::static_pointer_cast<CLayerSwitch>(r: m_pLayer);
681 else if(pLayerTiles->m_HasTune)
682 Map()->m_pTuneLayer = std::static_pointer_cast<CLayerTune>(r: m_pLayer);
683 }
684
685 vLayers.insert(position: vLayers.begin() + m_LayerIndex, x: m_pLayer);
686
687 Map()->m_vpGroups[m_GroupIndex]->m_Collapse = false;
688 Editor()->SelectLayer(LayerIndex: m_LayerIndex, GroupIndex: m_GroupIndex);
689 Map()->OnModify();
690}
691
692CEditorActionGroup::CEditorActionGroup(CEditorMap *pMap, int GroupIndex, bool Delete) :
693 IEditorAction(pMap), m_GroupIndex(GroupIndex), m_Delete(Delete)
694{
695 m_pGroup = Map()->m_vpGroups[GroupIndex];
696 if(m_Delete)
697 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Delete group %d", m_GroupIndex);
698 else
699 str_copy(dst: m_aDisplayText, src: "New group", dst_size: sizeof(m_aDisplayText));
700}
701
702void CEditorActionGroup::Undo()
703{
704 if(m_Delete)
705 {
706 // Undo: add back the group
707 Map()->m_vpGroups.insert(position: Map()->m_vpGroups.begin() + m_GroupIndex, x: m_pGroup);
708 Editor()->m_SelectedGroup = m_GroupIndex;
709 Map()->OnModify();
710 }
711 else
712 {
713 // Undo: delete the group
714 Map()->DeleteGroup(Index: m_GroupIndex);
715 Editor()->m_SelectedGroup = maximum(a: 0, b: m_GroupIndex - 1);
716 }
717
718 Map()->OnModify();
719}
720
721void CEditorActionGroup::Redo()
722{
723 if(!m_Delete)
724 {
725 // Redo: add back the group
726 Map()->m_vpGroups.insert(position: Map()->m_vpGroups.begin() + m_GroupIndex, x: m_pGroup);
727 Editor()->m_SelectedGroup = m_GroupIndex;
728 }
729 else
730 {
731 // Redo: delete the group
732 Map()->DeleteGroup(Index: m_GroupIndex);
733 Editor()->m_SelectedGroup = maximum(a: 0, b: m_GroupIndex - 1);
734 }
735
736 Map()->OnModify();
737}
738
739CEditorActionEditGroupProp::CEditorActionEditGroupProp(CEditorMap *pMap, int GroupIndex, EGroupProp Prop, int Previous, int Current) :
740 IEditorAction(pMap), m_GroupIndex(GroupIndex), m_Prop(Prop), m_Previous(Previous), m_Current(Current)
741{
742 static const char *s_apNames[] = {
743 "order",
744 "pos X",
745 "pos Y",
746 "para X",
747 "para Y",
748 "use clipping",
749 "clip X",
750 "clip Y",
751 "clip W",
752 "clip H"};
753 static_assert(std::size(s_apNames) == (size_t)EGroupProp::NUM_PROPS);
754
755 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit group %d %s property", m_GroupIndex, s_apNames[(int)Prop]);
756}
757
758void CEditorActionEditGroupProp::Undo()
759{
760 auto pGroup = Map()->m_vpGroups[m_GroupIndex];
761
762 if(m_Prop == EGroupProp::PROP_ORDER)
763 {
764 Editor()->m_SelectedGroup = Map()->MoveGroup(IndexFrom: m_Current, IndexTo: m_Previous);
765 }
766 else
767 Apply(Value: m_Previous);
768}
769
770void CEditorActionEditGroupProp::Redo()
771{
772 auto pGroup = Map()->m_vpGroups[m_GroupIndex];
773
774 if(m_Prop == EGroupProp::PROP_ORDER)
775 {
776 Editor()->m_SelectedGroup = Map()->MoveGroup(IndexFrom: m_Previous, IndexTo: m_Current);
777 }
778 else
779 Apply(Value: m_Current);
780}
781
782void CEditorActionEditGroupProp::Apply(int Value)
783{
784 auto pGroup = Map()->m_vpGroups[m_GroupIndex];
785
786 if(m_Prop == EGroupProp::PROP_POS_X)
787 pGroup->m_OffsetX = Value;
788 if(m_Prop == EGroupProp::PROP_POS_Y)
789 pGroup->m_OffsetY = Value;
790 if(m_Prop == EGroupProp::PROP_PARA_X)
791 pGroup->m_ParallaxX = Value;
792 if(m_Prop == EGroupProp::PROP_PARA_Y)
793 pGroup->m_ParallaxY = Value;
794 if(m_Prop == EGroupProp::PROP_USE_CLIPPING)
795 pGroup->m_UseClipping = Value;
796 if(m_Prop == EGroupProp::PROP_CLIP_X)
797 pGroup->m_ClipX = Value;
798 if(m_Prop == EGroupProp::PROP_CLIP_Y)
799 pGroup->m_ClipY = Value;
800 if(m_Prop == EGroupProp::PROP_CLIP_W)
801 pGroup->m_ClipW = Value;
802 if(m_Prop == EGroupProp::PROP_CLIP_H)
803 pGroup->m_ClipH = Value;
804
805 Map()->OnModify();
806}
807
808template<typename E>
809CEditorActionEditLayerPropBase<E>::CEditorActionEditLayerPropBase(CEditorMap *pMap, int GroupIndex, int LayerIndex, E Prop, int Previous, int Current) :
810 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_Prop(Prop), m_Previous(Previous), m_Current(Current)
811{
812}
813
814CEditorActionEditLayerProp::CEditorActionEditLayerProp(CEditorMap *pMap, int GroupIndex, int LayerIndex, ELayerProp Prop, int Previous, int Current) :
815 CEditorActionEditLayerPropBase(pMap, GroupIndex, LayerIndex, Prop, Previous, Current)
816{
817 static const char *s_apNames[] = {
818 "group",
819 "order",
820 "HQ"};
821 static_assert(std::size(s_apNames) == (size_t)ELayerProp::NUM_PROPS);
822
823 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit layer %d in group %d %s property", m_LayerIndex, m_GroupIndex, s_apNames[(int)m_Prop]);
824}
825
826void CEditorActionEditLayerProp::Undo()
827{
828 auto pCurrentGroup = Map()->m_vpGroups[m_GroupIndex];
829
830 if(m_Prop == ELayerProp::PROP_ORDER)
831 {
832 Editor()->SelectLayer(LayerIndex: pCurrentGroup->MoveLayer(IndexFrom: m_Current, IndexTo: m_Previous));
833 }
834 else
835 Apply(Value: m_Previous);
836}
837
838void CEditorActionEditLayerProp::Redo()
839{
840 auto pCurrentGroup = Map()->m_vpGroups[m_GroupIndex];
841
842 if(m_Prop == ELayerProp::PROP_ORDER)
843 {
844 Editor()->SelectLayer(LayerIndex: pCurrentGroup->MoveLayer(IndexFrom: m_Previous, IndexTo: m_Current));
845 }
846 else
847 Apply(Value: m_Current);
848}
849
850void CEditorActionEditLayerProp::Apply(int Value)
851{
852 if(m_Prop == ELayerProp::PROP_GROUP)
853 {
854 auto pCurrentGroup = Map()->m_vpGroups[Value == m_Previous ? m_Current : m_Previous];
855 auto pPreviousGroup = Map()->m_vpGroups[Value];
856 pCurrentGroup->m_vpLayers.erase(position: pCurrentGroup->m_vpLayers.begin() + pCurrentGroup->m_vpLayers.size() - 1);
857 if(Value == m_Previous)
858 pPreviousGroup->m_vpLayers.insert(position: pPreviousGroup->m_vpLayers.begin() + m_LayerIndex, x: m_pLayer);
859 else
860 pPreviousGroup->m_vpLayers.push_back(x: m_pLayer);
861 Editor()->m_SelectedGroup = Value;
862 Editor()->SelectLayer(LayerIndex: m_LayerIndex);
863 }
864 else if(m_Prop == ELayerProp::PROP_HQ)
865 {
866 m_pLayer->m_Flags = Value;
867 }
868
869 Map()->OnModify();
870}
871
872CEditorActionEditLayerTilesProp::CEditorActionEditLayerTilesProp(CEditorMap *pMap, int GroupIndex, int LayerIndex, ETilesProp Prop, int Previous, int Current) :
873 CEditorActionEditLayerPropBase(pMap, GroupIndex, LayerIndex, Prop, Previous, Current)
874{
875 static const char *s_apNames[] = {
876 "width",
877 "height",
878 "shift",
879 "shift by",
880 "image",
881 "color",
882 "color env",
883 "color env offset",
884 "automapper",
885 "automapper reference",
886 "live gametiles",
887 "seed"};
888 static_assert(std::size(s_apNames) == (size_t)ETilesProp::NUM_PROPS);
889
890 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit tiles layer %d in group %d %s property", m_LayerIndex, m_GroupIndex, s_apNames[(int)Prop]);
891}
892
893void CEditorActionEditLayerTilesProp::SetSavedLayers(const std::map<int, std::shared_ptr<CLayer>> &SavedLayers)
894{
895 m_SavedLayers = std::map(SavedLayers);
896}
897
898void CEditorActionEditLayerTilesProp::Undo()
899{
900 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: m_pLayer);
901 std::shared_ptr<CLayerTiles> pSavedLayerTiles = nullptr;
902
903 if(m_Prop == ETilesProp::PROP_WIDTH || m_Prop == ETilesProp::PROP_HEIGHT)
904 {
905 if(m_Prop == ETilesProp::PROP_HEIGHT)
906 pLayerTiles->Resize(NewW: pLayerTiles->m_Width, NewH: m_Previous);
907 else if(m_Prop == ETilesProp::PROP_WIDTH)
908 pLayerTiles->Resize(NewW: m_Previous, NewH: pLayerTiles->m_Height);
909
910 RestoreLayer(Layer: LAYERTYPE_TILES, pLayerTiles);
911 if(pLayerTiles->m_HasGame || pLayerTiles->m_HasFront || pLayerTiles->m_HasSwitch || pLayerTiles->m_HasSpeedup || pLayerTiles->m_HasTune)
912 {
913 if(Map()->m_pFrontLayer && !pLayerTiles->m_HasFront)
914 RestoreLayer(Layer: LAYERTYPE_FRONT, pLayerTiles: Map()->m_pFrontLayer);
915 if(Map()->m_pTeleLayer && !pLayerTiles->m_HasTele)
916 RestoreLayer(Layer: LAYERTYPE_TELE, pLayerTiles: Map()->m_pTeleLayer);
917 if(Map()->m_pSwitchLayer && !pLayerTiles->m_HasSwitch)
918 RestoreLayer(Layer: LAYERTYPE_SWITCH, pLayerTiles: Map()->m_pSwitchLayer);
919 if(Map()->m_pSpeedupLayer && !pLayerTiles->m_HasSpeedup)
920 RestoreLayer(Layer: LAYERTYPE_SPEEDUP, pLayerTiles: Map()->m_pSpeedupLayer);
921 if(Map()->m_pTuneLayer && !pLayerTiles->m_HasTune)
922 RestoreLayer(Layer: LAYERTYPE_TUNE, pLayerTiles: Map()->m_pTuneLayer);
923 if(!pLayerTiles->m_HasGame)
924 RestoreLayer(Layer: LAYERTYPE_GAME, pLayerTiles: Map()->m_pGameLayer);
925 }
926 }
927 else if(m_Prop == ETilesProp::PROP_SHIFT)
928 {
929 RestoreLayer(Layer: LAYERTYPE_TILES, pLayerTiles);
930 }
931 else if(m_Prop == ETilesProp::PROP_SHIFT_BY)
932 {
933 Editor()->m_ShiftBy = m_Previous;
934 }
935 else if(m_Prop == ETilesProp::PROP_IMAGE)
936 {
937 if(m_Previous == -1 || Map()->m_vpImages.empty())
938 {
939 pLayerTiles->m_Image = -1;
940 }
941 else
942 {
943 pLayerTiles->m_Image = m_Previous % Map()->m_vpImages.size();
944 pLayerTiles->m_AutoMapperConfig = -1;
945 }
946 }
947 else if(m_Prop == ETilesProp::PROP_COLOR)
948 {
949 const ColorRGBA ColorPick = ColorRGBA::UnpackAlphaLast<ColorRGBA>(Color: m_Previous);
950
951 pLayerTiles->m_Color.r = ColorPick.r * 255.0f;
952 pLayerTiles->m_Color.g = ColorPick.g * 255.0f;
953 pLayerTiles->m_Color.b = ColorPick.b * 255.0f;
954 pLayerTiles->m_Color.a = ColorPick.a * 255.0f;
955
956 Editor()->m_ColorPickerPopupContext.m_RgbaColor = ColorPick;
957 Editor()->m_ColorPickerPopupContext.m_HslaColor = color_cast<ColorHSLA>(rgb: ColorPick);
958 Editor()->m_ColorPickerPopupContext.m_HsvaColor = color_cast<ColorHSVA>(hsl: Editor()->m_ColorPickerPopupContext.m_HslaColor);
959 }
960 else if(m_Prop == ETilesProp::PROP_COLOR_ENV)
961 {
962 pLayerTiles->m_ColorEnv = m_Previous;
963 }
964 else if(m_Prop == ETilesProp::PROP_COLOR_ENV_OFFSET)
965 {
966 pLayerTiles->m_ColorEnvOffset = m_Previous;
967 }
968 else if(m_Prop == ETilesProp::PROP_AUTOMAPPER)
969 {
970 pLayerTiles->m_AutoMapperConfig = m_Previous;
971 }
972 else if(m_Prop == ETilesProp::PROP_LIVE_GAMETILES)
973 {
974 pLayerTiles->m_LiveGameTiles = m_Previous;
975 }
976 else if(m_Prop == ETilesProp::PROP_SEED)
977 {
978 pLayerTiles->m_Seed = m_Previous;
979 }
980
981 Map()->OnModify();
982}
983
984void CEditorActionEditLayerTilesProp::Redo()
985{
986 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: m_pLayer);
987
988 if(m_Prop == ETilesProp::PROP_WIDTH || m_Prop == ETilesProp::PROP_HEIGHT)
989 {
990 if(m_Prop == ETilesProp::PROP_HEIGHT)
991 pLayerTiles->Resize(NewW: pLayerTiles->m_Width, NewH: m_Current);
992 else if(m_Prop == ETilesProp::PROP_WIDTH)
993 pLayerTiles->Resize(NewW: m_Current, NewH: pLayerTiles->m_Height);
994
995 if(pLayerTiles->m_HasGame || pLayerTiles->m_HasFront || pLayerTiles->m_HasSwitch || pLayerTiles->m_HasSpeedup || pLayerTiles->m_HasTune)
996 {
997 if(Map()->m_pFrontLayer && !pLayerTiles->m_HasFront)
998 Map()->m_pFrontLayer->Resize(NewW: pLayerTiles->m_Width, NewH: pLayerTiles->m_Height);
999 if(Map()->m_pTeleLayer && !pLayerTiles->m_HasTele)
1000 Map()->m_pTeleLayer->Resize(NewW: pLayerTiles->m_Width, NewH: pLayerTiles->m_Height);
1001 if(Map()->m_pSwitchLayer && !pLayerTiles->m_HasSwitch)
1002 Map()->m_pSwitchLayer->Resize(NewW: pLayerTiles->m_Width, NewH: pLayerTiles->m_Height);
1003 if(Map()->m_pSpeedupLayer && !pLayerTiles->m_HasSpeedup)
1004 Map()->m_pSpeedupLayer->Resize(NewW: pLayerTiles->m_Width, NewH: pLayerTiles->m_Height);
1005 if(Map()->m_pTuneLayer && !pLayerTiles->m_HasTune)
1006 Map()->m_pTuneLayer->Resize(NewW: pLayerTiles->m_Width, NewH: pLayerTiles->m_Height);
1007 if(!pLayerTiles->m_HasGame)
1008 Map()->m_pGameLayer->Resize(NewW: pLayerTiles->m_Width, NewH: pLayerTiles->m_Height);
1009 }
1010 }
1011 else if(m_Prop == ETilesProp::PROP_SHIFT)
1012 {
1013 pLayerTiles->Shift(Direction: (EShiftDirection)m_Current);
1014 }
1015 else if(m_Prop == ETilesProp::PROP_SHIFT_BY)
1016 {
1017 Editor()->m_ShiftBy = m_Current;
1018 }
1019 else if(m_Prop == ETilesProp::PROP_IMAGE)
1020 {
1021 if(m_Current == -1 || Map()->m_vpImages.empty())
1022 {
1023 pLayerTiles->m_Image = -1;
1024 }
1025 else
1026 {
1027 pLayerTiles->m_Image = m_Current % Map()->m_vpImages.size();
1028 pLayerTiles->m_AutoMapperConfig = -1;
1029 }
1030 }
1031 else if(m_Prop == ETilesProp::PROP_COLOR)
1032 {
1033 const ColorRGBA ColorPick = ColorRGBA::UnpackAlphaLast<ColorRGBA>(Color: m_Current);
1034
1035 pLayerTiles->m_Color.r = ColorPick.r * 255.0f;
1036 pLayerTiles->m_Color.g = ColorPick.g * 255.0f;
1037 pLayerTiles->m_Color.b = ColorPick.b * 255.0f;
1038 pLayerTiles->m_Color.a = ColorPick.a * 255.0f;
1039
1040 Editor()->m_ColorPickerPopupContext.m_RgbaColor = ColorPick;
1041 Editor()->m_ColorPickerPopupContext.m_HslaColor = color_cast<ColorHSLA>(rgb: ColorPick);
1042 Editor()->m_ColorPickerPopupContext.m_HsvaColor = color_cast<ColorHSVA>(hsl: Editor()->m_ColorPickerPopupContext.m_HslaColor);
1043 }
1044 else if(m_Prop == ETilesProp::PROP_COLOR_ENV)
1045 {
1046 pLayerTiles->m_ColorEnv = m_Current;
1047 }
1048 else if(m_Prop == ETilesProp::PROP_COLOR_ENV_OFFSET)
1049 {
1050 pLayerTiles->m_ColorEnvOffset = m_Current;
1051 }
1052 else if(m_Prop == ETilesProp::PROP_AUTOMAPPER)
1053 {
1054 pLayerTiles->m_AutoMapperConfig = m_Current;
1055 }
1056 else if(m_Prop == ETilesProp::PROP_LIVE_GAMETILES)
1057 {
1058 pLayerTiles->m_LiveGameTiles = m_Current;
1059 }
1060 else if(m_Prop == ETilesProp::PROP_SEED)
1061 {
1062 pLayerTiles->m_Seed = m_Current;
1063 }
1064
1065 Map()->OnModify();
1066}
1067
1068void CEditorActionEditLayerTilesProp::RestoreLayer(int Layer, const std::shared_ptr<CLayerTiles> &pLayerTiles)
1069{
1070 if(m_SavedLayers[Layer] != nullptr)
1071 {
1072 std::shared_ptr<CLayerTiles> pSavedLayerTiles = std::static_pointer_cast<CLayerTiles>(r: m_SavedLayers[Layer]);
1073 mem_copy(dest: pLayerTiles->m_pTiles, source: pSavedLayerTiles->m_pTiles, size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile));
1074
1075 if(pLayerTiles->m_HasTele)
1076 {
1077 std::shared_ptr<CLayerTele> pLayerTele = std::static_pointer_cast<CLayerTele>(r: pLayerTiles);
1078 std::shared_ptr<CLayerTele> pSavedLayerTele = std::static_pointer_cast<CLayerTele>(r: pSavedLayerTiles);
1079 mem_copy(dest: pLayerTele->m_pTeleTile, source: pSavedLayerTele->m_pTeleTile, size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTeleTile));
1080 }
1081 else if(pLayerTiles->m_HasSpeedup)
1082 {
1083 std::shared_ptr<CLayerSpeedup> pLayerSpeedup = std::static_pointer_cast<CLayerSpeedup>(r: pLayerTiles);
1084 std::shared_ptr<CLayerSpeedup> pSavedLayerSpeedup = std::static_pointer_cast<CLayerSpeedup>(r: pSavedLayerTiles);
1085 mem_copy(dest: pLayerSpeedup->m_pSpeedupTile, source: pSavedLayerSpeedup->m_pSpeedupTile, size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CSpeedupTile));
1086 }
1087 else if(pLayerTiles->m_HasSwitch)
1088 {
1089 std::shared_ptr<CLayerSwitch> pLayerSwitch = std::static_pointer_cast<CLayerSwitch>(r: pLayerTiles);
1090 std::shared_ptr<CLayerSwitch> pSavedLayerSwitch = std::static_pointer_cast<CLayerSwitch>(r: pSavedLayerTiles);
1091 mem_copy(dest: pLayerSwitch->m_pSwitchTile, source: pSavedLayerSwitch->m_pSwitchTile, size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CSwitchTile));
1092 }
1093 else if(pLayerTiles->m_HasTune)
1094 {
1095 std::shared_ptr<CLayerTune> pLayerTune = std::static_pointer_cast<CLayerTune>(r: pLayerTiles);
1096 std::shared_ptr<CLayerTune> pSavedLayerTune = std::static_pointer_cast<CLayerTune>(r: pSavedLayerTiles);
1097 mem_copy(dest: pLayerTune->m_pTuneTile, source: pSavedLayerTune->m_pTuneTile, size: (size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTuneTile));
1098 }
1099 }
1100}
1101
1102CEditorActionEditLayerQuadsProp::CEditorActionEditLayerQuadsProp(CEditorMap *pMap, int GroupIndex, int LayerIndex, ELayerQuadsProp Prop, int Previous, int Current) :
1103 CEditorActionEditLayerPropBase(pMap, GroupIndex, LayerIndex, Prop, Previous, Current)
1104{
1105 static const char *s_apNames[] = {
1106 "image"};
1107 static_assert(std::size(s_apNames) == (size_t)ELayerQuadsProp::NUM_PROPS);
1108 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit quads layer %d in group %d %s property", m_LayerIndex, m_GroupIndex, s_apNames[(int)m_Prop]);
1109}
1110
1111void CEditorActionEditLayerQuadsProp::Undo()
1112{
1113 Apply(Value: m_Previous);
1114}
1115
1116void CEditorActionEditLayerQuadsProp::Redo()
1117{
1118 Apply(Value: m_Current);
1119}
1120
1121void CEditorActionEditLayerQuadsProp::Apply(int Value)
1122{
1123 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
1124 if(m_Prop == ELayerQuadsProp::PROP_IMAGE)
1125 {
1126 if(Value >= 0 && !Map()->m_vpImages.empty())
1127 pLayerQuads->m_Image = Value % Map()->m_vpImages.size();
1128 else
1129 pLayerQuads->m_Image = -1;
1130 }
1131
1132 Map()->OnModify();
1133}
1134
1135// --------------------------------------------------------------
1136
1137CEditorActionEditLayersGroupAndOrder::CEditorActionEditLayersGroupAndOrder(CEditorMap *pMap, int GroupIndex, const std::vector<int> &LayerIndices, int NewGroupIndex, const std::vector<int> &NewLayerIndices) :
1138 IEditorAction(pMap), m_GroupIndex(GroupIndex), m_LayerIndices(LayerIndices), m_NewGroupIndex(NewGroupIndex), m_NewLayerIndices(NewLayerIndices)
1139{
1140 std::sort(first: m_LayerIndices.begin(), last: m_LayerIndices.end());
1141 std::sort(first: m_NewLayerIndices.begin(), last: m_NewLayerIndices.end());
1142
1143 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit layers group and order (x%d)", (int)m_LayerIndices.size());
1144}
1145
1146void CEditorActionEditLayersGroupAndOrder::Undo()
1147{
1148 // Undo : restore group and order
1149 auto &pCurrentGroup = Map()->m_vpGroups[m_NewGroupIndex];
1150 auto &pPreviousGroup = Map()->m_vpGroups[m_GroupIndex];
1151 std::vector<std::shared_ptr<CLayer>> vpLayers;
1152 vpLayers.reserve(n: m_NewLayerIndices.size());
1153 for(auto &LayerIndex : m_NewLayerIndices)
1154 vpLayers.push_back(x: pCurrentGroup->m_vpLayers[LayerIndex]);
1155
1156 int k = 0;
1157 for(auto &pLayer : vpLayers)
1158 {
1159 pCurrentGroup->m_vpLayers.erase(position: std::find(first: pCurrentGroup->m_vpLayers.begin(), last: pCurrentGroup->m_vpLayers.end(), val: pLayer));
1160 pPreviousGroup->m_vpLayers.insert(position: pPreviousGroup->m_vpLayers.begin() + m_LayerIndices[k++], x: pLayer);
1161 }
1162
1163 Editor()->m_vSelectedLayers = m_LayerIndices;
1164 Editor()->m_SelectedGroup = m_GroupIndex;
1165}
1166
1167void CEditorActionEditLayersGroupAndOrder::Redo()
1168{
1169 // Redo : move layers
1170 auto &pCurrentGroup = Map()->m_vpGroups[m_GroupIndex];
1171 auto &pPreviousGroup = Map()->m_vpGroups[m_NewGroupIndex];
1172 std::vector<std::shared_ptr<CLayer>> vpLayers;
1173 vpLayers.reserve(n: m_LayerIndices.size());
1174 for(auto &LayerIndex : m_LayerIndices)
1175 vpLayers.push_back(x: pCurrentGroup->m_vpLayers[LayerIndex]);
1176
1177 int k = 0;
1178 for(auto &pLayer : vpLayers)
1179 {
1180 pCurrentGroup->m_vpLayers.erase(position: std::find(first: pCurrentGroup->m_vpLayers.begin(), last: pCurrentGroup->m_vpLayers.end(), val: pLayer));
1181 pPreviousGroup->m_vpLayers.insert(position: pPreviousGroup->m_vpLayers.begin() + m_NewLayerIndices[k++], x: pLayer);
1182 }
1183
1184 Editor()->m_vSelectedLayers = m_NewLayerIndices;
1185 Editor()->m_SelectedGroup = m_NewGroupIndex;
1186}
1187
1188// -----------------------------------
1189
1190CEditorActionAppendMap::CEditorActionAppendMap(CEditorMap *pMap, const char *pMapName, const SPrevInfo &PrevInfo, std::vector<int> &vImageIndexMap) :
1191 IEditorAction(pMap), m_PrevInfo(PrevInfo), m_vImageIndexMap(vImageIndexMap)
1192{
1193 str_copy(dst&: m_aMapName, src: pMapName);
1194 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Append %s", m_aMapName);
1195}
1196
1197void CEditorActionAppendMap::Undo()
1198{
1199 // Undo append:
1200 // - delete added groups
1201 // - delete added envelopes
1202 // - delete added images
1203 // - delete added sounds
1204
1205 // Delete added groups
1206 while((int)Map()->m_vpGroups.size() != m_PrevInfo.m_Groups)
1207 {
1208 Map()->m_vpGroups.pop_back();
1209 }
1210
1211 // Delete added envelopes
1212 while((int)Map()->m_vpEnvelopes.size() != m_PrevInfo.m_Envelopes)
1213 {
1214 Map()->m_vpEnvelopes.pop_back();
1215 }
1216
1217 // Delete added sounds
1218 while((int)Map()->m_vpSounds.size() != m_PrevInfo.m_Sounds)
1219 {
1220 Map()->m_vpSounds.pop_back();
1221 }
1222
1223 // Delete added images
1224 // Images are sorted when appending, so we need to revert sorting before deleting the images
1225 if(!m_vImageIndexMap.empty())
1226 {
1227 std::vector<int> vReverseIndexMap;
1228 vReverseIndexMap.resize(new_size: m_vImageIndexMap.size());
1229
1230 for(int k = 0; k < (int)m_vImageIndexMap.size(); k++)
1231 vReverseIndexMap[m_vImageIndexMap[k]] = k;
1232
1233 std::vector<std::shared_ptr<CEditorImage>> vpRevertedImages;
1234 vpRevertedImages.resize(new_size: Map()->m_vpImages.size());
1235
1236 for(int k = 0; k < (int)vReverseIndexMap.size(); k++)
1237 {
1238 vpRevertedImages[vReverseIndexMap[k]] = Map()->m_vpImages[k];
1239 }
1240 Map()->m_vpImages = vpRevertedImages;
1241
1242 Map()->ModifyImageIndex(IndexModifyFunction: [vReverseIndexMap](int *pIndex) {
1243 if(*pIndex >= 0)
1244 {
1245 *pIndex = vReverseIndexMap[*pIndex];
1246 }
1247 });
1248 }
1249
1250 while((int)Map()->m_vpImages.size() != m_PrevInfo.m_Images)
1251 {
1252 Map()->m_vpImages.pop_back();
1253 }
1254}
1255
1256void CEditorActionAppendMap::Redo()
1257{
1258 // Redo is just re-appending the same map
1259 Editor()->Append(pFilename: m_aMapName, StorageType: IStorage::TYPE_ALL, IgnoreHistory: true);
1260}
1261
1262// ---------------------------
1263
1264CEditorActionTileArt::CEditorActionTileArt(CEditorMap *pMap, int PreviousImageCount, const char *pTileArtFile, std::vector<int> &vImageIndexMap) :
1265 IEditorAction(pMap), m_PreviousImageCount(PreviousImageCount), m_vImageIndexMap(vImageIndexMap)
1266{
1267 str_copy(dst&: m_aTileArtFile, src: pTileArtFile);
1268 str_copy(dst&: m_aDisplayText, src: "Tile art");
1269}
1270
1271void CEditorActionTileArt::Undo()
1272{
1273 // Delete added group
1274 Map()->m_vpGroups.pop_back();
1275
1276 // Delete added images
1277 // Images are sorted when appending, so we need to revert sorting before deleting the images
1278 if(!m_vImageIndexMap.empty())
1279 {
1280 std::vector<int> vReverseIndexMap;
1281 vReverseIndexMap.resize(new_size: m_vImageIndexMap.size());
1282
1283 for(int k = 0; k < (int)m_vImageIndexMap.size(); k++)
1284 vReverseIndexMap[m_vImageIndexMap[k]] = k;
1285
1286 std::vector<std::shared_ptr<CEditorImage>> vpRevertedImages;
1287 vpRevertedImages.resize(new_size: Map()->m_vpImages.size());
1288
1289 for(int k = 0; k < (int)vReverseIndexMap.size(); k++)
1290 {
1291 vpRevertedImages[vReverseIndexMap[k]] = Map()->m_vpImages[k];
1292 }
1293 Map()->m_vpImages = vpRevertedImages;
1294
1295 Map()->ModifyImageIndex(IndexModifyFunction: [vReverseIndexMap](int *pIndex) {
1296 if(*pIndex >= 0)
1297 {
1298 *pIndex = vReverseIndexMap[*pIndex];
1299 }
1300 });
1301 }
1302
1303 while((int)Map()->m_vpImages.size() != m_PreviousImageCount)
1304 {
1305 Map()->m_vpImages.pop_back();
1306 }
1307}
1308
1309void CEditorActionTileArt::Redo()
1310{
1311 if(!Graphics()->LoadPng(Image&: Editor()->m_TileartImageInfo, pFilename: m_aTileArtFile, StorageType: IStorage::TYPE_ALL))
1312 {
1313 Editor()->ShowFileDialogError(pFormat: "Failed to load image from file '%s'.", m_aTileArtFile);
1314 return;
1315 }
1316
1317 IStorage::StripPathAndExtension(pFilename: m_aTileArtFile, pBuffer: Editor()->m_aTileartFilename, BufferSize: sizeof(Editor()->m_aTileartFilename));
1318 Editor()->AddTileart(IgnoreHistory: true);
1319}
1320
1321// ---------------------------
1322
1323CEditorActionQuadArt::CEditorActionQuadArt(CEditorMap *pMap, CQuadArtParameters Parameters) :
1324 IEditorAction(pMap), m_Parameters(Parameters)
1325{
1326 str_copy(dst&: m_aDisplayText, src: "Create Quadart");
1327}
1328
1329void CEditorActionQuadArt::Undo()
1330{
1331 // Delete added group
1332 Map()->m_vpGroups.pop_back();
1333}
1334
1335void CEditorActionQuadArt::Redo()
1336{
1337 Editor()->m_QuadArtParameters = m_Parameters;
1338 str_copy(dst: Editor()->m_QuadArtParameters.m_aFilename, src: m_Parameters.m_aFilename, dst_size: sizeof(Editor()->m_QuadArtParameters.m_aFilename));
1339
1340 if(!Graphics()->LoadPng(Image&: Editor()->m_QuadArtImageInfo, pFilename: Editor()->m_QuadArtParameters.m_aFilename, StorageType: IStorage::TYPE_ALL))
1341 {
1342 Editor()->ShowFileDialogError(pFormat: "Failed to load image from file '%s'.", Editor()->m_QuadArtParameters.m_aFilename);
1343 return;
1344 }
1345 Editor()->AddQuadArt(IgnoreHistory: true);
1346}
1347
1348// ---------------------------------
1349
1350CEditorCommandAction::CEditorCommandAction(CEditorMap *pMap, EType Type, int *pSelectedCommandIndex, int CommandIndex, const char *pPreviousCommand, const char *pCurrentCommand) :
1351 IEditorAction(pMap), m_Type(Type), m_pSelectedCommandIndex(pSelectedCommandIndex), m_CommandIndex(CommandIndex)
1352{
1353 if(pPreviousCommand != nullptr)
1354 m_PreviousCommand = std::string(pPreviousCommand);
1355 if(pCurrentCommand != nullptr)
1356 m_CurrentCommand = std::string(pCurrentCommand);
1357
1358 switch(m_Type)
1359 {
1360 case EType::ADD:
1361 str_copy(dst&: m_aDisplayText, src: "Add command");
1362 break;
1363 case EType::EDIT:
1364 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit command %d", m_CommandIndex);
1365 break;
1366 case EType::DELETE:
1367 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Delete command %d", m_CommandIndex);
1368 break;
1369 case EType::MOVE_UP:
1370 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Move command %d up", m_CommandIndex);
1371 break;
1372 case EType::MOVE_DOWN:
1373 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Move command %d down", m_CommandIndex);
1374 break;
1375 default:
1376 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit command %d", m_CommandIndex);
1377 break;
1378 }
1379}
1380
1381void CEditorCommandAction::Undo()
1382{
1383 switch(m_Type)
1384 {
1385 case EType::DELETE:
1386 {
1387 Map()->m_vSettings.insert(position: Map()->m_vSettings.begin() + m_CommandIndex, x: m_PreviousCommand.c_str());
1388 *m_pSelectedCommandIndex = m_CommandIndex;
1389 break;
1390 }
1391 case EType::ADD:
1392 {
1393 Map()->m_vSettings.erase(position: Map()->m_vSettings.begin() + m_CommandIndex);
1394 *m_pSelectedCommandIndex = Map()->m_vSettings.size() - 1;
1395 break;
1396 }
1397 case EType::EDIT:
1398 {
1399 str_copy(dst&: Map()->m_vSettings[m_CommandIndex].m_aCommand, src: m_PreviousCommand.c_str());
1400 *m_pSelectedCommandIndex = m_CommandIndex;
1401 break;
1402 }
1403 case EType::MOVE_DOWN:
1404 {
1405 std::swap(a&: Map()->m_vSettings[m_CommandIndex], b&: Map()->m_vSettings[m_CommandIndex + 1]);
1406 *m_pSelectedCommandIndex = m_CommandIndex;
1407 break;
1408 }
1409 case EType::MOVE_UP:
1410 {
1411 std::swap(a&: Map()->m_vSettings[m_CommandIndex], b&: Map()->m_vSettings[m_CommandIndex - 1]);
1412 *m_pSelectedCommandIndex = m_CommandIndex;
1413 break;
1414 }
1415 }
1416}
1417
1418void CEditorCommandAction::Redo()
1419{
1420 switch(m_Type)
1421 {
1422 case EType::DELETE:
1423 {
1424 Map()->m_vSettings.erase(position: Map()->m_vSettings.begin() + m_CommandIndex);
1425 *m_pSelectedCommandIndex = Map()->m_vSettings.size() - 1;
1426 break;
1427 }
1428 case EType::ADD:
1429 {
1430 Map()->m_vSettings.insert(position: Map()->m_vSettings.begin() + m_CommandIndex, x: m_PreviousCommand.c_str());
1431 *m_pSelectedCommandIndex = m_CommandIndex;
1432 break;
1433 }
1434 case EType::EDIT:
1435 {
1436 str_copy(dst&: Map()->m_vSettings[m_CommandIndex].m_aCommand, src: m_CurrentCommand.c_str());
1437 *m_pSelectedCommandIndex = m_CommandIndex;
1438 break;
1439 }
1440 case EType::MOVE_DOWN:
1441 {
1442 std::swap(a&: Map()->m_vSettings[m_CommandIndex], b&: Map()->m_vSettings[m_CommandIndex + 1]);
1443 *m_pSelectedCommandIndex = m_CommandIndex;
1444 break;
1445 }
1446 case EType::MOVE_UP:
1447 {
1448 std::swap(a&: Map()->m_vSettings[m_CommandIndex], b&: Map()->m_vSettings[m_CommandIndex - 1]);
1449 *m_pSelectedCommandIndex = m_CommandIndex;
1450 break;
1451 }
1452 }
1453}
1454
1455// ------------------------------------------------
1456
1457CEditorActionEnvelopeAdd::CEditorActionEnvelopeAdd(CEditorMap *pMap, CEnvelope::EType EnvelopeType) :
1458 IEditorAction(pMap),
1459 m_EnvelopeType(EnvelopeType)
1460{
1461 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Add new %s envelope", EnvelopeType == CEnvelope::EType::COLOR ? "color" : (EnvelopeType == CEnvelope::EType::POSITION ? "position" : "sound"));
1462 m_PreviousSelectedEnvelope = Editor()->m_SelectedEnvelope;
1463}
1464
1465void CEditorActionEnvelopeAdd::Undo()
1466{
1467 // Undo is removing the envelope, which was added at the back of the list
1468 Map()->m_vpEnvelopes.pop_back();
1469 Map()->OnModify();
1470 Editor()->m_SelectedEnvelope = m_PreviousSelectedEnvelope;
1471}
1472
1473void CEditorActionEnvelopeAdd::Redo()
1474{
1475 // Redo is adding a new envelope at the back of the list
1476 Map()->NewEnvelope(Type: m_EnvelopeType);
1477 Editor()->m_SelectedEnvelope = Map()->m_vpEnvelopes.size() - 1;
1478}
1479
1480CEditorActionEnvelopeDelete::CEditorActionEnvelopeDelete(CEditorMap *pMap, int EnvelopeIndex, std::vector<std::shared_ptr<IEditorEnvelopeReference>> &vpObjectReferences, std::shared_ptr<CEnvelope> &pEnvelope) :
1481 IEditorAction(pMap), m_EnvelopeIndex(EnvelopeIndex), m_pEnv(pEnvelope), m_vpObjectReferences(vpObjectReferences)
1482{
1483 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Delete envelope %d", m_EnvelopeIndex);
1484}
1485
1486void CEditorActionEnvelopeDelete::Undo()
1487{
1488 // Undo is adding back the envelope
1489 Map()->InsertEnvelope(Index: m_EnvelopeIndex, pEnvelope&: m_pEnv);
1490 Map()->UpdateEnvelopeReferences(Index: m_EnvelopeIndex, pEnvelope&: m_pEnv, vpEditorObjectReferences&: m_vpObjectReferences);
1491}
1492
1493void CEditorActionEnvelopeDelete::Redo()
1494{
1495 // Redo is erasing the same envelope index
1496 Map()->DeleteEnvelope(Index: m_EnvelopeIndex);
1497}
1498
1499CEditorActionEnvelopeEdit::CEditorActionEnvelopeEdit(CEditorMap *pMap, int EnvelopeIndex, EEditType EditType, int Previous, int Current) :
1500 IEditorAction(pMap), m_EnvelopeIndex(EnvelopeIndex), m_EditType(EditType), m_Previous(Previous), m_Current(Current), m_pEnv(Map()->m_vpEnvelopes[EnvelopeIndex])
1501{
1502 static const char *s_apNames[] = {
1503 "sync",
1504 "order"};
1505 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit envelope %d %s", m_EnvelopeIndex, s_apNames[(int)m_EditType]);
1506}
1507
1508void CEditorActionEnvelopeEdit::Undo()
1509{
1510 switch(m_EditType)
1511 {
1512 case EEditType::ORDER:
1513 {
1514 Map()->MoveEnvelope(IndexFrom: m_Current, IndexTo: m_Previous);
1515 break;
1516 }
1517 case EEditType::SYNC:
1518 {
1519 m_pEnv->m_Synchronized = m_Previous;
1520 break;
1521 }
1522 }
1523 Map()->OnModify();
1524 Editor()->m_SelectedEnvelope = m_EnvelopeIndex;
1525}
1526
1527void CEditorActionEnvelopeEdit::Redo()
1528{
1529 switch(m_EditType)
1530 {
1531 case EEditType::ORDER:
1532 {
1533 Map()->MoveEnvelope(IndexFrom: m_Previous, IndexTo: m_Current);
1534 break;
1535 }
1536 case EEditType::SYNC:
1537 {
1538 m_pEnv->m_Synchronized = m_Current;
1539 break;
1540 }
1541 }
1542 Map()->OnModify();
1543 Editor()->m_SelectedEnvelope = m_EnvelopeIndex;
1544}
1545
1546CEditorActionEnvelopeEditPointTime::CEditorActionEnvelopeEditPointTime(CEditorMap *pMap, int EnvelopeIndex, int PointIndex, CFixedTime Previous, CFixedTime Current) :
1547 IEditorAction(pMap), m_EnvelopeIndex(EnvelopeIndex), m_PointIndex(PointIndex), m_Previous(Previous), m_Current(Current), m_pEnv(Map()->m_vpEnvelopes[EnvelopeIndex])
1548{
1549 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit time of point %d of env %d", m_PointIndex, m_EnvelopeIndex);
1550}
1551
1552void CEditorActionEnvelopeEditPointTime::Undo()
1553{
1554 Apply(Value: m_Previous);
1555}
1556
1557void CEditorActionEnvelopeEditPointTime::Redo()
1558{
1559 Apply(Value: m_Current);
1560}
1561
1562void CEditorActionEnvelopeEditPointTime::Apply(CFixedTime Value)
1563{
1564 m_pEnv->m_vPoints[m_PointIndex].m_Time = Value;
1565 Map()->OnModify();
1566}
1567
1568CEditorActionEnvelopeEditPoint::CEditorActionEnvelopeEditPoint(CEditorMap *pMap, int EnvelopeIndex, int PointIndex, int Channel, EEditType EditType, int Previous, int Current) :
1569 IEditorAction(pMap), m_EnvelopeIndex(EnvelopeIndex), m_PointIndex(PointIndex), m_Channel(Channel), m_EditType(EditType), m_Previous(Previous), m_Current(Current), m_pEnv(Map()->m_vpEnvelopes[EnvelopeIndex])
1570{
1571 static const char *s_apNames[] = {
1572 "value",
1573 "curve type"};
1574 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit %s of point %d (channel %d) of env %d", s_apNames[(int)m_EditType], m_PointIndex, m_Channel, m_EnvelopeIndex);
1575}
1576
1577void CEditorActionEnvelopeEditPoint::Undo()
1578{
1579 Apply(Value: m_Previous);
1580}
1581
1582void CEditorActionEnvelopeEditPoint::Redo()
1583{
1584 Apply(Value: m_Current);
1585}
1586
1587void CEditorActionEnvelopeEditPoint::Apply(int Value)
1588{
1589 if(m_EditType == EEditType::VALUE)
1590 {
1591 m_pEnv->m_vPoints[m_PointIndex].m_aValues[m_Channel] = Value;
1592
1593 if(m_pEnv->GetChannels() == 4)
1594 {
1595 Editor()->m_ColorPickerPopupContext.m_RgbaColor = m_pEnv->m_vPoints[m_PointIndex].ColorValue();
1596 Editor()->m_ColorPickerPopupContext.m_HslaColor = color_cast<ColorHSLA>(rgb: Editor()->m_ColorPickerPopupContext.m_RgbaColor);
1597 Editor()->m_ColorPickerPopupContext.m_HsvaColor = color_cast<ColorHSVA>(hsl: Editor()->m_ColorPickerPopupContext.m_HslaColor);
1598 }
1599 }
1600 else if(m_EditType == EEditType::CURVE_TYPE)
1601 {
1602 m_pEnv->m_vPoints[m_PointIndex].m_Curvetype = Value;
1603 }
1604
1605 Map()->OnModify();
1606}
1607
1608// ----
1609
1610CEditorActionEditEnvelopePointValue::CEditorActionEditEnvelopePointValue(CEditorMap *pMap, int EnvIndex, int PointIndex, int Channel, EType Type, CFixedTime OldTime, int OldValue, CFixedTime NewTime, int NewValue) :
1611 IEditorAction(pMap), m_EnvIndex(EnvIndex), m_PtIndex(PointIndex), m_Channel(Channel), m_Type(Type), m_OldTime(OldTime), m_OldValue(OldValue), m_NewTime(NewTime), m_NewValue(NewValue)
1612{
1613 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit point %d%s value (envelope %d, channel %d)", PointIndex, m_Type == EType::TANGENT_IN ? "tangent in" : (m_Type == EType::TANGENT_OUT ? "tangent out" : ""), m_EnvIndex, m_Channel);
1614}
1615
1616void CEditorActionEditEnvelopePointValue::Undo()
1617{
1618 Apply(Undo: true);
1619}
1620
1621void CEditorActionEditEnvelopePointValue::Redo()
1622{
1623 Apply(Undo: false);
1624}
1625
1626void CEditorActionEditEnvelopePointValue::Apply(bool Undo)
1627{
1628 float CurrentValue = fx2f(v: Undo ? m_OldValue : m_NewValue);
1629 CFixedTime CurrentTime = (Undo ? m_OldTime : m_NewTime);
1630
1631 std::shared_ptr<CEnvelope> pEnvelope = Map()->m_vpEnvelopes[m_EnvIndex];
1632 if(m_Type == EType::TANGENT_IN)
1633 {
1634 pEnvelope->m_vPoints[m_PtIndex].m_Bezier.m_aInTangentDeltaX[m_Channel] = std::min(a: CurrentTime - pEnvelope->m_vPoints[m_PtIndex].m_Time, b: CFixedTime(0));
1635 pEnvelope->m_vPoints[m_PtIndex].m_Bezier.m_aInTangentDeltaY[m_Channel] = f2fx(v: CurrentValue) - pEnvelope->m_vPoints[m_PtIndex].m_aValues[m_Channel];
1636 }
1637 else if(m_Type == EType::TANGENT_OUT)
1638 {
1639 pEnvelope->m_vPoints[m_PtIndex].m_Bezier.m_aOutTangentDeltaX[m_Channel] = std::max(a: CurrentTime - pEnvelope->m_vPoints[m_PtIndex].m_Time, b: CFixedTime(0));
1640 pEnvelope->m_vPoints[m_PtIndex].m_Bezier.m_aOutTangentDeltaY[m_Channel] = f2fx(v: CurrentValue) - pEnvelope->m_vPoints[m_PtIndex].m_aValues[m_Channel];
1641 }
1642 else
1643 {
1644 if(pEnvelope->GetChannels() == 1 || pEnvelope->GetChannels() == 4)
1645 CurrentValue = std::clamp(val: CurrentValue, lo: 0.0f, hi: 1.0f);
1646 pEnvelope->m_vPoints[m_PtIndex].m_aValues[m_Channel] = f2fx(v: CurrentValue);
1647
1648 if(m_PtIndex != 0)
1649 {
1650 pEnvelope->m_vPoints[m_PtIndex].m_Time = CurrentTime;
1651
1652 if(pEnvelope->m_vPoints[m_PtIndex].m_Time < pEnvelope->m_vPoints[m_PtIndex - 1].m_Time)
1653 pEnvelope->m_vPoints[m_PtIndex].m_Time = pEnvelope->m_vPoints[m_PtIndex - 1].m_Time + CFixedTime(1);
1654 if(static_cast<size_t>(m_PtIndex) + 1 != pEnvelope->m_vPoints.size() && pEnvelope->m_vPoints[m_PtIndex].m_Time > pEnvelope->m_vPoints[m_PtIndex + 1].m_Time)
1655 pEnvelope->m_vPoints[m_PtIndex].m_Time = pEnvelope->m_vPoints[m_PtIndex + 1].m_Time - CFixedTime(1);
1656 }
1657 else
1658 {
1659 pEnvelope->m_vPoints[m_PtIndex].m_Time = CFixedTime(0);
1660 }
1661 }
1662
1663 Map()->OnModify();
1664 Editor()->m_UpdateEnvPointInfo = true;
1665}
1666
1667// ---------------------
1668
1669CEditorActionResetEnvelopePointTangent::CEditorActionResetEnvelopePointTangent(CEditorMap *pMap, int EnvIndex, int PointIndex, int Channel, bool In) :
1670 IEditorAction(pMap), m_EnvIndex(EnvIndex), m_PointIndex(PointIndex), m_Channel(Channel), m_In(In)
1671{
1672 std::shared_ptr<CEnvelope> pEnvelope = Map()->m_vpEnvelopes[EnvIndex];
1673 if(In)
1674 {
1675 m_OldTime = pEnvelope->m_vPoints[PointIndex].m_Bezier.m_aInTangentDeltaX[Channel];
1676 m_OldValue = pEnvelope->m_vPoints[PointIndex].m_Bezier.m_aInTangentDeltaY[Channel];
1677 }
1678 else
1679 {
1680 m_OldTime = pEnvelope->m_vPoints[PointIndex].m_Bezier.m_aOutTangentDeltaX[Channel];
1681 m_OldValue = pEnvelope->m_vPoints[PointIndex].m_Bezier.m_aOutTangentDeltaY[Channel];
1682 }
1683
1684 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Reset point %d of env %d tangent %s", m_PointIndex, m_EnvIndex, m_In ? "in" : "out");
1685}
1686
1687void CEditorActionResetEnvelopePointTangent::Undo()
1688{
1689 std::shared_ptr<CEnvelope> pEnvelope = Map()->m_vpEnvelopes[m_EnvIndex];
1690 if(m_In)
1691 {
1692 pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aInTangentDeltaX[m_Channel] = m_OldTime;
1693 pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aInTangentDeltaY[m_Channel] = m_OldValue;
1694 }
1695 else
1696 {
1697 pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aOutTangentDeltaX[m_Channel] = m_OldTime;
1698 pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aOutTangentDeltaY[m_Channel] = m_OldValue;
1699 }
1700 Map()->OnModify();
1701}
1702
1703void CEditorActionResetEnvelopePointTangent::Redo()
1704{
1705 std::shared_ptr<CEnvelope> pEnvelope = Map()->m_vpEnvelopes[m_EnvIndex];
1706 if(m_In)
1707 {
1708 pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aInTangentDeltaX[m_Channel] = CFixedTime(0);
1709 pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aInTangentDeltaY[m_Channel] = 0.0f;
1710 }
1711 else
1712 {
1713 pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aOutTangentDeltaX[m_Channel] = CFixedTime(0);
1714 pEnvelope->m_vPoints[m_PointIndex].m_Bezier.m_aOutTangentDeltaY[m_Channel] = 0.0f;
1715 }
1716 Map()->OnModify();
1717}
1718
1719// ------------------
1720
1721CEditorActionAddEnvelopePoint::CEditorActionAddEnvelopePoint(CEditorMap *pMap, int EnvIndex, CFixedTime Time, ColorRGBA Channels) :
1722 IEditorAction(pMap), m_EnvIndex(EnvIndex), m_Time(Time), m_Channels(Channels)
1723{
1724 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Add new point in envelope %d at time %f", m_EnvIndex, Time.AsSeconds());
1725}
1726
1727void CEditorActionAddEnvelopePoint::Undo()
1728{
1729 // Delete added point
1730 auto pEnvelope = Map()->m_vpEnvelopes[m_EnvIndex];
1731 auto pIt = std::find_if(first: pEnvelope->m_vPoints.begin(), last: pEnvelope->m_vPoints.end(), pred: [this](const CEnvPoint_runtime &Point) {
1732 return Point.m_Time == m_Time;
1733 });
1734 if(pIt != pEnvelope->m_vPoints.end())
1735 {
1736 pEnvelope->m_vPoints.erase(position: pIt);
1737 }
1738
1739 Map()->OnModify();
1740}
1741
1742void CEditorActionAddEnvelopePoint::Redo()
1743{
1744 auto pEnvelope = Map()->m_vpEnvelopes[m_EnvIndex];
1745 pEnvelope->AddPoint(Time: m_Time, aValues: {f2fx(v: m_Channels.r), f2fx(v: m_Channels.g), f2fx(v: m_Channels.b), f2fx(v: m_Channels.a)});
1746
1747 Map()->OnModify();
1748}
1749
1750CEditorActionDeleteEnvelopePoint::CEditorActionDeleteEnvelopePoint(CEditorMap *pMap, int EnvIndex, int PointIndex) :
1751 IEditorAction(pMap), m_EnvIndex(EnvIndex), m_PointIndex(PointIndex), m_Point(Map()->m_vpEnvelopes[EnvIndex]->m_vPoints[PointIndex])
1752{
1753 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Delete point %d of envelope %d", m_PointIndex, m_EnvIndex);
1754}
1755
1756void CEditorActionDeleteEnvelopePoint::Undo()
1757{
1758 std::shared_ptr<CEnvelope> pEnvelope = Map()->m_vpEnvelopes[m_EnvIndex];
1759 pEnvelope->m_vPoints.insert(position: pEnvelope->m_vPoints.begin() + m_PointIndex, x: m_Point);
1760
1761 Map()->OnModify();
1762}
1763
1764void CEditorActionDeleteEnvelopePoint::Redo()
1765{
1766 std::shared_ptr<CEnvelope> pEnvelope = Map()->m_vpEnvelopes[m_EnvIndex];
1767 pEnvelope->m_vPoints.erase(position: pEnvelope->m_vPoints.begin() + m_PointIndex);
1768
1769 auto pSelectedPointIt = std::find_if(first: Editor()->m_vSelectedEnvelopePoints.begin(), last: Editor()->m_vSelectedEnvelopePoints.end(), pred: [this](const std::pair<int, int> Pair) {
1770 return Pair.first == m_PointIndex;
1771 });
1772
1773 if(pSelectedPointIt != Editor()->m_vSelectedEnvelopePoints.end())
1774 Editor()->m_vSelectedEnvelopePoints.erase(position: pSelectedPointIt);
1775
1776 Map()->OnModify();
1777}
1778
1779// -------------------------------
1780
1781CEditorActionEditLayerSoundsProp::CEditorActionEditLayerSoundsProp(CEditorMap *pMap, int GroupIndex, int LayerIndex, ELayerSoundsProp Prop, int Previous, int Current) :
1782 CEditorActionEditLayerPropBase(pMap, GroupIndex, LayerIndex, Prop, Previous, Current)
1783{
1784 static const char *s_apNames[] = {
1785 "sound"};
1786 static_assert(std::size(s_apNames) == (size_t)ELayerSoundsProp::NUM_PROPS);
1787 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit sounds layer %d in group %d %s property", m_LayerIndex, m_GroupIndex, s_apNames[(int)m_Prop]);
1788}
1789
1790void CEditorActionEditLayerSoundsProp::Undo()
1791{
1792 Apply(Value: m_Previous);
1793}
1794
1795void CEditorActionEditLayerSoundsProp::Redo()
1796{
1797 Apply(Value: m_Current);
1798}
1799
1800void CEditorActionEditLayerSoundsProp::Apply(int Value)
1801{
1802 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
1803 if(m_Prop == ELayerSoundsProp::PROP_SOUND)
1804 {
1805 if(Value >= 0 && !Map()->m_vpSounds.empty())
1806 pLayerSounds->m_Sound = Value % Map()->m_vpSounds.size();
1807 else
1808 pLayerSounds->m_Sound = -1;
1809 }
1810
1811 Map()->OnModify();
1812}
1813
1814// ---
1815
1816CEditorActionDeleteSoundSource::CEditorActionDeleteSoundSource(CEditorMap *pMap, int GroupIndex, int LayerIndex, int SourceIndex) :
1817 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_SourceIndex(SourceIndex)
1818{
1819 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
1820 m_Source = pLayerSounds->m_vSources[SourceIndex];
1821
1822 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Delete sound source %d in layer %d of group %d", SourceIndex, LayerIndex, GroupIndex);
1823}
1824
1825void CEditorActionDeleteSoundSource::Undo()
1826{
1827 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
1828 pLayerSounds->m_vSources.insert(position: pLayerSounds->m_vSources.begin() + m_SourceIndex, x: m_Source);
1829 Editor()->m_SelectedSource = m_SourceIndex;
1830 Map()->OnModify();
1831}
1832
1833void CEditorActionDeleteSoundSource::Redo()
1834{
1835 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
1836 pLayerSounds->m_vSources.erase(position: pLayerSounds->m_vSources.begin() + m_SourceIndex);
1837 Editor()->m_SelectedSource--;
1838 Map()->OnModify();
1839}
1840
1841// ---------------
1842
1843CEditorActionEditSoundSourceShape::CEditorActionEditSoundSourceShape(CEditorMap *pMap, int GroupIndex, int LayerIndex, int SourceIndex, int Value) :
1844 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_SourceIndex(SourceIndex), m_CurrentValue(Value)
1845{
1846 Save();
1847
1848 static const char *const SHAPE_NAMES[] = {
1849 "rectangle",
1850 "circle",
1851 };
1852 static_assert(std::size(SHAPE_NAMES) == (size_t)CSoundShape::NUM_SHAPES);
1853 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText),
1854 format: "Edit shape of sound source %d in layer %d of group %d to %s",
1855 SourceIndex, LayerIndex, GroupIndex, SHAPE_NAMES[Value]);
1856}
1857
1858void CEditorActionEditSoundSourceShape::Undo()
1859{
1860 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
1861 CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex];
1862
1863 pSource->m_Shape = m_SavedShape;
1864
1865 Map()->OnModify();
1866}
1867
1868void CEditorActionEditSoundSourceShape::Redo()
1869{
1870 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
1871 CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex];
1872
1873 pSource->m_Shape.m_Type = m_CurrentValue;
1874
1875 // set default values
1876 switch(pSource->m_Shape.m_Type)
1877 {
1878 case CSoundShape::SHAPE_CIRCLE:
1879 {
1880 pSource->m_Shape.m_Circle.m_Radius = 1000;
1881 break;
1882 }
1883 case CSoundShape::SHAPE_RECTANGLE:
1884 {
1885 pSource->m_Shape.m_Rectangle.m_Width = f2fx(v: 1000.0f);
1886 pSource->m_Shape.m_Rectangle.m_Height = f2fx(v: 800.0f);
1887 break;
1888 }
1889 }
1890
1891 Map()->OnModify();
1892}
1893
1894void CEditorActionEditSoundSourceShape::Save()
1895{
1896 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
1897 m_SavedShape = pLayerSounds->m_vSources[m_SourceIndex].m_Shape;
1898}
1899
1900// -----
1901
1902CEditorActionEditSoundSourceProp::CEditorActionEditSoundSourceProp(CEditorMap *pMap, int GroupIndex, int LayerIndex, int SourceIndex, ESoundProp Prop, int Previous, int Current) :
1903 CEditorActionEditLayerPropBase(pMap, GroupIndex, LayerIndex, Prop, Previous, Current), m_SourceIndex(SourceIndex)
1904{
1905 static const char *s_apNames[] = {
1906 "pos X",
1907 "pos Y",
1908 "loop",
1909 "pan",
1910 "time delay",
1911 "falloff",
1912 "pos env",
1913 "pos env offset",
1914 "sound env",
1915 "sound env offset"};
1916 static_assert(std::size(s_apNames) == (size_t)ESoundProp::NUM_PROPS);
1917 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit sound source %d in layer %d of group %d %s property", SourceIndex, LayerIndex, GroupIndex, s_apNames[(int)Prop]);
1918}
1919
1920void CEditorActionEditSoundSourceProp::Undo()
1921{
1922 Apply(Value: m_Previous);
1923}
1924
1925void CEditorActionEditSoundSourceProp::Redo()
1926{
1927 Apply(Value: m_Current);
1928}
1929
1930void CEditorActionEditSoundSourceProp::Apply(int Value)
1931{
1932 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
1933 CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex];
1934
1935 if(m_Prop == ESoundProp::PROP_POS_X)
1936 {
1937 pSource->m_Position.x = Value;
1938 }
1939 else if(m_Prop == ESoundProp::PROP_POS_Y)
1940 {
1941 pSource->m_Position.y = Value;
1942 }
1943 else if(m_Prop == ESoundProp::PROP_LOOP)
1944 {
1945 pSource->m_Loop = Value;
1946 }
1947 else if(m_Prop == ESoundProp::PROP_PAN)
1948 {
1949 pSource->m_Pan = Value;
1950 }
1951 else if(m_Prop == ESoundProp::PROP_TIME_DELAY)
1952 {
1953 pSource->m_TimeDelay = Value;
1954 }
1955 else if(m_Prop == ESoundProp::PROP_FALLOFF)
1956 {
1957 pSource->m_Falloff = Value;
1958 }
1959 else if(m_Prop == ESoundProp::PROP_POS_ENV)
1960 {
1961 pSource->m_PosEnv = Value;
1962 }
1963 else if(m_Prop == ESoundProp::PROP_POS_ENV_OFFSET)
1964 {
1965 pSource->m_PosEnvOffset = Value;
1966 }
1967 else if(m_Prop == ESoundProp::PROP_SOUND_ENV)
1968 {
1969 pSource->m_SoundEnv = Value;
1970 }
1971 else if(m_Prop == ESoundProp::PROP_SOUND_ENV_OFFSET)
1972 {
1973 pSource->m_SoundEnvOffset = Value;
1974 }
1975
1976 Map()->OnModify();
1977}
1978
1979CEditorActionEditRectSoundSourceShapeProp::CEditorActionEditRectSoundSourceShapeProp(CEditorMap *pMap, int GroupIndex, int LayerIndex, int SourceIndex, ERectangleShapeProp Prop, int Previous, int Current) :
1980 CEditorActionEditLayerPropBase(pMap, GroupIndex, LayerIndex, Prop, Previous, Current), m_SourceIndex(SourceIndex)
1981{
1982 static const char *s_apNames[] = {
1983 "width",
1984 "height"};
1985 static_assert(std::size(s_apNames) == (size_t)ERectangleShapeProp::NUM_PROPS);
1986 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit sound source %d in layer %d of group %d sound shape %s property", m_SourceIndex, m_LayerIndex, m_GroupIndex, s_apNames[(int)Prop]);
1987}
1988
1989void CEditorActionEditRectSoundSourceShapeProp::Undo()
1990{
1991 Apply(Value: m_Previous);
1992}
1993
1994void CEditorActionEditRectSoundSourceShapeProp::Redo()
1995{
1996 Apply(Value: m_Current);
1997}
1998
1999void CEditorActionEditRectSoundSourceShapeProp::Apply(int Value)
2000{
2001 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
2002 CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex];
2003
2004 if(m_Prop == ERectangleShapeProp::PROP_RECTANGLE_WIDTH)
2005 {
2006 pSource->m_Shape.m_Rectangle.m_Width = Value;
2007 }
2008 else if(m_Prop == ERectangleShapeProp::PROP_RECTANGLE_HEIGHT)
2009 {
2010 pSource->m_Shape.m_Rectangle.m_Height = Value;
2011 }
2012
2013 Map()->OnModify();
2014}
2015
2016CEditorActionEditCircleSoundSourceShapeProp::CEditorActionEditCircleSoundSourceShapeProp(CEditorMap *pMap, int GroupIndex, int LayerIndex, int SourceIndex, ECircleShapeProp Prop, int Previous, int Current) :
2017 CEditorActionEditLayerPropBase(pMap, GroupIndex, LayerIndex, Prop, Previous, Current), m_SourceIndex(SourceIndex)
2018{
2019 static const char *s_apNames[] = {
2020 "radius"};
2021 static_assert(std::size(s_apNames) == (size_t)ECircleShapeProp::NUM_PROPS);
2022 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Edit sound source %d in layer %d of group %d sound shape %s property", m_SourceIndex, m_LayerIndex, m_GroupIndex, s_apNames[(int)Prop]);
2023}
2024
2025void CEditorActionEditCircleSoundSourceShapeProp::Undo()
2026{
2027 Apply(Value: m_Previous);
2028}
2029
2030void CEditorActionEditCircleSoundSourceShapeProp::Redo()
2031{
2032 Apply(Value: m_Current);
2033}
2034
2035void CEditorActionEditCircleSoundSourceShapeProp::Apply(int Value)
2036{
2037 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
2038 CSoundSource *pSource = &pLayerSounds->m_vSources[m_SourceIndex];
2039
2040 if(m_Prop == ECircleShapeProp::PROP_CIRCLE_RADIUS)
2041 {
2042 pSource->m_Shape.m_Circle.m_Radius = Value;
2043 }
2044
2045 Map()->OnModify();
2046}
2047
2048// --------------------------
2049
2050CEditorActionNewEmptySound::CEditorActionNewEmptySound(CEditorMap *pMap, int GroupIndex, int LayerIndex, int x, int y) :
2051 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_X(x), m_Y(y)
2052{
2053 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "New sound in layer %d of group %d", LayerIndex, GroupIndex);
2054}
2055
2056void CEditorActionNewEmptySound::Undo()
2057{
2058 // Undo is simply deleting the added source
2059 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
2060 pLayerSounds->m_vSources.pop_back();
2061
2062 Map()->OnModify();
2063}
2064
2065void CEditorActionNewEmptySound::Redo()
2066{
2067 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: m_pLayer);
2068 pLayerSounds->NewSource(x: m_X, y: m_Y);
2069
2070 Map()->OnModify();
2071}
2072
2073CEditorActionNewEmptyQuad::CEditorActionNewEmptyQuad(CEditorMap *pMap, int GroupIndex, int LayerIndex, int x, int y) :
2074 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_X(x), m_Y(y)
2075{
2076 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "New quad in layer %d of group %d", LayerIndex, GroupIndex);
2077}
2078
2079void CEditorActionNewEmptyQuad::Undo()
2080{
2081 // Undo is simply deleting the added quad
2082 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
2083 pLayerQuads->m_vQuads.pop_back();
2084
2085 Map()->OnModify();
2086}
2087
2088void CEditorActionNewEmptyQuad::Redo()
2089{
2090 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
2091
2092 int Width = 64;
2093 int Height = 64;
2094 if(pLayerQuads->m_Image >= 0)
2095 {
2096 Width = Map()->m_vpImages[pLayerQuads->m_Image]->m_Width;
2097 Height = Map()->m_vpImages[pLayerQuads->m_Image]->m_Height;
2098 }
2099
2100 pLayerQuads->NewQuad(x: m_X, y: m_Y, Width, Height);
2101
2102 Map()->OnModify();
2103}
2104
2105// -------------
2106
2107CEditorActionNewQuad::CEditorActionNewQuad(CEditorMap *pMap, int GroupIndex, int LayerIndex) :
2108 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex)
2109{
2110 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
2111 m_Quad = pLayerQuads->m_vQuads[pLayerQuads->m_vQuads.size() - 1];
2112
2113 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "New quad in layer %d of group %d", LayerIndex, GroupIndex);
2114}
2115
2116void CEditorActionNewQuad::Undo()
2117{
2118 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
2119 pLayerQuads->m_vQuads.pop_back();
2120}
2121
2122void CEditorActionNewQuad::Redo()
2123{
2124 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: m_pLayer);
2125 pLayerQuads->m_vQuads.emplace_back(args&: m_Quad);
2126}
2127
2128// --------------
2129
2130CEditorActionMoveSoundSource::CEditorActionMoveSoundSource(CEditorMap *pMap, int GroupIndex, int LayerIndex, int SourceIndex, CPoint OriginalPosition, CPoint CurrentPosition) :
2131 CEditorActionLayerBase(pMap, GroupIndex, LayerIndex), m_SourceIndex(SourceIndex), m_OriginalPosition(OriginalPosition), m_CurrentPosition(CurrentPosition)
2132{
2133 str_format(buffer: m_aDisplayText, buffer_size: sizeof(m_aDisplayText), format: "Move sound source %d of layer %d in group %d", SourceIndex, LayerIndex, GroupIndex);
2134}
2135
2136void CEditorActionMoveSoundSource::Undo()
2137{
2138 dbg_assert(m_pLayer->m_Type == LAYERTYPE_SOUNDS, "Layer type does not match a sound layer");
2139 std::static_pointer_cast<CLayerSounds>(r: m_pLayer)->m_vSources[m_SourceIndex].m_Position = m_OriginalPosition;
2140}
2141
2142void CEditorActionMoveSoundSource::Redo()
2143{
2144 dbg_assert(m_pLayer->m_Type == LAYERTYPE_SOUNDS, "Layer type does not match a sound layer");
2145 std::static_pointer_cast<CLayerSounds>(r: m_pLayer)->m_vSources[m_SourceIndex].m_Position = m_CurrentPosition;
2146}
2147