1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* Dynamic object list */
18
19#include <C4Include.h>
20#include <C4ObjectList.h>
21
22#include <C4Object.h>
23#include <C4Wrappers.h>
24#include <C4Application.h>
25
26#include <format>
27
28C4ObjectList::C4ObjectList() : FirstIter(nullptr)
29{
30 Default();
31}
32
33C4ObjectList::C4ObjectList(const C4ObjectList &List) : FirstIter(nullptr)
34{
35 Default();
36 Copy(rList: List);
37}
38
39C4ObjectList::~C4ObjectList()
40{
41 Clear();
42}
43
44void C4ObjectList::Clear()
45{
46 C4ObjectLink *cLnk, *nextLnk;
47 for (cLnk = First; cLnk; cLnk = nextLnk)
48 {
49 nextLnk = cLnk->Next; delete cLnk;
50 }
51 First = Last = nullptr;
52 pEnumerated.reset();
53}
54
55const int MaxTempListID = 500;
56C4ID TempListID[MaxTempListID];
57
58C4ID C4ObjectList::GetListID(int32_t dwCategory, int Index)
59{
60 int clid;
61 C4ObjectLink *clnk;
62 C4Def *cdef;
63
64 // Create a temporary list of all id's and counts
65 for (clid = 0; clid < MaxTempListID; clid++) TempListID[clid] = C4ID_None;
66 for (clnk = First; clnk && clnk->Obj; clnk = clnk->Next)
67 if (clnk->Obj->Status)
68 if ((dwCategory == C4D_All) || ((cdef = C4Id2Def(id: clnk->Obj->Def->id)) && (cdef->Category & dwCategory)))
69 for (clid = 0; clid < MaxTempListID; clid++)
70 {
71 // Already there
72 if (TempListID[clid] == clnk->Obj->Def->id) break;
73 // End of list, add id
74 if (TempListID[clid] == C4ID_None) { TempListID[clid] = clnk->Obj->Def->id; break; }
75 }
76
77 // Returns indexed id
78 if (Inside(ival: Index, lbound: 0, rbound: MaxTempListID - 1)) return TempListID[Index];
79
80 return C4ID_None;
81}
82
83int C4ObjectList::ListIDCount(int32_t dwCategory)
84{
85 int clid;
86 C4ObjectLink *clnk;
87 C4Def *cdef;
88
89 // Create a temporary list of all id's and counts
90 for (clid = 0; clid < MaxTempListID; clid++) TempListID[clid] = C4ID_None;
91 for (clnk = First; clnk && clnk->Obj; clnk = clnk->Next)
92 if (clnk->Obj->Status)
93 if ((dwCategory == C4D_All) || ((cdef = C4Id2Def(id: clnk->Obj->Def->id)) && (cdef->Category & dwCategory)))
94 for (clid = 0; clid < MaxTempListID; clid++)
95 {
96 // Already there
97 if (TempListID[clid] == clnk->Obj->Def->id) break;
98 // End of list, add id
99 if (TempListID[clid] == C4ID_None) { TempListID[clid] = clnk->Obj->Def->id; break; }
100 }
101
102 // Count different id's
103 for (clid = 0; clid < MaxTempListID; clid++)
104 if (TempListID[clid] == C4ID_None)
105 return clid;
106
107 return MaxTempListID;
108}
109
110bool C4ObjectList::Add(C4Object *nObj, SortType eSort, C4ObjectList *pLstSorted)
111{
112 if (!nObj || !nObj->Def || !nObj->Status) return false;
113
114#ifndef NDEBUG
115 if (eSort == stMain)
116 {
117 CheckCategorySort();
118 if (pLstSorted)
119 assert(CheckSort(pLstSorted));
120 }
121#endif
122
123 // dbg: don't do double links
124 assert(!GetLink(nObj));
125
126 // no self-sort
127 assert(pLstSorted != this);
128
129 // Allocate new link
130 auto newLink = std::make_unique<C4ObjectLink>();
131 // Set link
132 newLink->Obj = nObj;
133
134 // Search insert position (default: end of list)
135 C4ObjectLink *cLnk = nullptr, *cPrev = Last;
136
137 // Should sort?
138 if (eSort == stReverse)
139 {
140 // reverse sort: Add to beginning of list
141 cLnk = First; cPrev = nullptr;
142 }
143 else if (eSort)
144 {
145 cLnk = nullptr; cPrev = Last;
146
147 // Sort override or line? Leave default as is.
148 bool fUnsorted = nObj->Unsorted || nObj->Def->Line;
149 if (!fUnsorted)
150 {
151 // Find successor by matching category / id
152 // Sort by matching category/id is necessary for inventory shifting.
153 // It is not done for static back to allow multiobject outside structure.
154 // Unsorted objects are ignored in comparison.
155 if (!(nObj->Category & C4D_StaticBack))
156 for (cPrev = nullptr, cLnk = First; cLnk; cLnk = cLnk->Next)
157 if (cLnk->Obj->Status && !cLnk->Obj->Unsorted)
158 {
159 if ((cLnk->Obj->Category & C4D_SortLimit) == (nObj->Category & C4D_SortLimit))
160 if (cLnk->Obj->id == nObj->id)
161 break;
162 cPrev = cLnk;
163 }
164
165 // Find successor by relative category
166 if (!cLnk)
167 for (cPrev = nullptr, cLnk = First; cLnk; cLnk = cLnk->Next)
168 if (cLnk->Obj->Status && !cLnk->Obj->Unsorted)
169 {
170 if ((cLnk->Obj->Category & C4D_SortLimit) <= (nObj->Category & C4D_SortLimit))
171 break;
172 cPrev = cLnk;
173 }
174
175 cLnk = cPrev ? cPrev->Next : First;
176 }
177
178 // Sort by master list?
179 if (pLstSorted)
180 {
181 assert(CheckSort(pLstSorted));
182
183 // Unsorted: Always search full list (start with first object in list)
184 if (fUnsorted) { cLnk = First; cPrev = nullptr; }
185
186 // As cPrev is the last link in front of the first position where the object could be inserted,
187 // the object should be after this point in the master list (given it's consistent).
188 // If we're about to insert the object at the end of the list, there is obviously nothing to do.
189#ifdef NDEBUG
190 if (cLnk)
191 {
192#endif
193 C4ObjectLink *cLnk2 = cPrev ? pLstSorted->GetLink(pObj: cPrev->Obj)->Next : pLstSorted->First;
194 for (; cLnk2; cLnk2 = cLnk2->Next)
195 if (cLnk2->Obj == nObj)
196 // Position found!
197 break;
198 else if (cLnk && cLnk2->Obj == cLnk->Obj)
199 {
200 // So cLnk->Obj is actually in front of nObj. Update insert position
201 cPrev = cLnk;
202 cLnk = cLnk->Next;
203#ifdef NDEBUG
204 // At end of list?
205 if (!cLnk) break;
206#endif
207 }
208
209 // No position found? This shouldn't happen with a consistent main list.
210 assert(cLnk2);
211#ifdef NDEBUG
212 }
213#endif
214 }
215 }
216
217 assert(!cPrev || cPrev->Next == cLnk);
218 assert(!cLnk || cLnk->Prev == cPrev);
219
220 // Insert new link after predecessor
221 InsertLink(pLink: newLink.get(), pAfter: cPrev);
222 newLink.release();
223
224#ifndef NDEBUG
225 // Debug: Check sort
226 if (eSort == stMain)
227 {
228 CheckCategorySort();
229 if (pLstSorted)
230 assert(CheckSort(pLstSorted));
231 }
232#endif
233
234 // Add mass
235 Mass += nObj->Mass;
236
237 return true;
238}
239
240bool C4ObjectList::Remove(C4Object *pObj)
241{
242 C4ObjectLink *cLnk;
243
244 // Find link
245 for (cLnk = First; cLnk; cLnk = cLnk->Next)
246 if (cLnk->Obj == pObj) break;
247 if (!cLnk) return false;
248
249 // Fix iterators
250 for (iterator *i = FirstIter; i; i = i->Next)
251 {
252 if (i->pLink == cLnk) i->pLink = cLnk->*(i->direction);
253 }
254
255 // Remove link from list
256 RemoveLink(pLnk: cLnk);
257
258 // Deallocate link
259 delete cLnk;
260
261 // Remove mass
262 Mass -= pObj->Mass; if (Mass < 0) Mass = 0;
263
264#ifndef NDEBUG
265 if (GetLink(pObj)) BREAKPOINT_HERE;
266#endif
267
268 return true;
269}
270
271C4Object *C4ObjectList::Find(C4ID id, int owner, uint32_t dwOCF)
272{
273 C4ObjectLink *cLnk;
274 // Find link and object
275 for (cLnk = First; cLnk; cLnk = cLnk->Next)
276 if (cLnk->Obj->Status)
277 if (cLnk->Obj->Def->id == id)
278 if ((owner == ANY_OWNER) || (cLnk->Obj->Owner == owner))
279 if (dwOCF & cLnk->Obj->OCF)
280 return cLnk->Obj;
281 return nullptr;
282}
283
284C4Object *C4ObjectList::FindOther(C4ID id, int owner)
285{
286 C4ObjectLink *cLnk;
287 // Find link and object
288 for (cLnk = First; cLnk; cLnk = cLnk->Next)
289 if (cLnk->Obj->Status)
290 if (cLnk->Obj->Def->id != id)
291 if ((owner == ANY_OWNER) || (cLnk->Obj->Owner == owner))
292 return cLnk->Obj;
293 return nullptr;
294}
295
296C4Object *C4ObjectList::GetObject(int Index)
297{
298 int cIdx;
299 C4ObjectLink *cLnk;
300 // Find link and object
301 for (cLnk = First, cIdx = 0; cLnk; cLnk = cLnk->Next)
302 if (cLnk->Obj->Status)
303 {
304 if (cIdx == Index) return cLnk->Obj;
305 cIdx++;
306 }
307 return nullptr;
308}
309
310C4ObjectLink *C4ObjectList::GetLink(C4Object *pObj)
311{
312 if (!pObj) return nullptr;
313 C4ObjectLink *cLnk;
314 for (cLnk = First; cLnk; cLnk = cLnk->Next)
315 if (cLnk->Obj == pObj)
316 return cLnk;
317 return nullptr;
318}
319
320int C4ObjectList::ObjectCount(C4ID id, int32_t dwCategory) const
321{
322 C4ObjectLink *cLnk;
323 int iCount = 0;
324 for (cLnk = First; cLnk; cLnk = cLnk->Next)
325 if (cLnk->Obj->Status)
326 if ((id == C4ID_None) || (cLnk->Obj->Def->id == id))
327 if ((dwCategory == C4D_All) || (cLnk->Obj->Category & dwCategory))
328 iCount++;
329 return iCount;
330}
331
332int C4ObjectList::MassCount()
333{
334 C4ObjectLink *cLnk;
335 int iMass = 0;
336 for (cLnk = First; cLnk; cLnk = cLnk->Next)
337 if (cLnk->Obj->Status)
338 iMass += cLnk->Obj->Mass;
339 Mass = iMass;
340 return iMass;
341}
342
343void C4ObjectList::DrawIDList(C4Facet &cgo, int iSelection,
344 C4DefList &rDefs, int32_t dwCategory,
345 C4RegionList *pRegions, int iRegionCom,
346 bool fDrawOneCounts)
347{
348 // Variables
349 int32_t cSec = 0;
350 int32_t iCount;
351 C4Facet cgo2;
352 C4Object *pFirstObj;
353 std::array<char, C4Strings::NumberOfCharactersForDigits<std::int32_t> + 1 + 1> buf;
354 // objects are sorted in the list already, so just draw them!
355 C4ObjectListIterator iter(*this);
356 while (pFirstObj = iter.GetNext(piCount: &iCount))
357 {
358 // Section
359 cgo2 = cgo.GetSection(iSection: cSec);
360 // draw picture
361 pFirstObj->DrawPicture(cgo&: cgo2, fSelected: cSec == iSelection);
362 // Draw count
363 char *const ptr{std::to_chars(first: buf.data(), last: buf.data() + buf.size() - 2, value: iCount).ptr};
364 ptr[0] = 'x';
365 ptr[1] = '\0';
366 if ((iCount != 1) || fDrawOneCounts)
367 Application.DDraw->TextOut(szText: buf.data(), rFont&: Game.GraphicsResource.FontRegular, fZoom: 1.0, sfcDest: cgo2.Surface, iTx: cgo2.X + cgo2.Wdt - 1, iTy: cgo2.Y + cgo2.Hgt - 1 - Game.GraphicsResource.FontRegular.GetLineHeight(), dwFCol: CStdDDraw::DEFAULT_MESSAGE_COLOR, byForm: ARight);
368 // Region
369 if (pRegions) pRegions->Add(iX: cgo2.X, iY: cgo2.Y, iWdt: cgo2.Wdt, iHgt: cgo2.Hgt, szCaption: pFirstObj->GetName(), iCom: iRegionCom, pTarget: pFirstObj, iMoveOverCom: COM_None, iHoldCom: COM_None, iData: pFirstObj->Number);
370 // Next section
371 cSec++;
372 }
373}
374
375int C4ObjectList::ClearPointers(C4Object *pObj)
376{
377 int rval = 0;
378 // Clear all primary list pointers
379 while (Remove(pObj)) rval++;
380 // Clear all sub pointers
381 C4Object *cobj; C4ObjectLink *clnk;
382 for (clnk = First; clnk && (cobj = clnk->Obj); clnk = clnk->Next)
383 cobj->ClearPointers(ptr: pObj);
384 return rval;
385}
386
387void C4ObjectList::DrawAll(C4FacetEx &cgo, int iPlayer)
388{
389 C4ObjectLink *clnk;
390 // Draw objects (base)
391 for (clnk = Last; clnk; clnk = clnk->Prev)
392 clnk->Obj->Draw(cgo, iByPlayer: iPlayer);
393 // Draw objects (top face)
394 for (clnk = Last; clnk; clnk = clnk->Prev)
395 clnk->Obj->DrawTopFace(cgo, iByPlayer: iPlayer);
396}
397
398void C4ObjectList::DrawIfCategory(C4FacetEx &cgo, int iPlayer, uint32_t dwCat, bool fInvert)
399{
400 C4ObjectLink *clnk;
401 // Draw objects (base)
402 for (clnk = Last; clnk; clnk = clnk->Prev)
403 if (!(clnk->Obj->Category & dwCat) == fInvert)
404 clnk->Obj->Draw(cgo, iByPlayer: iPlayer);
405 // Draw objects (top face)
406 for (clnk = Last; clnk; clnk = clnk->Prev)
407 if (!(clnk->Obj->Category & dwCat) == fInvert)
408 clnk->Obj->DrawTopFace(cgo, iByPlayer: iPlayer);
409}
410
411void C4ObjectList::Draw(C4FacetEx &cgo, int iPlayer)
412{
413 C4ObjectLink *clnk;
414 // Draw objects (base)
415 for (clnk = Last; clnk; clnk = clnk->Prev)
416 if (!(clnk->Obj->Category & C4D_BackgroundOrForeground))
417 clnk->Obj->Draw(cgo, iByPlayer: iPlayer);
418 // Draw objects (top face)
419 for (clnk = Last; clnk; clnk = clnk->Prev)
420 if (!(clnk->Obj->Category & C4D_BackgroundOrForeground))
421 clnk->Obj->DrawTopFace(cgo, iByPlayer: iPlayer);
422}
423
424void C4ObjectList::Enumerate()
425{
426 C4ObjectLink *cLnk;
427 // Enumerate object pointers
428 for (cLnk = First; cLnk; cLnk = cLnk->Next)
429 if (cLnk->Obj->Status)
430 cLnk->Obj->EnumeratePointers();
431}
432
433int32_t C4ObjectList::ObjectNumber(C4Object *pObj)
434{
435 C4ObjectLink *cLnk;
436 if (!pObj) return 0;
437 for (cLnk = First; cLnk; cLnk = cLnk->Next)
438 if (cLnk->Obj == pObj)
439 return cLnk->Obj->Number;
440 return 0;
441}
442
443bool C4ObjectList::IsContained(C4Object *pObj)
444{
445 C4ObjectLink *cLnk;
446 for (cLnk = First; cLnk; cLnk = cLnk->Next)
447 if (cLnk->Obj == pObj)
448 return true;
449 return false;
450}
451
452bool C4ObjectList::IsClear() const
453{
454 return (ObjectCount() == 0);
455}
456
457bool C4ObjectList::DenumerateRead()
458{
459 if (!pEnumerated) return false;
460 // Denumerate all object pointers
461 for (const auto num : *pEnumerated)
462 Add(nObj: Game.Objects.ObjectPointer(iNumber: num), eSort: stNone); // Add to tail, unsorted
463 // Delete old list
464 pEnumerated.reset();
465 return true;
466}
467
468void C4ObjectList::Denumerate()
469{
470 C4ObjectLink *cLnk;
471 for (cLnk = First; cLnk; cLnk = cLnk->Next)
472 if (cLnk->Obj->Status)
473 cLnk->Obj->DenumeratePointers();
474}
475
476void C4ObjectList::CompileFunc(StdCompiler *pComp, bool fSaveRefs, bool fSkipPlayerObjects)
477{
478 if (fSaveRefs)
479 {
480 // this mode not supported
481 assert(!fSkipPlayerObjects);
482 // (Re)create list
483 pEnumerated.reset(p: new decltype(pEnumerated)::element_type);
484 // Decompiling: Build list
485 if (!pComp->isCompiler())
486 for (C4ObjectLink *pPos = First; pPos; pPos = pPos->Next)
487 if (pPos->Obj->Status)
488 pEnumerated->push_back(x: pPos->Obj->Number);
489 // Compile list
490 pComp->Value(rStruct: mkSTLContainerAdapt(rTarget&: *pEnumerated, eSep: StdCompiler::SEP_SEP2));
491 // Decompiling: Delete list
492 if (!pComp->isCompiler())
493 {
494 pEnumerated.reset();
495 }
496 // Compiling: Nothing to do - list will e denumerated later
497 }
498 else
499 {
500 if (pComp->isDecompiler())
501 {
502 // skipping player objects would screw object counting in non-naming compilers
503 assert(!fSkipPlayerObjects || pComp->hasNaming());
504 // Put object count
505 int32_t iObjCnt = ObjectCount();
506 pComp->Value(rStruct: mkNamingCountAdapt(iCount&: iObjCnt, szName: "Object"));
507 // Decompile all objects in reverse order
508 for (C4ObjectLink *pPos = Last; pPos; pPos = pPos->Prev)
509 if (pPos->Obj->Status)
510 if (!fSkipPlayerObjects || !pPos->Obj->IsUserPlayerObject())
511 pComp->Value(rStruct: mkNamingAdapt(rValue&: *pPos->Obj, szName: "Object"));
512 }
513 else
514 {
515 // this mode not supported
516 assert(!fSkipPlayerObjects);
517 // Remove previous data
518 Clear();
519 // Get "Object" section count
520 int32_t iObjCnt;
521 pComp->Value(rStruct: mkNamingCountAdapt(iCount&: iObjCnt, szName: "Object"));
522 // Load objects, add them to the list.
523 for (int i = 0; i < iObjCnt; i++)
524 {
525 C4Object *pObj = nullptr;
526 try
527 {
528 pComp->Value(rStruct: mkNamingAdapt(rValue: mkPtrAdaptNoNull(rpObj&: pObj), szName: "Object"));
529 Add(nObj: pObj, eSort: stReverse);
530 }
531 catch (const StdCompiler::Exception &e)
532 {
533 // Failsafe object loading: If an error occurs during object loading, just skip that object and load the next one
534 if (e.Pos.empty())
535 LogNTr(level: spdlog::level::err, fmt: "Object loading: {}", args: e.what());
536 else
537 LogNTr(level: spdlog::level::err, fmt: "Object loading({}): {}", args: e.Pos, args: e.what());
538 }
539 }
540 }
541 }
542}
543
544C4Object *C4ObjectList::ObjectPointer(int32_t iNumber)
545{
546 C4ObjectLink *cLnk;
547 for (cLnk = First; cLnk; cLnk = cLnk->Next)
548 if (cLnk->Obj->Number == iNumber)
549 return cLnk->Obj;
550 return nullptr;
551}
552
553C4Object *C4ObjectList::SafeObjectPointer(int32_t iNumber)
554{
555 C4Object *pObj = ObjectPointer(iNumber);
556 if (pObj) if (!pObj->Status) return nullptr;
557 return pObj;
558}
559
560std::string C4ObjectList::GetNameList(C4DefList &rDefs, uint32_t dwCategory)
561{
562 int cpos, idcount;
563 C4ID c_id;
564 C4Def *cdef;
565 std::string result;
566 for (cpos = 0; c_id = GetListID(dwCategory, Index: cpos); cpos++)
567 if (cdef = rDefs.ID2Def(id: c_id))
568 {
569 idcount = ObjectCount(id: c_id);
570 if (cpos > 0) result += ", ";
571 result += std::format(fmt: "{}x {}", args&: idcount, args: cdef->GetName());
572 }
573 return result;
574}
575
576bool C4ObjectList::ValidateOwners()
577{
578 C4ObjectLink *cLnk;
579 for (cLnk = First; cLnk; cLnk = cLnk->Next)
580 if (cLnk->Obj->Status)
581 cLnk->Obj->ValidateOwner();
582 return true;
583}
584
585bool C4ObjectList::AssignInfo()
586{
587 // the list seems to be traced backwards here, to ensure crew objects are added in correct order
588 // (or semi-correct, because this will work only if the crew order matches the main object list order)
589 // this is obsolete now, because the crew list is stored in the savegame
590 C4ObjectLink *cLnk;
591 for (cLnk = Last; cLnk; cLnk = cLnk->Prev)
592 if (cLnk->Obj->Status)
593 cLnk->Obj->AssignInfo();
594 return true;
595}
596
597bool C4ObjectList::AssignPlrViewRange()
598{
599 C4ObjectLink *cLnk;
600 for (cLnk = Last; cLnk; cLnk = cLnk->Prev)
601 if (cLnk->Obj->Status)
602 cLnk->Obj->AssignPlrViewRange();
603 return true;
604}
605
606void C4ObjectList::ClearInfo(C4ObjectInfo *pInfo)
607{
608 C4ObjectLink *cLnk;
609 for (cLnk = First; cLnk; cLnk = cLnk->Next)
610 if (cLnk->Obj->Status)
611 cLnk->Obj->ClearInfo(pInfo);
612}
613
614void C4ObjectList::RemoveLink(C4ObjectLink *pLnk)
615{
616 if (pLnk->Prev) pLnk->Prev->Next = pLnk->Next; else First = pLnk->Next;
617 if (pLnk->Next) pLnk->Next->Prev = pLnk->Prev; else Last = pLnk->Prev;
618}
619
620void C4ObjectList::InsertLink(C4ObjectLink *pLnk, C4ObjectLink *pAfter)
621{
622 // Insert after
623 if (pAfter)
624 {
625 pLnk->Prev = pAfter; pLnk->Next = pAfter->Next;
626 if (pAfter->Next) pAfter->Next->Prev = pLnk; else Last = pLnk;
627 pAfter->Next = pLnk;
628 }
629 // Insert at head
630 else
631 {
632 pLnk->Prev = nullptr; pLnk->Next = First;
633 if (First) First->Prev = pLnk; else Last = pLnk;
634 First = pLnk;
635 }
636}
637
638void C4ObjectList::InsertLinkBefore(C4ObjectLink *pLnk, C4ObjectLink *pBefore)
639{
640 // Insert before
641 if (pBefore)
642 {
643 pLnk->Prev = pBefore->Prev;
644 if (pBefore->Prev) pBefore->Prev->Next = pLnk; else First = pLnk;
645 pLnk->Next = pBefore; pBefore->Prev = pLnk;
646 }
647 // Insert at end
648 else
649 {
650 pLnk->Next = nullptr; pLnk->Prev = Last;
651 if (Last) Last->Next = pLnk; else First = pLnk;
652 Last = pLnk;
653 }
654}
655
656void C4NotifyingObjectList::InsertLinkBefore(C4ObjectLink *pLink, C4ObjectLink *pBefore)
657{
658 C4ObjectList::InsertLinkBefore(pLnk: pLink, pBefore);
659 ObjectListChangeListener.OnObjectAdded(pList: this, pLnk: pLink);
660}
661
662void C4NotifyingObjectList::InsertLink(C4ObjectLink *pLink, C4ObjectLink *pAfter)
663{
664 C4ObjectList::InsertLink(pLnk: pLink, pAfter);
665 ObjectListChangeListener.OnObjectAdded(pList: this, pLnk: pLink);
666}
667
668void C4NotifyingObjectList::RemoveLink(C4ObjectLink *pLnk)
669{
670 C4ObjectList::RemoveLink(pLnk);
671 ObjectListChangeListener.OnObjectRemove(pList: this, pLnk);
672}
673
674void C4ObjectList::SyncClearance()
675{
676 C4ObjectLink *cLnk;
677 for (cLnk = First; cLnk; cLnk = cLnk->Next)
678 if (cLnk->Obj)
679 cLnk->Obj->SyncClearance();
680}
681
682void C4ObjectList::UpdateGraphics(bool fGraphicsChanged)
683{
684 C4ObjectLink *cLnk;
685 for (cLnk = First; cLnk; cLnk = cLnk->Next)
686 if (cLnk->Obj->Status)
687 cLnk->Obj->UpdateGraphics(fGraphicsChanged);
688}
689
690void C4ObjectList::UpdateFaces(bool bUpdateShapes)
691{
692 C4ObjectLink *cLnk;
693 for (cLnk = First; cLnk; cLnk = cLnk->Next)
694 if (cLnk->Obj->Status)
695 cLnk->Obj->UpdateFace(bUpdateShape: bUpdateShapes);
696}
697
698void C4ObjectList::DrawSelectMark(C4FacetEx &cgo)
699{
700 C4ObjectLink *cLnk;
701 for (cLnk = Last; cLnk; cLnk = cLnk->Prev)
702 cLnk->Obj->DrawSelectMark(cgo);
703}
704
705void C4ObjectList::CloseMenus()
706{
707 C4Object *cobj; C4ObjectLink *clnk;
708 for (clnk = First; clnk && (cobj = clnk->Obj); clnk = clnk->Next)
709 cobj->CloseMenu(fForce: true);
710}
711
712void C4ObjectList::SetOCF()
713{
714 C4ObjectLink *cLnk;
715 for (cLnk = First; cLnk; cLnk = cLnk->Next)
716 if (cLnk->Obj->Status)
717 cLnk->Obj->SetOCF();
718}
719
720void C4ObjectList::Copy(const C4ObjectList &rList)
721{
722 Clear(); Default();
723 C4ObjectLink *cLnk;
724 for (cLnk = rList.First; cLnk; cLnk = cLnk->Next) Add(nObj: cLnk->Obj, eSort: C4ObjectList::stNone);
725}
726
727void C4ObjectList::Default()
728{
729 First = Last = nullptr;
730 Mass = 0;
731 pEnumerated.reset();
732}
733
734void C4ObjectList::UpdateTransferZones()
735{
736 C4Object *cobj; C4ObjectLink *clnk;
737 for (clnk = First; clnk && (cobj = clnk->Obj); clnk = clnk->Next)
738 cobj->Call(PSF_UpdateTransferZone);
739}
740
741void C4ObjectList::ResetAudibility()
742{
743 C4Object *cobj; C4ObjectLink *clnk;
744 for (clnk = First; clnk && (cobj = clnk->Obj); clnk = clnk->Next)
745 {
746 cobj->ResetAudibility();
747 }
748}
749
750void C4ObjectList::SortByCategory()
751{
752 C4ObjectLink *cLnk;
753 bool fSorted;
754 // Sort by category
755 do
756 {
757 fSorted = true;
758 for (cLnk = First; cLnk && cLnk->Next; cLnk = cLnk->Next)
759 if ((cLnk->Obj->Category & C4D_SortLimit) < (cLnk->Next->Obj->Category & C4D_SortLimit))
760 {
761 RemoveLink(pLnk: cLnk);
762 InsertLink(pLnk: cLnk, pAfter: cLnk->Next);
763 fSorted = false;
764 break;
765 }
766 } while (!fSorted);
767}
768
769bool C4ObjectList::OrderObjectBefore(C4Object *pObj1, C4Object *pObj2)
770{
771 // safety
772 if (pObj1->Status != C4OS_NORMAL || pObj2->Status != C4OS_NORMAL) return false;
773 // get links (and check whether the objects are part of this list!)
774 C4ObjectLink *pLnk1 = GetLink(pObj: pObj1); if (!pLnk1) return false;
775 C4ObjectLink *pLnk2 = GetLink(pObj: pObj2); if (!pLnk2) return false;
776 // check if requirements are already fulfilled
777 C4ObjectLink *pLnk = pLnk1;
778 while (pLnk = pLnk->Next) if (pLnk == pLnk2) break;
779 if (pLnk) return true;
780 // if not, reorder pLnk1 directly before pLnk2
781 // unlink from current position
782 // no need to check pLnk1->Prev here, because pLnk1 cannot be first in the list
783 // (at least pLnk2 must lie before it!)
784 if (pLnk1->Prev->Next = pLnk1->Next) pLnk1->Next->Prev = pLnk1->Prev; else Last = pLnk1->Prev;
785 // relink into new one
786 if (pLnk1->Prev = pLnk2->Prev) pLnk2->Prev->Next = pLnk1; else First = pLnk1;
787 pLnk1->Next = pLnk2; pLnk2->Prev = pLnk1;
788 // done, success
789 return true;
790}
791
792bool C4ObjectList::OrderObjectAfter(C4Object *pObj1, C4Object *pObj2)
793{
794 // safety
795 if (pObj1->Status != C4OS_NORMAL || pObj2->Status != C4OS_NORMAL) return false;
796 // get links (and check whether the objects are part of this list!)
797 C4ObjectLink *pLnk1 = GetLink(pObj: pObj1); if (!pLnk1) return false;
798 C4ObjectLink *pLnk2 = GetLink(pObj: pObj2); if (!pLnk2) return false;
799 // check if requirements are already fulfilled
800 C4ObjectLink *pLnk = pLnk1;
801 while (pLnk = pLnk->Prev) if (pLnk == pLnk2) break;
802 if (pLnk) return true;
803 // if not, reorder pLnk1 directly after pLnk2
804 // unlink from current position
805 // no need to check pLnk1->Next here, because pLnk1 cannot be last in the list
806 // (at least pLnk2 must lie after it!)
807 if (pLnk1->Next->Prev = pLnk1->Prev) pLnk1->Prev->Next = pLnk1->Next; else First = pLnk1->Next;
808 // relink into new one
809 if (pLnk1->Next = pLnk2->Next) pLnk2->Next->Prev = pLnk1; else Last = pLnk1;
810 pLnk1->Prev = pLnk2; pLnk2->Next = pLnk1;
811 // done, success
812 return true;
813}
814
815bool C4ObjectList::ShiftContents(C4Object *pNewFirst)
816{
817 // get link of new first (this ensures list is not empty)
818 C4ObjectLink *pNewFirstLnk = GetLink(pObj: pNewFirst);
819 if (!pNewFirstLnk) return false;
820 // already at front?
821 if (pNewFirstLnk == First) return true;
822 // sort it there:
823 // 1. Make cyclic list
824 Last->Next = First; First->Prev = Last;
825 // 2. Re-set first and last
826 First = pNewFirstLnk;
827 Last = pNewFirstLnk->Prev;
828 // 3. Uncycle list
829 First->Prev = Last->Next = nullptr;
830 // done, success
831 return true;
832}
833
834void C4ObjectList::DeleteObjects()
835{
836 // delete links and objects
837 while (First)
838 {
839 C4Object *pObj = First->Obj;
840 Remove(pObj);
841 delete pObj;
842 }
843 // reset mass
844 Mass = 0;
845}
846
847// C4ObjectListIterator
848
849C4Object *C4ObjectListIterator::GetNext(int32_t *piCount, uint32_t dwCategory)
850{
851 // end reached?
852 if (pCurrID == rList.end()) return nullptr;
853 // not yet started?
854 if (pCurr == rList.end())
855 // then start at first ID list head
856 pCurr = pCurrID;
857 else
858 // next item
859 if (++pCurr == rList.end()) return nullptr;
860 // skip mismatched category
861 if (dwCategory)
862 while (!((*pCurr)->Category & dwCategory))
863 if (++pCurr == rList.end()) return nullptr;
864 // next ID section reached?
865 if ((*pCurr)->id != (*pCurrID)->id)
866 pCurrID = pCurr;
867 else
868 {
869 // otherwise, it must be checked, whether this is a duplicate item already iterated
870 // if so, advance the list
871 for (C4ObjectList::iterator pCheck = pCurrID; pCheck != pCurr; ++pCheck)
872 if (!dwCategory || ((*pCheck)->Category & dwCategory))
873 if ((*pCheck)->CanConcatPictureWith(pOtherObject: *pCurr))
874 {
875 // next object of matching category
876 if (++pCurr == rList.end()) return nullptr;
877 if (dwCategory)
878 while (!((*pCurr)->Category & dwCategory))
879 if (++pCurr == rList.end()) return nullptr;
880 // next ID chunk reached?
881 if ((*pCurr)->id != (*pCurrID)->id)
882 {
883 // then break here
884 pCurrID = pCurr;
885 break;
886 }
887 // restart check for next object
888 pCheck = pCurrID;
889 }
890 }
891 if (piCount)
892 {
893 // default count
894 *piCount = 1;
895 // add additional objects of same ID to the count
896 C4ObjectList::iterator pCheck(pCurr);
897 for (++pCheck; pCheck != rList.end() && (*pCheck)->id == (*pCurr)->id; ++pCheck)
898 if (!dwCategory || ((*pCheck)->Category & dwCategory))
899 if ((*pCheck)->CanConcatPictureWith(pOtherObject: *pCurr))
900 ++*piCount;
901 }
902 // return found object
903 return *pCurr;
904}
905
906void C4ObjectList::UpdateScriptPointers()
907{
908 for (C4ObjectLink *cLnk = First; cLnk; cLnk = cLnk->Next)
909 cLnk->Obj->UpdateScriptPointers();
910}
911
912struct C4ObjectListDumpHelper
913{
914 C4ObjectList *pLst;
915
916 void CompileFunc(StdCompiler *pComp) { pComp->Value(rStruct: mkNamingAdapt(rValue&: *pLst, szName: "Objects")); }
917
918 C4ObjectListDumpHelper(C4ObjectList *pLst) : pLst(pLst) {}
919};
920
921bool C4ObjectList::CheckSort(C4ObjectList *pList)
922{
923 C4ObjectLink *cLnk = First, *cLnk2 = pList->First;
924 while (cLnk && cLnk->Obj->Unsorted) cLnk = cLnk->Next;
925 while (cLnk)
926 if (!cLnk2)
927 {
928 LogNTr(level: spdlog::level::err, message: "CheckSort failure");
929 spdlog::error(msg: DecompileToBuf<StdCompilerINIWrite>(SrcStruct: mkNamingAdapt(rValue: C4ObjectListDumpHelper(this), szName: "SectorList")));
930 spdlog::error(msg: DecompileToBuf<StdCompilerINIWrite>(SrcStruct: mkNamingAdapt(rValue: C4ObjectListDumpHelper(pList), szName: "MainList")));
931 return false;
932 }
933 else
934 {
935 if (cLnk->Obj == cLnk2->Obj)
936 {
937 cLnk = cLnk->Next;
938 while (cLnk && cLnk->Obj->Unsorted) cLnk = cLnk->Next;
939 }
940 cLnk2 = cLnk2->Next;
941 }
942 return true;
943}
944
945void C4ObjectList::CheckCategorySort()
946{
947 // debug: Check whether object list is sorted correctly
948 C4ObjectLink *cLnk, *cPrev = nullptr;
949 for (cLnk = First; cLnk && cLnk->Next; cLnk = cLnk->Next)
950 if (!cLnk->Obj->Unsorted && cLnk->Obj->Status)
951 {
952 if (cPrev) assert((cPrev->Obj->Category & C4D_SortLimit) >= (cLnk->Obj->Category & C4D_SortLimit));
953 cPrev = cLnk;
954 }
955}
956
957C4ObjectList::iterator::iterator(C4ObjectList &List, C4ObjectLink *C4ObjectLink::*const direction) :
958 List(List), pLink(direction == &C4ObjectLink::Next ? List.First : List.Last), direction{direction}
959{
960 Next = List.AddIter(iter: this);
961}
962
963C4ObjectList::iterator::iterator(C4ObjectList &List, C4ObjectLink *pLink, C4ObjectLink *C4ObjectLink::*const direction) :
964 List(List), pLink(pLink), direction{direction}
965{
966 Next = List.AddIter(iter: this);
967}
968
969C4ObjectList::iterator::iterator(const C4ObjectList::iterator &iter) :
970 List(iter.List), pLink(iter.pLink), Next(), direction{iter.direction}
971{
972 Next = List.AddIter(iter: this);
973}
974
975C4ObjectList::iterator::~iterator()
976{
977 List.RemoveIter(iter: this);
978}
979
980C4ObjectList::iterator &C4ObjectList::iterator::operator++()
981{
982 pLink = pLink ? pLink->*direction : pLink;
983 return *this;
984}
985
986C4Object *C4ObjectList::iterator::operator*()
987{
988 return pLink ? pLink->Obj : nullptr;
989}
990
991bool C4ObjectList::iterator::operator==(const iterator &iter) const
992{
993 return &iter.List == &List && iter.pLink == pLink;
994}
995
996bool C4ObjectList::iterator::operator==(std::default_sentinel_t) const noexcept
997{
998 return pLink == nullptr;
999}
1000
1001C4ObjectList::iterator &C4ObjectList::iterator::operator=(const iterator &iter)
1002{
1003 // Can only assign iterators into the same list
1004 assert(&iter.List == &List);
1005
1006 pLink = iter.pLink;
1007 return *this;
1008}
1009
1010C4ObjectList::iterator C4ObjectList::begin()
1011{
1012 return iterator(*this);
1013}
1014
1015const C4ObjectList::iterator C4ObjectList::end()
1016{
1017 return iterator(*this, nullptr, &C4ObjectLink::Next);
1018}
1019
1020C4ObjectList::iterator C4ObjectList::BeginLast()
1021{
1022 return iterator(*this, &C4ObjectLink::Prev);
1023}
1024
1025std::default_sentinel_t C4ObjectList::EndLast()
1026{
1027 return {};
1028}
1029
1030C4ObjectList::iterator *C4ObjectList::AddIter(iterator *iter)
1031{
1032 iterator *r = FirstIter;
1033 FirstIter = iter;
1034 return r;
1035}
1036
1037void C4ObjectList::RemoveIter(iterator *iter)
1038{
1039 if (iter == FirstIter)
1040 FirstIter = iter->Next;
1041 else
1042 {
1043 iterator *i = FirstIter;
1044 while (i->Next && i->Next != iter)
1045 i = i->Next;
1046 i->Next = iter->Next;
1047 }
1048}
1049