1#include "map.h"
2
3#include <base/system.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_QuadKnife.m_Active = false;
94 m_QuadKnife.m_Count = 0;
95 m_QuadKnife.m_SelectedQuadIndex = -1;
96 std::fill(first: std::begin(arr&: m_QuadKnife.m_aPoints), last: std::end(arr&: m_QuadKnife.m_aPoints), value: vec2(0.0f, 0.0f));
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_QuadKnife.m_Active = false;
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->m_Type == LAYERTYPE_QUADS)
691 {
692 std::shared_ptr<CLayerQuads> pLayerQuads = std::static_pointer_cast<CLayerQuads>(r: pLayer);
693 for(const auto &Quad : pLayerQuads->m_vQuads)
694 {
695 if(Quad.m_PosEnv == EnvelopeIndex || Quad.m_ColorEnv == EnvelopeIndex)
696 {
697 return true;
698 }
699 }
700 }
701 else if(pLayer->m_Type == LAYERTYPE_SOUNDS)
702 {
703 std::shared_ptr<CLayerSounds> pLayerSounds = std::static_pointer_cast<CLayerSounds>(r: pLayer);
704 for(const auto &Source : pLayerSounds->m_vSources)
705 {
706 if(Source.m_PosEnv == EnvelopeIndex || Source.m_SoundEnv == EnvelopeIndex)
707 {
708 return true;
709 }
710 }
711 }
712 else if(pLayer->m_Type == LAYERTYPE_TILES)
713 {
714 std::shared_ptr<CLayerTiles> pLayerTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer);
715 if(pLayerTiles->m_ColorEnv == EnvelopeIndex)
716 {
717 return true;
718 }
719 }
720 }
721 }
722 return false;
723}
724
725void CEditorMap::RemoveUnusedEnvelopes()
726{
727 m_EnvelopeEditorHistory.BeginBulk();
728 int DeletedCount = 0;
729 for(size_t EnvelopeIndex = 0; EnvelopeIndex < m_vpEnvelopes.size();)
730 {
731 if(IsEnvelopeUsed(EnvelopeIndex))
732 {
733 ++EnvelopeIndex;
734 }
735 else
736 {
737 // deleting removes the shared ptr from the map
738 std::shared_ptr<CEnvelope> pEnvelope = m_vpEnvelopes[EnvelopeIndex];
739 auto vpObjectReferences = DeleteEnvelope(Index: EnvelopeIndex);
740 m_EnvelopeEditorHistory.RecordAction(pAction: std::make_shared<CEditorActionEnvelopeDelete>(args: this, args&: EnvelopeIndex, args&: vpObjectReferences, args&: pEnvelope));
741 DeletedCount++;
742 }
743 }
744 char aDisplay[256];
745 str_format(buffer: aDisplay, buffer_size: sizeof(aDisplay), format: "Tool 'Remove unused envelopes': delete %d envelopes", DeletedCount);
746 m_EnvelopeEditorHistory.EndBulk(pDisplay: aDisplay);
747}
748
749int CEditorMap::FindEnvPointIndex(int Index, int Channel) const
750{
751 auto Iter = std::find(
752 first: m_vSelectedEnvelopePoints.begin(),
753 last: m_vSelectedEnvelopePoints.end(),
754 val: std::pair(Index, Channel));
755
756 if(Iter != m_vSelectedEnvelopePoints.end())
757 return Iter - m_vSelectedEnvelopePoints.begin();
758 else
759 return -1;
760}
761
762void CEditorMap::SelectEnvPoint(int Index)
763{
764 m_vSelectedEnvelopePoints.clear();
765
766 for(int c = 0; c < CEnvPoint::MAX_CHANNELS; c++)
767 m_vSelectedEnvelopePoints.emplace_back(args&: Index, args&: c);
768}
769
770void CEditorMap::SelectEnvPoint(int Index, int Channel)
771{
772 DeselectEnvPoints();
773 m_vSelectedEnvelopePoints.emplace_back(args&: Index, args&: Channel);
774}
775
776void CEditorMap::ToggleEnvPoint(int Index, int Channel)
777{
778 if(IsTangentSelected())
779 DeselectEnvPoints();
780
781 int ListIndex = FindEnvPointIndex(Index, Channel);
782
783 if(ListIndex >= 0)
784 {
785 m_vSelectedEnvelopePoints.erase(position: m_vSelectedEnvelopePoints.begin() + ListIndex);
786 }
787 else
788 m_vSelectedEnvelopePoints.emplace_back(args&: Index, args&: Channel);
789}
790
791bool CEditorMap::IsEnvPointSelected(int Index, int Channel) const
792{
793 int ListIndex = FindEnvPointIndex(Index, Channel);
794
795 return ListIndex >= 0;
796}
797
798bool CEditorMap::IsEnvPointSelected(int Index) const
799{
800 auto Iter = std::find_if(
801 first: m_vSelectedEnvelopePoints.begin(),
802 last: m_vSelectedEnvelopePoints.end(),
803 pred: [&](const auto &Pair) { return Pair.first == Index; });
804
805 return Iter != m_vSelectedEnvelopePoints.end();
806}
807
808void CEditorMap::DeselectEnvPoints()
809{
810 m_vSelectedEnvelopePoints.clear();
811 m_SelectedTangentInPoint = std::pair(-1, -1);
812 m_SelectedTangentOutPoint = std::pair(-1, -1);
813}
814
815bool CEditorMap::IsTangentSelected() const
816{
817 return IsTangentInSelected() || IsTangentOutSelected();
818}
819
820bool CEditorMap::IsTangentOutPointSelected(int Index, int Channel) const
821{
822 return m_SelectedTangentOutPoint == std::pair(Index, Channel);
823}
824
825bool CEditorMap::IsTangentOutSelected() const
826{
827 return m_SelectedTangentOutPoint != std::pair(-1, -1);
828}
829
830void CEditorMap::SelectTangentOutPoint(int Index, int Channel)
831{
832 DeselectEnvPoints();
833 m_SelectedTangentOutPoint = std::pair(Index, Channel);
834}
835
836bool CEditorMap::IsTangentInPointSelected(int Index, int Channel) const
837{
838 return m_SelectedTangentInPoint == std::pair(Index, Channel);
839}
840
841bool CEditorMap::IsTangentInSelected() const
842{
843 return m_SelectedTangentInPoint != std::pair(-1, -1);
844}
845
846void CEditorMap::SelectTangentInPoint(int Index, int Channel)
847{
848 DeselectEnvPoints();
849 m_SelectedTangentInPoint = std::pair(Index, Channel);
850}
851
852std::pair<CFixedTime, int> CEditorMap::SelectedEnvelopeTimeAndValue() const
853{
854 if(m_SelectedEnvelope < 0 || m_SelectedEnvelope >= (int)m_vpEnvelopes.size())
855 return {};
856
857 std::shared_ptr<CEnvelope> pEnvelope = m_vpEnvelopes[m_SelectedEnvelope];
858 CFixedTime CurrentTime;
859 int CurrentValue;
860 if(IsTangentInSelected())
861 {
862 auto [SelectedIndex, SelectedChannel] = m_SelectedTangentInPoint;
863 CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel];
864 CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaY[SelectedChannel];
865 }
866 else if(IsTangentOutSelected())
867 {
868 auto [SelectedIndex, SelectedChannel] = m_SelectedTangentOutPoint;
869 CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel];
870 CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel] + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaY[SelectedChannel];
871 }
872 else
873 {
874 auto [SelectedIndex, SelectedChannel] = m_vSelectedEnvelopePoints.front();
875 CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time;
876 CurrentValue = pEnvelope->m_vPoints[SelectedIndex].m_aValues[SelectedChannel];
877 }
878
879 return std::pair<CFixedTime, int>{CurrentTime, CurrentValue};
880}
881
882std::shared_ptr<CEditorImage> CEditorMap::SelectedImage() const
883{
884 if(m_SelectedImage < 0 || (size_t)m_SelectedImage >= m_vpImages.size())
885 {
886 return nullptr;
887 }
888 return m_vpImages[m_SelectedImage];
889}
890
891void CEditorMap::SelectImage(const std::shared_ptr<CEditorImage> &pImage)
892{
893 for(size_t i = 0; i < m_vpImages.size(); ++i)
894 {
895 if(m_vpImages[i] == pImage)
896 {
897 m_SelectedImage = i;
898 break;
899 }
900 }
901}
902
903void CEditorMap::SelectNextImage()
904{
905 const int OldImage = m_SelectedImage;
906 m_SelectedImage = std::clamp(val: m_SelectedImage, lo: 0, hi: (int)m_vpImages.size() - 1);
907 for(size_t i = m_SelectedImage + 1; i < m_vpImages.size(); i++)
908 {
909 if(m_vpImages[i]->m_External == m_vpImages[m_SelectedImage]->m_External)
910 {
911 m_SelectedImage = i;
912 break;
913 }
914 }
915 if(m_SelectedImage == OldImage && !m_vpImages[m_SelectedImage]->m_External)
916 {
917 for(size_t i = 0; i < m_vpImages.size(); i++)
918 {
919 if(m_vpImages[i]->m_External)
920 {
921 m_SelectedImage = i;
922 break;
923 }
924 }
925 }
926}
927
928void CEditorMap::SelectPreviousImage()
929{
930 const int OldImage = m_SelectedImage;
931 m_SelectedImage = std::clamp(val: m_SelectedImage, lo: 0, hi: (int)m_vpImages.size() - 1);
932 for(int i = m_SelectedImage - 1; i >= 0; i--)
933 {
934 if(m_vpImages[i]->m_External == m_vpImages[m_SelectedImage]->m_External)
935 {
936 m_SelectedImage = i;
937 break;
938 }
939 }
940 if(m_SelectedImage == OldImage && m_vpImages[m_SelectedImage]->m_External)
941 {
942 for(int i = (int)m_vpImages.size() - 1; i >= 0; i--)
943 {
944 if(!m_vpImages[i]->m_External)
945 {
946 m_SelectedImage = i;
947 break;
948 }
949 }
950 }
951}
952
953bool CEditorMap::IsImageUsed(int ImageIndex) const
954{
955 for(const auto &pGroup : m_vpGroups)
956 {
957 for(const auto &pLayer : pGroup->m_vpLayers)
958 {
959 if(pLayer->m_Type == LAYERTYPE_TILES)
960 {
961 const std::shared_ptr<CLayerTiles> pTiles = std::static_pointer_cast<CLayerTiles>(r: pLayer);
962 if(pTiles->m_Image == ImageIndex)
963 {
964 return true;
965 }
966 }
967 else if(pLayer->m_Type == LAYERTYPE_QUADS)
968 {
969 const std::shared_ptr<CLayerQuads> pQuads = std::static_pointer_cast<CLayerQuads>(r: pLayer);
970 if(pQuads->m_Image == ImageIndex)
971 {
972 return true;
973 }
974 }
975 }
976 }
977 return false;
978}
979
980std::vector<int> CEditorMap::SortImages()
981{
982 static const auto &&s_ImageNameComparator = [](const std::shared_ptr<CEditorImage> &pLhs, const std::shared_ptr<CEditorImage> &pRhs) {
983 return str_comp(a: pLhs->m_aName, b: pRhs->m_aName) < 0;
984 };
985 if(std::is_sorted(first: m_vpImages.begin(), last: m_vpImages.end(), comp: s_ImageNameComparator))
986 {
987 return std::vector<int>();
988 }
989
990 const std::vector<std::shared_ptr<CEditorImage>> vpTemp = m_vpImages;
991 std::vector<int> vSortedIndex;
992 vSortedIndex.resize(new_size: vpTemp.size());
993
994 std::sort(first: m_vpImages.begin(), last: m_vpImages.end(), comp: s_ImageNameComparator);
995 for(size_t OldIndex = 0; OldIndex < vpTemp.size(); OldIndex++)
996 {
997 for(size_t NewIndex = 0; NewIndex < m_vpImages.size(); NewIndex++)
998 {
999 if(vpTemp[OldIndex] == m_vpImages[NewIndex])
1000 {
1001 vSortedIndex[OldIndex] = NewIndex;
1002 break;
1003 }
1004 }
1005 }
1006 ModifyImageIndex(IndexModifyFunction: [vSortedIndex](int *pIndex) {
1007 if(*pIndex >= 0)
1008 {
1009 *pIndex = vSortedIndex[*pIndex];
1010 }
1011 });
1012
1013 return vSortedIndex;
1014}
1015
1016std::shared_ptr<CEditorSound> CEditorMap::SelectedSound() const
1017{
1018 if(m_SelectedSound < 0 || (size_t)m_SelectedSound >= m_vpSounds.size())
1019 {
1020 return nullptr;
1021 }
1022 return m_vpSounds[m_SelectedSound];
1023}
1024
1025void CEditorMap::SelectSound(const std::shared_ptr<CEditorSound> &pSound)
1026{
1027 for(size_t i = 0; i < m_vpSounds.size(); ++i)
1028 {
1029 if(m_vpSounds[i] == pSound)
1030 {
1031 m_SelectedSound = i;
1032 break;
1033 }
1034 }
1035}
1036
1037void CEditorMap::SelectNextSound()
1038{
1039 m_SelectedSound = (m_SelectedSound + 1) % m_vpSounds.size();
1040}
1041
1042void CEditorMap::SelectPreviousSound()
1043{
1044 m_SelectedSound = (m_SelectedSound + m_vpSounds.size() - 1) % m_vpSounds.size();
1045}
1046
1047bool CEditorMap::IsSoundUsed(int SoundIndex) const
1048{
1049 for(const auto &pGroup : m_vpGroups)
1050 {
1051 for(const auto &pLayer : pGroup->m_vpLayers)
1052 {
1053 if(pLayer->m_Type == LAYERTYPE_SOUNDS)
1054 {
1055 std::shared_ptr<CLayerSounds> pSounds = std::static_pointer_cast<CLayerSounds>(r: pLayer);
1056 if(pSounds->m_Sound == SoundIndex)
1057 {
1058 return true;
1059 }
1060 }
1061 }
1062 }
1063 return false;
1064}
1065
1066CSoundSource *CEditorMap::SelectedSoundSource() const
1067{
1068 std::shared_ptr<CLayerSounds> pSounds = std::static_pointer_cast<CLayerSounds>(r: SelectedLayerType(Index: 0, Type: LAYERTYPE_SOUNDS));
1069 if(!pSounds)
1070 return nullptr;
1071 if(m_SelectedSoundSource >= 0 && m_SelectedSoundSource < (int)pSounds->m_vSources.size())
1072 return &pSounds->m_vSources[m_SelectedSoundSource];
1073 return nullptr;
1074}
1075