1#include "map.h"
2
3#include <base/str.h>
4
5#include <game/editor/editor.h>
6#include <game/editor/editor_actions.h>
7#include <game/editor/mapitems/image.h>
8#include <game/editor/mapitems/layer_front.h>
9#include <game/editor/mapitems/layer_game.h>
10#include <game/editor/mapitems/layer_group.h>
11#include <game/editor/mapitems/layer_quads.h>
12#include <game/editor/mapitems/layer_sounds.h>
13#include <game/editor/mapitems/layer_tiles.h>
14#include <game/editor/mapitems/sound.h>
15#include <game/editor/references.h>
16
17void CEditorMap::CMapInfo::Reset()
18{
19 m_aAuthor[0] = '\0';
20 m_aVersion[0] = '\0';
21 m_aCredits[0] = '\0';
22 m_aLicense[0] = '\0';
23}
24
25void CEditorMap::CMapInfo::Copy(const CMapInfo &Source)
26{
27 str_copy(dst&: m_aAuthor, src: Source.m_aAuthor);
28 str_copy(dst&: m_aVersion, src: Source.m_aVersion);
29 str_copy(dst&: m_aCredits, src: Source.m_aCredits);
30 str_copy(dst&: m_aLicense, src: Source.m_aLicense);
31}
32
33void CEditorMap::OnModify()
34{
35 m_Modified = true;
36 m_ModifiedAuto = true;
37 m_LastModifiedTime = Editor()->Client()->GlobalTime();
38}
39
40void CEditorMap::ResetModifiedState()
41{
42 m_Modified = false;
43 m_ModifiedAuto = false;
44 m_LastModifiedTime = -1.0f;
45 m_LastSaveTime = Editor()->Client()->GlobalTime();
46}
47
48void CEditorMap::Clean()
49{
50 m_aFilename[0] = '\0';
51 m_ValidSaveFilename = false;
52 ResetModifiedState();
53
54 m_vpGroups.clear();
55 m_vpEnvelopes.clear();
56 m_vpImages.clear();
57 m_vpSounds.clear();
58 m_vSettings.clear();
59
60 m_pGameGroup = nullptr;
61 m_pGameLayer = nullptr;
62 m_pTeleLayer = nullptr;
63 m_pSpeedupLayer = nullptr;
64 m_pFrontLayer = nullptr;
65 m_pSwitchLayer = nullptr;
66 m_pTuneLayer = nullptr;
67
68 m_MapInfo.Reset();
69 m_MapInfoTmp.Reset();
70
71 m_EditorHistory.Clear();
72 m_EnvelopeEditorHistory.Clear();
73 m_ServerSettingsHistory.Clear();
74 m_EnvOpTracker.Reset();
75
76 m_SelectedGroup = 0;
77 m_vSelectedLayers.clear();
78 DeselectQuads();
79 DeselectQuadPoints();
80 m_SelectedQuadEnvelope = -1;
81 m_CurrentQuadIndex = -1;
82 m_SelectedEnvelope = 0;
83 m_UpdateEnvPointInfo = false;
84 m_vSelectedEnvelopePoints.clear();
85 m_SelectedTangentInPoint = std::pair(-1, -1);
86 m_SelectedTangentOutPoint = std::pair(-1, -1);
87 m_SelectedImage = 0;
88 m_SelectedSound = 0;
89 m_SelectedSoundSource = -1;
90
91 m_ShiftBy = 1;
92
93 m_MapViewState.Reset(pEditor: Editor());
94 m_MapGridState.Reset();
95 m_ProofModeState.Reset();
96 m_QuadKnifeState.Reset();
97}
98
99void CEditorMap::CreateDefault()
100{
101 // Add default background group, quad layer and quad
102 std::shared_ptr<CLayerGroup> pGroup = NewGroup();
103 pGroup->m_ParallaxX = 0;
104 pGroup->m_ParallaxY = 0;
105 std::shared_ptr<CLayerQuads> pLayer = std::make_shared<CLayerQuads>(args: this);
106 CQuad *pQuad = pLayer->NewQuad(x: 0, y: 0, Width: 1600, Height: 1200);
107 pQuad->m_aColors[0].r = pQuad->m_aColors[1].r = 94;
108 pQuad->m_aColors[0].g = pQuad->m_aColors[1].g = 132;
109 pQuad->m_aColors[0].b = pQuad->m_aColors[1].b = 174;
110 pQuad->m_aColors[2].r = pQuad->m_aColors[3].r = 204;
111 pQuad->m_aColors[2].g = pQuad->m_aColors[3].g = 232;
112 pQuad->m_aColors[2].b = pQuad->m_aColors[3].b = 255;
113 pGroup->AddLayer(pLayer);
114
115 // Add game group and layer
116 MakeGameGroup(pGroup: NewGroup());
117 MakeGameLayer(pLayer: std::make_shared<CLayerGame>(args: this, args: 50, args: 50));
118 m_pGameGroup->AddLayer(pLayer: m_pGameLayer);
119
120 ResetModifiedState();
121 CheckIntegrity();
122 SelectGameLayer();
123}
124
125void CEditorMap::CheckIntegrity()
126{
127 const auto &&CheckObjectInMap = [&](const CMapObject *pMapObject, const char *pName) {
128 dbg_assert(pMapObject != nullptr, "%s missing in map", pName);
129 dbg_assert(pMapObject->Map() == this, "%s does not belong to map (object_map=%p, this_map=%p)", pName, pMapObject->Map(), this);
130 };
131 bool GameGroupMissing = true;
132 CheckObjectInMap(m_pGameGroup.get(), "Game group");
133 dbg_assert(m_pGameGroup->m_GameGroup, "Game group not marked as such");
134 bool GameLayerMissing = true;
135 CheckObjectInMap(m_pGameLayer.get(), "Game layer");
136 dbg_assert(m_pGameLayer->m_HasGame, "Game layer not marked as such");
137 bool FrontLayerMissing = false;
138 if(m_pFrontLayer != nullptr)
139 {
140 FrontLayerMissing = true;
141 CheckObjectInMap(m_pFrontLayer.get(), "Front layer");
142 dbg_assert(m_pFrontLayer->m_HasFront, "Front layer not marked as such");
143 }
144 bool TeleLayerMissing = false;
145 if(m_pTeleLayer != nullptr)
146 {
147 TeleLayerMissing = true;
148 CheckObjectInMap(m_pTeleLayer.get(), "Tele layer");
149 dbg_assert(m_pTeleLayer->m_HasTele, "Tele layer not marked as such");
150 }
151 bool SpeedupLayerMissing = false;
152 if(m_pSpeedupLayer != nullptr)
153 {
154 SpeedupLayerMissing = true;
155 CheckObjectInMap(m_pSpeedupLayer.get(), "Speedup layer");
156 dbg_assert(m_pSpeedupLayer->m_HasSpeedup, "Speedup layer not marked as such");
157 }
158 bool SwitchLayerMissing = false;
159 if(m_pSwitchLayer != nullptr)
160 {
161 SwitchLayerMissing = true;
162 CheckObjectInMap(m_pSwitchLayer.get(), "Switch layer");
163 dbg_assert(m_pSwitchLayer->m_HasSwitch, "Switch layer not marked as such");
164 }
165 bool TuneLayerMissing = false;
166 if(m_pTuneLayer != nullptr)
167 {
168 TuneLayerMissing = true;
169 CheckObjectInMap(m_pTuneLayer.get(), "Tune layer");
170 dbg_assert(m_pTuneLayer->m_HasTune, "Tune layer not marked as such");
171 }
172 for(const auto &pGroup : m_vpGroups)
173 {
174 CheckObjectInMap(pGroup.get(), "Group");
175 for(const auto &pLayer : pGroup->m_vpLayers)
176 {
177 CheckObjectInMap(pLayer.get(), "Layer");
178 }
179 if(pGroup == m_pGameGroup)
180 {
181 GameGroupMissing = false;
182 for(const auto &pLayer : pGroup->m_vpLayers)
183 {
184 if(pLayer == m_pGameLayer)
185 {
186 GameLayerMissing = false;
187 }
188 if(pLayer == m_pFrontLayer)
189 {
190 FrontLayerMissing = false;
191 }
192 if(pLayer == m_pTeleLayer)
193 {
194 TeleLayerMissing = false;
195 }
196 if(pLayer == m_pSpeedupLayer)
197 {
198 SpeedupLayerMissing = false;
199 }
200 if(pLayer == m_pSwitchLayer)
201 {
202 SwitchLayerMissing = false;
203 }
204 if(pLayer == m_pTuneLayer)
205 {
206 TuneLayerMissing = false;
207 }
208 }
209 dbg_assert(!GameLayerMissing, "Game layer missing in game group");
210 dbg_assert(!FrontLayerMissing, "Front layer missing in game group");
211 dbg_assert(!TeleLayerMissing, "Tele layer missing in game group");
212 dbg_assert(!SpeedupLayerMissing, "Speedup layer missing in game group");
213 dbg_assert(!SwitchLayerMissing, "Switch layer missing in game group");
214 dbg_assert(!TuneLayerMissing, "Tune layer missing in game group");
215 }
216 }
217 dbg_assert(!GameGroupMissing, "Game group missing in list of groups");
218 for(const auto &pImage : m_vpImages)
219 {
220 CheckObjectInMap(pImage.get(), "Image");
221 }
222 for(const auto &pSound : m_vpSounds)
223 {
224 CheckObjectInMap(pSound.get(), "Sound");
225 }
226}
227
228void CEditorMap::ModifyImageIndex(const FIndexModifyFunction &IndexModifyFunction)
229{
230 OnModify();
231 for(auto &pGroup : m_vpGroups)
232 {
233 pGroup->ModifyImageIndex(IndexModifyFunction);
234 }
235}
236
237void CEditorMap::ModifyEnvelopeIndex(const FIndexModifyFunction &IndexModifyFunction)
238{
239 OnModify();
240 for(auto &pGroup : m_vpGroups)
241 {
242 pGroup->ModifyEnvelopeIndex(IndexModifyFunction);
243 }
244}
245
246void CEditorMap::ModifySoundIndex(const FIndexModifyFunction &IndexModifyFunction)
247{
248 OnModify();
249 for(auto &pGroup : m_vpGroups)
250 {
251 pGroup->ModifySoundIndex(IndexModifyFunction);
252 }
253}
254
255std::shared_ptr<CLayerGroup> CEditorMap::SelectedGroup() const
256{
257 if(m_SelectedGroup >= 0 && m_SelectedGroup < (int)m_vpGroups.size())
258 return m_vpGroups[m_SelectedGroup];
259 return nullptr;
260}
261
262std::shared_ptr<CLayerGroup> CEditorMap::NewGroup()
263{
264 OnModify();
265 std::shared_ptr<CLayerGroup> pGroup = std::make_shared<CLayerGroup>(args: this);
266 m_vpGroups.push_back(x: pGroup);
267 return pGroup;
268}
269
270int CEditorMap::MoveGroup(int IndexFrom, int IndexTo)
271{
272 if(IndexFrom < 0 || IndexFrom >= (int)m_vpGroups.size())
273 return IndexFrom;
274 if(IndexTo < 0 || IndexTo >= (int)m_vpGroups.size())
275 return IndexFrom;
276 if(IndexFrom == IndexTo)
277 return IndexFrom;
278 OnModify();
279 auto pMovedGroup = m_vpGroups[IndexFrom];
280 m_vpGroups.erase(position: m_vpGroups.begin() + IndexFrom);
281 m_vpGroups.insert(position: m_vpGroups.begin() + IndexTo, x: pMovedGroup);
282 return IndexTo;
283}
284
285void CEditorMap::DeleteGroup(int Index)
286{
287 if(Index < 0 || Index >= (int)m_vpGroups.size())
288 return;
289 OnModify();
290 m_vpGroups.erase(position: m_vpGroups.begin() + Index);
291}
292
293void CEditorMap::MakeGameGroup(std::shared_ptr<CLayerGroup> pGroup)
294{
295 m_pGameGroup = std::move(pGroup);
296 m_pGameGroup->m_GameGroup = true;
297 str_copy(dst&: m_pGameGroup->m_aName, src: "Game");
298}
299
300std::shared_ptr<CLayer> CEditorMap::SelectedLayer(int Index) const
301{
302 std::shared_ptr<CLayerGroup> pGroup = SelectedGroup();
303 if(!pGroup)
304 return nullptr;
305
306 if(Index < 0 || Index >= (int)m_vSelectedLayers.size())
307 return nullptr;
308
309 int LayerIndex = m_vSelectedLayers[Index];
310
311 if(LayerIndex >= 0 && LayerIndex < (int)m_vpGroups[m_SelectedGroup]->m_vpLayers.size())
312 return pGroup->m_vpLayers[LayerIndex];
313 return nullptr;
314}
315
316std::shared_ptr<CLayer> CEditorMap::SelectedLayerType(int Index, int Type) const
317{
318 std::shared_ptr<CLayer> pLayer = SelectedLayer(Index);
319 if(pLayer && pLayer->m_Type == Type)
320 return pLayer;
321 return nullptr;
322}
323
324void CEditorMap::SelectLayer(int LayerIndex, int GroupIndex)
325{
326 if(GroupIndex != -1)
327 m_SelectedGroup = GroupIndex;
328
329 m_vSelectedLayers.clear();
330 DeselectQuads();
331 DeselectQuadPoints();
332 AddSelectedLayer(LayerIndex);
333}
334
335void CEditorMap::AddSelectedLayer(int LayerIndex)
336{
337 m_vSelectedLayers.push_back(x: LayerIndex);
338 m_QuadKnifeState.Reset();
339}
340
341void CEditorMap::SelectNextLayer()
342{
343 int CurrentLayer = 0;
344 for(const auto &Selected : m_vSelectedLayers)
345 CurrentLayer = maximum(a: Selected, b: CurrentLayer);
346 SelectLayer(LayerIndex: CurrentLayer);
347
348 if(m_vSelectedLayers[0] < (int)m_vpGroups[m_SelectedGroup]->m_vpLayers.size() - 1)
349 {
350 SelectLayer(LayerIndex: m_vSelectedLayers[0] + 1);
351 }
352 else
353 {
354 for(size_t Group = m_SelectedGroup + 1; Group < m_vpGroups.size(); Group++)
355 {
356 if(!m_vpGroups[Group]->m_vpLayers.empty())
357 {
358 SelectLayer(LayerIndex: 0, GroupIndex: Group);
359 break;
360 }
361 }
362 }
363}
364
365void CEditorMap::SelectPreviousLayer()
366{
367 int CurrentLayer = std::numeric_limits<int>::max();
368 for(const auto &Selected : m_vSelectedLayers)
369 CurrentLayer = minimum(a: Selected, b: CurrentLayer);
370 SelectLayer(LayerIndex: CurrentLayer);
371
372 if(m_vSelectedLayers[0] > 0)
373 {
374 SelectLayer(LayerIndex: m_vSelectedLayers[0] - 1);
375 }
376 else
377 {
378 for(int Group = m_SelectedGroup - 1; Group >= 0; Group--)
379 {
380 if(!m_vpGroups[Group]->m_vpLayers.empty())
381 {
382 SelectLayer(LayerIndex: m_vpGroups[Group]->m_vpLayers.size() - 1, GroupIndex: Group);
383 break;
384 }
385 }
386 }
387}
388
389void CEditorMap::SelectGameLayer()
390{
391 for(size_t g = 0; g < m_vpGroups.size(); g++)
392 {
393 for(size_t i = 0; i < m_vpGroups[g]->m_vpLayers.size(); i++)
394 {
395 if(m_vpGroups[g]->m_vpLayers[i] == m_pGameLayer)
396 {
397 SelectLayer(LayerIndex: i, GroupIndex: g);
398 return;
399 }
400 }
401 }
402}
403
404void CEditorMap::MakeGameLayer(const std::shared_ptr<CLayer> &pLayer)
405{
406 m_pGameLayer = std::static_pointer_cast<CLayerGame>(r: pLayer);
407}
408
409void CEditorMap::MakeTeleLayer(const std::shared_ptr<CLayer> &pLayer)
410{
411 m_pTeleLayer = std::static_pointer_cast<CLayerTele>(r: pLayer);
412}
413
414void CEditorMap::MakeSpeedupLayer(const std::shared_ptr<CLayer> &pLayer)
415{
416 m_pSpeedupLayer = std::static_pointer_cast<CLayerSpeedup>(r: pLayer);
417}
418
419void CEditorMap::MakeFrontLayer(const std::shared_ptr<CLayer> &pLayer)
420{
421 m_pFrontLayer = std::static_pointer_cast<CLayerFront>(r: pLayer);
422}
423
424void CEditorMap::MakeSwitchLayer(const std::shared_ptr<CLayer> &pLayer)
425{
426 m_pSwitchLayer = std::static_pointer_cast<CLayerSwitch>(r: pLayer);
427}
428
429void CEditorMap::MakeTuneLayer(const std::shared_ptr<CLayer> &pLayer)
430{
431 m_pTuneLayer = std::static_pointer_cast<CLayerTune>(r: pLayer);
432}
433
434std::vector<CQuad *> CEditorMap::SelectedQuads()
435{
436 std::shared_ptr<CLayerQuads> pQuadLayer = std::static_pointer_cast<CLayerQuads>(r: SelectedLayerType(Index: 0, Type: LAYERTYPE_QUADS));
437 std::vector<CQuad *> vpQuads;
438 if(!pQuadLayer)
439 return vpQuads;
440 vpQuads.reserve(n: m_vSelectedQuads.size());
441 for(const auto &SelectedQuad : m_vSelectedQuads)
442 {
443 if(SelectedQuad >= (int)pQuadLayer->m_vQuads.size())
444 continue;
445 vpQuads.push_back(x: &pQuadLayer->m_vQuads[SelectedQuad]);
446 }
447 return vpQuads;
448}
449
450bool CEditorMap::IsQuadSelected(int Index) const
451{
452 return FindSelectedQuadIndex(Index) >= 0;
453}
454
455int CEditorMap::FindSelectedQuadIndex(int Index) const
456{
457 for(size_t i = 0; i < m_vSelectedQuads.size(); ++i)
458 if(m_vSelectedQuads[i] == Index)
459 return i;
460 return -1;
461}
462
463void CEditorMap::SelectQuad(int Index)
464{
465 m_vSelectedQuads.clear();
466 m_vSelectedQuads.push_back(x: Index);
467}
468
469void CEditorMap::ToggleSelectQuad(int Index)
470{
471 int ListIndex = FindSelectedQuadIndex(Index);
472 if(ListIndex < 0)
473 m_vSelectedQuads.push_back(x: Index);
474 else
475 m_vSelectedQuads.erase(position: m_vSelectedQuads.begin() + ListIndex);
476}
477
478void CEditorMap::DeselectQuads()
479{
480 m_vSelectedQuads.clear();
481}
482
483bool CEditorMap::IsQuadCornerSelected(int Index) const
484{
485 return m_SelectedQuadPoints & (1 << Index);
486}
487
488bool CEditorMap::IsQuadPointSelected(int QuadIndex, int Index) const
489{
490 return IsQuadSelected(Index: QuadIndex) && IsQuadCornerSelected(Index);
491}
492
493void CEditorMap::SelectQuadPoint(int QuadIndex, int Index)
494{
495 SelectQuad(Index: QuadIndex);
496 m_SelectedQuadPoints = 1 << Index;
497}
498
499void CEditorMap::ToggleSelectQuadPoint(int QuadIndex, int Index)
500{
501 if(IsQuadPointSelected(QuadIndex, Index))
502 {
503 m_SelectedQuadPoints ^= 1 << Index;
504 }
505 else
506 {
507 if(!IsQuadSelected(Index: QuadIndex))
508 {
509 ToggleSelectQuad(Index: QuadIndex);
510 }
511
512 if(!(m_SelectedQuadPoints & 1 << Index))
513 {
514 m_SelectedQuadPoints ^= 1 << Index;
515 }
516 }
517}
518
519void CEditorMap::DeselectQuadPoints()
520{
521 m_SelectedQuadPoints = 0;
522}
523
524void CEditorMap::DeleteSelectedQuads()
525{
526 std::shared_ptr<CLayerQuads> pLayer = std::static_pointer_cast<CLayerQuads>(r: SelectedLayerType(Index: 0, Type: LAYERTYPE_QUADS));
527 if(!pLayer)
528 return;
529
530 std::vector<int> vSelectedQuads(m_vSelectedQuads);
531 std::vector<CQuad> vDeletedQuads;
532 vDeletedQuads.reserve(n: m_vSelectedQuads.size());
533 for(int i = 0; i < (int)m_vSelectedQuads.size(); ++i)
534 {
535 auto const &Quad = pLayer->m_vQuads[m_vSelectedQuads[i]];
536 vDeletedQuads.push_back(x: Quad);
537
538 pLayer->m_vQuads.erase(position: pLayer->m_vQuads.begin() + m_vSelectedQuads[i]);
539 for(int j = i + 1; j < (int)m_vSelectedQuads.size(); ++j)
540 if(m_vSelectedQuads[j] > m_vSelectedQuads[i])
541 m_vSelectedQuads[j]--;
542
543 m_vSelectedQuads.erase(position: m_vSelectedQuads.begin() + i);
544 i--;
545 }
546
547 m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionDeleteQuad>(args: this, args&: m_SelectedGroup, args&: m_vSelectedLayers[0], args&: vSelectedQuads, args&: vDeletedQuads));
548}
549
550std::shared_ptr<CEnvelope> CEditorMap::NewEnvelope(CEnvelope::EType Type)
551{
552 OnModify();
553 std::shared_ptr<CEnvelope> pEnvelope = std::make_shared<CEnvelope>(args&: Type);
554 if(Type == CEnvelope::EType::COLOR)
555 {
556 pEnvelope->AddPoint(Time: CFixedTime::FromSeconds(Seconds: 0.0f), aValues: {f2fx(v: 1.0f), f2fx(v: 1.0f), f2fx(v: 1.0f), f2fx(v: 1.0f)});
557 pEnvelope->AddPoint(Time: CFixedTime::FromSeconds(Seconds: 1.0f), aValues: {f2fx(v: 1.0f), f2fx(v: 1.0f), f2fx(v: 1.0f), f2fx(v: 1.0f)});
558 }
559 else
560 {
561 pEnvelope->AddPoint(Time: CFixedTime::FromSeconds(Seconds: 0.0f), aValues: {0, 0, 0, 0});
562 pEnvelope->AddPoint(Time: CFixedTime::FromSeconds(Seconds: 1.0f), aValues: {0, 0, 0, 0});
563 }
564 m_vpEnvelopes.push_back(x: pEnvelope);
565 return pEnvelope;
566}
567
568void CEditorMap::InsertEnvelope(int Index, std::shared_ptr<CEnvelope> &pEnvelope)
569{
570 if(Index < 0 || Index >= (int)m_vpEnvelopes.size() + 1)
571 return;
572 m_vpEnvelopes.push_back(x: pEnvelope);
573 m_SelectedEnvelope = MoveEnvelope(IndexFrom: (int)m_vpEnvelopes.size() - 1, IndexTo: Index);
574}
575
576void CEditorMap::UpdateEnvelopeReferences(int Index, std::shared_ptr<CEnvelope> &pEnvelope, std::vector<std::shared_ptr<IEditorEnvelopeReference>> &vpEditorObjectReferences)
577{
578 // update unrestored quad and soundsource references
579 for(auto &pEditorObjRef : vpEditorObjectReferences)
580 pEditorObjRef->SetEnvelope(pEnvelope, EnvelopeIndex: Index);
581}
582
583std::vector<std::shared_ptr<IEditorEnvelopeReference>> CEditorMap::DeleteEnvelope(int Index)
584{
585 if(Index < 0 || Index >= (int)m_vpEnvelopes.size())
586 return std::vector<std::shared_ptr<IEditorEnvelopeReference>>();
587
588 OnModify();
589
590 std::vector<std::shared_ptr<IEditorEnvelopeReference>> vpEditorObjectReferences = VisitEnvelopeReferences(Visitor: [Index](int &ElementIndex) {
591 if(ElementIndex == Index)
592 {
593 ElementIndex = -1;
594 return true;
595 }
596 else if(ElementIndex > Index)
597 ElementIndex--;
598 return false;
599 });
600
601 m_vpEnvelopes.erase(position: m_vpEnvelopes.begin() + Index);
602 return vpEditorObjectReferences;
603}
604
605int CEditorMap::MoveEnvelope(int IndexFrom, int IndexTo)
606{
607 if(IndexFrom < 0 || IndexFrom >= (int)m_vpEnvelopes.size())
608 return IndexFrom;
609 if(IndexTo < 0 || IndexTo >= (int)m_vpEnvelopes.size())
610 return IndexFrom;
611 if(IndexFrom == IndexTo)
612 return IndexFrom;
613
614 OnModify();
615
616 VisitEnvelopeReferences(Visitor: [IndexFrom, IndexTo](int &ElementIndex) {
617 if(ElementIndex == IndexFrom)
618 ElementIndex = IndexTo;
619 else if(IndexFrom < IndexTo && ElementIndex > IndexFrom && ElementIndex <= IndexTo)
620 ElementIndex--;
621 else if(IndexTo < IndexFrom && ElementIndex < IndexFrom && ElementIndex >= IndexTo)
622 ElementIndex++;
623 return false;
624 });
625
626 auto pMovedEnvelope = m_vpEnvelopes[IndexFrom];
627 m_vpEnvelopes.erase(position: m_vpEnvelopes.begin() + IndexFrom);
628 m_vpEnvelopes.insert(position: m_vpEnvelopes.begin() + IndexTo, x: pMovedEnvelope);
629
630 return IndexTo;
631}
632
633template<typename F>
634std::vector<std::shared_ptr<IEditorEnvelopeReference>> CEditorMap::VisitEnvelopeReferences(F &&Visitor)
635{
636 std::vector<std::shared_ptr<IEditorEnvelopeReference>> vpUpdatedReferences;
637 for(auto &pGroup : m_vpGroups)
638 {
639 for(auto &pLayer : pGroup->m_vpLayers)
640 {
641 if(pLayer->m_Type == LAYERTYPE_QUADS)
642 {
643 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: pLayer);
644 std::shared_ptr<CLayerQuadsEnvelopeReference> pQuadLayerReference = std::make_shared<CLayerQuadsEnvelopeReference>(args&: pLayerQuads);
645 for(int QuadId = 0; QuadId < (int)pLayerQuads->m_vQuads.size(); ++QuadId)
646 {
647 auto &Quad = pLayerQuads->m_vQuads[QuadId];
648 if(Visitor(Quad.m_PosEnv))
649 pQuadLayerReference->AddQuadIndex(QuadIndex: QuadId);
650 if(Visitor(Quad.m_ColorEnv))
651 pQuadLayerReference->AddQuadIndex(QuadIndex: QuadId);
652 }
653 if(!pQuadLayerReference->Empty())
654 vpUpdatedReferences.push_back(x: pQuadLayerReference);
655 }
656 else if(pLayer->m_Type == LAYERTYPE_TILES)
657 {
658 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer);
659 std::shared_ptr<CLayerTilesEnvelopeReference> pTileLayerReference = std::make_shared<CLayerTilesEnvelopeReference>(args&: pLayerTiles);
660 if(Visitor(pLayerTiles->m_ColorEnv))
661 vpUpdatedReferences.push_back(x: pTileLayerReference);
662 }
663 else if(pLayer->m_Type == LAYERTYPE_SOUNDS)
664 {
665 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: pLayer);
666 std::shared_ptr<CLayerSoundEnvelopeReference> pSoundLayerReference = std::make_shared<CLayerSoundEnvelopeReference>(args&: pLayerSounds);
667
668 for(int SourceId = 0; SourceId < (int)pLayerSounds->m_vSources.size(); ++SourceId)
669 {
670 auto &Source = pLayerSounds->m_vSources[SourceId];
671 if(Visitor(Source.m_PosEnv))
672 pSoundLayerReference->AddSoundSourceIndex(SoundSourceIndex: SourceId);
673 if(Visitor(Source.m_SoundEnv))
674 pSoundLayerReference->AddSoundSourceIndex(SoundSourceIndex: SourceId);
675 }
676 if(!pSoundLayerReference->Empty())
677 vpUpdatedReferences.push_back(x: pSoundLayerReference);
678 }
679 }
680 }
681 return vpUpdatedReferences;
682}
683
684bool CEditorMap::IsEnvelopeUsed(int EnvelopeIndex) const
685{
686 for(const auto &pGroup : m_vpGroups)
687 {
688 for(const auto &pLayer : pGroup->m_vpLayers)
689 {
690 if(pLayer->IsEnvelopeUsed(EnvelopeIndex))
691 {
692 return true;
693 }
694 }
695 }
696 return false;
697}
698
699void CEditorMap::RemoveUnusedEnvelopes()
700{
701 m_EnvelopeEditorHistory.BeginBulk();
702 int DeletedCount = 0;
703 for(size_t EnvelopeIndex = 0; EnvelopeIndex < m_vpEnvelopes.size();)
704 {
705 if(IsEnvelopeUsed(EnvelopeIndex))
706 {
707 ++EnvelopeIndex;
708 }
709 else
710 {
711 // deleting removes the shared ptr from the map
712 std::shared_ptr<CEnvelope> pEnvelope = m_vpEnvelopes[EnvelopeIndex];
713 auto vpObjectReferences = DeleteEnvelope(Index: EnvelopeIndex);
714 m_EnvelopeEditorHistory.RecordAction(pAction: std::make_shared<CEditorActionEnvelopeDelete>(args: this, args&: EnvelopeIndex, args&: vpObjectReferences, args&: pEnvelope));
715 DeletedCount++;
716 }
717 }
718 char aDisplay[256];
719 str_format(buffer: aDisplay, buffer_size: sizeof(aDisplay), format: "Tool 'Remove unused envelopes': delete %d envelopes", DeletedCount);
720 m_EnvelopeEditorHistory.EndBulk(pDisplay: aDisplay);
721}
722
723int CEditorMap::FindEnvPointIndex(int Index, int Channel) const
724{
725 auto Iter = std::find(
726 first: m_vSelectedEnvelopePoints.begin(),
727 last: m_vSelectedEnvelopePoints.end(),
728 val: std::pair(Index, Channel));
729
730 if(Iter != m_vSelectedEnvelopePoints.end())
731 return Iter - m_vSelectedEnvelopePoints.begin();
732 else
733 return -1;
734}
735
736void CEditorMap::SelectEnvPoint(int Index)
737{
738 m_vSelectedEnvelopePoints.clear();
739
740 for(int c = 0; c < CEnvPoint::MAX_CHANNELS; c++)
741 m_vSelectedEnvelopePoints.emplace_back(args&: Index, args&: c);
742}
743
744void CEditorMap::SelectEnvPoint(int Index, int Channel)
745{
746 DeselectEnvPoints();
747 m_vSelectedEnvelopePoints.emplace_back(args&: Index, args&: Channel);
748}
749
750void CEditorMap::ToggleEnvPoint(int Index, int Channel)
751{
752 if(IsTangentSelected())
753 DeselectEnvPoints();
754
755 int ListIndex = FindEnvPointIndex(Index, Channel);
756
757 if(ListIndex >= 0)
758 {
759 m_vSelectedEnvelopePoints.erase(position: m_vSelectedEnvelopePoints.begin() + ListIndex);
760 }
761 else
762 m_vSelectedEnvelopePoints.emplace_back(args&: Index, args&: Channel);
763}
764
765bool CEditorMap::IsEnvPointSelected(int Index, int Channel) const
766{
767 int ListIndex = FindEnvPointIndex(Index, Channel);
768
769 return ListIndex >= 0;
770}
771
772bool CEditorMap::IsEnvPointSelected(int Index) const
773{
774 auto Iter = std::find_if(
775 first: m_vSelectedEnvelopePoints.begin(),
776 last: m_vSelectedEnvelopePoints.end(),
777 pred: [&](const auto &Pair) { return Pair.first == Index; });
778
779 return Iter != m_vSelectedEnvelopePoints.end();
780}
781
782void CEditorMap::DeselectEnvPoints()
783{
784 m_vSelectedEnvelopePoints.clear();
785 m_SelectedTangentInPoint = std::pair(-1, -1);
786 m_SelectedTangentOutPoint = std::pair(-1, -1);
787}
788
789bool CEditorMap::IsTangentSelected() const
790{
791 return IsTangentInSelected() || IsTangentOutSelected();
792}
793
794bool CEditorMap::IsTangentOutPointSelected(int Index, int Channel) const
795{
796 return m_SelectedTangentOutPoint == std::pair(Index, Channel);
797}
798
799bool CEditorMap::IsTangentOutSelected() const
800{
801 return m_SelectedTangentOutPoint != std::pair(-1, -1);
802}
803
804void CEditorMap::SelectTangentOutPoint(int Index, int Channel)
805{
806 DeselectEnvPoints();
807 m_SelectedTangentOutPoint = std::pair(Index, Channel);
808}
809
810bool CEditorMap::IsTangentInPointSelected(int Index, int Channel) const
811{
812 return m_SelectedTangentInPoint == std::pair(Index, Channel);
813}
814
815bool CEditorMap::IsTangentInSelected() const
816{
817 return m_SelectedTangentInPoint != std::pair(-1, -1);
818}
819
820void CEditorMap::SelectTangentInPoint(int Index, int Channel)
821{
822 DeselectEnvPoints();
823 m_SelectedTangentInPoint = std::pair(Index, Channel);
824}
825
826std::pair<CFixedTime, int> CEditorMap::SelectedEnvelopeTimeAndValue() const
827{
828 if(m_SelectedEnvelope < 0 || m_SelectedEnvelope >= (int)m_vpEnvelopes.size())
829 return {};
830
831 std::shared_ptr<CEnvelope> pEnvelope = m_vpEnvelopes[m_SelectedEnvelope];
832 CFixedTime CurrentTime;
833 int CurrentValue;
834 if(IsTangentInSelected())
835 {
836 auto [SelectedIndex, SelectedChannel] = m_SelectedTangentInPoint;
837 CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel];
838 CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaY[SelectedChannel];
839 }
840 else if(IsTangentOutSelected())
841 {
842 auto [SelectedIndex, SelectedChannel] = m_SelectedTangentOutPoint;
843 CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel];
844 CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaY[SelectedChannel];
845 }
846 else
847 {
848 auto [SelectedIndex, SelectedChannel] = m_vSelectedEnvelopePoints.front();
849 CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time;
850 CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel];
851 }
852
853 return std::pair<CFixedTime, int>{CurrentTime, CurrentValue};
854}
855
856std::shared_ptr<CEditorImage> CEditorMap::SelectedImage() const
857{
858 if(m_SelectedImage < 0 || (size_t)m_SelectedImage >= m_vpImages.size())
859 {
860 return nullptr;
861 }
862 return m_vpImages[m_SelectedImage];
863}
864
865void CEditorMap::SelectImage(const std::shared_ptr<CEditorImage> &pImage)
866{
867 for(size_t i = 0; i < m_vpImages.size(); ++i)
868 {
869 if(m_vpImages[i] == pImage)
870 {
871 m_SelectedImage = i;
872 break;
873 }
874 }
875}
876
877void CEditorMap::SelectNextImage()
878{
879 const int OldImage = m_SelectedImage;
880 m_SelectedImage = std::clamp(val: m_SelectedImage, lo: 0, hi: (int)m_vpImages.size() - 1);
881 for(size_t i = m_SelectedImage + 1; i < m_vpImages.size(); i++)
882 {
883 if(m_vpImages[i]->m_External == m_vpImages[m_SelectedImage]->m_External)
884 {
885 m_SelectedImage = i;
886 break;
887 }
888 }
889 if(m_SelectedImage == OldImage && !m_vpImages[m_SelectedImage]->m_External)
890 {
891 for(size_t i = 0; i < m_vpImages.size(); i++)
892 {
893 if(m_vpImages[i]->m_External)
894 {
895 m_SelectedImage = i;
896 break;
897 }
898 }
899 }
900}
901
902void CEditorMap::SelectPreviousImage()
903{
904 const int OldImage = m_SelectedImage;
905 m_SelectedImage = std::clamp(val: m_SelectedImage, lo: 0, hi: (int)m_vpImages.size() - 1);
906 for(int i = m_SelectedImage - 1; i >= 0; i--)
907 {
908 if(m_vpImages[i]->m_External == m_vpImages[m_SelectedImage]->m_External)
909 {
910 m_SelectedImage = i;
911 break;
912 }
913 }
914 if(m_SelectedImage == OldImage && m_vpImages[m_SelectedImage]->m_External)
915 {
916 for(int i = (int)m_vpImages.size() - 1; i >= 0; i--)
917 {
918 if(!m_vpImages[i]->m_External)
919 {
920 m_SelectedImage = i;
921 break;
922 }
923 }
924 }
925}
926
927bool CEditorMap::IsImageUsed(int ImageIndex) const
928{
929 for(const auto &pGroup : m_vpGroups)
930 {
931 for(const auto &pLayer : pGroup->m_vpLayers)
932 {
933 if(pLayer->IsImageUsed(ImageIndex))
934 {
935 return true;
936 }
937 }
938 }
939 return false;
940}
941
942std::vector<int> CEditorMap::SortImages()
943{
944 static const auto &&s_ImageNameComparator = [](const std::shared_ptr<CEditorImage> &pLhs, const std::shared_ptr<CEditorImage> &pRhs) {
945 return str_comp(a: pLhs->m_aName, b: pRhs->m_aName) < 0;
946 };
947 if(std::is_sorted(first: m_vpImages.begin(), last: m_vpImages.end(), comp: s_ImageNameComparator))
948 {
949 return std::vector<int>();
950 }
951
952 const std::vector<std::shared_ptr<CEditorImage>> vpTemp = m_vpImages;
953 std::vector<int> vSortedIndex;
954 vSortedIndex.resize(sz: vpTemp.size());
955
956 std::sort(first: m_vpImages.begin(), last: m_vpImages.end(), comp: s_ImageNameComparator);
957 for(size_t OldIndex = 0; OldIndex < vpTemp.size(); OldIndex++)
958 {
959 for(size_t NewIndex = 0; NewIndex < m_vpImages.size(); NewIndex++)
960 {
961 if(vpTemp[OldIndex] == m_vpImages[NewIndex])
962 {
963 vSortedIndex[OldIndex] = NewIndex;
964 break;
965 }
966 }
967 }
968 ModifyImageIndex(IndexModifyFunction: [vSortedIndex](int *pIndex) {
969 if(*pIndex >= 0)
970 {
971 *pIndex = vSortedIndex[*pIndex];
972 }
973 });
974
975 return vSortedIndex;
976}
977
978std::shared_ptr<CEditorSound> CEditorMap::SelectedSound() const
979{
980 if(m_SelectedSound < 0 || (size_t)m_SelectedSound >= m_vpSounds.size())
981 {
982 return nullptr;
983 }
984 return m_vpSounds[m_SelectedSound];
985}
986
987void CEditorMap::SelectSound(const std::shared_ptr<CEditorSound> &pSound)
988{
989 for(size_t i = 0; i < m_vpSounds.size(); ++i)
990 {
991 if(m_vpSounds[i] == pSound)
992 {
993 m_SelectedSound = i;
994 break;
995 }
996 }
997}
998
999void CEditorMap::SelectNextSound()
1000{
1001 m_SelectedSound = (m_SelectedSound + 1) % m_vpSounds.size();
1002}
1003
1004void CEditorMap::SelectPreviousSound()
1005{
1006 m_SelectedSound = (m_SelectedSound + m_vpSounds.size() - 1) % m_vpSounds.size();
1007}
1008
1009bool CEditorMap::IsSoundUsed(int SoundIndex) const
1010{
1011 for(const auto &pGroup : m_vpGroups)
1012 {
1013 for(const auto &pLayer : pGroup->m_vpLayers)
1014 {
1015 if(pLayer->IsSoundUsed(SoundIndex))
1016 {
1017 return true;
1018 }
1019 }
1020 }
1021 return false;
1022}
1023
1024CSoundSource *CEditorMap::SelectedSoundSource() const
1025{
1026 std::shared_ptr<CLayerSounds> pSounds = std::static_pointer_cast<CLayerSounds>(r: SelectedLayerType(Index: 0, Type: LAYERTYPE_SOUNDS));
1027 if(!pSounds)
1028 return nullptr;
1029 if(m_SelectedSoundSource >= 0 && m_SelectedSoundSource < (int)pSounds->m_vSources.size())
1030 return &pSounds->m_vSources[m_SelectedSoundSource];
1031 return nullptr;
1032}
1033
1034void CEditorMap::PlaceBorderTiles()
1035{
1036 std::shared_ptr<CLayerTiles> pT = std::static_pointer_cast<CLayerTiles>(r: SelectedLayerType(Index: 0, Type: LAYERTYPE_TILES));
1037
1038 for(int i = 0; i < pT->m_Width * pT->m_Height; ++i)
1039 {
1040 if(i % pT->m_Width < 2 || i % pT->m_Width > pT->m_Width - 3 || i < pT->m_Width * 2 || i > pT->m_Width * (pT->m_Height - 2))
1041 {
1042 int x = i % pT->m_Width;
1043 int y = i / pT->m_Width;
1044
1045 CTile Current = pT->m_pTiles[i];
1046 Current.m_Index = 1;
1047 pT->SetTile(x, y, Tile: Current);
1048 }
1049 }
1050
1051 int GameGroupIndex = std::find(first: m_vpGroups.begin(), last: m_vpGroups.end(), val: m_pGameGroup) - m_vpGroups.begin();
1052 m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorBrushDrawAction>(args: this, args&: GameGroupIndex), pDisplay: "Tool 'Make borders'");
1053
1054 OnModify();
1055}
1056