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