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