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