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