1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2001, Sven2
6 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// landscape sector base class
19
20#include <C4Include.h>
21#include <C4Sector.h>
22
23#include <C4Game.h>
24#include <C4Object.h>
25#include <C4Log.h>
26#include <C4Record.h>
27
28/* sector */
29
30void C4LSector::Init(int ix, int iy)
31{
32 // clear any previous initialization
33 Clear();
34 // store class members
35 x = ix; y = iy;
36}
37
38void C4LSector::Clear()
39{
40 // clear objects
41 Objects.Clear();
42 ObjectShapes.Clear();
43}
44
45void C4LSector::CompileFunc(StdCompiler *pComp)
46{
47 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntAdapt(rValue&: x), szName: "x"));
48 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntAdapt(rValue&: y), szName: "y"));
49 pComp->Value(rStruct: mkNamingAdapt(rValue&: Objects, szName: "Objects"));
50 pComp->Value(rStruct: mkNamingAdapt(rValue&: ObjectShapes, szName: "ObjectShapes"));
51}
52
53/* sector map */
54
55void C4LSectors::Init(int iWdt, int iHgt)
56{
57 // clear any previous initialization
58 Clear();
59 // store class members, calc size
60 Wdt = ((PxWdt = iWdt) - 1) / C4LSectorWdt + 1;
61 Hgt = ((PxHgt = iHgt) - 1) / C4LSectorHgt + 1;
62 // create sectors
63 Sectors = new C4LSector[Size = Wdt * Hgt];
64 // init sectors
65 C4LSector *sct = Sectors;
66 for (int cnt = 0; cnt < Size; cnt++, sct++)
67 sct->Init(ix: cnt % Wdt, iy: cnt / Wdt);
68 SectorOut.Init(ix: -1, iy: -1); // outpos at -1,-1 - MUST NOT intersect with an inside sector!
69}
70
71void C4LSectors::Clear()
72{
73 // clear out-sector
74 SectorOut.Clear();
75 // free sectors
76 delete[] Sectors; Sectors = nullptr;
77}
78
79C4LSector *C4LSectors::SectorAt(int ix, int iy)
80{
81 // check bounds
82 if (ix < 0 || iy < 0 || ix >= PxWdt || iy >= PxHgt)
83 return &SectorOut;
84 // get sector
85 return Sectors + (iy / C4LSectorHgt) * Wdt + (ix / C4LSectorWdt);
86}
87
88void C4LSectors::Add(C4Object *pObj, C4ObjectList *pMainList)
89{
90 assert(Sectors);
91 // Add to owning sector
92 C4LSector *pSct = SectorAt(ix: pObj->x, iy: pObj->y);
93 pSct->Objects.Add(nObj: pObj, eSort: C4ObjectList::stMain, pLstSorted: pMainList);
94 // Save position
95 pObj->old_x = pObj->x; pObj->old_y = pObj->y;
96 // Add to all sectors in shape area
97 pObj->Area.Set(pSectors: this, pObj);
98 for (pSct = pObj->Area.First(); pSct; pSct = pObj->Area.Next(pPrev: pSct))
99 {
100 pSct->ObjectShapes.Add(nObj: pObj, eSort: C4ObjectList::stMain, pLstSorted: pMainList);
101 }
102#ifdef DEBUGREC
103 pObj->Area.DebugRec(pObj, 'A');
104#endif
105}
106
107void C4LSectors::Update(C4Object *pObj, C4ObjectList *pMainList)
108{
109 assert(Sectors);
110 // Not added yet?
111 if (pObj->Area.IsNull())
112 {
113 Add(pObj, pMainList);
114 return;
115 }
116 C4LSector *pOld, *pNew;
117 if (pObj->old_x != pObj->x || pObj->old_y != pObj->y)
118 {
119 // Get involved sectors
120 pOld = SectorAt(ix: pObj->old_x, iy: pObj->old_y);
121 pNew = SectorAt(ix: pObj->x, iy: pObj->y);
122 if (pOld != pNew)
123 {
124 pOld->Objects.Remove(pObj);
125 pNew->Objects.Add(nObj: pObj, eSort: C4ObjectList::stMain, pLstSorted: pMainList);
126 }
127 // Save position
128 pObj->old_x = pObj->x; pObj->old_y = pObj->y;
129 }
130 // New area
131 C4LArea NewArea(this, pObj);
132 if (pObj->Area == NewArea) return;
133 // Remove from all old sectors in shape area
134 for (pOld = pObj->Area.First(); pOld; pOld = pObj->Area.Next(pPrev: pOld))
135 if (!NewArea.Contains(pSct: pOld))
136 pOld->ObjectShapes.Remove(pObj);
137 // Add to all new sectors in shape area
138 for (pNew = NewArea.First(); pNew; pNew = NewArea.Next(pPrev: pNew))
139 if (!pObj->Area.Contains(pSct: pNew))
140 {
141 pNew->ObjectShapes.Add(nObj: pObj, eSort: C4ObjectList::stMain, pLstSorted: pMainList);
142 }
143 // Update area
144 pObj->Area = NewArea;
145#ifdef DEBUGREC
146 pObj->Area.DebugRec(pObj, 'U');
147#endif
148}
149
150void C4LSectors::Remove(C4Object *pObj)
151{
152 assert(Sectors); assert(pObj);
153 // Remove from owning sector
154 C4LSector *pSct = SectorAt(ix: pObj->old_x, iy: pObj->old_y);
155 if (!pSct->Objects.Remove(pObj))
156 {
157#ifndef NDEBUG
158 LogNTr(spdlog::level::warn, "Object {} of type {} deleted but not found in pos sector list!", pObj->Number, C4IdText(pObj->id));
159#endif
160 // if it was not found in owning sector, it must be somewhere else. yeah...
161 bool fFound = false;
162 for (pSct = pObj->Area.First(); pSct; pSct = pObj->Area.Next(pPrev: pSct))
163 if (pSct->Objects.Remove(pObj)) { fFound = true; break; }
164 // yukh, somewhere else entirely...
165 if (!fFound)
166 {
167 fFound = !!SectorOut.Objects.Remove(pObj);
168 if (!fFound)
169 {
170 pSct = Sectors;
171 for (int cnt = 0; cnt < Size; cnt++, pSct++)
172 if (pSct->Objects.Remove(pObj)) { fFound = true; break; }
173 }
174 assert(fFound);
175 }
176 }
177 // Remove from all sectors in shape area
178 for (pSct = pObj->Area.First(); pSct; pSct = pObj->Area.Next(pPrev: pSct))
179 pSct->ObjectShapes.Remove(pObj);
180#ifdef DEBUGREC
181 pObj->Area.DebugRec(pObj, 'R');
182#endif
183}
184
185void C4LSectors::AssertObjectNotInList(C4Object *pObj)
186{
187 C4LSector *sct = Sectors;
188 for (int cnt = 0; cnt < Size; cnt++, sct++)
189 {
190 if (sct->Objects.IsContained(pObj)) assert(false);
191 if (sct->ObjectShapes.IsContained(pObj)) assert(false);
192 }
193 if (SectorOut.Objects.IsContained(pObj)) assert(false);
194 if (SectorOut.ObjectShapes.IsContained(pObj)) assert(false);
195}
196
197int C4LSectors::getShapeSum() const
198{
199 int iSum = 0;
200 for (int cnt = 0; cnt < Size; cnt++)
201 iSum += Sectors[cnt].ObjectShapes.ObjectCount();
202 return iSum;
203}
204
205void C4LSectors::Dump()
206{
207 spdlog::debug(msg: DecompileToBuf<StdCompilerINIWrite>(
208 SrcStruct: mkNamingAdapt(
209 rValue: mkArrayAdaptS(array: Sectors, size: Size),
210 szName: "Sector")));
211}
212
213bool C4LSectors::CheckSort()
214{
215 for (int cnt = 0; cnt < Size; cnt++)
216 if (!Sectors[cnt].Objects.CheckSort(pList: &Game.Objects))
217 return false;
218 if (!SectorOut.Objects.CheckSort(pList: &Game.Objects)) return false;
219 return true;
220}
221
222/* landscape area */
223
224bool C4LArea::operator==(const C4LArea &Area) const
225{
226 return pFirst == Area.pFirst &&
227 xL == Area.xL &&
228 yL == Area.yL &&
229 pOut == Area.pOut;
230}
231
232void C4LArea::Set(C4LSectors *pSectors, const C4Rect &Rect)
233{
234 // default: no area
235 pFirst = nullptr; pOut = nullptr;
236 // check bounds
237 C4Rect ClippedRect(Rect),
238 Bounds(0, 0, pSectors->PxWdt, pSectors->PxHgt);
239 ClippedRect.Normalize();
240 if (!Bounds.Contains(rect: ClippedRect))
241 {
242 ClippedRect.Intersect(r2: Bounds);
243 pOut = &pSectors->SectorOut;
244 }
245 // calc first sector
246 pFirst = pSectors->SectorAt(ix: ClippedRect.x, iy: ClippedRect.y);
247 // assert the rect isn't degenerated for the following calculations
248 // (note this will associate areas that are above landscape bounds with sectors inside)
249 if (!ClippedRect.Wdt) ClippedRect.Wdt = 1;
250 if (!ClippedRect.Hgt) ClippedRect.Hgt = 1;
251 // calc bounds
252 xL = (ClippedRect.x + ClippedRect.Wdt - 1) / C4LSectorWdt;
253 yL = (ClippedRect.y + ClippedRect.Hgt - 1) / C4LSectorHgt;
254 // calc pitch
255 dpitch = pSectors->Wdt - (ClippedRect.x + ClippedRect.Wdt - 1) / C4LSectorWdt + ClippedRect.x / C4LSectorWdt;
256}
257
258void C4LArea::Set(C4LSectors *pSectors, C4Object *pObj)
259{
260 // set to object facet rect
261 Set(pSectors, Rect: C4Rect(pObj->Left(), pObj->Top(), pObj->Width(), pObj->Height()));
262}
263
264C4LSector *C4LArea::Next(C4LSector *pPrev) const
265{
266 // the outside-sector is the last sector that is returned
267 if (pPrev == pOut)
268 return nullptr;
269 // within one line?
270 if (pPrev->x < xL)
271 return pPrev + 1;
272 // within the area?
273 if (pPrev->y < yL)
274 return pPrev + dpitch;
275 // end reached - return outside-sector if applicable
276 return pOut;
277}
278
279bool C4LArea::Contains(C4LSector *pSct) const
280{
281 assert(pSct);
282 // no area
283 if (!pFirst) return false;
284 // outside?
285 if (pSct == pOut) return true;
286 if (pFirst == pOut) return false;
287 // check bounds
288 return (pSct->x >= pFirst->x && pSct->y >= pFirst->y && pSct->x <= xL && pSct->y <= yL);
289}
290
291C4ObjectList *C4LArea::NextObjects(C4ObjectList *pPrev, C4LSector **ppSct)
292{
293 // get next sector
294 if (!*ppSct)
295 *ppSct = First();
296 else
297 *ppSct = Next(pPrev: *ppSct);
298 // nothing left?
299 if (!*ppSct)
300 return nullptr;
301 // return object list
302 return &(*ppSct)->Objects;
303}
304
305C4ObjectList *C4LArea::NextObjectShapes(C4ObjectList *pPrev, C4LSector **ppSct)
306{
307 // get next sector
308 if (!*ppSct)
309 *ppSct = First();
310 else
311 *ppSct = Next(pPrev: *ppSct);
312 // nothing left?
313 if (!*ppSct)
314 return nullptr;
315 // return object list
316 return &(*ppSct)->ObjectShapes;
317}
318
319#ifdef DEBUGREC
320void C4LArea::DebugRec(class C4Object *pObj, char cMarker)
321{
322 C4RCArea rc;
323 rc.op = cMarker;
324 rc.obj = pObj ? pObj->Number : -1;
325 rc.x1 = pFirst ? pFirst->x : -1;
326 rc.y1 = pFirst ? pFirst->x /* 2do: y */ : -1;
327 rc.xL = xL;
328 rc.yL = yL;
329 rc.dpitch = dpitch;
330 rc.out = !!pOut;
331 AddDbgRec(RCT_Area, &rc, sizeof(C4RCArea));
332}
333#endif
334