1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 1998-2000, Matthes Bender
5 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6 * Copyright (c) 2009-2016, The OpenClonk 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 /* Handles landscape and sky */
19
20 #include "C4Include.h"
21 #include "C4ForbidLibraryCompilation.h"
22 #include "landscape/C4Landscape.h"
23
24 #include "c4group/C4Components.h"
25 #include "control/C4Record.h"
26 #include "editor/C4ToolsDlg.h"
27 #include "game/C4GraphicsSystem.h"
28 #include "game/C4Physics.h"
29 #include "graphics/C4GraphicsResource.h"
30 #include "gui/C4GameMessage.h"
31 #include "landscape/C4LandscapeRender.h"
32 #include "landscape/C4Map.h"
33 #include "landscape/C4MapCreatorS2.h"
34 #include "landscape/C4MapScript.h"
35 #include "landscape/C4MassMover.h"
36 #include "landscape/C4Material.h"
37 #include "landscape/C4MaterialList.h"
38 #include "landscape/C4PXS.h"
39 #include "landscape/C4Sky.h"
40 #include "landscape/C4SolidMask.h"
41 #include "landscape/C4Texture.h"
42 #include "landscape/C4Weather.h"
43 #include "landscape/fow/C4FoW.h"
44 #include "lib/C4Random.h"
45 #include "lib/StdColors.h"
46 #include "object/C4Def.h"
47 #include "object/C4FindObject.h"
48 #include "object/C4GameObjects.h"
49
50 #include <array>
51
52 struct C4Landscape::P
53 {
54 std::unique_ptr<CSurface8> Surface8;
55 std::unique_ptr<CSurface8> Surface8Bkg; // Background material
56 std::unique_ptr<CSurface8> Map;
57 std::unique_ptr<CSurface8> MapBkg;
58 std::unique_ptr<C4LandscapeRender> pLandscapeRender;
59 // array size of landscape width/height: Filled with 0s for border pixels that are open and MCVehic for pixels that are closed
60 std::vector<uint8_t> TopRowPix, BottomRowPix, LeftColPix, RightColPix;
61 int32_t Pix2Mat[C4M_MaxTexIndex], Pix2Dens[C4M_MaxTexIndex], Pix2Place[C4M_MaxTexIndex];
62 bool Pix2Light[C4M_MaxTexIndex];
63 int32_t PixCntPitch = 0;
64 std::vector<uint8_t> PixCnt;
65 std::array<C4Rect, C4LS_MaxRelights> Relights;
66 mutable std::array<std::unique_ptr<uint8_t[]>, C4M_MaxTexIndex> BridgeMatConversion; // NoSave //
67
68 LandscapeMode mode = LandscapeMode::Undefined;
69 int32_t Width = 0, Height = 0;
70 int32_t MapWidth = 0, MapHeight = 0, MapZoom = 0;
71 std::array<DWORD, C4MaxMaterial> MatCount{}; // NoSave //
72 std::array<DWORD, C4MaxMaterial> EffectiveMatCount{}; // NoSave //
73
74 bool NoScan = false; // ExecuteScan() disabled
75 int32_t ScanX = 0, ScanSpeed = 2; // SyncClearance-NoSave //
76 C4Real Gravity = DefaultGravAccel;
77 uint32_t Modulation = 0; // landscape blit modulation; 0 means normal
78 int32_t MapSeed = 0; // random seed for MapToLandscape
79 C4Sky Sky;
80 std::unique_ptr<C4MapCreatorS2> pMapCreator; // map creator for script-generated maps
81 bool fMapChanged = false;
82 std::unique_ptr<BYTE[]> pInitial; // Initial landscape after creation - used for diff
83 std::unique_ptr<BYTE[]> pInitialBkg; // Initial bkg landscape after creation - used for diff
84 std::unique_ptr<C4FoW> pFoW;
85
86 void ClearMatCount();
87
88 void ExecuteScan(C4Landscape *);
89 int32_t DoScan(C4Landscape *, int32_t x, int32_t y, int32_t mat, int32_t dir);
90 uint32_t ChunkyRandom(uint32_t &iOffset, uint32_t iRange) const; // return static random value, according to offset and MapSeed
91 void DrawChunk(C4Landscape *, int32_t tx, int32_t ty, int32_t wdt, int32_t hgt, uint8_t mcol, uint8_t mcolBkg, C4MaterialCoreShape Shape, uint32_t cro);
92 void DrawSmoothOChunk(C4Landscape *, int32_t tx, int32_t ty, int32_t wdt, int32_t hgt, uint8_t mcol, uint8_t mcolBkg, int flip, uint32_t cro);
93 void ChunkOZoom(C4Landscape *, const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, uint8_t iTexture, int32_t iOffX = 0, int32_t iOffY = 0);
94 bool TexOZoom(C4Landscape *, const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, DWORD *dwpTextureUsage, int32_t iToX = 0, int32_t iToY = 0);
95 bool MapToSurface(C4Landscape *, const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, int32_t iToX, int32_t iToY, int32_t iToWdt, int32_t iToHgt, int32_t iOffX, int32_t iOffY);
96 bool MapToLandscape(C4Landscape *d, const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, int32_t iOffsX = 0, int32_t iOffsY = 0, bool noClear = false); // zoom map segment to surface (or sector surfaces)
97 bool InitBorderPix(); // init out-of-landscape pixels for ALL sides
98 bool GetMapColorIndex(const char *szMaterial, const char *szTexture, BYTE &rbyCol) const;
99 //bool SkyToLandscape(int32_t iToX, int32_t iToY, int32_t iToWdt, int32_t iToHgt, int32_t iOffX, int32_t iOffY);
100 bool CreateMap(CSurface8*& sfcMap, CSurface8*& sfcMapBkg); // create map by landscape attributes
101 bool CreateMapS2(C4Group &ScenFile, CSurface8*& sfcMap, CSurface8*& sfcMapBkg); // create map by def file
102 bool Mat2Pal(); // assign material colors to landscape palette
103 void UpdatePixCnt(const C4Landscape *, const C4Rect &Rect, bool fCheck = false);
104 void UpdateMatCnt(const C4Landscape *, C4Rect Rect, bool fPlus);
105 void PrepareChange(const C4Landscape *d, const C4Rect &BoundingBox);
106 void FinishChange(C4Landscape *d, C4Rect BoundingBox);
107 bool DrawLineLandscape(int32_t iX, int32_t iY, int32_t iGrade, uint8_t line_color, uint8_t line_color_bkg);
108 bool DrawLineMap(int32_t iX, int32_t iY, int32_t iRadius, uint8_t line_color, uint8_t line_color_bkg);
109 uint8_t *GetBridgeMatConversion(const C4Landscape *d, int32_t for_material_col) const;
110 bool SaveInternal(const C4Landscape *d, C4Group &hGroup) const;
111 bool SaveDiffInternal(const C4Landscape *d, C4Group &hGroup, bool fSyncSave) const;
112
113 int32_t ForPolygon(C4Landscape *d, int *vtcs, int length, const std::function<bool(int32_t, int32_t)> &callback,
114 C4MaterialList *mats_count = nullptr, uint8_t col = 0, uint8_t colBkg = 0, uint8_t *conversion_table = nullptr);
115
116 std::unique_ptr<CSurface8> CreateDefaultBkgSurface(CSurface8& sfcFg, bool msbAsIft) const;
117 void DigMaterial2Objects(int32_t tx, int32_t ty, C4MaterialList *mat_list, C4Object *pCollect = nullptr);
118 void BlastMaterial2Objects(int32_t tx, int32_t ty, C4MaterialList *mat_list, int32_t caused_by, int32_t str, C4ValueArray *out_objects);
119
120 bool DigFreePix(C4Landscape *d, int32_t tx, int32_t ty);
121 bool DigFreePixNoInstability(C4Landscape *d, int32_t tx, int32_t ty);
122 bool BlastFreePix(C4Landscape *d, int32_t tx, int32_t ty);
123 bool ShakeFreePix(C4Landscape *d, int32_t tx, int32_t ty);
124
125 C4ValueArray *PrepareFreeShape(C4Rect &BoundingBox, C4Object *by_object);
126 void PostFreeShape(C4ValueArray *dig_objects, C4Object *by_object);
127 BYTE DefaultBkgMat(BYTE fg) const;
128 };
129
130 namespace
131 {
ForLine(int32_t x1,int32_t y1,int32_t x2,int32_t y2,std::function<bool (int32_t,int32_t)> fnCallback,int32_t * lastx=nullptr,int32_t * lasty=nullptr)132 bool ForLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2,
133 std::function<bool(int32_t, int32_t)> fnCallback,
134 int32_t *lastx = nullptr, int32_t *lasty = nullptr)
135 {
136 int d, dx, dy, aincr, bincr, xincr, yincr, x, y;
137 if (Abs(x2 - x1) < Abs(y2 - y1))
138 {
139 if (y1 > y2) { std::swap(x1, x2); std::swap(y1, y2); }
140 xincr = (x2 > x1) ? 1 : -1;
141 dy = y2 - y1; dx = Abs(x2 - x1);
142 d = 2 * dx - dy; aincr = 2 * (dx - dy); bincr = 2 * dx; x = x1; y = y1;
143 if (!fnCallback(x, y))
144 {
145 if (lastx) *lastx = x; if (lasty) *lasty = y;
146 return false;
147 }
148 for (y = y1 + 1; y <= y2; ++y)
149 {
150 if (d >= 0) { x += xincr; d += aincr; }
151 else d += bincr;
152 if (!fnCallback(x, y))
153 {
154 if (lastx) *lastx = x; if (lasty) *lasty = y;
155 return false;
156 }
157 }
158 }
159 else
160 {
161 if (x1 > x2) { std::swap(x1, x2); std::swap(y1, y2); }
162 yincr = (y2 > y1) ? 1 : -1;
163 dx = x2 - x1; dy = Abs(y2 - y1);
164 d = 2 * dy - dx; aincr = 2 * (dy - dx); bincr = 2 * dy; x = x1; y = y1;
165 if (!fnCallback(x, y))
166 {
167 if (lastx) *lastx = x; if (lasty) *lasty = y;
168 return false;
169 }
170 for (x = x1 + 1; x <= x2; ++x)
171 {
172 if (d >= 0) { y += yincr; d += aincr; }
173 else d += bincr;
174 if (!fnCallback(x, y))
175 {
176 if (lastx) *lastx = x; if (lasty) *lasty = y;
177 return false;
178 }
179 }
180 }
181 return true;
182 }
183 }
184
C4Landscape()185 C4Landscape::C4Landscape()
186 : p(new P)
187 {
188 Default();
189 }
190
~C4Landscape()191 C4Landscape::~C4Landscape()
192 {
193 Clear();
194 }
195
196 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
197 /* +++++++++++++++++++++++++ Execute and display +++++++++++++++++++++++++++ */
198 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
199
Execute()200 void C4Landscape::Execute()
201 {
202 // Landscape scan
203 if (!p->NoScan)
204 p->ExecuteScan(this);
205 // move sky
206 p->Sky.Execute();
207
208 // Queued Relights -- note that normally we process them before drawing every frame;
209 // this just makes sure relights don't accumulate over a long period of time if no
210 // viewport is open (developer mode).
211 if (!::Game.iTick35)
212 DoRelights();
213 }
214
215
ExecuteScan(C4Landscape * d)216 void C4Landscape::P::ExecuteScan(C4Landscape *d)
217 {
218 int32_t cy, mat;
219
220 // Check: Scan needed?
221 const int32_t iTemperature = ::Weather.GetTemperature();
222 for (mat = 0; mat < ::MaterialMap.Num; mat++)
223 if (MatCount[mat])
224 {
225 if (::MaterialMap.Map[mat].BelowTempConvertTo &&
226 iTemperature < ::MaterialMap.Map[mat].BelowTempConvert)
227 break;
228 else if (::MaterialMap.Map[mat].AboveTempConvertTo &&
229 iTemperature > ::MaterialMap.Map[mat].AboveTempConvert)
230 break;
231 }
232 if (mat >= ::MaterialMap.Num)
233 return;
234
235 if (DEBUGREC_MATSCAN && Config.General.DebugRec)
236 AddDbgRec(RCT_MatScan, &ScanX, sizeof(ScanX));
237
238 for (int32_t cnt = 0; cnt < ScanSpeed; cnt++)
239 {
240
241 // Scan landscape column: sectors down
242 int32_t last_mat = -1;
243 for (cy = 0; cy < Height; cy++)
244 {
245 mat = d->_GetMat(ScanX, cy);
246 // material change?
247 if (last_mat != mat)
248 {
249 // upwards
250 if (last_mat != -1)
251 DoScan(d, ScanX, cy - 1, last_mat, 1);
252 // downwards
253 if (mat != -1)
254 cy += DoScan(d, ScanX, cy, mat, 0);
255 }
256 last_mat = mat;
257 }
258
259 // Scan advance & rewind
260 ScanX++;
261 if (ScanX >= Width)
262 ScanX = 0;
263
264 }
265
266 }
267
268 #define PRETTY_TEMP_CONV
269
DoScan(C4Landscape * d,int32_t cx,int32_t cy,int32_t mat,int32_t dir)270 int32_t C4Landscape::P::DoScan(C4Landscape *d, int32_t cx, int32_t cy, int32_t mat, int32_t dir)
271 {
272 int32_t conv_to_tex = 0;
273 int32_t iTemperature = ::Weather.GetTemperature();
274 // Check below conv
275 if (::MaterialMap.Map[mat].BelowTempConvertDir == dir)
276 if (::MaterialMap.Map[mat].BelowTempConvertTo)
277 if (iTemperature< ::MaterialMap.Map[mat].BelowTempConvert)
278 conv_to_tex = ::MaterialMap.Map[mat].BelowTempConvertTo;
279 // Check above conv
280 if (::MaterialMap.Map[mat].AboveTempConvertDir == dir)
281 if (::MaterialMap.Map[mat].AboveTempConvertTo)
282 if (iTemperature>::MaterialMap.Map[mat].AboveTempConvert)
283 conv_to_tex = ::MaterialMap.Map[mat].AboveTempConvertTo;
284 // nothing to do?
285 if (!conv_to_tex) return 0;
286 // find material
287 int32_t conv_to = ::TextureMap.GetEntry(conv_to_tex)->GetMaterialIndex();
288 // find mat top
289 int32_t mconv = ::MaterialMap.Map[mat].TempConvStrength,
290 mconvs = mconv;
291 if (DEBUGREC_MATSCAN && Config.General.DebugRec)
292 {
293 C4RCMatScan rc = { cx, cy, mat, conv_to, dir, mconvs };
294 AddDbgRec(RCT_MatScanDo, &rc, sizeof(C4RCMatScan));
295 }
296 int32_t ydir = (dir == 0 ? +1 : -1), cy2;
297 #ifdef PRETTY_TEMP_CONV
298 // get left pixel
299 int32_t lmat = (cx > 0 ? d->_GetMat(cx - 1, cy) : -1);
300 // left pixel not converted? do nothing
301 if (lmat == mat) return 0;
302 // left pixel converted? suppose it was done by a prior scan and skip check
303 if (lmat != conv_to)
304 {
305 int32_t iSearchRange = std::max<int32_t>(5, mconvs);
306 // search upper/lower bound
307 int32_t cys = cy, cxs = cx;
308 while (cxs < ::Landscape.GetWidth() - 1)
309 {
310 // one step right
311 cxs++;
312 if (d->_GetMat(cxs, cys) == mat)
313 {
314 // search surface
315 cys -= ydir;
316 while (Inside<int32_t>(cys, 0, ::Landscape.GetHeight() - 1) && d->_GetMat(cxs, cys) == mat)
317 {
318 cys -= ydir;
319 if ((mconvs = std::min(mconv - Abs(cys - cy), mconvs)) < 0)
320 return 0;
321 }
322 // out of bounds?
323 if (!Inside<int32_t>(cys, 0, ::Landscape.GetHeight() - 1)) break;
324 // back one step
325 cys += ydir;
326 }
327 else
328 {
329 // search surface
330 cys += ydir;
331 while (Inside<int32_t>(cys, 0, ::Landscape.GetHeight() - 1) && d->_GetMat(cxs, cys) != mat)
332 {
333 cys += ydir;
334 if (Abs(cys - cy) > iSearchRange)
335 break;
336 }
337 // out of bounds?
338 if (!Inside<int32_t>(cys, 0, ::Landscape.GetHeight() - 1)) break;
339 if (Abs(cys - cy) > iSearchRange) break;
340 }
341 }
342 }
343 #endif
344 // Conversion
345 bool conv_to_is_solid = (conv_to > -1) && DensitySolid(::MaterialMap.Map[conv_to].Density);
346 for (cy2 = cy; mconvs >= 0 && Inside<int32_t>(cy2, 0, ::Landscape.GetHeight() - 1); cy2 += ydir, mconvs--)
347 {
348 // material changed?
349 int32_t pix = d->_GetPix(cx, cy2);
350 if (PixCol2Mat(pix) != mat) break;
351 #ifdef PRETTY_TEMP_CONV
352 // get left pixel
353 int32_t lmat = (cx > 0 ? d->_GetMat(cx - 1, cy2) : -1);
354 // left pixel not converted? break
355 if (lmat == mat) break;
356 #endif
357 // set mat (and keep background material)
358 d->SetPix2(cx, cy2, MatTex2PixCol(conv_to_tex), Transparent);
359 if (!conv_to_is_solid) d->CheckInstabilityRange(cx, cy2);
360 }
361 // return pixel converted
362 return Abs(cy2 - cy);
363 }
364
Draw(C4TargetFacet & cgo,C4FoWRegion * pLight)365 void C4Landscape::Draw(C4TargetFacet &cgo, C4FoWRegion *pLight)
366 {
367 uint32_t clrMod = 0xffffffff;
368 if (p->Modulation)
369 {
370 pDraw->ActivateBlitModulation(p->Modulation);
371 clrMod = p->Modulation;
372 }
373 // blit landscape
374 if (::GraphicsSystem.Show8BitSurface == 1)
375 pDraw->Blit8Fast(p->Surface8.get(), cgo.TargetX, cgo.TargetY, cgo.Surface, cgo.X, cgo.Y, cgo.Wdt, cgo.Hgt);
376 else if (::GraphicsSystem.Show8BitSurface == 2)
377 pDraw->Blit8Fast(p->Surface8Bkg.get(), cgo.TargetX, cgo.TargetY, cgo.Surface, cgo.X, cgo.Y, cgo.Wdt, cgo.Hgt);
378 else if (p->pLandscapeRender)
379 {
380 DoRelights();
381 p->pLandscapeRender->Draw(cgo, pLight, clrMod);
382 }
383 if (p->Modulation) pDraw->DeactivateBlitModulation();
384 }
385
DoRelights()386 bool C4Landscape::DoRelights()
387 {
388 if (!p->pLandscapeRender) return true;
389 for (int32_t i = 0; i < C4LS_MaxRelights; i++)
390 {
391 if (!p->Relights[i].Wdt)
392 break;
393 // Remove all solid masks in the (twice!) extended region around the change
394 C4Rect SolidMaskRect = p->pLandscapeRender->GetAffectedRect(p->Relights[i]);
395 C4SolidMask * pSolid;
396 for (pSolid = C4SolidMask::Last; pSolid; pSolid = pSolid->Prev)
397 pSolid->RemoveTemporary(SolidMaskRect);
398 // Perform the update
399 p->pLandscapeRender->Update(p->Relights[i], this);
400 if (p->pFoW) p->pFoW->Ambient.UpdateFromLandscape(*this, p->Relights[i]);
401 // Restore Solidmasks
402 for (pSolid = C4SolidMask::First; pSolid; pSolid = pSolid->Next)
403 pSolid->PutTemporary(SolidMaskRect);
404 C4SolidMask::CheckConsistency();
405 // Clear slot
406 p->Relights[i].Default();
407 }
408 return true;
409 }
410
411
412 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
413 /* +++++++++++++++++++++++ Add and destroy landscape++++++++++++++++++++++++ */
414 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
GetRoundPolygon(int32_t x,int32_t y,int32_t size,int32_t smoothness)415 static std::vector<int32_t> GetRoundPolygon(int32_t x, int32_t y, int32_t size, int32_t smoothness)
416 {
417 /*
418 So what is this? It's basically a circle with the radius 'size'. The radius
419 is adjusted by two sin/cos waves. The random lies in the phase of the sin/cos
420 and in the factor the wave is added to the normal circle shape. smoothness from
421 0 to 100. 0 gives an exagerated 'explosion' shape while 100 is almost a circle
422 */
423
424 if (smoothness > 100) smoothness = 100;
425 if (smoothness < 0) smoothness = 0;
426 if (size <= 0) size = 1;
427
428 // vertex count of the polygon
429 int32_t count = 2 * size / 3 + 6;
430
431 std::vector<int32_t> vertices;
432 vertices.reserve(count * 2);
433
434 // varying phase of the sin/cos waves
435 C4Real begin = itofix(360)*(int32_t)Random(100) / 100;
436 C4Real begin2 = itofix(360)*(int32_t)Random(100) / 100;
437
438 // parameters:
439 // the bigger the factor, the smaller the divergence from a circle
440 C4Real anticircle = itofix(3) + smoothness / 16 * smoothness / 16;
441 // the bigger the factor the more random is the divergence from the circle
442 int random = 80 * (200 - smoothness);
443
444 for (int i = 0; i < count; ++i)
445 {
446 C4Real angle = itofix(360)*i / count;
447
448 C4Real currsize = itofix(size);
449 currsize += Sin(angle * 3 + begin + itofix(Random(random)) / 100) * size / anticircle; // +sin
450 currsize += Cos(angle * 5 + begin2 + itofix(Random(random)) / 100) * size / anticircle / 2; // +cos
451
452 vertices.push_back(x + fixtoi(Sin(angle)*currsize));
453 vertices.push_back(y - fixtoi(Cos(angle)*currsize));
454 }
455
456 return vertices;
457 }
458
GetRectangle(int32_t tx,int32_t ty,int32_t wdt,int32_t hgt)459 static std::vector<int32_t> GetRectangle(int32_t tx, int32_t ty, int32_t wdt, int32_t hgt)
460 {
461 std::vector<int32_t> vertices;
462 vertices.resize(8);
463
464 vertices[0] = tx; vertices[1] = ty;
465 vertices[2] = tx; vertices[3] = ty + hgt;
466 vertices[4] = tx + wdt; vertices[5] = ty + hgt;
467 vertices[6] = tx + wdt; vertices[7] = ty;
468
469 return vertices;
470 }
471
getBoundingBox(int * vtcs,int length)472 static C4Rect getBoundingBox(int *vtcs, int length)
473 {
474 C4Rect BoundingBox(vtcs[0], vtcs[1], 1, 1);
475 for (int32_t i = 2; i + 1 < length; i += 2)
476 {
477 BoundingBox.Add(C4Rect(vtcs[i], vtcs[i + 1], 1, 1));
478 }
479 return BoundingBox;
480 }
481
ClearFreeRect(int32_t tx,int32_t ty,int32_t wdt,int32_t hgt)482 void C4Landscape::ClearFreeRect(int32_t tx, int32_t ty, int32_t wdt, int32_t hgt)
483 {
484 std::vector<int32_t> vertices(GetRectangle(tx, ty, wdt, hgt));
485 C4Rect r(tx, ty, wdt, hgt);
486 p->PrepareChange(this, r);
487 p->ForPolygon(this, &vertices[0], vertices.size() / 2, [this](int32_t x, int32_t y) { return ClearPix(x, y); });
488 p->FinishChange(this, r);
489 }
490
DigFreeRect(int32_t tx,int32_t ty,int32_t wdt,int32_t hgt,C4Object * by_object,bool no_dig2objects,bool no_instability_check)491 int32_t C4Landscape::DigFreeRect(int32_t tx, int32_t ty, int32_t wdt, int32_t hgt, C4Object *by_object, bool no_dig2objects, bool no_instability_check)
492 {
493 std::vector<int32_t> vertices(GetRectangle(tx, ty, wdt, hgt));
494 return DigFreeShape(&vertices[0], vertices.size(), by_object, no_dig2objects, no_instability_check);
495 }
496
DigFree(int32_t tx,int32_t ty,int32_t rad,C4Object * by_object,bool no_dig2objects,bool no_instability_check)497 int32_t C4Landscape::DigFree(int32_t tx, int32_t ty, int32_t rad, C4Object *by_object, bool no_dig2objects, bool no_instability_check)
498 {
499 std::vector<int32_t> vertices(GetRoundPolygon(tx, ty, rad, 80));
500 return DigFreeShape(&vertices[0], vertices.size(), by_object, no_dig2objects, no_instability_check);
501 }
502
BlastFree(int32_t tx,int32_t ty,int32_t rad,int32_t caused_by,C4Object * by_object,int32_t iMaxDensity)503 void C4Landscape::BlastFree(int32_t tx, int32_t ty, int32_t rad, int32_t caused_by, C4Object *by_object, int32_t iMaxDensity)
504 {
505 std::vector<int32_t> vertices(GetRoundPolygon(tx, ty, rad, 30));
506 BlastFreeShape(&vertices[0], vertices.size(), by_object, caused_by, iMaxDensity);
507 }
508
ShakeFree(int32_t tx,int32_t ty,int32_t rad)509 void C4Landscape::ShakeFree(int32_t tx, int32_t ty, int32_t rad)
510 {
511 std::vector<int32_t> vertices(GetRoundPolygon(tx, ty, rad, 50));
512 p->ForPolygon(this, &vertices[0], vertices.size() / 2, [this](int32_t x, int32_t y) { return p->ShakeFreePix(this, x, y); });
513 }
514
PrepareFreeShape(C4Rect & BoundingBox,C4Object * by_object)515 C4ValueArray *C4Landscape::P::PrepareFreeShape(C4Rect &BoundingBox, C4Object *by_object)
516 {
517 // Remember any in-earth objects in area
518 C4FindObjectInRect fo_inrect(BoundingBox);
519 C4FindObjectOCF fo_insolid(OCF_InSolid);
520 C4FindObjectLayer fo_layer(by_object ? by_object->Layer : nullptr);
521 C4FindObject *fo_list[] = { &fo_inrect, &fo_insolid, &fo_layer };
522 C4FindObjectAndStatic fo_srch(3, fo_list);
523 return fo_srch.FindMany(::Objects, ::Objects.Sectors);
524 }
525
PostFreeShape(C4ValueArray * dig_objects,C4Object * by_object)526 void C4Landscape::P::PostFreeShape(C4ValueArray *dig_objects, C4Object *by_object)
527 {
528 // Do callbacks to digger and dug out objects for objects that are now dug free
529 if (by_object)
530 {
531 for (int32_t i = 0; i < dig_objects->GetSize(); ++i)
532 {
533 C4Object *dig_object = dig_objects->GetItem(i).getObj();
534 if (dig_object && !GBackSolid(dig_object->GetX(), dig_object->GetY()))
535 if (dig_object != by_object)
536 if (!dig_object->Contained && dig_object->Status)
537 {
538 C4AulParSet pars(by_object);
539 dig_object->Call(PSF_OnDugOut, &pars);
540 if (dig_object->Status && by_object->Status)
541 {
542 C4AulParSet pars(dig_object);
543 by_object->Call(PSF_DigOutObject, &pars);
544 }
545 }
546 }
547 }
548 }
549
DigFreeShape(int * vtcs,int length,C4Object * by_object,bool no_dig2objects,bool no_instability_check)550 int32_t C4Landscape::DigFreeShape(int *vtcs, int length, C4Object *by_object, bool no_dig2objects, bool no_instability_check)
551 {
552 using namespace std::placeholders;
553
554 C4Rect BoundingBox = getBoundingBox(vtcs, length);
555 int32_t amount;
556
557 // Remember any collectible objects in area
558 std::unique_ptr<C4ValueArray> dig_objects(p->PrepareFreeShape(BoundingBox, by_object));
559
560 std::function<bool(int32_t, int32_t)> callback;
561 if (no_instability_check)
562 callback = [this](int32_t x, int32_t y) { return p->DigFreePixNoInstability(this, x, y); };
563 else
564 callback = [this](int32_t x, int32_t y) { return p->DigFreePix(this, x, y); };
565
566 if (by_object)
567 {
568 if (!by_object->MaterialContents)
569 by_object->MaterialContents = new C4MaterialList;
570 amount = p->ForPolygon(this, vtcs, length / 2, callback, by_object->MaterialContents);
571 }
572 else
573 amount = p->ForPolygon(this, vtcs, length / 2, callback, nullptr);
574
575 // create objects from the material
576 if (!::Game.iTick5)
577 {
578 if (!no_dig2objects)
579 if (by_object)
580 if (by_object->MaterialContents)
581 {
582 int32_t tx = BoundingBox.GetMiddleX(), ty = BoundingBox.GetBottom();
583 p->DigMaterial2Objects(tx, ty, by_object->MaterialContents, by_object);
584 }
585 }
586
587 // Do callbacks to digger for objects that are now dug free
588 p->PostFreeShape(dig_objects.get(), by_object);
589
590 return amount;
591 }
592
BlastFreeShape(int * vtcs,int length,C4Object * by_object,int32_t by_player,int32_t iMaxDensity)593 void C4Landscape::BlastFreeShape(int *vtcs, int length, C4Object *by_object, int32_t by_player, int32_t iMaxDensity)
594 {
595 C4MaterialList *MaterialContents = nullptr;
596
597 C4Rect BoundingBox = getBoundingBox(vtcs, length);
598
599 // Remember any collectible objects in area
600 std::unique_ptr<C4ValueArray> dig_objects(p->PrepareFreeShape(BoundingBox, by_object));
601
602 uint8_t *pblast_tbl = nullptr, blast_tbl[C4M_MaxTexIndex];
603 if (iMaxDensity < C4M_Vehicle)
604 {
605 for (int32_t i = 0; i < C4M_MaxTexIndex; ++i) blast_tbl[i] = (GetPixDensity(i) <= iMaxDensity);
606 pblast_tbl = blast_tbl;
607 }
608
609 if (by_object)
610 {
611 if (!by_object->MaterialContents)
612 by_object->MaterialContents = new C4MaterialList;
613 p->ForPolygon(this, vtcs, length / 2, [this](int32_t x, int32_t y) { return p->BlastFreePix(this, x, y); }, by_object->MaterialContents, 0, 0, pblast_tbl);
614 }
615 else
616 {
617 MaterialContents = new C4MaterialList;
618 p->ForPolygon(this, vtcs, length / 2, [this](int32_t x, int32_t y) { return p->BlastFreePix(this, x, y); }, MaterialContents, iMaxDensity);
619 }
620
621 // create objects from the material
622 C4MaterialList *mat_list = nullptr;
623 if (by_object)
624 mat_list = by_object->MaterialContents;
625 else
626 mat_list = MaterialContents;
627
628 int32_t tx = BoundingBox.GetMiddleX(), ty = BoundingBox.GetMiddleY();
629 p->BlastMaterial2Objects(tx, ty, mat_list, by_player, (BoundingBox.Wdt + BoundingBox.Hgt) / 4, dig_objects.get());
630
631 if (MaterialContents) delete MaterialContents;
632
633 // Do callbacks to digger for objects that are now dug free
634 p->PostFreeShape(dig_objects.get(), by_object);
635 }
636
BlastMaterial2Objects(int32_t tx,int32_t ty,C4MaterialList * mat_list,int32_t caused_by,int32_t str,C4ValueArray * out_objects)637 void C4Landscape::P::BlastMaterial2Objects(int32_t tx, int32_t ty, C4MaterialList *mat_list, int32_t caused_by, int32_t str, C4ValueArray *out_objects)
638 {
639 for (int32_t mat = 0; mat < ::MaterialMap.Num; mat++)
640 {
641 if (mat_list->Amount[mat])
642 {
643 int32_t cast_strength = str;
644 int32_t pxsamount = 0, blastamount = 0;
645
646 if (::MaterialMap.Map[mat].Blast2PXSRatio != 0)
647 {
648 pxsamount = mat_list->Amount[mat] / ::MaterialMap.Map[mat].Blast2PXSRatio;
649 ::PXS.Cast(mat, pxsamount, tx, ty, cast_strength * 2);
650 }
651
652 if (::MaterialMap.Map[mat].Blast2Object != C4ID::None)
653 {
654 if (::MaterialMap.Map[mat].Blast2ObjectRatio != 0)
655 {
656 blastamount = mat_list->Amount[mat] / ::MaterialMap.Map[mat].Blast2ObjectRatio;
657 Game.CastObjects(::MaterialMap.Map[mat].Blast2Object, nullptr, blastamount, cast_strength, tx, ty, NO_OWNER, caused_by, out_objects);
658 }
659 }
660
661 mat_list->Amount[mat] -= std::max(blastamount * ::MaterialMap.Map[mat].Blast2ObjectRatio,
662 pxsamount * ::MaterialMap.Map[mat].Blast2PXSRatio);
663 }
664 }
665 }
666
DigMaterial2Objects(int32_t tx,int32_t ty,C4MaterialList * mat_list,C4Object * pCollect)667 void C4Landscape::P::DigMaterial2Objects(int32_t tx, int32_t ty, C4MaterialList *mat_list, C4Object *pCollect)
668 {
669 C4AulParSet pars(pCollect);
670 for (int32_t mat = 0; mat < ::MaterialMap.Num; mat++)
671 {
672 if (mat_list->Amount[mat])
673 {
674 if (::MaterialMap.Map[mat].Dig2Object != C4ID::None)
675 if (::MaterialMap.Map[mat].Dig2ObjectRatio != 0)
676 while (mat_list->Amount[mat] >= ::MaterialMap.Map[mat].Dig2ObjectRatio)
677 {
678 mat_list->Amount[mat] -= ::MaterialMap.Map[mat].Dig2ObjectRatio;
679 C4Object *pObj = Game.CreateObject(::MaterialMap.Map[mat].Dig2Object, nullptr, NO_OWNER, tx, ty);
680 if (!pObj || !pObj->Status) continue;
681 // Set controller to the controller of the object responsible for digging out
682 if (pCollect && pCollect->Status)
683 pObj->Controller = pCollect->Controller;
684 // Do callbacks to dug object and digger
685 pObj->Call(PSF_OnDugOut, &pars);
686 if (!pObj->Status || !pCollect || !pCollect->Status || pObj->Contained) continue;
687 C4AulParSet pars(C4VObj(pObj));
688 pCollect->Call(PSF_DigOutObject, &pars);
689 if (!pObj->Status || !pCollect->Status || pObj->Contained) continue;
690 // Try to collect object
691 if (::MaterialMap.Map[mat].Dig2ObjectCollect)
692 if (pCollect && pCollect->Status && pObj && pObj->Status)
693 if (!pCollect->Collect(pObj))
694 // Collection forced? Don't generate objects
695 if (::MaterialMap.Map[mat].Dig2ObjectCollect == 2)
696 {
697 pObj->AssignRemoval();
698 // Cap so we never have more than one object worth of material in the store
699 mat_list->Amount[mat] = ::MaterialMap.Map[mat].Dig2ObjectRatio;
700 break;
701 }
702 }
703 }
704 }
705 }
706
DigFreePixNoInstability(C4Landscape * d,int32_t tx,int32_t ty)707 bool C4Landscape::P::DigFreePixNoInstability(C4Landscape *d, int32_t tx, int32_t ty)
708 {
709 int32_t mat = d->GetMat(tx, ty);
710 if (MatValid(mat))
711 if (::MaterialMap.Map[mat].DigFree)
712 {
713 d->ClearPix(tx, ty);
714 return true;
715 }
716 return false;
717 }
718
DigFreePix(C4Landscape * d,int32_t tx,int32_t ty)719 bool C4Landscape::P::DigFreePix(C4Landscape *d, int32_t tx, int32_t ty)
720 {
721 int32_t mat = d->GetMat(tx, ty);
722 if (MatValid(mat))
723 if (::MaterialMap.Map[mat].DigFree)
724 {
725 d->ClearPix(tx, ty);
726 // check for instable materials to start moving by the cleared space
727 d->CheckInstabilityRange(tx, ty);
728 return true;
729 }
730 return false;
731 }
732
BlastFreePix(C4Landscape * d,int32_t tx,int32_t ty)733 bool C4Landscape::P::BlastFreePix(C4Landscape *d, int32_t tx, int32_t ty)
734 {
735 int32_t mat = d->GetMat(tx, ty);
736 if (MatValid(mat))
737 {
738 // for blast, either shift to different material or blast free
739 if (::MaterialMap.Map[mat].BlastFree)
740 {
741 d->ClearPix(tx, ty);
742 // check for instable materials to start moving by the cleared space
743 d->CheckInstabilityRange(tx, ty);
744 return true;
745 }
746 else
747 if (::MaterialMap.Map[mat].BlastShiftTo)
748 d->SetPix2(tx, ty, MatTex2PixCol(::MaterialMap.Map[mat].BlastShiftTo), Transparent);
749 }
750 return false;
751 }
752
ShakeFreePix(C4Landscape * d,int32_t tx,int32_t ty)753 bool C4Landscape::P::ShakeFreePix(C4Landscape *d, int32_t tx, int32_t ty)
754 {
755 int32_t mat = d->GetMat(tx, ty);
756 if (MatValid(mat))
757 {
758 if (::MaterialMap.Map[mat].DigFree)
759 {
760 d->ClearPix(tx, ty);
761 ::PXS.Create(mat, itofix(tx), itofix(ty));
762 // check for instable materials to start moving by the cleared space
763 d->CheckInstabilityRange(tx, ty);
764 return true;
765 }
766 }
767 return false;
768 }
769
ClearPix(int32_t tx,int32_t ty)770 bool C4Landscape::ClearPix(int32_t tx, int32_t ty)
771 {
772 // Replace pixel with background pixel
773 BYTE bkgPix = p->Surface8Bkg->GetPix(tx, ty);
774 return SetPix2(tx, ty, bkgPix, bkgPix);
775 }
776
ClearPointers(C4Object * pObj)777 void C4Landscape::ClearPointers(C4Object * pObj)
778 {
779 if (p->pFoW) p->pFoW->Remove(pObj);
780 }
781
SetPix2(int32_t x,int32_t y,BYTE fgPix,BYTE bgPix)782 bool C4Landscape::SetPix2(int32_t x, int32_t y, BYTE fgPix, BYTE bgPix)
783 {
784 // check bounds
785 if (x < 0 || y < 0 || x >= GetWidth() || y >= GetHeight())
786 return false;
787 // no change?
788 if ((fgPix == Transparent || fgPix == _GetPix(x, y)) && (bgPix == Transparent || bgPix == p->Surface8Bkg->_GetPix(x, y)))
789 return true;
790 // set pixel
791 return _SetPix2(x, y, fgPix, bgPix);
792 }
793
_SetPix2(int32_t x,int32_t y,BYTE fgPix,BYTE bgPix)794 bool C4Landscape::_SetPix2(int32_t x, int32_t y, BYTE fgPix, BYTE bgPix)
795 {
796 if (Config.General.DebugRec)
797 {
798 C4RCSetPix rc;
799 rc.x = x; rc.y = y; rc.clr = fgPix; rc.bgClr = fgPix;
800 AddDbgRec(RCT_SetPix, &rc, sizeof(rc));
801 }
802 assert(x >= 0 && y >= 0 && x < GetWidth() && y < GetHeight());
803 // get pixel and resolve transparency to already existing pixel
804 BYTE opix = _GetPix(x, y);
805 if (fgPix == Transparent) fgPix = opix;
806 if (bgPix == Transparent) bgPix = p->Surface8Bkg->_GetPix(x, y);
807 // check pixel
808 if (fgPix == opix && bgPix == p->Surface8Bkg->_GetPix(x, y)) return true;
809 // count pixels
810 if (p->Pix2Dens[fgPix])
811 {
812 if (!p->Pix2Dens[opix]) p->PixCnt[(y / 15) + (x / 17) * p->PixCntPitch]++;
813 }
814 else
815 {
816 if (p->Pix2Dens[opix]) p->PixCnt[(y / 15) + (x / 17) * p->PixCntPitch]--;
817 }
818 // count material
819 assert(!fgPix || MatValid(p->Pix2Mat[fgPix]));
820 int32_t omat = p->Pix2Mat[opix], nmat = p->Pix2Mat[fgPix];
821 if (opix) p->MatCount[omat]--;
822 if (fgPix) p->MatCount[nmat]++;
823 // count effective material
824 if (omat != nmat)
825 {
826 if (fgPix && ::MaterialMap.Map[nmat].MinHeightCount)
827 {
828 // Check for material above & below
829 int iMinHeight = ::MaterialMap.Map[nmat].MinHeightCount,
830 iBelow = GetMatHeight(x, y + 1, +1, nmat, iMinHeight),
831 iAbove = GetMatHeight(x, y - 1, -1, nmat, iMinHeight);
832 // Will be above treshold?
833 if (iBelow + iAbove + 1 >= iMinHeight)
834 {
835 int iChange = 1;
836 // Check for heights below threshold
837 if (iBelow < iMinHeight) iChange += iBelow;
838 if (iAbove < iMinHeight) iChange += iAbove;
839 // Change
840 p->EffectiveMatCount[nmat] += iChange;
841 }
842 }
843 if (opix && ::MaterialMap.Map[omat].MinHeightCount)
844 {
845 // Check for material above & below
846 int iMinHeight = ::MaterialMap.Map[omat].MinHeightCount,
847 iBelow = GetMatHeight(x, y + 1, +1, omat, iMinHeight),
848 iAbove = GetMatHeight(x, y - 1, -1, omat, iMinHeight);
849 // Not already below threshold?
850 if (iBelow + iAbove + 1 >= iMinHeight)
851 {
852 int iChange = 1;
853 // Check for heights that will get below threshold
854 if (iBelow < iMinHeight) iChange += iBelow;
855 if (iAbove < iMinHeight) iChange += iAbove;
856 // Change
857 p->EffectiveMatCount[omat] -= iChange;
858 }
859 }
860 }
861 // set 8bpp-surface only!
862 p->Surface8->SetPix(x, y, fgPix);
863 p->Surface8Bkg->SetPix(x, y, bgPix);
864 // note for relight
865 if (p->pLandscapeRender)
866 {
867 C4Rect CheckRect = p->pLandscapeRender->GetAffectedRect(C4Rect(x, y, 1, 1));
868 for (int32_t i = 0; i < C4LS_MaxRelights; i++)
869 if (!p->Relights[i].Wdt || p->Relights[i].Overlap(CheckRect) || i + 1 >= C4LS_MaxRelights)
870 {
871 p->Relights[i].Add(CheckRect);
872 break;
873 }
874 // Invalidate FoW
875 if (p->pFoW)
876 p->pFoW->Invalidate(CheckRect);
877 }
878 // success
879 return true;
880 }
881
_SetPix2Tmp(int32_t x,int32_t y,BYTE fgPix,BYTE bgPix)882 void C4Landscape::_SetPix2Tmp(int32_t x, int32_t y, BYTE fgPix, BYTE bgPix)
883 {
884 // set 8bpp-surface only!
885 assert(x >= 0 && y >= 0 && x < GetWidth() && y < GetHeight());
886 if (fgPix != Transparent) p->Surface8->SetPix(x, y, fgPix);
887 if (bgPix != Transparent) p->Surface8Bkg->SetPix(x, y, bgPix);
888 }
889
CheckInstability(int32_t tx,int32_t ty,int32_t recursion_count)890 bool C4Landscape::CheckInstability(int32_t tx, int32_t ty, int32_t recursion_count)
891 {
892 int32_t mat = GetMat(tx, ty);
893 if (MatValid(mat)) {
894 const C4Material &material = MaterialMap.Map[mat];
895 if (material.Instable)
896 return ::MassMover.Create(tx, ty);
897 // Get rid of single pixels
898 else if (DensitySolid(material.Density) && !material.KeepSinglePixels && recursion_count < 10)
899 if ((!::GBackSolid(tx, ty + 1)) + (!::GBackSolid(tx, ty - 1)) + (!::GBackSolid(tx + 1, ty)) + (!::GBackSolid(tx - 1, ty)) >= 3)
900 {
901 if (!ClearPix(tx, ty)) return false;
902 // Diggable material drops; other material just gets removed
903 if (material.DigFree) ::PXS.Create(mat, itofix(tx), itofix(ty));
904 // check other pixels around this
905 // Note this cannot lead to an endless recursion (unless you do funny stuff like e.g. set DigFree=1 in material Tunnel).
906 // Check recursion anyway, because very large strips of single pixel width might cause sufficient recursion to crash
907 CheckInstability(tx + 1, ty, ++recursion_count);
908 CheckInstability(tx - 1, ty, recursion_count);
909 CheckInstability(tx, ty - 1, recursion_count);
910 CheckInstability(tx, ty + 1, recursion_count);
911 return true;
912 }
913 }
914 return false;
915 }
916
CheckInstabilityRange(int32_t tx,int32_t ty)917 void C4Landscape::CheckInstabilityRange(int32_t tx, int32_t ty)
918 {
919 if (!CheckInstability(tx, ty))
920 {
921 CheckInstability(tx, ty - 1);
922 CheckInstability(tx, ty - 2);
923 CheckInstability(tx - 1, ty);
924 CheckInstability(tx + 1, ty);
925 }
926 }
DrawMaterialRect(int32_t mat,int32_t tx,int32_t ty,int32_t wdt,int32_t hgt)927 void C4Landscape::DrawMaterialRect(int32_t mat, int32_t tx, int32_t ty, int32_t wdt, int32_t hgt)
928 {
929 int32_t cx, cy;
930 for (cy = ty; cy < ty + hgt; cy++)
931 for (cx = tx; cx < tx + wdt; cx++)
932 if ((MatDensity(mat) >= GetDensity(cx, cy)))
933 SetPix2(cx, cy, Mat2PixColDefault(mat), p->Surface8Bkg->GetPix(cx, cy));
934 }
935
RaiseTerrain(int32_t tx,int32_t ty,int32_t wdt)936 void C4Landscape::RaiseTerrain(int32_t tx, int32_t ty, int32_t wdt)
937 {
938 int32_t cx, cy;
939 BYTE cpix;
940 for (cx = tx; cx < tx + wdt; cx++)
941 {
942 for (cy = ty; (cy + 1 < ::Landscape.GetHeight()) && !GBackSolid(cx, cy + 1); cy++) {}
943 if (cy + 1 < ::Landscape.GetHeight()) if (cy - ty < 20)
944 {
945 cpix = GetPix(cx, cy + 1);
946 if (!MatVehicle(PixCol2Mat(cpix)))
947 while (cy >= ty) { SetPix2(cx, cy, cpix, GetBackPix(cx, cy + 1)); cy--; }
948 }
949 }
950 }
951
952
ExtractMaterial(int32_t fx,int32_t fy,bool distant_first)953 int32_t C4Landscape::ExtractMaterial(int32_t fx, int32_t fy, bool distant_first)
954 {
955 int32_t mat = GetMat(fx, fy);
956 if (mat == MNone) return MNone;
957 FindMatTop(mat, fx, fy, distant_first);
958 ClearPix(fx, fy);
959 CheckInstabilityRange(fx, fy);
960 return mat;
961 }
962
InsertMaterialOutsideLandscape(int32_t tx,int32_t ty,int32_t mdens)963 bool C4Landscape::InsertMaterialOutsideLandscape(int32_t tx, int32_t ty, int32_t mdens)
964 {
965 // Out-of-bounds insertion considered successful if inserted into same or lower density
966 // This ensures pumping out of map works
967 // Do allow insertion into same density because it covers the case of e.g. pumping water into the upper ocean of an underwater scenario
968 return GetDensity(tx, ty) <= mdens;
969 }
970
InsertMaterial(int32_t mat,int32_t * tx,int32_t * ty,int32_t vx,int32_t vy,bool query_only)971 bool C4Landscape::InsertMaterial(int32_t mat, int32_t *tx, int32_t *ty, int32_t vx, int32_t vy, bool query_only)
972 {
973 assert(tx); assert(ty);
974 int32_t mdens;
975 if (!MatValid(mat)) return false;
976 mdens = std::min(MatDensity(mat), C4M_Solid);
977 if (!mdens) return true;
978
979 // Bounds
980 if (!Inside<int32_t>(*tx, 0, GetWidth() - 1) || !Inside<int32_t>(*ty, 0, GetHeight())) return InsertMaterialOutsideLandscape(*tx, *ty, mdens);
981
982 if (Game.C4S.Game.Realism.LandscapePushPull)
983 {
984 // Same or higher density?
985 if (GetDensity(*tx, *ty) >= mdens)
986 // Push
987 if (!FindMatPathPush(*tx, *ty, mdens, ::MaterialMap.Map[mat].MaxSlide, !!::MaterialMap.Map[mat].Instable))
988 // Or die
989 return false;
990 }
991 else
992 {
993 // Move up above same density
994 while (mdens == std::min(GetDensity(*tx, *ty), C4M_Solid))
995 {
996 (*ty)--; if (*ty < 0) return false;
997 // Primitive slide (1)
998 if (GetDensity(*tx - 1, *ty) < mdens) (*tx)--;
999 if (GetDensity(*tx + 1, *ty) < mdens) (*tx)++;
1000 }
1001 // Stuck in higher density
1002 if (GetDensity(*tx, *ty) > mdens) return false;
1003 }
1004
1005 // Try slide
1006 while (FindMatSlide(*tx, *ty, +1, mdens, ::MaterialMap.Map[mat].MaxSlide))
1007 if (GetDensity(*tx, *ty + 1) < mdens)
1008 {
1009 if (!query_only)
1010 return ::PXS.Create(mat, itofix(*tx), itofix(*ty), C4REAL10(vx), C4REAL10(vy));
1011 return true;
1012 }
1013
1014 if (query_only)
1015 {
1016 // since tx and ty changed, we need to re-check the bounds here
1017 // if we really inserted it, the check is made again in InsertDeadMaterial
1018 if (!Inside<int32_t>(*tx, 0, GetWidth() - 1) || !Inside<int32_t>(*ty, 0, GetHeight())) return InsertMaterialOutsideLandscape(*tx, *ty, mdens);
1019 return true;
1020 }
1021
1022 // Try reaction with material below and at insertion position
1023 C4MaterialReaction *pReact; int32_t tmat;
1024 int32_t check_dir = 0;
1025 for (int32_t i = 0; i < 2; ++i)
1026 {
1027 if ((pReact = ::MaterialMap.GetReactionUnsafe(mat, tmat = GetMat(*tx, *ty + check_dir))))
1028 {
1029 C4Real fvx = C4REAL10(vx), fvy = C4REAL10(vy);
1030 if ((*pReact->pFunc)(pReact, *tx, *ty, *tx, *ty + check_dir, fvx, fvy, mat, tmat, meePXSPos, nullptr))
1031 {
1032 // the material to be inserted killed itself in some material reaction below
1033 return true;
1034 }
1035 }
1036 if (!(check_dir = Sign(GravAccel))) break;
1037 }
1038
1039 // Insert as dead material
1040 return InsertDeadMaterial(mat, *tx, *ty);
1041 }
1042
InsertDeadMaterial(int32_t mat,int32_t tx,int32_t ty)1043 bool C4Landscape::InsertDeadMaterial(int32_t mat, int32_t tx, int32_t ty)
1044 {
1045 // Check bounds
1046 if (tx < 0 || ty < 0 || tx >= GetWidth() || ty >= GetHeight())
1047 return InsertMaterialOutsideLandscape(tx, ty, std::min(MatDensity(mat), C4M_Solid));
1048
1049 // Save back original material so we can insert it later
1050 int omat = 0;
1051 if (Game.C4S.Game.Realism.LandscapeInsertThrust)
1052 omat = GetMat(tx, ty);
1053
1054 // Check surroundings for inspiration for texture to use
1055 int n = 0; int pix = -1;
1056 if (tx > 0 && _GetMat(tx - 1, ty) == mat)
1057 if (!Random(++n)) pix = _GetPix(tx - 1, ty);
1058 if (ty > 0 && _GetMat(tx, ty - 1) == mat)
1059 if (!Random(++n)) pix = _GetPix(tx, ty - 1);
1060 if (tx + 1 < GetWidth() && _GetMat(tx + 1, ty) == mat)
1061 if (!Random(++n)) pix = _GetPix(tx + 1, ty);
1062 if (ty + 1 < GetHeight() && _GetMat(tx, ty + 1) == mat)
1063 if (!Random(++n)) pix = _GetPix(tx, ty + 1);
1064 if (pix < 0)
1065 pix = Mat2PixColDefault(mat);
1066
1067 // Insert dead material
1068 SetPix2(tx, ty, pix, Transparent);
1069
1070 // Search a position for the old material pixel
1071 if (Game.C4S.Game.Realism.LandscapeInsertThrust && MatValid(omat))
1072 {
1073 int32_t tyo = ty - 1;
1074 InsertMaterial(omat, &tx, &tyo);
1075 }
1076
1077 return true;
1078 }
1079
Incinerate(int32_t x,int32_t y,int32_t cause_player)1080 bool C4Landscape::Incinerate(int32_t x, int32_t y, int32_t cause_player)
1081 {
1082 int32_t mat = GetMat(x, y);
1083 if (MatValid(mat))
1084 if (::MaterialMap.Map[mat].Inflammable)
1085 {
1086 C4AulParSet pars(C4VInt(x), C4VInt(y), C4VInt(cause_player));
1087 ::ScriptEngine.GetPropList()->Call(P_InflameLandscape, &pars);
1088 }
1089 return false;
1090 }
1091
DefaultBkgMat(BYTE fg) const1092 BYTE C4Landscape::P::DefaultBkgMat(BYTE fg) const
1093 {
1094 return ::TextureMap.DefaultBkgMatTex(fg);
1095 }
1096
CreateDefaultBkgSurface(CSurface8 & sfcFg,bool msbAsIft) const1097 std::unique_ptr<CSurface8> C4Landscape::P::CreateDefaultBkgSurface(CSurface8& sfcFg, bool msbAsIft) const
1098 {
1099 auto sfcBg = std::make_unique<CSurface8>();
1100 if (!sfcBg->Create(sfcFg.Wdt, sfcFg.Hgt))
1101 {
1102 return nullptr;
1103 }
1104
1105 for (int32_t y = 0; y < sfcFg.Hgt; ++y)
1106 {
1107 for (int32_t x = 0; x < sfcFg.Wdt; ++x)
1108 {
1109 BYTE fgPix = sfcFg._GetPix(x, y);
1110 BYTE bgPix;
1111
1112 // If we treat the most significant bit as the IFT flag
1113 // (compatibility option for pre-7.0 maps), then set
1114 // the background pixel to 0 if the foreground does not
1115 // have IFT set, and remove the IFT flag from the
1116 // foreground pixel.
1117 if (msbAsIft)
1118 {
1119 if (fgPix & 0x80)
1120 {
1121 fgPix &= ~0x80;
1122 sfcFg._SetPix(x, y, fgPix);
1123 bgPix = DefaultBkgMat(fgPix);
1124 }
1125 else
1126 {
1127 bgPix = 0;
1128 }
1129 }
1130 else
1131 {
1132 bgPix = DefaultBkgMat(fgPix);
1133 }
1134
1135 sfcBg->_SetPix(x, y, bgPix);
1136 }
1137 }
1138
1139 return sfcBg;
1140 }
1141
1142 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
1143 /* ++++ Polygon drawing code extracted from ALLEGRO by Shawn Hargreaves ++++ */
1144 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
1145
1146 struct CPolyEdge // An edge for the polygon drawer
1147 {
1148 int y; // Current (starting at the top) y position
1149 int bottom; // bottom y position of this edge
1150 int x; // Fixed point x position
1151 int dx; // Fixed point x gradient
1152 int w; // Width of line segment
1153 struct CPolyEdge *prev; // Doubly linked list
1154 struct CPolyEdge *next;
1155 };
1156
1157 #define POLYGON_FIX_SHIFT 16
1158
fill_edge_structure(CPolyEdge * edge,int * i1,int * i2)1159 static void fill_edge_structure(CPolyEdge *edge, int *i1, int *i2)
1160 {
1161 if (i2[1] < i1[1]) // Swap
1162 {
1163 int *t = i1; i1 = i2; i2 = t;
1164 }
1165 edge->y = i1[1];
1166 edge->bottom = i2[1] - 1;
1167 edge->dx = ((i2[0] - i1[0]) << POLYGON_FIX_SHIFT) / (i2[1] - i1[1]);
1168 edge->x = (i1[0] << POLYGON_FIX_SHIFT) + (1 << (POLYGON_FIX_SHIFT - 1)) - 1;
1169 edge->prev = nullptr;
1170 edge->next = nullptr;
1171 if (edge->dx < 0)
1172 edge->x += std::min<int>(edge->dx + (1 << POLYGON_FIX_SHIFT), 0);
1173 edge->w = std::max<int>(Abs(edge->dx) - (1 << POLYGON_FIX_SHIFT), 0);
1174 }
1175
add_edge(CPolyEdge * list,CPolyEdge * edge,int sort_by_x)1176 static CPolyEdge *add_edge(CPolyEdge *list, CPolyEdge *edge, int sort_by_x)
1177 {
1178 CPolyEdge *pos = list;
1179 CPolyEdge *prev = nullptr;
1180 if (sort_by_x)
1181 {
1182 while ((pos) && (pos->x + pos->w / 2 < edge->x + edge->w / 2))
1183 {
1184 prev = pos; pos = pos->next;
1185 }
1186 }
1187 else
1188 {
1189 while ((pos) && (pos->y < edge->y))
1190 {
1191 prev = pos; pos = pos->next;
1192 }
1193 }
1194 edge->next = pos;
1195 edge->prev = prev;
1196 if (pos) pos->prev = edge;
1197 if (prev) { prev->next = edge; return list; }
1198 else return edge;
1199 }
1200
remove_edge(CPolyEdge * list,CPolyEdge * edge)1201 static CPolyEdge *remove_edge(CPolyEdge *list, CPolyEdge *edge)
1202 {
1203 if (edge->next) edge->next->prev = edge->prev;
1204 if (edge->prev) { edge->prev->next = edge->next; return list; }
1205 else return edge->next;
1206 }
1207
1208 // Global polygon quick buffer
1209 const int QuickPolyBufSize = 20;
1210 CPolyEdge QuickPolyBuf[QuickPolyBufSize];
1211
ForPolygon(C4Landscape * d,int * vtcs,int length,const std::function<bool (int32_t,int32_t)> & callback,C4MaterialList * mats_count,uint8_t col,uint8_t colBkg,uint8_t * conversion_table)1212 int32_t C4Landscape::P::ForPolygon(C4Landscape *d, int *vtcs, int length, const std::function<bool(int32_t, int32_t)> &callback,
1213 C4MaterialList *mats_count, uint8_t col, uint8_t colBkg, uint8_t *conversion_table)
1214 {
1215 // Variables for polygon drawer
1216 int c, x1, x2, y;
1217 int top = INT_MAX;
1218 int bottom = INT_MIN;
1219 int *i1, *i2;
1220 CPolyEdge *edge, *next_edge, *edgebuf;
1221 CPolyEdge *active_edges = nullptr;
1222 CPolyEdge *inactive_edges = nullptr;
1223 bool use_qpb = false;
1224
1225 // Return value
1226 int32_t amount = 0;
1227 // Poly Buf
1228 if (length <= QuickPolyBufSize)
1229 {
1230 edgebuf = QuickPolyBuf; use_qpb = true;
1231 }
1232 else if (!(edgebuf = new CPolyEdge[length])) { return 0; }
1233
1234 // Fill the edge table
1235 edge = edgebuf;
1236 i1 = vtcs;
1237 i2 = vtcs + (length - 1) * 2;
1238 for (c = 0; c < length; c++)
1239 {
1240 if (i1[1] != i2[1])
1241 {
1242 fill_edge_structure(edge, i1, i2);
1243 if (edge->bottom >= edge->y)
1244 {
1245 if (edge->y < top) top = edge->y;
1246 if (edge->bottom > bottom) bottom = edge->bottom;
1247 inactive_edges = add_edge(inactive_edges, edge, false);
1248 edge++;
1249 }
1250 }
1251 i2 = i1; i1 += 2;
1252 }
1253
1254 // For each scanline in the polygon...
1255 for (c = top; c <= bottom; c++)
1256 {
1257 // Check for newly active edges
1258 edge = inactive_edges;
1259 while ((edge) && (edge->y == c))
1260 {
1261 next_edge = edge->next;
1262 inactive_edges = remove_edge(inactive_edges, edge);
1263 active_edges = add_edge(active_edges, edge, true);
1264 edge = next_edge;
1265 }
1266
1267 // Draw horizontal line segments
1268 edge = active_edges;
1269 while ((edge) && (edge->next))
1270 {
1271 x1 = edge->x >> POLYGON_FIX_SHIFT;
1272 x2 = (edge->next->x + edge->next->w) >> POLYGON_FIX_SHIFT;
1273 y = c;
1274 // Fix coordinates
1275 if (x1 > x2) std::swap(x1, x2);
1276 // Set line
1277 if (callback)
1278 {
1279 for (int xcnt = x2 - x1 - 1; xcnt >= 0; xcnt--)
1280 {
1281 uint8_t pix = d->GetPix(x1 + xcnt, y);
1282 if (!conversion_table || conversion_table[pix])
1283 {
1284 int32_t mat = d->GetPixMat(pix);
1285 if (callback(x1 + xcnt, y))
1286 if (mats_count)
1287 {
1288 mats_count->Add(mat, 1);
1289 amount++;
1290 }
1291 }
1292 }
1293 }
1294 else if (conversion_table)
1295 for (int xcnt = x2 - x1 - 1; xcnt >= 0; xcnt--)
1296 {
1297 const uint8_t pix = conversion_table[uint8_t(d->GetPix(x1 + xcnt, y))];
1298 Surface8->SetPix(x1 + xcnt, y, pix);
1299 if (colBkg != Transparent) Surface8Bkg->SetPix(x1 + xcnt, y, colBkg);
1300 }
1301 else
1302 for (int xcnt = x2 - x1 - 1; xcnt >= 0; xcnt--)
1303 {
1304 if (col != Transparent) Surface8->SetPix(x1 + xcnt, y, col);
1305 if (colBkg != Transparent) Surface8Bkg->SetPix(x1 + xcnt, y, colBkg);
1306 }
1307 edge = edge->next->next;
1308 }
1309
1310 // Update edges, sorting and removing dead ones
1311 edge = active_edges;
1312 while (edge)
1313 {
1314 next_edge = edge->next;
1315 if (c >= edge->bottom)
1316 {
1317 active_edges = remove_edge(active_edges, edge);
1318 }
1319 else
1320 {
1321 edge->x += edge->dx;
1322 while ((edge->prev) && (edge->x + edge->w / 2 < edge->prev->x + edge->prev->w / 2))
1323 {
1324 if (edge->next) edge->next->prev = edge->prev;
1325 edge->prev->next = edge->next;
1326 edge->next = edge->prev;
1327 edge->prev = edge->prev->prev;
1328 edge->next->prev = edge;
1329 if (edge->prev) edge->prev->next = edge;
1330 else active_edges = edge;
1331 }
1332 }
1333 edge = next_edge;
1334 }
1335 }
1336
1337 // Clear scratch memory
1338 if (!use_qpb) delete[] edgebuf;
1339
1340 return amount;
1341 }
1342
1343 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
1344 /* +++++++++++++++++++++++++ Save, Init and load +++++++++++++++++++++++ */
1345 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
1346
ScenarioInit()1347 void C4Landscape::ScenarioInit()
1348 {
1349 // Gravity
1350 p->Gravity = C4REAL100(Game.C4S.Landscape.Gravity.Evaluate()) * DefaultGravAccel;
1351 }
1352
Clear(bool fClearMapCreator,bool fClearSky,bool fClearRenderer)1353 void C4Landscape::Clear(bool fClearMapCreator, bool fClearSky, bool fClearRenderer)
1354 {
1355 if (fClearMapCreator) { p->pMapCreator.reset(); }
1356 // clear sky
1357 if (fClearSky) p->Sky.Clear();
1358 // clear surfaces, if assigned
1359 if (fClearRenderer) { p->pLandscapeRender.reset(); }
1360 p->TopRowPix.clear();
1361 p->BottomRowPix.clear();
1362 p->LeftColPix.clear();
1363 p->RightColPix.clear();
1364 p->Surface8.reset();
1365 p->Surface8Bkg.reset();
1366 p->Map.reset();
1367 p->MapBkg.reset();
1368 // clear initial landscape
1369 p->pInitial.reset();
1370 p->pInitialBkg.reset();
1371 p->pFoW.reset();
1372 // clear relight array
1373 for (auto &relight : p->Relights)
1374 relight.Default();
1375 // clear scan
1376 p->ScanX = 0;
1377 p->mode = LandscapeMode::Undefined;
1378 // clear pixel count
1379 p->PixCnt.clear();
1380 p->PixCntPitch = 0;
1381 // clear bridge material conversion temp buffers
1382 for (auto &conv : p->BridgeMatConversion)
1383 conv.reset();
1384 }
1385
CompileFunc(StdCompiler * pComp)1386 void C4Landscape::CompileFunc(StdCompiler *pComp)
1387 {
1388 pComp->Value(mkNamingAdapt(p->MapSeed, "MapSeed", 0));
1389 pComp->Value(mkNamingAdapt(mkCastIntAdapt(p->Gravity), "Gravity", DefaultGravAccel));
1390 pComp->Value(mkNamingAdapt(p->Modulation, "MatModulation", 0U));
1391 pComp->Value(mkNamingAdapt(mkCastIntAdapt(p->mode), "Mode", LandscapeMode::Undefined));
1392
1393 if (pComp->isDeserializer())
1394 {
1395 int32_t ambient_brightness;
1396 pComp->Value(mkNamingAdapt(ambient_brightness, "AmbientBrightness", 255));
1397 if (p->pFoW) p->pFoW->Ambient.SetBrightness(ambient_brightness / static_cast<double>(255));
1398 }
1399 else
1400 {
1401 if (p->pFoW)
1402 {
1403 int32_t ambient_brightness = static_cast<int32_t>(p->pFoW->Ambient.GetBrightness() * 255 + 0.5);
1404 pComp->Value(mkNamingAdapt(ambient_brightness, "AmbientBrightness", 255));
1405 }
1406 }
1407 }
1408
GroupReadSurface8(C4Group & hGroup,const char * szWildCard)1409 static std::unique_ptr<CSurface8> GroupReadSurface8(C4Group &hGroup, const char *szWildCard)
1410 {
1411 if (!hGroup.AccessEntry(szWildCard))
1412 return nullptr;
1413 // create surface
1414 auto pSfc = std::make_unique<CSurface8>();
1415 if (!pSfc->Read(hGroup))
1416 {
1417 return nullptr;
1418 }
1419 return pSfc;
1420 }
1421
Init(C4Group & hGroup,bool fOverloadCurrent,bool fLoadSky,bool & rfLoaded,bool fSavegame)1422 bool C4Landscape::Init(C4Group &hGroup, bool fOverloadCurrent, bool fLoadSky, bool &rfLoaded, bool fSavegame)
1423 {
1424 // set map seed, if not pre-assigned
1425 if (!p->MapSeed) p->MapSeed = Random(3133700);
1426
1427 // increase max map size, since developers might set a greater one here
1428 Game.C4S.Landscape.MapWdt.Max = 10000;
1429 Game.C4S.Landscape.MapHgt.Max = 10000;
1430
1431 // map and landscape must be initialized with fixed random, so runtime joining clients may recreate it
1432 // with same seed
1433 // after map/landscape creation, the seed must be fixed again, so there's no difference between clients creating
1434 // and not creating the map
1435 // this, however, would cause syncloss to DebugRecs
1436 C4DebugRecOff DBGRECOFF(!!Game.C4S.Landscape.ExactLandscape);
1437
1438 Game.FixRandom(Game.RandomSeed);
1439
1440 // map is like it's loaded for regular gamestart
1441 // but it's changed and would have to be saved if a new section is loaded
1442 p->fMapChanged = fOverloadCurrent;
1443
1444 // don't change landscape mode in runtime joins
1445 bool fLandscapeModeSet = (p->mode != LandscapeMode::Undefined);
1446
1447 // Make pixel maps
1448 // Pixel maps only depend on loaded materials and textures
1449 // They might be accessed in map scripts, so they should be ready before map creation
1450 UpdatePixMaps();
1451
1452 Game.SetInitProgress(60);
1453 // create map if necessary
1454 if (!Game.C4S.Landscape.ExactLandscape)
1455 {
1456 std::unique_ptr<CSurface8> sfcMap, sfcMapBkg;
1457
1458 // Static map from scenario: Old-style Map.bmp with highest bit IFT
1459 if ((sfcMap = GroupReadSurface8(hGroup, C4CFN_Map)))
1460 {
1461 if (!fLandscapeModeSet) p->mode = LandscapeMode::Static;
1462 sfcMapBkg = p->CreateDefaultBkgSurface(*sfcMap, true);
1463 if (!sfcMapBkg) return false;
1464 }
1465
1466 // Static map from scenario: New-style MapFg.bmp and MapBg.bmp with
1467 // full 255 mat-tex combinations. Background map is optional, if not
1468 // given default will be created with tunnel background for all
1469 // semisolid pixels.
1470 if (!sfcMap)
1471 {
1472 if ((sfcMap = GroupReadSurface8(hGroup, C4CFN_MapFg)))
1473 {
1474 if (!fLandscapeModeSet) p->mode = LandscapeMode::Static;
1475 sfcMapBkg = GroupReadSurface8(hGroup, C4CFN_MapBg);
1476 if (!sfcMapBkg) sfcMapBkg = p->CreateDefaultBkgSurface(*sfcMap, false);
1477 if (!sfcMapBkg) return false;
1478 }
1479 }
1480
1481 // dynamic map from Landscape.txt
1482 CSurface8 *rendered_map = nullptr, *rendered_bkg = nullptr;
1483 if (!sfcMap)
1484 {
1485 if (p->CreateMapS2(hGroup, rendered_map, rendered_bkg))
1486 {
1487 sfcMap.reset(rendered_map);
1488 sfcMapBkg.reset(rendered_bkg);
1489 if (!fLandscapeModeSet) p->mode = LandscapeMode::Dynamic;
1490 }
1491 }
1492
1493 // script may create or edit map
1494 if (MapScript.InitializeMap(&Game.C4S.Landscape, &::TextureMap, &::MaterialMap, Game.StartupPlayerCount, &sfcMap, &sfcMapBkg))
1495 {
1496 if (!fLandscapeModeSet) p->mode = LandscapeMode::Dynamic;
1497 }
1498
1499 // Dynamic map by scenario
1500 if (!sfcMap && !fOverloadCurrent)
1501 {
1502 if (p->CreateMap(rendered_map, rendered_bkg))
1503 {
1504 sfcMap.reset(rendered_map);
1505 sfcMapBkg.reset(rendered_bkg);
1506 // Although this is a dynamic map, it's probably just the empty default map.
1507 // Set the mode to static so people can start drawing directly in the editor.
1508 if (!fLandscapeModeSet) p->mode = LandscapeMode::Static;
1509 }
1510 }
1511
1512 // No map failure
1513 if (!sfcMap)
1514 {
1515 // no problem if only overloading
1516 if (!fOverloadCurrent) return false;
1517 if (fLoadSky) if (!p->Sky.Init(fSavegame)) return false;
1518 return true;
1519 }
1520
1521 assert(sfcMapBkg != nullptr);
1522
1523 if (Config.General.DebugRec)
1524 {
1525 AddDbgRec(RCT_Block, "|---MAP---|", 12);
1526 AddDbgRec(RCT_Map, sfcMap->Bits, sfcMap->Pitch*sfcMap->Hgt);
1527 }
1528
1529 // Store map size and calculate map zoom
1530 int iWdt, iHgt;
1531 sfcMap->GetSurfaceSize(iWdt, iHgt);
1532 p->MapWidth = iWdt; p->MapHeight = iHgt;
1533 p->MapZoom = Game.C4S.Landscape.MapZoom.Evaluate();
1534
1535 // Calculate landscape size
1536 p->Width = p->MapZoom * p->MapWidth;
1537 p->Height = p->MapZoom * p->MapHeight;
1538 p->Width = std::max<int32_t>(p->Width, 100);
1539 p->Height = std::max<int32_t>(p->Height, 100);
1540
1541 // if overloading, clear current landscape (and sections, etc.)
1542 // must clear, of course, before new sky is eventually read
1543 if (fOverloadCurrent) Clear(!Game.C4S.Landscape.KeepMapCreator, fLoadSky, false);
1544
1545 // assign new map
1546 assert(p->Map == nullptr);
1547 assert(p->MapBkg == nullptr);
1548 p->Map = std::move(sfcMap);
1549 p->MapBkg = std::move(sfcMapBkg);
1550
1551 // Sky (might need to know landscape height)
1552 if (fLoadSky)
1553 {
1554 Game.SetInitProgress(70);
1555 if (!p->Sky.Init(fSavegame)) return false;
1556 }
1557 }
1558
1559 // Exact landscape from scenario (no map or exact recreation)
1560 else /* if (Game.C4S.Landscape.ExactLandscape) */
1561 {
1562 C4DebugRecOff DBGRECOFF;
1563 // if overloading, clear current
1564 if (fOverloadCurrent) Clear(!Game.C4S.Landscape.KeepMapCreator, fLoadSky, false);
1565 // load it
1566 if (!fLandscapeModeSet) p->mode = LandscapeMode::Exact;
1567 rfLoaded = true;
1568 if (!Load(hGroup, fLoadSky, fSavegame)) return false;
1569 }
1570
1571 // progress
1572 Game.SetInitProgress(75);
1573
1574 // copy noscan-var
1575 p->NoScan = Game.C4S.Landscape.NoScan != 0;
1576
1577 // Scan settings
1578 p->ScanSpeed = Clamp(GetWidth() / 500, 2, 15);
1579
1580 // Create pixel count array before any SetPix operations may take place
1581 // Proper pixel counts will be done later, but needs to have the arrays redy to avoid dead pointer access.
1582 // We will use 15x17 blocks so the pixel count can't get over 255.
1583 int32_t PixCntWidth = (GetWidth() + 16) / 17;
1584 p->PixCntPitch = (GetHeight() + 14) / 15;
1585 p->PixCnt.resize(PixCntWidth * p->PixCntPitch);
1586
1587 // map to big surface and sectionize it
1588 // (not for shaders though - they require continous textures)
1589 if (!Game.C4S.Landscape.ExactLandscape)
1590 {
1591 assert(p->Surface8 == nullptr);
1592 assert(p->Surface8Bkg == nullptr);
1593
1594 // Create landscape surfaces
1595 {
1596 auto sf8 = std::make_unique<CSurface8>();
1597 auto sfb8 = std::make_unique<CSurface8>();
1598 if (!sf8->Create(GetWidth(), GetHeight()) || !sfb8->Create(GetWidth(), GetHeight()))
1599 return false;
1600 p->Surface8 = std::move(sf8);
1601 p->Surface8Bkg = std::move(sfb8);
1602 if (!p->Mat2Pal())
1603 return false;
1604 }
1605
1606 // Map to landscape
1607 // Landscape render disabled during initial landscape zoom (will be updated later)
1608 std::unique_ptr<C4LandscapeRender> lsrender_backup;
1609 lsrender_backup.swap(p->pLandscapeRender);
1610 bool map2landscape_success = MapToLandscape();
1611 lsrender_backup.swap(p->pLandscapeRender);
1612 if (!map2landscape_success) return false;
1613 }
1614
1615 // Init out-of-landscape pixels for bottom
1616 p->InitBorderPix();
1617
1618 Game.SetInitProgress(80);
1619
1620 if (Config.General.DebugRec)
1621 {
1622 AddDbgRec(RCT_Block, "|---LANDSCAPE---|", 18);
1623 AddDbgRec(RCT_Map, p->Surface8->Bits, p->Surface8->Pitch * p->Surface8->Hgt);
1624
1625 AddDbgRec(RCT_Block, "|---LANDSCAPE BKG---|", 22);
1626 AddDbgRec(RCT_Map, p->Surface8Bkg->Bits, p->Surface8Bkg->Pitch * p->Surface8Bkg->Hgt);
1627 }
1628
1629 // Create FoW
1630 assert(p->pFoW == nullptr);
1631 if (Game.C4S.Game.FoWEnabled)
1632 p->pFoW = std::make_unique<C4FoW>();
1633
1634 // Create renderer
1635 #ifndef USE_CONSOLE
1636 if (!p->pLandscapeRender)
1637 p->pLandscapeRender = std::make_unique<C4LandscapeRenderGL>();
1638 #endif
1639
1640 if (p->pLandscapeRender)
1641 {
1642 // Initialize renderer
1643 if (fOverloadCurrent)
1644 {
1645 if (!p->pLandscapeRender->ReInit(GetWidth(), GetHeight()))
1646 return false;
1647 }
1648 else
1649 {
1650 if (!p->pLandscapeRender->Init(GetWidth(), GetHeight(), &::TextureMap, &::GraphicsResource.Files))
1651 return false;
1652 }
1653 }
1654
1655 // Save initial landscape
1656 if (!SaveInitial())
1657 return false;
1658
1659 // Load diff, if existant
1660 ApplyDiff(hGroup);
1661
1662 // Pixel count tracking from landscape zoom is incomplete, so recalculate it.
1663 p->UpdatePixCnt(this, C4Rect(0, 0, GetWidth(), GetHeight()));
1664 p->ClearMatCount();
1665 p->UpdateMatCnt(this, C4Rect(0, 0, GetWidth(), GetHeight()), true);
1666
1667 // Create initial landscape render data (after applying diff so landscape is complete)
1668 if (p->pLandscapeRender) p->pLandscapeRender->Update(C4Rect(0, 0, GetWidth(), GetHeight()), this);
1669 Game.SetInitProgress(87);
1670
1671 // after map/landscape creation, the seed must be fixed again, so there's no difference between clients creating
1672 // and not creating the map
1673 Game.FixRandom(Game.RandomSeed);
1674
1675 // Create ambient light map after landscape creation
1676 if (p->pFoW) p->pFoW->Ambient.CreateFromLandscape(*this, 10., 50., 0.25);
1677 Game.SetInitProgress(84);
1678
1679 // Success
1680 rfLoaded = true;
1681 return true;
1682 }
1683
HasMap() const1684 bool C4Landscape::HasMap() const
1685 {
1686 return p->Map != nullptr && p->MapBkg != nullptr;
1687 }
1688
Save(C4Group & hGroup) const1689 bool C4Landscape::Save(C4Group &hGroup) const
1690 {
1691 C4SolidMask::RemoveSolidMasks();
1692 bool r = p->SaveInternal(this, hGroup);
1693 C4SolidMask::PutSolidMasks();
1694 return r;
1695 }
1696
SaveInternal(const C4Landscape * d,C4Group & hGroup) const1697 bool C4Landscape::P::SaveInternal(const C4Landscape *d, C4Group &hGroup) const
1698 {
1699 // Save landscape surface
1700 char szTempLandscape[_MAX_PATH + 1];
1701 SCopy(Config.AtTempPath(C4CFN_TempLandscape), szTempLandscape);
1702 MakeTempFilename(szTempLandscape);
1703 if (!Surface8->Save(szTempLandscape))
1704 return false;
1705
1706 // Move temp file to group
1707 if (!hGroup.Move(szTempLandscape, C4CFN_LandscapeFg))
1708 return false;
1709
1710 // Same for background surface
1711 SCopy(Config.AtTempPath(C4CFN_TempLandscapeBkg), szTempLandscape);
1712 MakeTempFilename(szTempLandscape);
1713 if (!Surface8Bkg->Save(szTempLandscape))
1714 return false;
1715 if (!hGroup.Move(szTempLandscape, C4CFN_LandscapeBg))
1716 return false;
1717
1718 // Save map
1719 if (fMapChanged && Map)
1720 if (!d->SaveMap(hGroup))
1721 return false;
1722
1723 // save textures (if changed)
1724 if (!d->SaveTextures(hGroup))
1725 return false;
1726
1727 return true;
1728 }
1729
SaveDiff(C4Group & hGroup,bool fSyncSave) const1730 bool C4Landscape::SaveDiff(C4Group &hGroup, bool fSyncSave) const
1731 {
1732 C4SolidMask::RemoveSolidMasks();
1733 bool r = p->SaveDiffInternal(this, hGroup, fSyncSave);
1734 C4SolidMask::PutSolidMasks();
1735 return r;
1736 }
1737
SaveDiffInternal(const C4Landscape * d,C4Group & hGroup,bool fSyncSave) const1738 bool C4Landscape::P::SaveDiffInternal(const C4Landscape *d, C4Group &hGroup, bool fSyncSave) const
1739 {
1740 assert(pInitial && pInitialBkg);
1741 if (!pInitial || !pInitialBkg) return false;
1742
1743 // If it shouldn't be sync-save: Clear all bytes that have not changed, i.e.
1744 // set them to C4M_MaxTexIndex
1745 bool fChanged = false, fChangedBkg = false;;
1746 if (!fSyncSave)
1747 for (int y = 0; y < Height; y++)
1748 for (int x = 0; x < Width; x++)
1749 {
1750 if (pInitial[y * Width + x] == Surface8->_GetPix(x, y))
1751 Surface8->SetPix(x, y, C4M_MaxTexIndex);
1752 else
1753 fChanged = true;
1754
1755 if (pInitialBkg[y * Width + x] == Surface8Bkg->_GetPix(x, y))
1756 Surface8Bkg->SetPix(x, y, C4M_MaxTexIndex);
1757 else
1758 fChangedBkg = true;
1759 }
1760
1761 if (fSyncSave || fChanged)
1762 {
1763 // Save landscape surface
1764 if (!Surface8->Save(Config.AtTempPath(C4CFN_TempLandscape)))
1765 return false;
1766
1767 // Move temp file to group
1768 if (!hGroup.Move(Config.AtTempPath(C4CFN_TempLandscape),
1769 C4CFN_DiffLandscape))
1770 return false;
1771 }
1772
1773 if (fSyncSave || fChangedBkg)
1774 {
1775 // Save landscape surface
1776 if (!Surface8Bkg->Save(Config.AtTempPath(C4CFN_TempLandscapeBkg)))
1777 return false;
1778
1779 // Move temp file to group
1780 if (!hGroup.Move(Config.AtTempPath(C4CFN_TempLandscapeBkg),
1781 C4CFN_DiffLandscapeBkg))
1782 return false;
1783 }
1784
1785 // Restore landscape pixels
1786 if (!fSyncSave)
1787 for (int y = 0; y < Height; y++)
1788 for (int x = 0; x < Width; x++)
1789 {
1790 if (Surface8->_GetPix(x, y) == C4M_MaxTexIndex)
1791 Surface8->SetPix(x, y, pInitial[y * Width + x]);
1792 if (Surface8Bkg->_GetPix(x, y) == C4M_MaxTexIndex)
1793 Surface8Bkg->SetPix(x, y, pInitialBkg[y * Width + x]);
1794 }
1795
1796 // Save changed map, too
1797 if (fMapChanged && Map)
1798 if (!d->SaveMap(hGroup)) return false;
1799
1800 // and textures (if changed)
1801 if (!d->SaveTextures(hGroup)) return false;
1802
1803 return true;
1804 }
1805
SaveInitial()1806 bool C4Landscape::SaveInitial()
1807 {
1808
1809 // Create array
1810 p->pInitial = std::make_unique<BYTE[]>(GetWidth() * GetHeight());
1811 p->pInitialBkg = std::make_unique<BYTE[]>(GetWidth() * GetHeight());
1812
1813 // Save material data
1814 for (int y = 0; y < GetHeight(); y++)
1815 {
1816 const int pitch = y * GetWidth();
1817 for (int x = 0; x < GetWidth(); x++)
1818 {
1819 p->pInitial[pitch + x] = p->Surface8->_GetPix(x, y);
1820 p->pInitialBkg[pitch + x] = p->Surface8Bkg->_GetPix(x, y);
1821 }
1822 }
1823
1824 return true;
1825 }
1826
Load(C4Group & hGroup,bool fLoadSky,bool fSavegame)1827 bool C4Landscape::Load(C4Group &hGroup, bool fLoadSky, bool fSavegame)
1828 {
1829 assert(!p->Surface8 && !p->Surface8Bkg);
1830
1831 // Load exact landscape from group
1832 if ((p->Surface8 = GroupReadSurface8(hGroup, C4CFN_Landscape)) == nullptr)
1833 {
1834 if ((p->Surface8 = GroupReadSurface8(hGroup, C4CFN_LandscapeFg)) == nullptr) return false;
1835 p->Surface8Bkg = GroupReadSurface8(hGroup, C4CFN_LandscapeBg);
1836
1837 if (p->Surface8Bkg)
1838 {
1839 if (p->Surface8->Wdt != p->Surface8Bkg->Wdt || p->Surface8->Hgt != p->Surface8Bkg->Hgt)
1840 {
1841 LogFatal(FormatString("Landscape has different dimensions than background landscape (%dx%d vs. %dx%d)", p->Surface8->Wdt, p->Surface8->Hgt, p->Surface8Bkg->Wdt, p->Surface8Bkg->Hgt).getData());
1842 return false;
1843 }
1844 }
1845 else
1846 {
1847 // LandscapeFg.bmp loaded: Assume full 8bit mat-tex values
1848 // when creating background surface.
1849 p->Surface8Bkg = p->CreateDefaultBkgSurface(*p->Surface8, false);
1850 }
1851 }
1852 else
1853 {
1854 // Landscape.bmp loaded: Assume msb is IFT flag when creating
1855 // background surface.
1856 p->Surface8Bkg = p->CreateDefaultBkgSurface(*p->Surface8, true);
1857 }
1858
1859 int iWidth, iHeight;
1860 p->Surface8->GetSurfaceSize(iWidth, iHeight);
1861 p->Width = iWidth; p->Height = iHeight;
1862
1863 // adjust pal
1864 if (!p->Mat2Pal()) return false;
1865 // Landscape should be in correct format: Make sure it is!
1866 for (int32_t y = 0; y < GetHeight(); ++y)
1867 for (int32_t x = 0; x < GetWidth(); ++x)
1868 {
1869 BYTE byPix = p->Surface8->_GetPix(x, y);
1870 int32_t iMat = PixCol2Mat(byPix);
1871
1872 if (byPix && !MatValid(iMat))
1873 {
1874 LogFatal(FormatString("Landscape loading error at (%d/%d): Pixel value %d not a valid material!", (int)x, (int)y, (int)byPix).getData());
1875 return false;
1876 }
1877
1878 BYTE byPixBkg = p->Surface8Bkg->_GetPix(x, y);
1879 int32_t iMatBkg = PixCol2Mat(byPixBkg);
1880
1881 if (byPixBkg && !MatValid(iMatBkg))
1882 {
1883 LogFatal(FormatString("Background Landscape loading error at (%d/%d): Pixel value %d not a valid material!", (int)x, (int)y, (int)byPixBkg).getData());
1884 return false;
1885 }
1886 }
1887
1888 // Init sky
1889 if (fLoadSky)
1890 {
1891 Game.SetInitProgress(70);
1892 if (p->Sky.Init(fSavegame)) return false;
1893 }
1894 // Success
1895 return true;
1896 }
ApplyDiff(C4Group & hGroup)1897 bool C4Landscape::ApplyDiff(C4Group &hGroup)
1898 {
1899 std::unique_ptr<CSurface8> pDiff, pDiffBkg;
1900 // Load diff landscape from group
1901 pDiff = GroupReadSurface8(hGroup, C4CFN_DiffLandscape);
1902 pDiffBkg = GroupReadSurface8(hGroup, C4CFN_DiffLandscapeBkg);
1903 if (pDiff == nullptr && pDiffBkg == nullptr) return false;
1904
1905 // convert all pixels: keep if same material; re-set if different material
1906 BYTE byPix;
1907 for (int32_t y = 0; y < GetHeight(); ++y) for (int32_t x = 0; x < GetWidth(); ++x)
1908 {
1909 if (pDiff && pDiff->GetPix(x, y) != C4M_MaxTexIndex)
1910 if (p->Surface8->_GetPix(x, y) != (byPix = pDiff->_GetPix(x, y)))
1911 // material has changed here: readjust with new texture
1912 p->Surface8->SetPix(x, y, byPix);
1913 if (pDiffBkg && pDiffBkg->GetPix(x, y) != C4M_MaxTexIndex)
1914 if (p->Surface8Bkg->_GetPix(x, y) != (byPix = pDiffBkg->_GetPix(x, y)))
1915 p->Surface8Bkg->_SetPix(x, y, byPix);
1916 }
1917
1918 // done
1919 return true;
1920 }
1921
Default()1922 void C4Landscape::Default()
1923 {
1924 p = std::make_unique<P>();
1925 }
1926
ClearMatCount()1927 void C4Landscape::P::ClearMatCount()
1928 {
1929 std::fill(MatCount.begin(), MatCount.end(), 0);
1930 std::fill(EffectiveMatCount.begin(), EffectiveMatCount.end(), 0);
1931 }
1932
Synchronize()1933 void C4Landscape::Synchronize()
1934 {
1935 p->ScanX = 0;
1936 }
1937
1938
PixCol2Mat(BYTE pixc)1939 int32_t PixCol2Mat(BYTE pixc)
1940 {
1941 // Get texture
1942 int32_t iTex = PixCol2Tex(pixc);
1943 if (!iTex) return MNone;
1944 // Get material-texture mapping
1945 const C4TexMapEntry *pTex = ::TextureMap.GetEntry(iTex);
1946 // Return material
1947 return pTex ? pTex->GetMaterialIndex() : MNone;
1948 }
1949
SaveMap(C4Group & hGroup) const1950 bool C4Landscape::SaveMap(C4Group &hGroup) const
1951 {
1952 // No map
1953 if (!p->Map) return false;
1954 assert(p->MapBkg != nullptr);
1955
1956 // Create map palette
1957 CStdPalette Palette;
1958 ::TextureMap.StoreMapPalette(&Palette, ::MaterialMap);
1959
1960 // Save map surface
1961 if (!p->Map->Save(Config.AtTempPath(C4CFN_TempMapFg), &Palette))
1962 return false;
1963
1964 // Move temp file to group
1965 if (!hGroup.Move(Config.AtTempPath(C4CFN_TempMapFg),
1966 C4CFN_MapFg))
1967 return false;
1968
1969 // Save background map surface
1970 if (!p->MapBkg->Save(Config.AtTempPath(C4CFN_TempMapBg), &Palette))
1971 return false;
1972
1973 // Move temp file to group
1974 if (!hGroup.Move(Config.AtTempPath(C4CFN_TempMapBg),
1975 C4CFN_MapBg))
1976 return false;
1977
1978 // Success
1979 return true;
1980 }
1981
SaveTextures(C4Group & hGroup) const1982 bool C4Landscape::SaveTextures(C4Group &hGroup) const
1983 {
1984 // if material-texture-combinations have been added, write the texture map
1985 if (::TextureMap.fEntriesAdded)
1986 {
1987 C4Group *pMatGroup = new C4Group();
1988 bool fSuccess = false;
1989 // create local material group
1990 if (!hGroup.FindEntry(C4CFN_Material))
1991 {
1992 // delete previous item at temp path
1993 EraseItem(Config.AtTempPath(C4CFN_Material));
1994 // create at temp path
1995 if (pMatGroup->Open(Config.AtTempPath(C4CFN_Material), true))
1996 // write to it
1997 if (::TextureMap.SaveMap(*pMatGroup, C4CFN_TexMap))
1998 // close (flush)
1999 if (pMatGroup->Close())
2000 // add it
2001 if (hGroup.Move(Config.AtTempPath(C4CFN_Material), C4CFN_Material))
2002 fSuccess = true;
2003 // temp group must remain for scenario file closure
2004 // it will be deleted when the group is closed
2005 }
2006 else
2007 // simply write it to the local material file
2008 if (pMatGroup->OpenAsChild(&hGroup, C4CFN_Material))
2009 fSuccess = ::TextureMap.SaveMap(*pMatGroup, C4CFN_TexMap);
2010 // close material group again
2011 if (pMatGroup->IsOpen()) pMatGroup->Close();
2012 delete pMatGroup;
2013 // fail if unsuccessful
2014 if (!fSuccess) return false;
2015 }
2016 // done, success
2017 return true;
2018 }
2019
InitBorderPix()2020 bool C4Landscape::P::InitBorderPix()
2021 {
2022 assert(Width > 0);
2023 // Init Top-/BottomRowPix array, which determines if out-of-landscape pixels on top/bottom side of the map are solid or not
2024 // In case of Top-/BottomOpen=2, unit by map and not landscape to avoid runtime join sync losses
2025 if (!Width) return true;
2026 TopRowPix.clear();
2027 BottomRowPix.clear();
2028 LeftColPix.clear();
2029 RightColPix.clear();
2030 // must access Game.C4S here because Landscape.TopOpen / Landscape.BottomOpen may not be initialized yet
2031 // why is there a local copy of that static variable anyway?
2032 int32_t top_open_flag = Game.C4S.Landscape.TopOpen;
2033 int32_t bottom_open_flag = Game.C4S.Landscape.BottomOpen;
2034 if (top_open_flag == 2 && !Map) top_open_flag = 1;
2035 if (bottom_open_flag == 2 && !Map) bottom_open_flag = 1;
2036
2037 // Init TopRowPix
2038 switch (top_open_flag)
2039 {
2040 // TopOpen=0: Top is closed
2041 case 0: TopRowPix.assign(Width, MCVehic); break;
2042 // TopOpen=2: Top is open when pixel below has sky background
2043 case 2:
2044 TopRowPix.resize(Width);
2045 for (int32_t x = 0; x < Width; ++x)
2046 {
2047 uint8_t map_pix = MapBkg->GetPix(x / MapZoom, 0);
2048 TopRowPix[x] = ((map_pix != 0) ? MCVehic : 0);
2049 }
2050 break;
2051 // TopOpen=1: Top is open
2052 default: TopRowPix.assign(Width, 0); break;
2053 }
2054
2055 // Init BottomRowPix
2056 switch (bottom_open_flag)
2057 {
2058 // BottomOpen=0: Bottom is closed
2059 case 0: BottomRowPix.assign(Width, MCVehic); break;
2060 // BottomOpen=2: Bottom is open when pixel above has sky background
2061 case 2:
2062 BottomRowPix.resize(Width);
2063 for (int32_t x = 0; x < Width; ++x)
2064 {
2065 uint8_t map_pix = MapBkg->GetPix(x / MapZoom, Map->Hgt - 1);
2066 BottomRowPix[x] = ((map_pix != 0) ? MCVehic : 0);
2067 }
2068 break;
2069 // BottomOpen=1: Bottom is open
2070 default: BottomRowPix.assign(Width, 0); break;
2071 }
2072
2073 if (Game.C4S.Landscape.AutoScanSideOpen)
2074 {
2075 // Compatibility: check both foreground and background material per
2076 // default, Top/BottomOpen=2-like behavior with AutoScanSideOpen=2.
2077 bool only_bg = Game.C4S.Landscape.AutoScanSideOpen == 2;
2078 LeftColPix.resize(Height);
2079 RightColPix.resize(Height);
2080 uint8_t map_pix;
2081 for (int32_t y = 0; y < Height; ++y)
2082 {
2083 map_pix = MapBkg->GetPix(0, y / MapZoom);
2084 if (!only_bg) map_pix += Map->GetPix(0, y / MapZoom);
2085 LeftColPix[y] = ((map_pix != 0) ? MCVehic : 0);
2086 map_pix = MapBkg->GetPix(Map->Wdt - 1, y / MapZoom);
2087 if (!only_bg) map_pix += Map->GetPix(Map->Wdt - 1, y / MapZoom);
2088 RightColPix[y] = ((map_pix != 0) ? MCVehic : 0);
2089 }
2090 }
2091 else
2092 {
2093 int32_t LeftOpen = std::min(Height, Game.C4S.Landscape.LeftOpen);
2094 int32_t RightOpen = std::min(Height, Game.C4S.Landscape.RightOpen);
2095 LeftColPix.assign(Height, MCVehic);
2096 RightColPix.assign(Height, MCVehic);
2097 for (int32_t cy = 0; cy < LeftOpen; cy++)
2098 LeftColPix[cy] = 0;
2099 for (int32_t cy = 0; cy < RightOpen; cy++)
2100 RightColPix[cy] = 0;
2101 }
2102
2103 return true;
2104 }
2105
MapToLandscape()2106 bool C4Landscape::MapToLandscape()
2107 {
2108 // zoom map to landscape
2109 return p->MapToLandscape(this, *p->Map, *p->MapBkg, 0, 0, p->MapWidth, p->MapHeight);
2110 }
2111
2112
ChunkyRandom(uint32_t & iOffset,uint32_t iRange) const2113 uint32_t C4Landscape::P::ChunkyRandom(uint32_t & iOffset, uint32_t iRange) const
2114 {
2115 if (!iRange) return 0;
2116 iOffset = (iOffset * 16807) % 2147483647;
2117 return (iOffset ^ MapSeed) % iRange;
2118 }
2119
DrawChunk(C4Landscape * d,int32_t tx,int32_t ty,int32_t wdt,int32_t hgt,uint8_t mcol,uint8_t mcolBkg,C4MaterialCoreShape Shape,uint32_t cro)2120 void C4Landscape::P::DrawChunk(C4Landscape *d, int32_t tx, int32_t ty, int32_t wdt, int32_t hgt, uint8_t mcol, uint8_t mcolBkg, C4MaterialCoreShape Shape, uint32_t cro)
2121 {
2122 unsigned int top_rough = 0, side_rough = 0, bottom_rough = 0;
2123 // what to do?
2124 switch (Shape)
2125 {
2126 case C4M_Flat: case C4M_Octagon:
2127 if (mcol != Transparent) Surface8->Box(tx, ty, tx + wdt, ty + hgt, mcol);
2128 if (mcolBkg != Transparent) Surface8Bkg->Box(tx, ty, tx + wdt, ty + hgt, mcolBkg);
2129 return;
2130 case C4M_TopFlat:
2131 top_rough = 0; side_rough = 2; bottom_rough = 4;
2132 break;
2133 case C4M_Smooth:
2134 top_rough = 2; side_rough = 2; bottom_rough = 2;
2135 break;
2136 case C4M_Rough:
2137 top_rough = 4; side_rough = 4; bottom_rough = 4;
2138 break;
2139 case C4M_Smoother:
2140 top_rough = 1; side_rough = 1; bottom_rough = 1;
2141 break;
2142 }
2143 int vtcs[16];
2144 unsigned int rx = std::max(wdt / 2, 1);
2145
2146 vtcs[0] = tx - ChunkyRandom(cro, rx * side_rough / 4); vtcs[1] = ty - ChunkyRandom(cro, rx * top_rough / 4);
2147 vtcs[2] = tx - ChunkyRandom(cro, rx * side_rough / 2); vtcs[3] = ty + hgt / 2;
2148 vtcs[4] = tx - ChunkyRandom(cro, rx * side_rough / 4); vtcs[5] = ty + hgt + ChunkyRandom(cro, rx * bottom_rough / 4);
2149 vtcs[6] = tx + wdt / 2; vtcs[7] = ty + hgt + ChunkyRandom(cro, rx * bottom_rough / 2);
2150 vtcs[8] = tx + wdt + ChunkyRandom(cro, rx * side_rough / 4); vtcs[9] = ty + hgt + ChunkyRandom(cro, rx * bottom_rough / 4);
2151 vtcs[10] = tx + wdt + ChunkyRandom(cro, rx * side_rough / 2); vtcs[11] = ty + hgt / 2;
2152 vtcs[12] = tx + wdt + ChunkyRandom(cro, rx * side_rough / 4); vtcs[13] = ty - ChunkyRandom(cro, rx * top_rough / 4);
2153 vtcs[14] = tx + wdt / 2; vtcs[15] = ty - ChunkyRandom(cro, rx * top_rough / 2);
2154
2155 ForPolygon(d, vtcs, 8, nullptr, nullptr, mcol, mcolBkg);
2156 }
2157
DrawSmoothOChunk(C4Landscape * d,int32_t tx,int32_t ty,int32_t wdt,int32_t hgt,uint8_t mcol,uint8_t mcolBkg,int flip,uint32_t cro)2158 void C4Landscape::P::DrawSmoothOChunk(C4Landscape *d, int32_t tx, int32_t ty, int32_t wdt, int32_t hgt, uint8_t mcol, uint8_t mcolBkg, int flip, uint32_t cro)
2159 {
2160 int vtcs[8];
2161 unsigned int rx = std::max(wdt / 2, 1);
2162
2163 vtcs[0] = tx; vtcs[1] = ty;
2164 vtcs[2] = tx; vtcs[3] = ty + hgt;
2165 vtcs[4] = tx + wdt; vtcs[5] = ty + hgt;
2166 vtcs[6] = tx + wdt; vtcs[7] = ty;
2167
2168 switch (flip)
2169 {
2170 case 0: vtcs[0] = tx + wdt / 2; vtcs[1] += hgt / 3; vtcs[7] -= ChunkyRandom(cro, rx / 2); break;
2171 case 1: vtcs[2] = tx + wdt / 2; vtcs[3] -= hgt / 3; vtcs[5] += ChunkyRandom(cro, rx / 2); break;
2172 case 2: vtcs[4] = tx + wdt / 2; vtcs[5] -= hgt / 3; vtcs[3] += ChunkyRandom(cro, rx / 2); break;
2173 case 3: vtcs[6] = tx + wdt / 2; vtcs[7] += hgt / 3; vtcs[1] -= ChunkyRandom(cro, rx / 2); break;
2174 case 4: vtcs[0] = tx + wdt / 2; vtcs[1] += hgt / 2; break;
2175 case 5: vtcs[2] = tx + wdt / 2; vtcs[3] -= hgt / 2; break;
2176 case 6: vtcs[4] = tx + wdt / 2; vtcs[5] -= hgt / 2; break;
2177 case 7: vtcs[6] = tx + wdt / 2; vtcs[7] += hgt / 2; break;
2178 }
2179
2180 ForPolygon(d, vtcs, 4, nullptr, nullptr, mcol, mcolBkg);
2181 }
2182
ChunkOZoom(C4Landscape * d,const CSurface8 & sfcMap,const CSurface8 & sfcMapBkg,int32_t iMapX,int32_t iMapY,int32_t iMapWdt,int32_t iMapHgt,uint8_t iTexture,int32_t iOffX,int32_t iOffY)2183 void C4Landscape::P::ChunkOZoom(C4Landscape *d, const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, uint8_t iTexture, int32_t iOffX, int32_t iOffY)
2184 {
2185 const C4TexMapEntry *entry = ::TextureMap.GetEntry(iTexture);
2186 C4Material *pMaterial = entry->GetMaterial();
2187 if (!pMaterial) return;
2188 const char *texture_name = entry->GetTextureName();
2189 C4Texture *texture = ::TextureMap.GetTexture(texture_name);
2190 C4TextureShape *shape = texture ? texture->GetMaterialShape() : nullptr;
2191 // Chunk type by material
2192 C4MaterialCoreShape iChunkType = ::Game.C4S.Landscape.FlatChunkShapes ? C4M_Flat : pMaterial->MapChunkType;
2193 // Get map & landscape size
2194 int iMapWidth, iMapHeight;
2195 sfcMap.GetSurfaceSize(iMapWidth, iMapHeight);
2196 // Clip desired map segment to map size
2197 iMapX = Clamp<int32_t>(iMapX, 0, iMapWidth - 1);
2198 iMapY = Clamp<int32_t>(iMapY, 0, iMapHeight - 1);
2199 iMapWdt = Clamp<int32_t>(iMapWdt, 0, iMapWidth - iMapX);
2200 iMapHgt = Clamp<int32_t>(iMapHgt, 0, iMapHeight - iMapY);
2201 // get chunk size
2202 int iChunkWidth = MapZoom, iChunkHeight = MapZoom;
2203 // Scan map lines
2204 for (int iY = iMapY; iY < iMapY + iMapHgt; iY++)
2205 {
2206 // Landscape target coordinate vertical
2207 int iToY = iY * iChunkHeight + iOffY;
2208 // Scan map line
2209 for (int iX = iMapX; iX < iMapX + iMapWdt; iX++)
2210 {
2211 // Map scan line start
2212 uint8_t MapPixel = sfcMap._GetPix(iX, iY);
2213 uint8_t MapPixelBkg = sfcMapBkg._GetPix(iX, iY);
2214 // Landscape target coordinate horizontal
2215 int iToX = iX * iChunkWidth + iOffX;
2216 // Here's a chunk of the texture-material to zoom
2217 if (MapPixel == iTexture)
2218 {
2219 // Draw chunk
2220 DrawChunk(d, iToX, iToY, iChunkWidth, iChunkHeight, MapPixel, MapPixelBkg, iChunkType, (iX << 16) + iY);
2221 }
2222 // Other chunk, check for slope smoothers
2223 else if (iChunkType == C4M_Smooth || iChunkType == C4M_Smoother || iChunkType == C4M_Octagon)
2224 {
2225 // Map scan line pixel below
2226 uint8_t below = sfcMap.GetPix(iX, iY + 1);
2227 uint8_t above = sfcMap.GetPix(iX, iY - 1);
2228 uint8_t left = sfcMap.GetPix(iX - 1, iY);
2229 uint8_t right = sfcMap.GetPix(iX + 1, iY);
2230 uint8_t leftBkg = sfcMapBkg.GetPix(iX - 1, iY);
2231 uint8_t rightBkg = sfcMapBkg.GetPix(iX + 1, iY);
2232 // do not fill a tiny hole
2233 if (below == iTexture && above == iTexture && left == iTexture && right == iTexture)
2234 continue;
2235 int flat = iChunkType == C4M_Octagon ? 4 : 0;
2236 // Smooth chunk & same texture-material below
2237 if (iY < iMapHeight - 1 && below == iTexture)
2238 {
2239 // Same texture-material on left
2240 if (iX > 0 && left == iTexture)
2241 {
2242 // Draw smoother
2243 DrawSmoothOChunk(d, iToX, iToY, iChunkWidth, iChunkHeight, left, leftBkg, 3 + flat, (iX << 16) + iY);
2244 }
2245 // Same texture-material on right
2246 if (iX < iMapWidth - 1 && right == iTexture)
2247 {
2248 // Draw smoother
2249 DrawSmoothOChunk(d, iToX, iToY, iChunkWidth, iChunkHeight, right, rightBkg, 0 + flat, (iX << 16) + iY);
2250 }
2251 }
2252 // Smooth chunk & same texture-material above
2253 if (iY > 0 && above == iTexture)
2254 {
2255 // Same texture-material on left
2256 if (iX > 0 && left == iTexture)
2257 {
2258 // Draw smoother
2259 DrawSmoothOChunk(d, iToX, iToY, iChunkWidth, iChunkHeight, left, leftBkg, 2 + flat, (iX << 16) + iY);
2260 }
2261 // Same texture-material on right
2262 if (iX < iMapWidth - 1 && right == iTexture)
2263 {
2264 // Draw smoother
2265 DrawSmoothOChunk(d, iToX, iToY, iChunkWidth, iChunkHeight, right, rightBkg, 1 + flat, (iX << 16) + iY);
2266 }
2267 }
2268 }
2269 }
2270 }
2271 // Draw custom shapes on top of regular materials
2272 if (shape && !::Game.C4S.Landscape.FlatChunkShapes) shape->Draw(sfcMap, sfcMapBkg, iMapX, iMapY, iMapWdt, iMapHgt, iTexture, iOffX, iOffY, MapZoom, pMaterial->MinShapeOverlap);
2273 }
2274
GetTexUsage(const CSurface8 & sfcMap,const CSurface8 & sfcMapBkg,int32_t iMapX,int32_t iMapY,int32_t iMapWdt,int32_t iMapHgt,DWORD * dwpTextureUsage)2275 static bool GetTexUsage(const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, DWORD *dwpTextureUsage)
2276 {
2277 int iX, iY;
2278 // No good parameters
2279 if (!dwpTextureUsage) return false;
2280 // Clip desired map segment to map size
2281 iMapX = Clamp<int32_t>(iMapX, 0, sfcMap.Wdt - 1); iMapY = Clamp<int32_t>(iMapY, 0, sfcMap.Hgt - 1);
2282 iMapWdt = Clamp<int32_t>(iMapWdt, 0, sfcMap.Wdt - iMapX); iMapHgt = Clamp<int32_t>(iMapHgt, 0, sfcMap.Hgt - iMapY);
2283 // Zero texture usage list
2284 for (int32_t cnt = 0; cnt < C4M_MaxTexIndex; cnt++) dwpTextureUsage[cnt] = 0;
2285 // Scan map pixels
2286 for (iY = iMapY; iY < iMapY + iMapHgt; iY++)
2287 for (iX = iMapX; iX < iMapX + iMapWdt; iX++)
2288 {
2289 // Count texture map index
2290 const int32_t tex = sfcMap.GetPix(iX, iY);
2291 assert(tex < C4M_MaxTexIndex);
2292
2293 if (!dwpTextureUsage[tex]++) if (tex)
2294 {
2295 // Check if texture actually exists
2296 if (!::TextureMap.GetEntry(tex)->GetMaterial())
2297 LogF("Map2Landscape error: Texture index %d at (%d/%d) in map not defined in texture map!", (int)tex, (int)iX, (int)iY);
2298 // No error. Landscape is usually fine but might contain some holes where material should be
2299 }
2300
2301 // Ignore background texture for now -- this is only used for ChunkOZoom,
2302 // for which only the foreground texture is relevant.
2303
2304 /*
2305 // Count texture map index
2306 const int32_t texBkg = sfcMapBkg.GetPix(iX, iY);
2307 if (!dwpTextureUsage[texBkg]++) if (texBkg)
2308 {
2309 // Check if texture actually exists
2310 if (!::TextureMap.GetEntry(texBkg)->GetMaterial())
2311 LogF("Map2Landscape error: Texture index %d at (%d/%d) in background map not defined in texture map!", (int) texBkg, (int) iX, (int) iY);
2312 // No error. Landscape is usually fine but might contain some holes where material should be
2313 }
2314 */
2315
2316 }
2317 // Done
2318 return true;
2319 }
2320
TexOZoom(C4Landscape * d,const CSurface8 & sfcMap,const CSurface8 & sfcMapBkg,int32_t iMapX,int32_t iMapY,int32_t iMapWdt,int32_t iMapHgt,DWORD * dwpTextureUsage,int32_t iToX,int32_t iToY)2321 bool C4Landscape::P::TexOZoom(C4Landscape *d, const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, DWORD *dwpTextureUsage, int32_t iToX, int32_t iToY)
2322 {
2323 // ChunkOZoom all used textures
2324 for (auto iIndex : ::TextureMap.Order)
2325 {
2326 if (dwpTextureUsage[iIndex] > 0)
2327 {
2328 // ChunkOZoom map to landscape
2329 ChunkOZoom(d, sfcMap, sfcMapBkg, iMapX, iMapY, iMapWdt, iMapHgt, iIndex, iToX, iToY);
2330 }
2331 }
2332
2333 // Done
2334 return true;
2335 }
2336
MapToSurface(C4Landscape * d,const CSurface8 & sfcMap,const CSurface8 & sfcMapBkg,int32_t iMapX,int32_t iMapY,int32_t iMapWdt,int32_t iMapHgt,int32_t iToX,int32_t iToY,int32_t iToWdt,int32_t iToHgt,int32_t iOffX,int32_t iOffY)2337 bool C4Landscape::P::MapToSurface(C4Landscape *d, const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, int32_t iToX, int32_t iToY, int32_t iToWdt, int32_t iToHgt, int32_t iOffX, int32_t iOffY)
2338 {
2339
2340 // assign clipper
2341 Surface8->Clip(iToX, iToY, iToX + iToWdt - 1, iToY + iToHgt - 1);
2342 Surface8Bkg->Clip(iToX, iToY, iToX + iToWdt - 1, iToY + iToHgt - 1);
2343 pDraw->NoPrimaryClipper();
2344
2345 // Enlarge map segment for chunky rim
2346 iMapX -= 2 + MaterialMap.max_shape_width / MapZoom;
2347 iMapY -= 2 + MaterialMap.max_shape_height / MapZoom;
2348 iMapWdt += 4 + MaterialMap.max_shape_width / MapZoom * 2;
2349 iMapHgt += 4 + MaterialMap.max_shape_height / MapZoom * 2;
2350
2351 // Determine texture usage in map segment
2352 DWORD dwTexUsage[C4M_MaxTexIndex];
2353 if (!GetTexUsage(sfcMap, sfcMapBkg, iMapX, iMapY, iMapWdt, iMapHgt, dwTexUsage)) return false;
2354 // Texture zoom map to landscape
2355 if (!TexOZoom(d, sfcMap, sfcMapBkg, iMapX, iMapY, iMapWdt, iMapHgt, dwTexUsage, iOffX, iOffY)) return false;
2356
2357 // remove clipper
2358 Surface8->NoClip();
2359 Surface8Bkg->NoClip();
2360
2361 // success
2362 return true;
2363 }
2364
MapToLandscape(C4Landscape * d,const CSurface8 & sfcMap,const CSurface8 & sfcMapBkg,int32_t iMapX,int32_t iMapY,int32_t iMapWdt,int32_t iMapHgt,int32_t iOffsX,int32_t iOffsY,bool noClear)2365 bool C4Landscape::P::MapToLandscape(C4Landscape *d, const CSurface8 &sfcMap, const CSurface8 &sfcMapBkg, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, int32_t iOffsX, int32_t iOffsY, bool noClear)
2366 {
2367 assert(Surface8 && Surface8Bkg);
2368 // Clip to map/landscape segment
2369 int iMapWidth, iMapHeight, iLandscapeWidth, iLandscapeHeight;
2370 // Get map & landscape size
2371 sfcMap.GetSurfaceSize(iMapWidth, iMapHeight);
2372 Surface8->GetSurfaceSize(iLandscapeWidth, iLandscapeHeight);
2373 // Clip map segment to map size
2374 iMapX = Clamp<int32_t>(iMapX, 0, iMapWidth - 1); iMapY = Clamp<int32_t>(iMapY, 0, iMapHeight - 1);
2375 iMapWdt = Clamp<int32_t>(iMapWdt, 0, iMapWidth - iMapX); iMapHgt = Clamp<int32_t>(iMapHgt, 0, iMapHeight - iMapY);
2376 // No segment
2377 if (!iMapWdt || !iMapHgt) return true;
2378
2379 // Get affected landscape rect
2380 C4Rect To;
2381 To.x = iMapX*MapZoom + iOffsX;
2382 To.y = iMapY*MapZoom + iOffsY;
2383 To.Wdt = iMapWdt*MapZoom;
2384 To.Hgt = iMapHgt*MapZoom;
2385
2386 PrepareChange(d, To);
2387
2388 // clear the old landscape if not supressed
2389 if (!noClear)
2390 {
2391 Surface8->ClearBox8Only(To.x, To.y, To.Wdt, To.Hgt);
2392 Surface8Bkg->ClearBox8Only(To.x, To.y, To.Wdt, To.Hgt);
2393 }
2394
2395 MapToSurface(d, sfcMap, sfcMapBkg, iMapX, iMapY, iMapWdt, iMapHgt, To.x, To.y, To.Wdt, To.Hgt, iOffsX, iOffsY);
2396 FinishChange(d, To);
2397 return true;
2398 }
2399
CreateMap(CSurface8 * & sfcMap,CSurface8 * & sfcMapBkg)2400 bool C4Landscape::P::CreateMap(CSurface8*& sfcMap, CSurface8*& sfcMapBkg)
2401 {
2402 int32_t iWidth = 0, iHeight = 0;
2403
2404 // Create map surface
2405 Game.C4S.Landscape.GetMapSize(iWidth, iHeight, Game.StartupPlayerCount);
2406 auto fg_map = std::make_unique<CSurface8>(iWidth, iHeight);
2407
2408 // Fill sfcMap
2409 C4MapCreator MapCreator;
2410 MapCreator.Create(fg_map.get(),
2411 Game.C4S.Landscape, ::TextureMap,
2412 Game.StartupPlayerCount);
2413
2414 auto bg_map = CreateDefaultBkgSurface(*fg_map, false);
2415 if (!bg_map)
2416 return false;
2417
2418 sfcMap = fg_map.release();
2419 sfcMapBkg = bg_map.release();
2420 return true;
2421 }
2422
CreateMapS2(C4Group & ScenFile,CSurface8 * & sfcMap,CSurface8 * & sfcMapBkg)2423 bool C4Landscape::P::CreateMapS2(C4Group &ScenFile, CSurface8*& sfcMap, CSurface8*& sfcMapBkg)
2424 {
2425 // file present?
2426 if (!ScenFile.AccessEntry(C4CFN_DynLandscape)) return false;
2427
2428 // create map creator
2429 if (!pMapCreator)
2430 pMapCreator = std::make_unique<C4MapCreatorS2>(&Game.C4S.Landscape, &::TextureMap, &::MaterialMap, Game.StartupPlayerCount);
2431
2432 // read file
2433 pMapCreator->ReadFile(C4CFN_DynLandscape, &ScenFile);
2434 // render landscape
2435 if (!pMapCreator->Render(nullptr, sfcMap, sfcMapBkg))
2436 return false;
2437
2438 // keep map creator until script callbacks have been done
2439 return true;
2440 }
2441
PostInitMap()2442 bool C4Landscape::PostInitMap()
2443 {
2444 // map creator present?
2445 if (!p->pMapCreator) return true;
2446 // call scripts
2447 p->pMapCreator->ExecuteCallbacks(p->MapZoom);
2448 // destroy map creator, if not needed later
2449 if (!Game.C4S.Landscape.KeepMapCreator) { p->pMapCreator.reset(); }
2450 // done, success
2451 return true;
2452 }
2453 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
2454 /* +++++++++++++++ Searching for features in the landscape +++++++++++++++++ */
2455 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
2456
GetMatHeight(int32_t x,int32_t y,int32_t iYDir,int32_t iMat,int32_t iMax) const2457 int32_t C4Landscape::GetMatHeight(int32_t x, int32_t y, int32_t iYDir, int32_t iMat, int32_t iMax) const
2458 {
2459 if (iYDir > 0)
2460 {
2461 iMax = std::min<int32_t>(iMax, GetHeight() - y);
2462 for (int32_t i = 0; i < iMax; i++)
2463 if (_GetMat(x, y + i) != iMat)
2464 return i;
2465 }
2466 else
2467 {
2468 iMax = std::min<int32_t>(iMax, y + 1);
2469 for (int32_t i = 0; i < iMax; i++)
2470 if (_GetMat(x, y - i) != iMat)
2471 return i;
2472 }
2473 return iMax;
2474 }
2475 // Nearest free above semi solid
AboveSemiSolid(int32_t & rx,int32_t & ry)2476 bool AboveSemiSolid(int32_t &rx, int32_t &ry)
2477 {
2478 int32_t cy1 = ry, cy2 = ry;
2479 bool UseUpwardsNextFree = false, UseDownwardsNextSolid = false;
2480
2481 while ((cy1 >= 0) || (cy2 < ::Landscape.GetHeight()))
2482 {
2483 // Check upwards
2484 if (cy1 >= 0)
2485 {
2486 if (GBackSemiSolid(rx, cy1)) UseUpwardsNextFree = true;
2487 else if (UseUpwardsNextFree) { ry = cy1; return true; }
2488 }
2489 // Check downwards
2490 if (cy2 < ::Landscape.GetHeight())
2491 {
2492 if (!GBackSemiSolid(rx, cy2)) UseDownwardsNextSolid = true;
2493 else if (UseDownwardsNextSolid) { ry = cy2; return true; }
2494 }
2495 // Advance
2496 cy1--; cy2++;
2497 }
2498
2499 return false;
2500 }
2501
2502 // Nearest free directly above solid
AboveSolid(int32_t & rx,int32_t & ry)2503 bool AboveSolid(int32_t &rx, int32_t &ry)
2504 {
2505 int32_t cy1 = ry, cy2 = ry;
2506
2507 while ((cy1 >= 0) || (cy2 < ::Landscape.GetHeight()))
2508 {
2509 // Check upwards
2510 if (cy1 >= 0)
2511 if (!GBackSemiSolid(rx, cy1))
2512 if (GBackSolid(rx, cy1 + 1))
2513 {
2514 ry = cy1; return true;
2515 }
2516 // Check downwards
2517 if (cy2 + 1 < ::Landscape.GetHeight())
2518 if (!GBackSemiSolid(rx, cy2))
2519 if (GBackSolid(rx, cy2 + 1))
2520 {
2521 ry = cy2; return true;
2522 }
2523 // Advance
2524 cy1--; cy2++;
2525 }
2526
2527 return false;
2528 }
2529
2530 // Nearest free/semi above solid
SemiAboveSolid(int32_t & rx,int32_t & ry)2531 bool SemiAboveSolid(int32_t &rx, int32_t &ry)
2532 {
2533 int32_t cy1 = ry, cy2 = ry;
2534
2535 while ((cy1 >= 0) || (cy2 < ::Landscape.GetHeight()))
2536 {
2537 // Check upwards
2538 if (cy1 >= 0)
2539 if (!GBackSolid(rx, cy1))
2540 if (GBackSolid(rx, cy1 + 1))
2541 {
2542 ry = cy1; return true;
2543 }
2544 // Check downwards
2545 if (cy2 + 1 < ::Landscape.GetHeight())
2546 if (!GBackSolid(rx, cy2))
2547 if (GBackSolid(rx, cy2 + 1))
2548 {
2549 ry = cy2; return true;
2550 }
2551 // Advance
2552 cy1--; cy2++;
2553 }
2554
2555 return false;
2556 }
2557
FindLiquidHeight(int32_t cx,int32_t & ry,int32_t hgt)2558 bool FindLiquidHeight(int32_t cx, int32_t &ry, int32_t hgt)
2559 {
2560 int32_t cy1 = ry, cy2 = ry, rl1 = 0, rl2 = 0;
2561
2562 while ((cy1 >= 0) || (cy2 < ::Landscape.GetHeight()))
2563 {
2564 // Check upwards
2565 if (cy1 >= 0)
2566 {
2567 if (GBackLiquid(cx, cy1))
2568 {
2569 rl1++; if (rl1 >= hgt) { ry = cy1 + hgt / 2; return true; }
2570 }
2571 else rl1 = 0;
2572 }
2573 // Check downwards
2574 if (cy2 + 1 < ::Landscape.GetHeight())
2575 {
2576 if (GBackLiquid(cx, cy2))
2577 {
2578 rl2++; if (rl2 >= hgt) { ry = cy2 - hgt / 2; return true; }
2579 }
2580 else rl2 = 0;
2581 }
2582 // Advance
2583 cy1--; cy2++;
2584 }
2585
2586 return false;
2587 }
2588
FindTunnelHeight(int32_t cx,int32_t & ry,int32_t hgt)2589 bool FindTunnelHeight(int32_t cx, int32_t &ry, int32_t hgt)
2590 {
2591 int32_t cy1 = ry, cy2 = ry, rl1 = 0, rl2 = 0;
2592
2593 while ((cy1 >= 0) || (cy2 < ::Landscape.GetHeight()))
2594 {
2595 // Check upwards
2596 if (cy1 >= 0)
2597 {
2598 if (Landscape.GetBackPix(cx, cy1) != 0 && MatDensity(GBackMat(cx, cy1)) < C4M_Liquid)
2599 {
2600 rl1++; if (rl1 >= hgt) { ry = cy1 + hgt / 2; return true; }
2601 }
2602 else rl1 = 0;
2603 }
2604 // Check downwards
2605 if (cy2 + 1 < ::Landscape.GetHeight())
2606 {
2607 if (Landscape.GetBackPix(cx, cy2) != 0 && MatDensity(GBackMat(cx, cy2)) < C4M_Liquid)
2608 {
2609 rl2++; if (rl2 >= hgt) { ry = cy2 - hgt / 2; return true; }
2610 }
2611 else rl2 = 0;
2612 }
2613 // Advance
2614 cy1--; cy2++;
2615 }
2616
2617 return false;
2618 }
2619
2620 // Starting from rx/ry, searches for a width of solid ground.
2621 // Returns bottom center of surface space found.
FindSolidGround(int32_t & rx,int32_t & ry,int32_t width)2622 bool FindSolidGround(int32_t &rx, int32_t &ry, int32_t width)
2623 {
2624 bool fFound = false;
2625
2626 int32_t cx1, cx2, cy1, cy2, rl1 = 0, rl2 = 0;
2627
2628 for (cx1 = cx2 = rx, cy1 = cy2 = ry; (cx1 > 0) || (cx2 < ::Landscape.GetWidth()); cx1--, cx2++)
2629 {
2630 // Left search
2631 if (cx1 >= 0) // Still going
2632 {
2633 if (AboveSolid(cx1, cy1)) rl1++; // Run okay
2634 else rl1 = 0; // No run
2635 }
2636 // Right search
2637 if (cx2 < ::Landscape.GetWidth()) // Still going
2638 {
2639 if (AboveSolid(cx2, cy2)) rl2++; // Run okay
2640 else rl2 = 0; // No run
2641 }
2642 // Check runs
2643 if (rl1 >= width) { rx = cx1 + rl1 / 2; ry = cy1; fFound = true; break; }
2644 if (rl2 >= width) { rx = cx2 - rl2 / 2; ry = cy2; fFound = true; break; }
2645 }
2646
2647 if (fFound) AboveSemiSolid(rx, ry);
2648
2649 return fFound;
2650 }
2651
FindSurfaceLiquid(int32_t & rx,int32_t & ry,int32_t width,int32_t height)2652 bool FindSurfaceLiquid(int32_t &rx, int32_t &ry, int32_t width, int32_t height)
2653 {
2654 bool fFound = false;
2655
2656 int32_t cx1, cx2, cy1, cy2, rl1 = 0, rl2 = 0, cnt;
2657 bool lokay;
2658 for (cx1 = cx2 = rx, cy1 = cy2 = ry; (cx1 > 0) || (cx2 < ::Landscape.GetWidth()); cx1--, cx2++)
2659 {
2660 // Left search
2661 if (cx1 > 0) // Still going
2662 {
2663 if (!AboveSemiSolid(cx1, cy1)) cx1 = -1; // Abort left
2664 else
2665 {
2666 for (lokay = true, cnt = 0; cnt < height; cnt++) if (!GBackLiquid(cx1, cy1 + 1 + cnt)) lokay = false;
2667 if (lokay) rl1++; // Run okay
2668 else rl1 = 0; // No run
2669 }
2670 }
2671 // Right search
2672 if (cx2 < ::Landscape.GetWidth()) // Still going
2673 {
2674 if (!AboveSemiSolid(cx2, cy2)) cx2 = ::Landscape.GetWidth(); // Abort right
2675 else
2676 {
2677 for (lokay = true, cnt = 0; cnt < height; cnt++) if (!GBackLiquid(cx2, cy2 + 1 + cnt)) lokay = false;
2678 if (lokay) rl2++; // Run okay
2679 else rl2 = 0; // No run
2680 }
2681 }
2682 // Check runs
2683 if (rl1 >= width) { rx = cx1 + rl1 / 2; ry = cy1; fFound = true; break; }
2684 if (rl2 >= width) { rx = cx2 - rl2 / 2; ry = cy2; fFound = true; break; }
2685 }
2686
2687 if (fFound) AboveSemiSolid(rx, ry);
2688
2689 return fFound;
2690 }
2691
FindLiquid(int32_t & rx,int32_t & ry,int32_t width,int32_t height)2692 bool FindLiquid(int32_t &rx, int32_t &ry, int32_t width, int32_t height)
2693 {
2694 int32_t cx1, cx2, cy1, cy2, rl1 = 0, rl2 = 0;
2695
2696 for (cx1 = cx2 = rx, cy1 = cy2 = ry; (cx1 > 0) || (cx2 < ::Landscape.GetWidth()); cx1--, cx2++)
2697 {
2698 // Left search
2699 if (cx1 > 0)
2700 {
2701 if (FindLiquidHeight(cx1, cy1, height)) rl1++;
2702 else rl1 = 0;
2703 }
2704 // Right search
2705 if (cx2 < ::Landscape.GetWidth())
2706 {
2707 if (FindLiquidHeight(cx2, cy2, height)) rl2++;
2708 else rl2 = 0;
2709 }
2710 // Check runs
2711 if (rl1 >= width) { rx = cx1 + rl1 / 2; ry = cy1; return true; }
2712 if (rl2 >= width) { rx = cx2 - rl2 / 2; ry = cy2; return true; }
2713 }
2714
2715 return false;
2716 }
2717
2718 // Starting from rx/ry, searches for tunnel background
2719 // Tunnel background == no sky && no semi/solid material (density < 25)
FindTunnel(int32_t & rx,int32_t & ry,int32_t width,int32_t height)2720 bool FindTunnel(int32_t &rx, int32_t &ry, int32_t width, int32_t height)
2721 {
2722 int32_t cx1, cx2, cy1, cy2, rl1 = 0, rl2 = 0;
2723
2724 for (cx1 = cx2 = rx, cy1 = cy2 = ry; (cx1 > 0) || (cx2 < ::Landscape.GetWidth()); cx1--, cx2++)
2725 {
2726 // Left search
2727 if (cx1 > 0)
2728 {
2729 if (FindTunnelHeight(cx1, cy1, height)) rl1++;
2730 else rl1 = 0;
2731 }
2732 // Right search
2733 if (cx2 < ::Landscape.GetWidth())
2734 {
2735 if (FindTunnelHeight(cx2, cy2, height)) rl2++;
2736 else rl2 = 0;
2737 }
2738 // Check runs
2739 if (rl1 >= width) { rx = cx1 + rl1 / 2; ry = cy1; return true; }
2740 if (rl2 >= width) { rx = cx2 - rl2 / 2; ry = cy2; return true; }
2741 }
2742
2743 return false;
2744 }
2745
2746 // Starting from rx/ry, searches for a width of solid ground. Extreme distances
2747 // may not exceed hrange. Returns bottom center of surface found.
FindLevelGround(int32_t & rx,int32_t & ry,int32_t width,int32_t hrange)2748 bool FindLevelGround(int32_t &rx, int32_t &ry, int32_t width, int32_t hrange)
2749 {
2750 bool fFound = false;
2751
2752 int32_t cx1, cx2, cy1, cy2, rh1, rh2, rl1, rl2;
2753
2754 cx1 = cx2 = rx; cy1 = cy2 = ry;
2755 rh1 = cy1; rh2 = cy2;
2756 rl1 = rl2 = 0;
2757
2758 for (cx1--, cx2++; (cx1 > 0) || (cx2 < ::Landscape.GetWidth()); cx1--, cx2++)
2759 {
2760 // Left search
2761 if (cx1 > 0) // Still going
2762 {
2763 if (!AboveSemiSolid(cx1, cy1)) cx1 = -1; // Abort left
2764 else
2765 {
2766 if (GBackSolid(cx1, cy1 + 1) && (Abs(cy1 - rh1) < hrange))
2767 rl1++; // Run okay
2768 else
2769 {
2770 rl1 = 0; rh1 = cy1;
2771 } // No run
2772 }
2773 }
2774
2775 // Right search
2776 if (cx2 < ::Landscape.GetWidth()) // Still going
2777 {
2778 if (!AboveSemiSolid(cx2, cy2)) cx2 = ::Landscape.GetWidth(); // Abort right
2779 else
2780 {
2781 if (GBackSolid(cx2, cy2 + 1) && (Abs(cy2 - rh2) < hrange))
2782 rl2++; // Run okay
2783 else
2784 {
2785 rl2 = 0; rh2 = cy2;
2786 } // No run
2787 }
2788 }
2789
2790 // Check runs
2791 if (rl1 >= width) { rx = cx1 + rl1 / 2; ry = cy1; fFound = true; break; }
2792 if (rl2 >= width) { rx = cx2 - rl2 / 2; ry = cy2; fFound = true; break; }
2793 }
2794
2795 if (fFound) AboveSemiSolid(rx, ry);
2796
2797 return fFound;
2798 }
2799
2800 // Starting from rx/ry, searches for a width of solid level ground with
2801 // structure clearance (category). Returns bottom center of surface found.
FindConSiteSpot(int32_t & rx,int32_t & ry,int32_t wdt,int32_t hgt,int32_t hrange)2802 bool FindConSiteSpot(int32_t &rx, int32_t &ry, int32_t wdt, int32_t hgt, int32_t hrange)
2803 {
2804 bool fFound = false;
2805
2806 // No hrange limit, use standard smooth surface limit
2807 if (hrange == -1) hrange = std::max(wdt / 4, 5);
2808
2809 int32_t cx1, cx2, cy1, cy2, rh1, rh2, rl1, rl2;
2810
2811 // Left offset starting position
2812 cx1 = std::min(rx + wdt / 2, ::Landscape.GetWidth() - 1); cy1 = ry;
2813 // No good: use centered starting position
2814 if (!AboveSemiSolid(cx1, cy1)) { cx1 = std::min<int32_t>(rx, ::Landscape.GetWidth() - 1); cy1 = ry; }
2815 // Right offset starting position
2816 cx2 = std::max(rx - wdt / 2, 0); cy2 = ry;
2817 // No good: use centered starting position
2818 if (!AboveSemiSolid(cx2, cy2)) { cx2 = std::min<int32_t>(rx, ::Landscape.GetWidth() - 1); cy2 = ry; }
2819
2820 rh1 = cy1; rh2 = cy2; rl1 = rl2 = 0;
2821
2822 for (cx1--, cx2++; (cx1 > 0) || (cx2 < ::Landscape.GetWidth()); cx1--, cx2++)
2823 {
2824 // Left search
2825 if (cx1 > 0) // Still going
2826 {
2827 if (!AboveSemiSolid(cx1, cy1))
2828 cx1 = -1; // Abort left
2829 else
2830 {
2831 if (GBackSolid(cx1, cy1 + 1) && (Abs(cy1 - rh1) < hrange))
2832 rl1++; // Run okay
2833 else
2834 {
2835 rl1 = 0; rh1 = cy1;
2836 } // No run
2837 }
2838 }
2839
2840 // Right search
2841 if (cx2 < ::Landscape.GetWidth()) // Still going
2842 {
2843 if (!AboveSemiSolid(cx2, cy2))
2844 cx2 = ::Landscape.GetWidth(); // Abort right
2845 else
2846 {
2847 if (GBackSolid(cx2, cy2 + 1) && (Abs(cy2 - rh2) < hrange))
2848 rl2++; // Run okay
2849 else
2850 {
2851 rl2 = 0; rh2 = cy2;
2852 } // No run
2853 }
2854 }
2855
2856 // Check runs & object overlap
2857 if (rl1 >= wdt) if (cx1 > 0)
2858 if (!Game.FindConstuctionSiteBlock(cx1, cy1 - hgt - 10, wdt, hgt + 40))
2859 {
2860 rx = cx1 + wdt / 2; ry = cy1; fFound = true; break;
2861 }
2862 if (rl2 >= wdt) if (cx2 < ::Landscape.GetWidth())
2863 if (!Game.FindConstuctionSiteBlock(cx2 - wdt, cy2 - hgt - 10, wdt, hgt + 40))
2864 {
2865 rx = cx2 - wdt / 2; ry = cy2; fFound = true; break;
2866 }
2867 }
2868
2869 if (fFound) AboveSemiSolid(rx, ry);
2870
2871 return fFound;
2872 }
2873
2874 // Returns false on any solid pix in path.
PathFreePix(int32_t x,int32_t y)2875 bool PathFreePix(int32_t x, int32_t y)
2876 {
2877 return !GBackSolid(x, y);
2878 }
2879
PathFree(int32_t x1,int32_t y1,int32_t x2,int32_t y2)2880 bool PathFree(int32_t x1, int32_t y1, int32_t x2, int32_t y2)
2881 {
2882 return ForLine(x1, y1, x2, y2, &PathFreePix);
2883 }
2884
PathFree(int32_t x1,int32_t y1,int32_t x2,int32_t y2,int32_t * ix,int32_t * iy)2885 bool PathFree(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t *ix, int32_t *iy)
2886 {
2887 // use the standard Bresenham algorithm and just adjust it to behave correctly in the inversed case
2888 bool reverse = false;
2889 bool steep = Abs(y2 - y1) > Abs(x2 - x1);
2890
2891 if (steep)
2892 {
2893 std::swap(x1, y1);
2894 std::swap(x2, y2);
2895 }
2896
2897 if (x1 > x2)
2898 {
2899 std::swap(x1, x2);
2900 std::swap(y1, y2);
2901 reverse = true;
2902 }
2903
2904 if (!reverse)
2905 {
2906 int32_t deltax = x2 - x1;
2907 int32_t deltay = Abs(y2 - y1);
2908 int32_t error = 0;
2909 int32_t ystep = (y1 < y2) ? 1 : -1;
2910 int32_t y = y1;
2911
2912 for (int32_t x = x1; x <= x2; x++)
2913 {
2914 if (steep)
2915 {
2916 if (GBackSolid(y, x))
2917 {
2918 if (ix) { *ix = y; *iy = x; }
2919 return false;
2920 }
2921 }
2922 else
2923 {
2924 if (GBackSolid(x, y))
2925 {
2926 if (ix) { *ix = x; *iy = y; }
2927 return false;
2928 }
2929 }
2930
2931 error += deltay;
2932 if (2 * error >= deltax)
2933 {
2934 y += ystep;
2935 error -= deltax;
2936 }
2937 }
2938 }
2939 else // reverse
2940 {
2941 int32_t deltax = x2 - x1;
2942 int32_t deltay = Abs(y2 - y1);
2943 int32_t error = 0;
2944 int32_t ystep = (y1 < y2) ? 1 : -1;
2945 int32_t y = y2;
2946
2947 // normal (inverse) routine
2948 for (int32_t x = x2; x >= x1; x--)
2949 {
2950 if (steep)
2951 {
2952 if (GBackSolid(y, x))
2953 {
2954 if (ix) { *ix = y; *iy = x; }
2955 return false;
2956 }
2957 }
2958 else
2959 {
2960 if (GBackSolid(x, y))
2961 {
2962 if (ix) { *ix = x; *iy = y; }
2963 return false;
2964 }
2965 }
2966
2967 error -= deltay;
2968 if (2 * error <= -deltax)
2969 {
2970 y -= ystep;
2971 error += deltax;
2972 }
2973
2974 }
2975 }
2976 // no solid material encountered: path free!
2977 return true;
2978 }
2979
PathFreeIgnoreVehiclePix(int32_t x,int32_t y)2980 bool PathFreeIgnoreVehiclePix(int32_t x, int32_t y)
2981 {
2982 BYTE byPix = ::Landscape.GetPix(x, y);
2983 return !byPix || !DensitySolid(::Landscape.GetPixMat(byPix)) || ::Landscape.GetPixMat(byPix) == MVehic;
2984 }
2985
PathFreeIgnoreVehicle(int32_t x1,int32_t y1,int32_t x2,int32_t y2,int32_t * ix,int32_t * iy)2986 bool PathFreeIgnoreVehicle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t *ix, int32_t *iy)
2987 {
2988 return ForLine(x1, y1, x2, y2, &PathFreeIgnoreVehiclePix, ix, iy);
2989 }
2990
TrajectoryDistance(int32_t iFx,int32_t iFy,C4Real iXDir,C4Real iYDir,int32_t iTx,int32_t iTy)2991 int32_t TrajectoryDistance(int32_t iFx, int32_t iFy, C4Real iXDir, C4Real iYDir, int32_t iTx, int32_t iTy)
2992 {
2993 int32_t iClosest = Distance(iFx, iFy, iTx, iTy);
2994 // Follow free trajectory, take closest point distance
2995 C4Real cx = itofix(iFx), cy = itofix(iFy);
2996 int32_t cdis;
2997 while (Inside(fixtoi(cx), 0, ::Landscape.GetWidth() - 1) && Inside(fixtoi(cy), 0, ::Landscape.GetHeight() - 1) && !GBackSolid(fixtoi(cx), fixtoi(cy)))
2998 {
2999 cdis = Distance(fixtoi(cx), fixtoi(cy), iTx, iTy);
3000 if (cdis < iClosest) iClosest = cdis;
3001 cx += iXDir; cy += iYDir; iYDir += GravAccel;
3002 }
3003 return iClosest;
3004 }
3005
3006 static constexpr int32_t
3007 Throwing_MaxVertical = 50,
3008 Throwing_MaxHorizontal = 60;
3009
FindThrowingPosition(int32_t iTx,int32_t iTy,C4Real fXDir,C4Real fYDir,int32_t iHeight,int32_t & rX,int32_t & rY)3010 bool FindThrowingPosition(int32_t iTx, int32_t iTy, C4Real fXDir, C4Real fYDir, int32_t iHeight, int32_t &rX, int32_t &rY)
3011 {
3012
3013 // Start underneath throwing target
3014 rX = iTx; rY = iTy; // improve: check from overhanging cliff
3015 if (!SemiAboveSolid(rX, rY)) return false;
3016
3017 // Target too far above surface
3018 if (!Inside(rY - iTy, -Throwing_MaxVertical, +Throwing_MaxVertical)) return false;
3019
3020 // Search in direction according to launch fXDir
3021 int32_t iDir = +1; if (fXDir > 0) iDir = -1;
3022
3023 // Move along surface
3024 for (int32_t cnt = 0; Inside<int32_t>(rX, 0, ::Landscape.GetWidth() - 1) && (cnt <= Throwing_MaxHorizontal); rX += iDir, cnt++)
3025 {
3026 // Adjust to surface
3027 if (!SemiAboveSolid(rX, rY)) return false;
3028
3029 // Check trajectory distance
3030 int32_t itjd = TrajectoryDistance(rX, rY - iHeight, fXDir, fYDir, iTx, iTy);
3031
3032 // Hitting range: success
3033 if (itjd <= 2) return true;
3034 }
3035
3036 // Failure
3037 return false;
3038
3039 }
3040
3041 static constexpr int32_t
3042 Closest_MaxRange = 200,
3043 Closest_Step = 10;
3044
FindClosestFree(int32_t & rX,int32_t & rY,int32_t iAngle1,int32_t iAngle2,int32_t iExcludeAngle1,int32_t iExcludeAngle2)3045 bool FindClosestFree(int32_t &rX, int32_t &rY, int32_t iAngle1, int32_t iAngle2,
3046 int32_t iExcludeAngle1, int32_t iExcludeAngle2)
3047 {
3048 int32_t iX, iY;
3049 for (int32_t iR = Closest_Step; iR < Closest_MaxRange; iR += Closest_Step)
3050 for (int32_t iAngle = iAngle1; iAngle < iAngle2; iAngle += Closest_Step)
3051 if (!Inside(iAngle, iExcludeAngle1, iExcludeAngle2))
3052 {
3053 iX = rX + fixtoi(Sin(itofix(iAngle))*iR);
3054 iY = rY - fixtoi(Cos(itofix(iAngle))*iR);
3055 if (Inside<int32_t>(iX, 0, ::Landscape.GetWidth() - 1))
3056 if (Inside<int32_t>(iY, 0, ::Landscape.GetHeight() - 1))
3057 if (!GBackSemiSolid(iX, iY))
3058 {
3059 rX = iX; rY = iY; return true;
3060 }
3061 }
3062 return false;
3063 }
3064
ConstructionCheck(C4PropList * PropList,int32_t iX,int32_t iY,C4Object * pByObj)3065 bool ConstructionCheck(C4PropList * PropList, int32_t iX, int32_t iY, C4Object *pByObj)
3066 {
3067 C4Def *ndef;
3068 // Check def
3069 if (!(ndef = PropList->GetDef()))
3070 {
3071 if (pByObj) GameMsgObjectError(FormatString(LoadResStr("IDS_OBJ_UNDEF"), PropList->GetName()).getData(), pByObj);
3072 return false;
3073 }
3074 // Constructable?
3075 if (!ndef->Constructable)
3076 {
3077 if (pByObj) GameMsgObjectError(FormatString(LoadResStr("IDS_OBJ_NOCON"), ndef->GetName()).getData(), pByObj);
3078 return false;
3079 }
3080 // Check area
3081 int32_t rtx, rty, wdt, hgt;
3082 wdt = ndef->Shape.Wdt; hgt = ndef->Shape.Hgt - ndef->ConSizeOff;
3083 rtx = iX - wdt / 2; rty = iY - hgt;
3084 if (::Landscape.AreaSolidCount(rtx, rty, wdt, hgt) > (wdt*hgt / 20))
3085 {
3086 if (pByObj) GameMsgObjectError(LoadResStr("IDS_OBJ_NOROOM"), pByObj);
3087 return false;
3088 }
3089 if (::Landscape.AreaSolidCount(rtx, rty + hgt, wdt, 5) < (wdt * 2))
3090 {
3091 if (pByObj) GameMsgObjectError(LoadResStr("IDS_OBJ_NOLEVEL"), pByObj);
3092 return false;
3093 }
3094 // Check other structures
3095 C4Object *other;
3096 if ((other = Game.FindConstuctionSiteBlock(rtx, rty, wdt, hgt)))
3097 {
3098 if (pByObj) GameMsgObjectError(FormatString(LoadResStr("IDS_OBJ_NOOTHER"), other->GetName()).getData(), pByObj);
3099 return false;
3100 }
3101 return true;
3102 }
3103
3104 // Finds the next pixel position moving to desired slide.
FindMatPath(int32_t & fx,int32_t & fy,int32_t ydir,int32_t mdens,int32_t mslide) const3105 bool C4Landscape::FindMatPath(int32_t &fx, int32_t &fy, int32_t ydir, int32_t mdens, int32_t mslide) const
3106 {
3107 assert(mdens <= C4M_Solid); // mdens normalized in InsertMaterial
3108
3109 int32_t cslide;
3110 bool fLeft = true, fRight = true;
3111
3112 // One downwards
3113 if (GetDensity(fx, fy + ydir) < mdens) { fy += ydir; return true; }
3114
3115 // Find downwards slide path
3116 for (cslide = 1; (cslide <= mslide) && (fLeft || fRight); cslide++)
3117 {
3118 // Check left
3119 if (fLeft)
3120 {
3121 if (GetDensity(fx - cslide, fy) >= mdens) // Left clogged
3122 fLeft = false;
3123 else if (GetDensity(fx - cslide, fy + ydir) < mdens) // Left slide okay
3124 {
3125 fx--; return true;
3126 }
3127 }
3128 // Check right
3129 if (fRight)
3130 {
3131 if (GetDensity(fx + cslide, fy) >= mdens) // Right clogged
3132 fRight = false;
3133 else if (GetDensity(fx + cslide, fy + ydir) < mdens) // Right slide okay
3134 {
3135 fx++; return true;
3136 }
3137 }
3138 }
3139
3140 return false;
3141 }
3142
3143 // Finds the closest immediate slide position.
FindMatSlide(int32_t & fx,int32_t & fy,int32_t ydir,int32_t mdens,int32_t mslide) const3144 bool C4Landscape::FindMatSlide(int32_t &fx, int32_t &fy, int32_t ydir, int32_t mdens, int32_t mslide) const
3145 {
3146 assert(mdens <= C4M_Solid); // mdens normalized in InsertMaterial and mrfInsertCheck
3147 int32_t cslide;
3148 bool fLeft = true, fRight = true;
3149
3150 // One downwards
3151 if (GetDensity(fx, fy + ydir) < mdens) { fy += ydir; return true; }
3152
3153 // Find downwards slide path
3154 for (cslide = 1; (cslide <= mslide) && (fLeft || fRight); cslide++)
3155 {
3156 // Check left
3157 if (fLeft)
3158 {
3159 if (GetDensity(fx - cslide, fy) >= mdens && GetDensity(fx - cslide, fy + ydir) >= mdens) // Left clogged
3160 fLeft = false;
3161 else if (GetDensity(fx - cslide, fy + ydir) < mdens) // Left slide okay
3162 {
3163 fx -= cslide; fy += ydir; return true;
3164 }
3165 }
3166 // Check right
3167 if (fRight)
3168 {
3169 if (GetDensity(fx + cslide, fy) >= mdens && GetDensity(fx + cslide, fy + ydir) >= mdens) // Right clogged
3170 fRight = false;
3171 else if (GetDensity(fx + cslide, fy + ydir) < mdens) // Right slide okay
3172 {
3173 fx += cslide; fy += ydir; return true;
3174 }
3175 }
3176 }
3177
3178 return false;
3179 }
3180
3181 // Find closest point with density below mdens. Note this may return a point outside of the landscape,
3182 // Assumption: There are no holes with smaller density inside of material with greater
3183 // density.
FindMatPathPush(int32_t & fx,int32_t & fy,int32_t mdens,int32_t mslide,bool liquid) const3184 bool C4Landscape::FindMatPathPush(int32_t &fx, int32_t &fy, int32_t mdens, int32_t mslide, bool liquid) const
3185 {
3186 // Startpoint must be inside landscape
3187 fx = Clamp<int32_t>(fx, 0, GetWidth() - 1);
3188 fy = Clamp<int32_t>(fy, 0, GetHeight() - 1);
3189 // Range to search, calculate bounds
3190 const int32_t iPushRange = 500;
3191 int32_t left = std::max<int32_t>(0, fx - iPushRange), right = std::min<int32_t>(GetWidth() - 1, fx + iPushRange),
3192 top = std::max<int32_t>(0, fy - iPushRange), bottom = std::min<int32_t>(GetHeight() - 1, fy + iPushRange);
3193 // Direction constants
3194 const int8_t R = 0, D = 1, L = 2, U = 3;
3195 int8_t dir = 0;
3196 int32_t x = fx, y = fy;
3197 // Get startpoint density
3198 int32_t dens = GetDensity(fx, fy);
3199 // Smaller density? We're done.
3200 if (dens < mdens)
3201 return true;
3202 // Right density?
3203 else if (dens == mdens)
3204 {
3205 // Find start point for border search
3206 for (int32_t i = 0; ; i++)
3207 if (x - i - 1 < left || GetDensity(x - i - 1, y) != mdens)
3208 {
3209 x -= i; dir = L; break;
3210 }
3211 else if (y - i - 1 < top || GetDensity(x, y - i - 1) != mdens)
3212 {
3213 y -= i; dir = U; break;
3214 }
3215 else if (x + i + 1 > right || GetDensity(x + i + 1, y) != mdens)
3216 {
3217 x += i; dir = R; break;
3218 }
3219 else if (y + i + 1 > bottom || GetDensity(x, y + i + 1) != mdens)
3220 {
3221 y += i; dir = D; break;
3222 }
3223 }
3224 // Greater density
3225 else
3226 {
3227 // Try to find a way out
3228 int i = 1;
3229 for (; i < iPushRange; i++)
3230 if (GetDensity(x - i, y) <= mdens)
3231 {
3232 x -= i; dir = R; break;
3233 }
3234 else if (GetDensity(x, y - i) <= mdens)
3235 {
3236 y -= i; dir = D; break;
3237 }
3238 else if (GetDensity(x + i, y) <= mdens)
3239 {
3240 x += i; dir = L; break;
3241 }
3242 else if (GetDensity(x, y + i) <= mdens)
3243 {
3244 y += i; dir = U; break;
3245 }
3246 // Not found?
3247 if (i >= iPushRange) return false;
3248 // Done?
3249 if (GetDensity(x, y) < mdens)
3250 {
3251 fx = x; fy = y;
3252 return true;
3253 }
3254 }
3255 // Save startpoint of search
3256 int32_t sx = x, sy = y, sdir = dir;
3257 // Best point so far
3258 bool fGotBest = false; int32_t bx = 0, by = 0, bdist = 0;
3259 // Start searching
3260 do
3261 {
3262 // We should always be in a material of same density
3263 assert(x >= left && y >= top && x <= right && y <= bottom && GetDensity(x, y) == mdens);
3264 // Calc new position
3265 int nx = x, ny = y;
3266 switch (dir)
3267 {
3268 case R: nx++; break;
3269 case D: ny++; break;
3270 case L: nx--; break;
3271 case U: ny--; break;
3272 default: assert(false);
3273 }
3274 // In bounds?
3275 bool fInBounds = (nx >= left && ny >= top && nx <= right && ny <= bottom);
3276 // Get density. Not this performs an SideOpen-check if outside landscape bounds.
3277 int32_t dens = GetDensity(nx, ny);
3278 // Flow possible?
3279 if (dens < mdens)
3280 {
3281 // Calculate "distance".
3282 int32_t dist = Abs(nx - fx) + mslide * (liquid ? fy - ny : Abs(fy - ny));
3283 // New best point?
3284 if (!fGotBest || dist < bdist)
3285 {
3286 // Save it
3287 bx = nx; by = ny; bdist = dist; fGotBest = true;
3288 // Adjust borders: We can obviously safely ignore anything at greater distance
3289 top = std::max<int32_t>(top, fy - dist / mslide - 1);
3290 if (!liquid)
3291 {
3292 bottom = std::min<int32_t>(bottom, fy + dist / mslide + 1);
3293 left = std::max<int32_t>(left, fx - dist - 1);
3294 right = std::min<int32_t>(right, fx + dist + 1);
3295 }
3296 // Set new startpoint
3297 sx = x; sy = y; sdir = dir;
3298 }
3299 }
3300 // Step?
3301 if (fInBounds && dens == mdens)
3302 {
3303 // New point
3304 x = nx; y = ny;
3305 // Turn left
3306 (dir += 3) %= 4;
3307 }
3308 // Otherwise: Turn right
3309 else
3310 {
3311 ++dir;
3312 dir %= 4;
3313 }
3314 } while (x != sx || y != sy || dir != sdir);
3315 // Nothing found?
3316 if (!fGotBest) return false;
3317 // Return it
3318 fx = bx; fy = by;
3319 return true;
3320 }
3321
AreaSolidCount(int32_t x,int32_t y,int32_t wdt,int32_t hgt) const3322 int32_t C4Landscape::AreaSolidCount(int32_t x, int32_t y, int32_t wdt, int32_t hgt) const
3323 {
3324 int32_t cx, cy, ascnt = 0;
3325 for (cy = y; cy < y + hgt; cy++)
3326 for (cx = x; cx < x + wdt; cx++)
3327 if (GBackSolid(cx, cy))
3328 ascnt++;
3329 return ascnt;
3330 }
3331
FindMatTop(int32_t mat,int32_t & x,int32_t & y,bool distant_first) const3332 void C4Landscape::FindMatTop(int32_t mat, int32_t &x, int32_t &y, bool distant_first) const
3333 {
3334 int32_t mslide, cslide, tslide, distant_x = 0;
3335 bool fLeft, fRight;
3336
3337 if (!MatValid(mat)) return;
3338 mslide = ::MaterialMap.Map[mat].MaxSlide;
3339
3340 do
3341 {
3342 // Catch most common case: Walk upwards until material changes
3343 while (GetMat(x, y - 1) == mat) --y;
3344
3345 // Find upwards slide
3346 fLeft = true; fRight = true; tslide = 0; distant_x = x;
3347 for (cslide = 1; (cslide <= mslide) && (fLeft || fRight); cslide++)
3348 {
3349 // Left
3350 if (fLeft)
3351 {
3352 if (GetMat(x - cslide, y) != mat) fLeft = false;
3353 else
3354 {
3355 distant_x = x - cslide;
3356 if (GetMat(distant_x, y - 1) == mat) { tslide = -cslide; break; }
3357 }
3358 }
3359 // Right
3360 if (fRight)
3361 {
3362 if (GetMat(x + cslide, y) != mat) fRight = false;
3363 else
3364 {
3365 distant_x = x + cslide;
3366 if (GetMat(distant_x, y - 1) == mat) { tslide = +cslide; break; }
3367 }
3368 }
3369 }
3370
3371 // Slide
3372 if (tslide) { x += tslide; y--; }
3373
3374 } while (tslide);
3375
3376 // return top pixel max slide away from center if desired
3377 if (distant_first) x = distant_x;
3378
3379 }
3380 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
3381 /* ++++++++++++++ Editor mode (draw landscape with brush)+++++++++++++++++++ */
3382 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
3383
SetMode(LandscapeMode mode)3384 void C4Landscape::SetMode(LandscapeMode mode)
3385 {
3386 p->mode = mode;
3387 }
3388
GetMode() const3389 LandscapeMode C4Landscape::GetMode() const
3390 {
3391 return p->mode;
3392 }
3393
GetMapColorIndex(const char * szMaterial,const char * szTexture,BYTE & rbyCol) const3394 bool C4Landscape::P::GetMapColorIndex(const char *szMaterial, const char *szTexture, BYTE & rbyCol) const
3395 {
3396 // Sky
3397 if (SEqual(szMaterial, C4TLS_MatSky))
3398 rbyCol = 0;
3399 // Material-Texture
3400 else
3401 {
3402 rbyCol = ::TextureMap.GetIndex(szMaterial, szTexture);
3403 if (!rbyCol) return false;
3404 }
3405 // Found
3406 return true;
3407 }
3408
DrawBrush(int32_t iX,int32_t iY,int32_t iGrade,const char * szMaterial,const char * szTexture,const char * szBackMaterial,const char * szBackTexture)3409 bool C4Landscape::DrawBrush(int32_t iX, int32_t iY, int32_t iGrade, const char *szMaterial, const char *szTexture, const char *szBackMaterial, const char *szBackTexture)
3410 {
3411 BYTE byCol, byColBkg;
3412 // Get map color index by material-texture
3413 if (!p->GetMapColorIndex(szMaterial, szTexture, byCol)) return false;
3414 if (!p->GetMapColorIndex(szBackMaterial, szBackTexture, byColBkg)) return false;
3415 // Get material shape size
3416 C4Texture *texture = ::TextureMap.GetTexture(szTexture);
3417 int32_t shape_wdt = 0, shape_hgt = 0;
3418 if (texture && texture->GetMaterialShape())
3419 {
3420 shape_wdt = texture->GetMaterialShape()->GetMaxPolyWidth() / p->MapZoom;
3421 shape_hgt = texture->GetMaterialShape()->GetMaxPolyHeight() / p->MapZoom;
3422 }
3423 // Draw
3424 switch (p->mode)
3425 {
3426 // Dynamic: ignore
3427 case LandscapeMode::Dynamic:
3428 break;
3429 // Static: draw to map by material-texture-index, chunk-o-zoom to landscape
3430 case LandscapeMode::Static:
3431 {
3432 // Draw to map
3433 int32_t iRadius = std::max<int32_t>(2 * iGrade / p->MapZoom, 1);
3434 if (iRadius == 1)
3435 {
3436 p->Map->SetPix(iX / p->MapZoom, iY / p->MapZoom, byCol);
3437 p->MapBkg->SetPix(iX / p->MapZoom, iY / p->MapZoom, byColBkg);
3438 }
3439 else
3440 {
3441 p->Map->Circle(iX / p->MapZoom, iY / p->MapZoom, iRadius, byCol);
3442 p->MapBkg->Circle(iX / p->MapZoom, iY / p->MapZoom, iRadius, byColBkg);
3443 }
3444 // Update landscape
3445 p->MapToLandscape(this, *p->Map, *p->MapBkg, iX / p->MapZoom - iRadius - 1 - shape_wdt, iY / p->MapZoom - iRadius - 1 - shape_hgt, 2 * iRadius + 2 + shape_wdt * 2, 2 * iRadius + 2 + shape_hgt * 2);
3446 SetMapChanged();
3447 }
3448 break;
3449 // Exact: draw directly to landscape by color & pattern
3450 case LandscapeMode::Exact:
3451 {
3452 C4Rect BoundingBox(iX - iGrade - 1, iY - iGrade - 1, iGrade * 2 + 2, iGrade * 2 + 2);
3453 // Draw to landscape
3454 p->PrepareChange(this, BoundingBox);
3455 p->Surface8->Circle(iX, iY, iGrade, byCol);
3456 p->Surface8Bkg->Circle(iX, iY, iGrade, byColBkg);
3457 p->FinishChange(this, BoundingBox);
3458 break;
3459 }
3460 case LandscapeMode::Undefined: assert(false); break;
3461 }
3462 return true;
3463 }
3464
DrawLineLandscape(int32_t iX,int32_t iY,int32_t iGrade,uint8_t line_color,uint8_t line_color_bkg)3465 bool C4Landscape::P::DrawLineLandscape(int32_t iX, int32_t iY, int32_t iGrade, uint8_t line_color, uint8_t line_color_bkg)
3466 {
3467 Surface8->Circle(iX, iY, iGrade, line_color);
3468 Surface8Bkg->Circle(iX, iY, iGrade, line_color_bkg);
3469 return true;
3470 }
3471
DrawLineMap(int32_t iX,int32_t iY,int32_t iRadius,uint8_t line_color,uint8_t line_color_bkg)3472 bool C4Landscape::P::DrawLineMap(int32_t iX, int32_t iY, int32_t iRadius, uint8_t line_color, uint8_t line_color_bkg)
3473 {
3474 if (!Map) return false;
3475 if (iRadius == 1)
3476 {
3477 Map->SetPix(iX, iY, line_color); MapBkg->SetPix(iX, iY, line_color_bkg);
3478 }
3479 else
3480 {
3481 Map->Circle(iX, iY, iRadius, line_color); MapBkg->Circle(iX, iY, iRadius, line_color_bkg);
3482 }
3483 return true;
3484 }
3485
DrawLine(int32_t iX1,int32_t iY1,int32_t iX2,int32_t iY2,int32_t iGrade,const char * szMaterial,const char * szTexture,const char * szBackMaterial,const char * szBackTexture)3486 bool C4Landscape::DrawLine(int32_t iX1, int32_t iY1, int32_t iX2, int32_t iY2, int32_t iGrade, const char *szMaterial, const char *szTexture, const char *szBackMaterial, const char *szBackTexture)
3487 {
3488 // Get map color index by material-texture
3489 uint8_t line_color, line_color_bkg;
3490 if (!p->GetMapColorIndex(szMaterial, szTexture, line_color)) return false;
3491 if (!p->GetMapColorIndex(szBackMaterial, szBackTexture, line_color_bkg)) return false;
3492 // Get material shape size
3493 C4Texture *texture = ::TextureMap.GetTexture(szTexture);
3494 int32_t shape_wdt = 0, shape_hgt = 0;
3495 if (texture && texture->GetMaterialShape())
3496 {
3497 shape_wdt = texture->GetMaterialShape()->GetMaxPolyWidth() / p->MapZoom;
3498 shape_hgt = texture->GetMaterialShape()->GetMaxPolyHeight() / p->MapZoom;
3499 }
3500 // Draw
3501 switch (p->mode)
3502 {
3503 // Dynamic: ignore
3504 case LandscapeMode::Dynamic:
3505 break;
3506 // Static: draw to map by material-texture-index, chunk-o-zoom to landscape
3507 case LandscapeMode::Static:
3508 {
3509 // Draw to map
3510 int32_t iRadius = std::max<int32_t>(2 * iGrade / p->MapZoom, 1);
3511 iX1 /= p->MapZoom; iY1 /= p->MapZoom; iX2 /= p->MapZoom; iY2 /= p->MapZoom;
3512 ForLine(iX1, iY1, iX2, iY2, [this, line_color, line_color_bkg, iRadius](int32_t x, int32_t y) { return p->DrawLineMap(x, y, iRadius, line_color, line_color_bkg); });
3513 // Update landscape
3514 int iUpX = std::min(iX1, iX2) - iRadius - 1;
3515 int iUpY = std::min(iY1, iY2) - iRadius - 1;
3516 int iUpWdt = Abs(iX2 - iX1) + 2 * iRadius + 2;
3517 int iUpHgt = Abs(iY2 - iY1) + 2 * iRadius + 2;
3518 p->MapToLandscape(this, *p->Map, *p->MapBkg, iUpX - shape_wdt, iUpY - shape_hgt, iUpWdt + shape_wdt * 2, iUpHgt + shape_hgt * 2);
3519 SetMapChanged();
3520 }
3521 break;
3522 // Exact: draw directly to landscape by color & pattern
3523 case LandscapeMode::Exact:
3524 {
3525 // Set texture pattern & get material color
3526 C4Rect BoundingBox(iX1 - iGrade, iY1 - iGrade, iGrade * 2 + 1, iGrade * 2 + 1);
3527 BoundingBox.Add(C4Rect(iX2 - iGrade, iY2 - iGrade, iGrade * 2 + 1, iGrade * 2 + 1));
3528 // Draw to landscape
3529 p->PrepareChange(this, BoundingBox);
3530 ForLine(iX1, iY1, iX2, iY2, [this, line_color, line_color_bkg, iGrade](int32_t x, int32_t y) { return p->DrawLineLandscape(x, y, iGrade, line_color, line_color_bkg); });
3531 p->FinishChange(this, BoundingBox);
3532 break;
3533 }
3534 case LandscapeMode::Undefined: assert(false); break;
3535 }
3536 return true;
3537 }
3538
DrawBox(int32_t iX1,int32_t iY1,int32_t iX2,int32_t iY2,int32_t iGrade,const char * szMaterial,const char * szTexture,const char * szBackMaterial,const char * szBackTexture)3539 bool C4Landscape::DrawBox(int32_t iX1, int32_t iY1, int32_t iX2, int32_t iY2, int32_t iGrade, const char *szMaterial, const char *szTexture, const char *szBackMaterial, const char *szBackTexture)
3540 {
3541 // get upper-left/lower-right - corners
3542 int32_t iX0 = std::min(iX1, iX2); int32_t iY0 = std::min(iY1, iY2);
3543 iX2 = std::max(iX1, iX2); iY2 = std::max(iY1, iY2); iX1 = iX0; iY1 = iY0;
3544 BYTE byCol, byColBkg;
3545 // Get map color index by material-texture
3546 if (!p->GetMapColorIndex(szMaterial, szTexture, byCol)) return false;
3547 if (!p->GetMapColorIndex(szBackMaterial, szBackTexture, byColBkg)) return false;
3548 // Get material shape size
3549 C4Texture *texture = ::TextureMap.GetTexture(szTexture);
3550 int32_t shape_wdt = 0, shape_hgt = 0;
3551 if (texture && texture->GetMaterialShape())
3552 {
3553 shape_wdt = texture->GetMaterialShape()->GetMaxPolyWidth() / p->MapZoom;
3554 shape_hgt = texture->GetMaterialShape()->GetMaxPolyHeight() / p->MapZoom;
3555 }
3556 // Draw
3557 switch (p->mode)
3558 {
3559 // Dynamic: ignore
3560 case LandscapeMode::Dynamic:
3561 break;
3562 // Static: draw to map by material-texture-index, chunk-o-zoom to landscape
3563 case LandscapeMode::Static:
3564 // Draw to map
3565 iX1 /= p->MapZoom; iY1 /= p->MapZoom; iX2 /= p->MapZoom; iY2 /= p->MapZoom;
3566 p->Map->Box(iX1, iY1, iX2, iY2, byCol);
3567 p->MapBkg->Box(iX1, iY1, iX2, iY2, byColBkg);
3568 // Update landscape
3569 p->MapToLandscape(this, *p->Map, *p->MapBkg, iX1 - 1 - shape_wdt, iY1 - 1 - shape_hgt, iX2 - iX1 + 3 + shape_wdt * 2, iY2 - iY1 + 3 + shape_hgt * 2);
3570 SetMapChanged();
3571 break;
3572 // Exact: draw directly to landscape by color & pattern
3573 case LandscapeMode::Exact:
3574 {
3575 C4Rect BoundingBox(iX1, iY1, iX2 - iX1 + 1, iY2 - iY1 + 1);
3576 // Draw to landscape
3577 p->PrepareChange(this, BoundingBox);
3578 p->Surface8->Box(iX1, iY1, iX2, iY2, byCol);
3579 p->Surface8Bkg->Box(iX1, iY1, iX2, iY2, byColBkg);
3580 p->FinishChange(this, BoundingBox);
3581 break;
3582 }
3583 case LandscapeMode::Undefined: assert(false); break;
3584 }
3585 return true;
3586 }
3587
DrawChunks(int32_t tx,int32_t ty,int32_t wdt,int32_t hgt,int32_t icntx,int32_t icnty,const char * szMaterial,const char * szTexture,bool bIFT)3588 bool C4Landscape::DrawChunks(int32_t tx, int32_t ty, int32_t wdt, int32_t hgt, int32_t icntx, int32_t icnty, const char *szMaterial, const char *szTexture, bool bIFT)
3589 {
3590 BYTE byColor;
3591 if (!p->GetMapColorIndex(szMaterial, szTexture, byColor)) return false;
3592
3593 int32_t iMaterial = ::MaterialMap.Get(szMaterial);
3594 if (!MatValid(iMaterial))
3595 return false;
3596
3597 C4MaterialCoreShape shape = ::Game.C4S.Landscape.FlatChunkShapes ? C4M_Flat : ::MaterialMap.Map[iMaterial].MapChunkType;
3598
3599 C4Rect BoundingBox(tx - 5, ty - 5, wdt + 10, hgt + 10);
3600 p->PrepareChange(this, BoundingBox);
3601
3602 // assign clipper
3603 p->Surface8->Clip(BoundingBox.x, BoundingBox.y, BoundingBox.x + BoundingBox.Wdt, BoundingBox.y + BoundingBox.Hgt);
3604 p->Surface8Bkg->Clip(BoundingBox.x, BoundingBox.y, BoundingBox.x + BoundingBox.Wdt, BoundingBox.y + BoundingBox.Hgt);
3605 pDraw->NoPrimaryClipper();
3606
3607 // draw all chunks
3608 int32_t x, y;
3609 for (x = 0; x < icntx; x++)
3610 for (y = 0; y < icnty; y++)
3611 p->DrawChunk(this, tx + wdt*x / icntx, ty + hgt*y / icnty, wdt / icntx, hgt / icnty, byColor, bIFT ? p->DefaultBkgMat(byColor) : 0, shape, Random(1000));
3612
3613 // remove clipper
3614 p->Surface8->NoClip();
3615 p->Surface8Bkg->NoClip();
3616
3617 p->FinishChange(this, BoundingBox);
3618
3619 // success
3620 return true;
3621 }
3622
DrawPolygon(int * vtcs,int length,const char * szMaterial,const char * szBackMaterial,bool fDrawBridge)3623 bool C4Landscape::DrawPolygon(int *vtcs, int length, const char *szMaterial, const char* szBackMaterial, bool fDrawBridge)
3624 {
3625 if (length < 6) return false;
3626 if (length % 2 == 1) return false;
3627 // get texture
3628 int32_t iMatTex = ::TextureMap.GetIndexMatTex(szMaterial);
3629 if (!iMatTex) return false;
3630 uint8_t mcol = MatTex2PixCol(iMatTex);
3631 // get background texture
3632 uint8_t mcolBkg = 0;
3633 if (szBackMaterial != nullptr)
3634 {
3635 const int32_t iBackMatTex = ::TextureMap.GetIndexMatTex(szBackMaterial);
3636 if (!iBackMatTex) return false;
3637 mcolBkg = MatTex2PixCol(iBackMatTex);
3638 }
3639 // do bridging?
3640 uint8_t *conversion_map = nullptr;
3641 if (fDrawBridge)
3642 {
3643 conversion_map = p->GetBridgeMatConversion(this, MatTex2PixCol(iMatTex));
3644 mcolBkg = Transparent;
3645 }
3646 // prepare pixel count update
3647 C4Rect BoundingBox = getBoundingBox(vtcs, length);
3648 // draw polygon
3649 p->PrepareChange(this, BoundingBox);
3650 p->ForPolygon(this, vtcs, length / 2, nullptr, nullptr, mcol, mcolBkg, conversion_map);
3651 p->FinishChange(this, BoundingBox);
3652 return true;
3653 }
3654
GetPal() const3655 CStdPalette * C4Landscape::GetPal() const
3656 {
3657 return p->Surface8 ? p->Surface8->pPal : nullptr;
3658 }
3659
GetWidth() const3660 int32_t C4Landscape::GetWidth() const
3661 {
3662 return p->Width;
3663 }
3664
GetHeight() const3665 int32_t C4Landscape::GetHeight() const
3666 {
3667 return p->Height;
3668 }
3669
GetMapZoom() const3670 int32_t C4Landscape::GetMapZoom() const
3671 {
3672 return p->MapZoom;
3673 }
3674
GetGravity() const3675 C4Real C4Landscape::GetGravity() const
3676 {
3677 return p->Gravity;
3678 }
3679
SetGravity(C4Real g)3680 void C4Landscape::SetGravity(C4Real g)
3681 {
3682 p->Gravity = g;
3683 }
3684
_GetPix(int32_t x,int32_t y) const3685 BYTE C4Landscape::_GetPix(int32_t x, int32_t y) const
3686 {
3687 #ifdef _DEBUG
3688 if (x < 0 || y < 0 || x >= p->Width || y >= p->Height) { BREAKPOINT_HERE; }
3689 #endif
3690 return p->Surface8->_GetPix(x, y);
3691 }
3692
GetPix(int32_t x,int32_t y) const3693 BYTE C4Landscape::GetPix(int32_t x, int32_t y) const // get landscape pixel (bounds checked)
3694 {
3695 extern BYTE MCVehic;
3696 // Border checks
3697 if (x < 0)
3698 {
3699 return p->LeftColPix[Clamp(y, 0, GetHeight()-1)];
3700 }
3701 if (static_cast<uint32_t>(x) >= static_cast<uint32_t>(p->Width))
3702 {
3703 return p->RightColPix[Clamp(y, 0, GetHeight()-1)];
3704 }
3705 if (y < 0)
3706 {
3707 return p->TopRowPix[x];
3708 }
3709 if (static_cast<uint32_t>(y) >= static_cast<uint32_t>(p->Height))
3710 {
3711 return p->BottomRowPix[x];
3712 }
3713 return p->Surface8->_GetPix(x, y);
3714 }
3715
_GetMat(int32_t x,int32_t y) const3716 int32_t C4Landscape::_GetMat(int32_t x, int32_t y) const
3717 {
3718 return p->Pix2Mat[_GetPix(x, y)];
3719 }
3720
_GetDensity(int32_t x,int32_t y) const3721 int32_t C4Landscape::_GetDensity(int32_t x, int32_t y) const // get landscape density (bounds not checked)
3722 {
3723 return p->Pix2Dens[_GetPix(x, y)];
3724 }
3725
_GetPlacement(int32_t x,int32_t y) const3726 int32_t C4Landscape::_GetPlacement(int32_t x, int32_t y) const // get landscape material placement (bounds not checked)
3727 {
3728 return p->Pix2Place[_GetPix(x, y)];
3729 }
3730
GetMat(int32_t x,int32_t y) const3731 int32_t C4Landscape::GetMat(int32_t x, int32_t y) const // get landscape material (bounds checked)
3732 {
3733 return p->Pix2Mat[GetPix(x, y)];
3734 }
3735
GetDensity(int32_t x,int32_t y) const3736 int32_t C4Landscape::GetDensity(int32_t x, int32_t y) const // get landscape density (bounds checked)
3737 {
3738 return p->Pix2Dens[GetPix(x, y)];
3739 }
3740
GetPlacement(int32_t x,int32_t y) const3741 int32_t C4Landscape::GetPlacement(int32_t x, int32_t y) const // get landscape material placement (bounds checked)
3742 {
3743 return p->Pix2Place[GetPix(x, y)];
3744 }
3745
_GetBackPix(int32_t x,int32_t y) const3746 BYTE C4Landscape::_GetBackPix(int32_t x, int32_t y) const // get landscape pixel (bounds not checked)
3747 {
3748 #ifdef _DEBUG
3749 if (x < 0 || y < 0 || x >= p->Width || y >= p->Height) { BREAKPOINT_HERE; }
3750 #endif
3751 return p->Surface8Bkg->_GetPix(x, y);
3752 }
3753
GetBackPix(int32_t x,int32_t y) const3754 BYTE C4Landscape::GetBackPix(int32_t x, int32_t y) const // get landscape pixel (bounds checked)
3755 {
3756 // Border checks
3757 if (x < 0)
3758 {
3759 return p->DefaultBkgMat(p->LeftColPix[Clamp(y, 0, GetHeight()-1)]);
3760 }
3761 if (static_cast<uint32_t>(x) >= static_cast<uint32_t>(GetWidth()))
3762 {
3763 return p->DefaultBkgMat(p->RightColPix[Clamp(y, 0, GetHeight()-1)]);
3764 }
3765 if (y < 0)
3766 {
3767 return p->DefaultBkgMat(p->TopRowPix[x]);
3768 }
3769 if (static_cast<uint32_t>(y) >= static_cast<uint32_t>(GetHeight()))
3770 {
3771 return p->DefaultBkgMat(p->BottomRowPix[x]);
3772 }
3773
3774 return p->Surface8Bkg->_GetPix(x, y);
3775 }
3776
_GetBackMat(int32_t x,int32_t y) const3777 int32_t C4Landscape::_GetBackMat(int32_t x, int32_t y) const // get landscape material (bounds not checked)
3778 {
3779 return p->Pix2Mat[_GetBackPix(x, y)];
3780 }
3781
_GetBackDensity(int32_t x,int32_t y) const3782 int32_t C4Landscape::_GetBackDensity(int32_t x, int32_t y) const // get landscape density (bounds not checked)
3783 {
3784 return p->Pix2Dens[_GetBackPix(x, y)];
3785 }
3786
_GetBackPlacement(int32_t x,int32_t y) const3787 int32_t C4Landscape::_GetBackPlacement(int32_t x, int32_t y) const // get landscape material placement (bounds not checked)
3788 {
3789 return p->Pix2Place[_GetBackPix(x, y)];
3790 }
3791
GetBackMat(int32_t x,int32_t y) const3792 int32_t C4Landscape::GetBackMat(int32_t x, int32_t y) const // get landscape material (bounds checked)
3793 {
3794 return p->Pix2Mat[GetBackPix(x, y)];
3795 }
3796
GetBackDensity(int32_t x,int32_t y) const3797 int32_t C4Landscape::GetBackDensity(int32_t x, int32_t y) const // get landscape density (bounds checked)
3798 {
3799 return p->Pix2Dens[GetBackPix(x, y)];
3800 }
3801
GetBackPlacement(int32_t x,int32_t y) const3802 int32_t C4Landscape::GetBackPlacement(int32_t x, int32_t y) const // get landscape material placement (bounds checked)
3803 {
3804 return p->Pix2Place[GetBackPix(x, y)];
3805 }
3806
GetLight(int32_t x,int32_t y)3807 bool C4Landscape::GetLight(int32_t x, int32_t y)
3808 {
3809 return GetBackPix(x, y) == 0 || p->Pix2Light[GetPix(x, y)];
3810 }
3811
_GetLight(int32_t x,int32_t y)3812 bool C4Landscape::_GetLight(int32_t x, int32_t y)
3813 {
3814 return _GetBackPix(x, y) == 0 || p->Pix2Light[_GetPix(x, y)];
3815 }
3816
_FastSolidCheck(int32_t x,int32_t y) const3817 bool C4Landscape::_FastSolidCheck(int32_t x, int32_t y) const // checks whether there *might* be something solid at the point
3818 {
3819 return p->PixCnt[(x / 17) * p->PixCntPitch + (y / 15)] > 0;
3820 }
3821
FastSolidCheckNextX(int32_t x)3822 int32_t C4Landscape::FastSolidCheckNextX(int32_t x)
3823 {
3824 return (x / 17) * 17 + 17;
3825 }
3826
GetPixMat(BYTE byPix) const3827 int32_t C4Landscape::GetPixMat(BYTE byPix) const { return p->Pix2Mat[byPix]; }
3828
GetPixDensity(BYTE byPix) const3829 int32_t C4Landscape::GetPixDensity(BYTE byPix) const { return p->Pix2Dens[byPix]; }
3830
_PathFree(int32_t x,int32_t y,int32_t x2,int32_t y2) const3831 bool C4Landscape::_PathFree(int32_t x, int32_t y, int32_t x2, int32_t y2) const
3832 {
3833 x /= 17; y /= 15; x2 /= 17; y2 /= 15;
3834 while (x != x2 && y != y2)
3835 {
3836 if (p->PixCnt[x * p->PixCntPitch + y])
3837 return false;
3838 if (x > x2) x--; else x++;
3839 if (y > y2) y--; else y++;
3840 }
3841 if (x != x2)
3842 do
3843 {
3844 if (p->PixCnt[x * p->PixCntPitch + y])
3845 return false;
3846 if (x > x2) x--; else x++;
3847 } while (x != x2);
3848 else
3849 while (y != y2)
3850 {
3851 if (p->PixCnt[x * p->PixCntPitch + y])
3852 return false;
3853 if (y > y2) y--; else y++;
3854 }
3855 return !p->PixCnt[x * p->PixCntPitch + y];
3856 }
3857
GetBridgeMatConversion(const C4Landscape * d,int32_t for_material_col) const3858 uint8_t *C4Landscape::P::GetBridgeMatConversion(const C4Landscape *d, int32_t for_material_col) const
3859 {
3860 // safety
3861 int32_t for_material = d->GetPixMat(for_material_col);
3862 if (for_material < 0 || for_material >= MaterialMap.Num) return nullptr;
3863 // query map. create if not done yet
3864 if (!BridgeMatConversion[for_material_col])
3865 {
3866 auto conv_map = std::make_unique<uint8_t[]>(C4M_MaxTexIndex);
3867 for (int32_t i = 0; i < C4M_MaxTexIndex; ++i)
3868 {
3869 if ((MatDensity(for_material) >= d->GetPixDensity(i)))
3870 {
3871 // bridge pixel OK here. change pixel.
3872 conv_map[i] = for_material_col;
3873 }
3874 else
3875 {
3876 // bridge pixel not OK - keep current pixel
3877 conv_map[i] = i;
3878 }
3879 }
3880 BridgeMatConversion[for_material_col] = std::move(conv_map);
3881 }
3882 return BridgeMatConversion[for_material_col].get();
3883 }
3884
DrawQuad(int32_t iX1,int32_t iY1,int32_t iX2,int32_t iY2,int32_t iX3,int32_t iY3,int32_t iX4,int32_t iY4,const char * szMaterial,const char * szBackMaterial,bool fDrawBridge)3885 bool C4Landscape::DrawQuad(int32_t iX1, int32_t iY1, int32_t iX2, int32_t iY2, int32_t iX3, int32_t iY3, int32_t iX4, int32_t iY4, const char *szMaterial, const char *szBackMaterial, bool fDrawBridge)
3886 {
3887 // set vertices
3888 int32_t vtcs[8];
3889 vtcs[0] = iX1; vtcs[1] = iY1;
3890 vtcs[2] = iX2; vtcs[3] = iY2;
3891 vtcs[4] = iX3; vtcs[5] = iY3;
3892 vtcs[6] = iX4; vtcs[7] = iY4;
3893 return DrawPolygon(vtcs, 8, szMaterial, szBackMaterial, fDrawBridge);
3894 }
3895
GetMapIndex(int32_t iX,int32_t iY) const3896 BYTE C4Landscape::GetMapIndex(int32_t iX, int32_t iY) const
3897 {
3898 if (!p->Map) return 0;
3899 return p->Map->GetPix(iX, iY);
3900 }
3901
GetBackMapIndex(int32_t iX,int32_t iY) const3902 BYTE C4Landscape::GetBackMapIndex(int32_t iX, int32_t iY) const
3903 {
3904 if (!p->MapBkg) return 0;
3905 return p->MapBkg->GetPix(iX, iY);
3906 }
3907
PrepareChange(const C4Landscape * d,const C4Rect & BoundingBox)3908 void C4Landscape::P::PrepareChange(const C4Landscape *d, const C4Rect &BoundingBox)
3909 {
3910 // move solidmasks out of the way
3911 C4Rect SolidMaskRect = BoundingBox;
3912 if (pLandscapeRender)
3913 SolidMaskRect = pLandscapeRender->GetAffectedRect(pLandscapeRender->GetAffectedRect(SolidMaskRect));
3914 for (C4SolidMask * pSolid = C4SolidMask::Last; pSolid; pSolid = pSolid->Prev)
3915 {
3916 pSolid->RemoveTemporary(SolidMaskRect);
3917 }
3918 UpdateMatCnt(d, BoundingBox, false);
3919 }
3920
FinishChange(C4Landscape * d,C4Rect BoundingBox)3921 void C4Landscape::P::FinishChange(C4Landscape *d, C4Rect BoundingBox)
3922 {
3923 // Intersect bounding box with landscape
3924 BoundingBox.Intersect(C4Rect(0, 0, Width, Height));
3925 if (!BoundingBox.Wdt || !BoundingBox.Hgt) return;
3926 // update render
3927 if (pLandscapeRender)
3928 pLandscapeRender->Update(BoundingBox, d);
3929 UpdateMatCnt(d, BoundingBox, true);
3930 // Restore Solidmasks
3931 C4Rect SolidMaskRect = BoundingBox;
3932 if (pLandscapeRender)
3933 SolidMaskRect = pLandscapeRender->GetAffectedRect(pLandscapeRender->GetAffectedRect(SolidMaskRect));
3934 for (C4SolidMask * pSolid = C4SolidMask::First; pSolid; pSolid = pSolid->Next)
3935 {
3936 pSolid->Repair(SolidMaskRect);
3937 }
3938 C4SolidMask::CheckConsistency();
3939 UpdatePixCnt(d, BoundingBox);
3940 // update FoW
3941 if (pFoW)
3942 {
3943 pFoW->Invalidate(BoundingBox);
3944 pFoW->Ambient.UpdateFromLandscape(*d, BoundingBox);
3945 }
3946 }
3947
3948
3949 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
3950 /* ++++++++++++++++++ Functions for Script interface +++++++++++++++++++++++ */
3951 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
3952
DrawMap(int32_t iX,int32_t iY,int32_t iWdt,int32_t iHgt,const char * szMapDef,bool ignoreSky)3953 bool C4Landscape::DrawMap(int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, const char *szMapDef, bool ignoreSky)
3954 {
3955 // safety
3956 if (!szMapDef) return false;
3957 // clip to landscape size
3958 if (!ClipRect(iX, iY, iWdt, iHgt)) return false;
3959 // get needed map size
3960 int32_t iMapWdt = (iWdt - 1) / p->MapZoom + 1;
3961 int32_t iMapHgt = (iHgt - 1) / p->MapZoom + 1;
3962 C4SLandscape FakeLS = Game.C4S.Landscape;
3963 FakeLS.MapWdt.Set(iMapWdt, 0, iMapWdt, iMapWdt);
3964 FakeLS.MapHgt.Set(iMapHgt, 0, iMapHgt, iMapHgt);
3965 // create map creator
3966 C4MapCreatorS2 MapCreator(&FakeLS, &::TextureMap, &::MaterialMap, Game.StartupPlayerCount);
3967 // read file
3968 MapCreator.ReadScript(szMapDef);
3969 // render map
3970 CSurface8* sfcMap = nullptr;
3971 CSurface8* sfcMapBkg = nullptr;
3972 if (!MapCreator.Render(nullptr, sfcMap, sfcMapBkg))
3973 return false;
3974 // map it to the landscape
3975 bool fSuccess = p->MapToLandscape(this, *sfcMap, *sfcMapBkg, 0, 0, iMapWdt, iMapHgt, iX, iY, ignoreSky);
3976 // cleanup
3977 delete sfcMap;
3978 delete sfcMapBkg;
3979 // return whether successful
3980 return fSuccess;
3981 }
3982
DrawDefMap(int32_t iX,int32_t iY,int32_t iWdt,int32_t iHgt,const char * szMapDef,bool ignoreSky)3983 bool C4Landscape::DrawDefMap(int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, const char *szMapDef, bool ignoreSky)
3984 {
3985 // safety
3986 if (!szMapDef || !p->pMapCreator) return false;
3987 // clip to landscape size
3988 if (!ClipRect(iX, iY, iWdt, iHgt)) return false;
3989 // get needed map size
3990 int32_t iMapWdt = (iWdt - 1) / p->MapZoom + 1;
3991 int32_t iMapHgt = (iHgt - 1) / p->MapZoom + 1;
3992 bool fSuccess = false;
3993 // render map
3994 C4MCMap *pMap = p->pMapCreator->GetMap(szMapDef);
3995 if (!pMap) return false;
3996 pMap->SetSize(iMapWdt, iMapHgt);
3997 CSurface8* sfcMap = nullptr;
3998 CSurface8* sfcMapBkg = nullptr;
3999 if (p->pMapCreator->Render(szMapDef, sfcMap, sfcMapBkg))
4000 {
4001 // map to landscape
4002 fSuccess = p->MapToLandscape(this, *sfcMap, *sfcMapBkg, 0, 0, iMapWdt, iMapHgt, iX, iY, ignoreSky);
4003 // cleanup
4004 delete sfcMap;
4005 delete sfcMapBkg;
4006 }
4007 // done
4008 return fSuccess;
4009 }
4010
4011 // creates and draws a map section using MapCreatorS2 and a map from the loaded Landscape.txt
4012
SetModulation(DWORD dwWithClr)4013 bool C4Landscape::SetModulation(DWORD dwWithClr) // adjust the way the landscape is blitted
4014 {
4015 p->Modulation = dwWithClr;
4016 return true;
4017 }
4018
GetModulation() const4019 DWORD C4Landscape::GetModulation() const { return p->Modulation; }
4020
ClipRect(int32_t & rX,int32_t & rY,int32_t & rWdt,int32_t & rHgt) const4021 bool C4Landscape::ClipRect(int32_t &rX, int32_t &rY, int32_t &rWdt, int32_t &rHgt) const
4022 {
4023 // clip by bounds
4024 if (rX < 0) { rWdt += rX; rX = 0; }
4025 if (rY < 0) { rHgt += rY; rY = 0; }
4026 int32_t iOver;
4027
4028 iOver = rX + rWdt - GetWidth();
4029 if (iOver > 0)
4030 rWdt -= iOver;
4031
4032 iOver = rY + rHgt - GetHeight();
4033 if (iOver > 0)
4034 rHgt -= iOver;
4035
4036 // anything left inside the bounds?
4037 return rWdt > 0 && rHgt > 0;
4038 }
4039
ReplaceMapColor(BYTE iOldIndex,BYTE iNewIndex)4040 bool C4Landscape::ReplaceMapColor(BYTE iOldIndex, BYTE iNewIndex)
4041 {
4042 // find every occurance of iOldIndex in map; replace it by new index
4043 if (!p->Map) return false;
4044 int iPitch, iMapWdt, iMapHgt;
4045 BYTE *pMap = p->Map->Bits;
4046 iMapWdt = p->Map->Wdt;
4047 iMapHgt = p->Map->Hgt;
4048 iPitch = p->Map->Pitch;
4049 if (!pMap) return false;
4050 for (int32_t y = 0; y < iMapHgt; ++y)
4051 {
4052 for (int32_t x = 0; x < iMapWdt; ++x)
4053 {
4054 if (*pMap == iOldIndex)
4055 *pMap = iNewIndex;
4056 ++pMap;
4057 }
4058 pMap += iPitch - iMapWdt;
4059 }
4060 return true;
4061 }
4062
SetTextureIndex(const char * szMatTex,BYTE iNewIndex,bool fInsert)4063 bool C4Landscape::SetTextureIndex(const char *szMatTex, BYTE iNewIndex, bool fInsert)
4064 {
4065 if (((!szMatTex || !*szMatTex) && !fInsert) || !Inside<int>(iNewIndex, 1, C4M_MaxTexIndex - 1))
4066 {
4067 DebugLogF("Cannot insert new texture %s to index %d: Invalid parameters.", (const char *)szMatTex, (int)iNewIndex);
4068 return false;
4069 }
4070 // get last mat index - returns zero for not found (valid for insertion mode)
4071 StdStrBuf Material, Texture;
4072 Material.CopyUntil(szMatTex, '-'); Texture.Copy(SSearch(szMatTex, "-"));
4073 BYTE iOldIndex = (szMatTex && *szMatTex) ? ::TextureMap.GetIndex(Material.getData(), Texture.getData(), false) : 0;
4074 // insertion mode?
4075 if (fInsert)
4076 {
4077 // there must be room to move up to
4078 BYTE byLastMoveIndex = C4M_MaxTexIndex - 1;
4079 while (::TextureMap.GetEntry(byLastMoveIndex))
4080 if (--byLastMoveIndex == iNewIndex)
4081 {
4082 DebugLogF("Cannot insert new texture %s to index %d: No room for insertion.", (const char *)szMatTex, (int)iNewIndex);
4083 return false;
4084 }
4085 // then move up all other textures first
4086 // could do this in one loop, but it's just a developement call anyway, so move one index at a time
4087 while (--byLastMoveIndex >= iNewIndex)
4088 if (::TextureMap.GetEntry(byLastMoveIndex))
4089 {
4090 ReplaceMapColor(byLastMoveIndex, byLastMoveIndex + 1);
4091 ::TextureMap.MoveIndex(byLastMoveIndex, byLastMoveIndex + 1);
4092 }
4093 // new insertion desired?
4094 if (szMatTex && *szMatTex)
4095 {
4096 // move from old or create new
4097 if (iOldIndex)
4098 {
4099 ReplaceMapColor(iOldIndex, iNewIndex);
4100 ::TextureMap.MoveIndex(iOldIndex, iNewIndex);
4101 }
4102 else
4103 {
4104 StdStrBuf Material, Texture;
4105 Material.CopyUntil(szMatTex, '-'); Texture.Copy(SSearch(szMatTex, "-"));
4106 // new insertion
4107 if (!::TextureMap.AddEntry(iNewIndex, Material.getData(), Texture.getData()))
4108 {
4109 LogF("Cannot insert new texture %s to index %d: Texture map entry error", (const char *)szMatTex, (int)iNewIndex);
4110 return false;
4111 }
4112 }
4113 }
4114 // done, success
4115 return true;
4116 }
4117 else
4118 {
4119 // new index must not be occupied
4120 const C4TexMapEntry *pOld;
4121 if ((pOld = ::TextureMap.GetEntry(iNewIndex)) && !pOld->isNull())
4122 {
4123 DebugLogF("Cannot move texture %s to index %d: Index occupied by %s-%s.", (const char *)szMatTex, (int)iNewIndex, pOld->GetMaterialName(), pOld->GetTextureName());
4124 return false;
4125 }
4126 // must only move existing textures
4127 if (!iOldIndex)
4128 {
4129 DebugLogF("Cannot move texture %s to index %d: Texture not found.", (const char *)szMatTex, (int)iNewIndex);
4130 return false;
4131 }
4132 // update map
4133 ReplaceMapColor(iOldIndex, iNewIndex);
4134 // change to new index in texmap
4135 ::TextureMap.MoveIndex(iOldIndex, iNewIndex);
4136 // done, success
4137 return true;
4138 }
4139 }
4140
4141 // change color index of map texture, or insert a new one
4142
SetMapChanged()4143 void C4Landscape::SetMapChanged() { p->fMapChanged = true; }
4144
RemoveUnusedTexMapEntries()4145 void C4Landscape::RemoveUnusedTexMapEntries()
4146 {
4147 // check usage in landscape
4148 bool fTexUsage[C4M_MaxTexIndex];
4149 int32_t iMatTex;
4150 for (iMatTex = 0; iMatTex < C4M_MaxTexIndex; ++iMatTex)
4151 fTexUsage[iMatTex] = false;
4152 for (int32_t y = 0; y < GetHeight(); ++y)
4153 for (int32_t x = 0; x < GetWidth(); ++x)
4154 {
4155 const BYTE pix = p->Surface8->GetPix(x, y);
4156 const BYTE backPix = p->Surface8Bkg->GetPix(x, y);
4157 assert(pix < C4M_MaxTexIndex);
4158 assert(backPix < C4M_MaxTexIndex);
4159
4160 fTexUsage[pix] = true;
4161 fTexUsage[backPix] = true;
4162 }
4163
4164 // check usage by materials
4165 for (int32_t iMat = 0; iMat < ::MaterialMap.Num; ++iMat)
4166 {
4167 C4Material *pMat = ::MaterialMap.Map + iMat;
4168 if (pMat->BlastShiftTo >= 0) fTexUsage[pMat->BlastShiftTo] = true;
4169 if (pMat->BelowTempConvertTo >= 0) fTexUsage[pMat->BelowTempConvertTo] = true;
4170 if (pMat->AboveTempConvertTo >= 0) fTexUsage[pMat->AboveTempConvertTo] = true;
4171 if (pMat->DefaultMatTex >= 0) fTexUsage[pMat->DefaultMatTex] = true;
4172 }
4173 // remove unused
4174 for (iMatTex = 1; iMatTex < C4M_MaxTexIndex; ++iMatTex)
4175 if (!fTexUsage[iMatTex])
4176 ::TextureMap.RemoveEntry(iMatTex);
4177 // flag rewrite
4178 ::TextureMap.fEntriesAdded = true;
4179 }
4180
GetSky()4181 C4Sky & C4Landscape::GetSky()
4182 {
4183 return p->Sky;
4184 }
4185
HasFoW() const4186 bool C4Landscape::HasFoW() const
4187 {
4188 return p->pFoW != nullptr;
4189 }
4190
GetFoW()4191 C4FoW * C4Landscape::GetFoW()
4192 {
4193 return p->pFoW.get();
4194 }
4195
GetMatCount(int material) const4196 int32_t C4Landscape::GetMatCount(int material) const
4197 {
4198 assert(material >= 0 && (unsigned) material < p->MatCount.size());
4199 return p->MatCount[material];
4200 }
4201
GetEffectiveMatCount(int material) const4202 int32_t C4Landscape::GetEffectiveMatCount(int material) const
4203 {
4204 assert(material >= 0 && (unsigned) material < p->EffectiveMatCount.size());
4205 return p->EffectiveMatCount[material];
4206 }
4207
4208 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
4209 /* +++++++++++++++++++++++++++ Update functions ++++++++++++++++++++++++++++ */
4210 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
4211
HandleTexMapUpdate()4212 void C4Landscape::HandleTexMapUpdate()
4213 {
4214 // Pixel maps must be update
4215 UpdatePixMaps();
4216 // Update landscape palette
4217 p->Mat2Pal();
4218 }
4219
UpdatePixMaps()4220 void C4Landscape::UpdatePixMaps()
4221 {
4222 int32_t i;
4223 for (i = 0; i < C4M_MaxTexIndex; i++) p->Pix2Mat[i] = PixCol2Mat(i);
4224 for (i = 0; i < C4M_MaxTexIndex; i++) p->Pix2Dens[i] = MatDensity(p->Pix2Mat[i]);
4225 for (i = 0; i < C4M_MaxTexIndex; i++) p->Pix2Place[i] = MatValid(p->Pix2Mat[i]) ? ::MaterialMap.Map[p->Pix2Mat[i]].Placement : 0;
4226 for (i = 0; i < C4M_MaxTexIndex; i++) p->Pix2Light[i] = MatValid(p->Pix2Mat[i]) && (::MaterialMap.Map[p->Pix2Mat[i]].Light>0);
4227 p->Pix2Place[0] = 0;
4228 // clear bridge mat conversion buffers
4229 std::fill(p->BridgeMatConversion.begin(), p->BridgeMatConversion.end(), nullptr);
4230 }
4231
Mat2Pal()4232 bool C4Landscape::P::Mat2Pal()
4233 {
4234 if (!Surface8 || !Surface8Bkg) return false;
4235 // set landscape pal
4236 int32_t tex;
4237 for (tex = 0; tex < C4M_MaxTexIndex; tex++)
4238 {
4239 const C4TexMapEntry *pTex = ::TextureMap.GetEntry(tex);
4240 if (!pTex || pTex->isNull())
4241 continue;
4242 // colors
4243 DWORD dwPix = pTex->GetPattern().PatternClr(0, 0);
4244 Surface8->pPal->Colors[MatTex2PixCol(tex)] = dwPix;
4245 Surface8Bkg->pPal->Colors[MatTex2PixCol(tex)] = dwPix;
4246 }
4247 // success
4248 return true;
4249 }
4250
4251
UpdatePixCnt(const C4Landscape * d,const C4Rect & Rect,bool fCheck)4252 void C4Landscape::P::UpdatePixCnt(const C4Landscape *d, const C4Rect &Rect, bool fCheck)
4253 {
4254 int32_t PixCntWidth = (Width + 16) / 17;
4255 for (int32_t y = std::max<int32_t>(0, Rect.y / 15); y < std::min<int32_t>(PixCntPitch, (Rect.y + Rect.Hgt + 14) / 15); y++)
4256 for (int32_t x = std::max<int32_t>(0, Rect.x / 17); x < std::min<int32_t>(PixCntWidth, (Rect.x + Rect.Wdt + 16) / 17); x++)
4257 {
4258 int iCnt = 0;
4259 for (int32_t x2 = x * 17; x2 < std::min<int32_t>(x * 17 + 17, Width); x2++)
4260 for (int32_t y2 = y * 15; y2 < std::min<int32_t>(y * 15 + 15, Height); y2++)
4261 if (d->_GetDensity(x2, y2))
4262 iCnt++;
4263 if (fCheck)
4264 assert(iCnt == PixCnt[x * PixCntPitch + y]);
4265 PixCnt[x * PixCntPitch + y] = iCnt;
4266 }
4267 }
4268
UpdateMatCnt(const C4Landscape * d,C4Rect Rect,bool fPlus)4269 void C4Landscape::P::UpdateMatCnt(const C4Landscape *d, C4Rect Rect, bool fPlus)
4270 {
4271 Rect.Intersect(C4Rect(0, 0, Width, Height));
4272 if (!Rect.Hgt || !Rect.Wdt) return;
4273 // Multiplicator for changes
4274 const int32_t iMul = fPlus ? +1 : -1;
4275 // Count pixels
4276 for (int32_t x = 0; x < Rect.Wdt; x++)
4277 {
4278 int iHgt = 0;
4279 int32_t y;
4280 for (y = 1; y < Rect.Hgt; y++)
4281 {
4282 int32_t iMat = d->_GetMat(Rect.x + x, Rect.y + y - 1);
4283 // Same material? Count it.
4284 if (iMat == d->_GetMat(Rect.x + x, Rect.y + y))
4285 iHgt++;
4286 else
4287 {
4288 if (iMat >= 0)
4289 {
4290 // Normal material counting
4291 MatCount[iMat] += iMul * (iHgt + 1);
4292 // Effective material counting enabled?
4293 if (int32_t iMinHgt = ::MaterialMap.Map[iMat].MinHeightCount)
4294 {
4295 // First chunk? Add any material above when checking chunk height
4296 int iAddedHeight = 0;
4297 if (Rect.y && iHgt + 1 == y)
4298 iAddedHeight = d->GetMatHeight(Rect.x + x, Rect.y - 1, -1, iMat, iMinHgt);
4299 // Check the chunk height
4300 if (iHgt + 1 + iAddedHeight >= iMinHgt)
4301 {
4302 EffectiveMatCount[iMat] += iMul * (iHgt + 1);
4303 if (iAddedHeight < iMinHgt)
4304 EffectiveMatCount[iMat] += iMul * iAddedHeight;
4305 }
4306 }
4307 }
4308 // Next chunk of material
4309 iHgt = 0;
4310 }
4311 }
4312 // Check last pixel
4313 int32_t iMat = d->_GetMat(Rect.x + x, Rect.y + Rect.Hgt - 1);
4314 if (iMat >= 0)
4315 {
4316 // Normal material counting
4317 MatCount[iMat] += iMul * (iHgt + 1);
4318 // Minimum height counting?
4319 if (int32_t iMinHgt = ::MaterialMap.Map[iMat].MinHeightCount)
4320 {
4321 int iAddedHeight1 = 0, iAddedHeight2 = 0;
4322 // Add any material above for chunk size check
4323 if (Rect.y && iHgt + 1 == Rect.Hgt)
4324 iAddedHeight1 = d->GetMatHeight(Rect.x + x, Rect.y - 1, -1, iMat, iMinHgt);
4325 // Add any material below for chunk size check
4326 if (Rect.y + y < Height)
4327 iAddedHeight2 = d->GetMatHeight(Rect.x + x, Rect.y + Rect.Hgt, 1, iMat, iMinHgt);
4328 // Chunk tall enough?
4329 if (iHgt + 1 + iAddedHeight1 + iAddedHeight2 >= ::MaterialMap.Map[iMat].MinHeightCount)
4330 {
4331 EffectiveMatCount[iMat] += iMul * (iHgt + 1);
4332 if (iAddedHeight1 < iMinHgt)
4333 EffectiveMatCount[iMat] += iMul * iAddedHeight1;
4334 if (iAddedHeight2 < iMinHgt)
4335 EffectiveMatCount[iMat] += iMul * iAddedHeight2;
4336 }
4337 }
4338 }
4339 }
4340 }
4341
4342
4343
4344 C4Landscape Landscape;
4345