1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* Handles landscape and sky */
18
19#include <C4Include.h>
20#include <C4Landscape.h>
21#include <C4SolidMask.h>
22
23#include <C4Map.h>
24#include <C4MapCreatorS2.h>
25#include <C4SolidMask.h>
26#include <C4Object.h>
27#include <C4Physics.h>
28#include <C4Random.h>
29#include <C4SurfaceFile.h>
30#include <C4ToolsDlg.h>
31#ifdef DEBUGREC
32#include <C4Record.h>
33#endif
34#include <C4Material.h>
35#include <C4Game.h>
36#include <C4Application.h>
37#include <C4Wrappers.h>
38
39#include <StdBitmap.h>
40#include <StdPNG.h>
41
42#include <cmath>
43#include <memory>
44#include <stdexcept>
45#include <utility>
46
47int32_t MVehic = MNone, MTunnel = MNone, MWater = MNone, MSnow = MNone, MEarth = MNone, MGranite = MNone;
48uint8_t MCVehic = 0;
49
50const int C4LS_MaxLightDistY = 8;
51const int C4LS_MaxLightDistX = 1;
52
53C4Landscape::C4Landscape()
54{
55 Default();
56}
57
58C4Landscape::~C4Landscape()
59{
60 Clear();
61}
62
63void C4Landscape::ScenarioInit()
64{
65 // Gravity
66 Gravity = FIXED100(x: Game.C4S.Landscape.Gravity.Evaluate()) / 5;
67 // Opens
68 LeftOpen = Game.C4S.Landscape.LeftOpen;
69 RightOpen = Game.C4S.Landscape.RightOpen;
70 TopOpen = Game.C4S.Landscape.TopOpen;
71 BottomOpen = Game.C4S.Landscape.BottomOpen;
72 // Side open scan
73 if (Game.C4S.Landscape.AutoScanSideOpen) ScanSideOpen();
74}
75
76void C4Landscape::Execute()
77{
78 // Landscape scan
79 if (!NoScan)
80 ExecuteScan();
81 // move sky
82 Sky.Execute();
83 // Relights
84 if (!Tick35)
85 DoRelights();
86}
87
88void C4Landscape::ExecuteScan()
89{
90 int32_t cy, mat;
91
92 // Check: Scan needed?
93 const int32_t iTemperature = Game.Weather.GetTemperature();
94 for (mat = 0; mat < Game.Material.Num; mat++)
95 if (MatCount[mat])
96 if (Game.Material.Map[mat].BelowTempConvertTo &&
97 iTemperature < Game.Material.Map[mat].BelowTempConvert)
98 break;
99 else if (Game.Material.Map[mat].AboveTempConvertTo &&
100 iTemperature > Game.Material.Map[mat].AboveTempConvert)
101 break;
102 if (mat >= Game.Material.Num)
103 return;
104
105#ifdef DEBUGREC_MATSCAN
106 AddDbgRec(RCT_MatScan, &ScanX, sizeof(ScanX));
107#endif
108
109 for (int32_t cnt = 0; cnt < ScanSpeed; cnt++)
110 {
111 // Scan landscape column: sectors down
112 int32_t last_mat = -1;
113 for (cy = 0; cy < Height; cy++)
114 {
115 mat = _GetMat(x: ScanX, y: cy);
116 // material change?
117 if (last_mat != mat)
118 {
119 // upwards
120 if (last_mat != -1)
121 DoScan(x: ScanX, y: cy - 1, mat: last_mat, dir: 1);
122 // downwards
123 if (mat != -1)
124 cy += DoScan(x: ScanX, y: cy, mat, dir: 0);
125 }
126 last_mat = mat;
127 }
128
129 // Scan advance & rewind
130 ScanX++;
131 if (ScanX >= Width)
132 ScanX = 0;
133 }
134}
135
136#define PRETTY_TEMP_CONV
137
138int32_t C4Landscape::DoScan(int32_t cx, int32_t cy, int32_t mat, int32_t dir)
139{
140 int32_t conv_to_tex = 0;
141 int32_t iTemperature = Game.Weather.GetTemperature();
142 // Check below conv
143 if (Game.Material.Map[mat].BelowTempConvertDir == dir)
144 if (Game.Material.Map[mat].BelowTempConvertTo)
145 if (iTemperature < Game.Material.Map[mat].BelowTempConvert)
146 conv_to_tex = Game.Material.Map[mat].BelowTempConvertTo;
147 // Check above conv
148 if (Game.Material.Map[mat].AboveTempConvertDir == dir)
149 if (Game.Material.Map[mat].AboveTempConvertTo)
150 if (iTemperature > Game.Material.Map[mat].AboveTempConvert)
151 conv_to_tex = Game.Material.Map[mat].AboveTempConvertTo;
152 // nothing to do?
153 if (!conv_to_tex) return 0;
154 // find material
155 int32_t conv_to = Game.TextureMap.GetEntry(iIndex: conv_to_tex)->GetMaterialIndex();
156 // find mat top
157 int32_t mconv = Game.Material.Map[mat].TempConvStrength,
158 mconvs = mconv;
159#ifdef DEBUGREC_MATSCAN
160 C4RCMatScan rc = { cx, cy, mat, conv_to, dir, mconvs };
161 AddDbgRec(RCT_MatScanDo, &rc, sizeof(C4RCMatScan));
162#endif
163 int32_t ydir = (dir == 0 ? +1 : -1), cy2;
164#ifdef PRETTY_TEMP_CONV
165 // get left pixel
166 int32_t lmat = (cx > 0 ? _GetMat(x: cx - 1, y: cy) : -1);
167 // left pixel not converted? do nothing
168 if (lmat == mat) return 0;
169 // left pixel converted? suppose it was done by a prior scan and skip check
170 if (lmat != conv_to)
171 {
172 int32_t iSearchRange = std::max<int32_t>(a: 5, b: mconvs);
173 // search upper/lower bound
174 int32_t cys = cy, cxs = cx;
175 while (cxs < GBackWdt - 1)
176 {
177 // one step right
178 cxs++;
179 if (_GetMat(x: cxs, y: cys) == mat)
180 {
181 // search surface
182 cys -= ydir;
183 while (Inside<int32_t>(ival: cys, lbound: 0, GBackHgt - 1) && _GetMat(x: cxs, y: cys) == mat)
184 {
185 cys -= ydir;
186 if ((mconvs = (std::min)(a: mconv - Abs(val: cys - cy), b: mconvs)) < 0)
187 return 0;
188 }
189 // out of bounds?
190 if (!Inside<int32_t>(ival: cys, lbound: 0, GBackHgt - 1)) break;
191 // back one step
192 cys += ydir;
193 }
194 else
195 {
196 // search surface
197 cys += ydir;
198 while (Inside<int32_t>(ival: cys, lbound: 0, GBackHgt - 1) && _GetMat(x: cxs, y: cys) != mat)
199 {
200 cys += ydir;
201 if (Abs(val: cys - cy) > iSearchRange)
202 break;
203 }
204 // out of bounds?
205 if (!Inside<int32_t>(ival: cys, lbound: 0, GBackHgt - 1)) break;
206 if (Abs(val: cys - cy) > iSearchRange) break;
207 }
208 }
209 }
210#endif
211 // Conversion
212 for (cy2 = cy; mconvs >= 0 && Inside<int32_t>(ival: cy2, lbound: 0, GBackHgt - 1); cy2 += ydir, mconvs--)
213 {
214 // material changed?
215 int32_t pix = _GetPix(x: cx, y: cy2);
216 if (PixCol2Mat(pixc: pix) != mat) break;
217#ifdef PRETTY_TEMP_CONV
218 // get left pixel
219 int32_t lmat = (cx > 0 ? _GetMat(x: cx - 1, y: cy2) : -1);
220 // left pixel not converted? break
221 if (lmat == mat) break;
222#endif
223 // set mat
224 SBackPix(x: cx, y: cy2, npix: MatTex2PixCol(tex: conv_to_tex) + PixColIFT(pixc: pix));
225 CheckInstabilityRange(tx: cx, ty: cy2);
226 }
227 // return pixel converted
228 return Abs(val: cy2 - cy);
229}
230
231void C4Landscape::ScanSideOpen()
232{
233 int32_t cy;
234 for (cy = 0; (cy < Height) && !GetPix(x: 0, y: cy); cy++);
235 LeftOpen = cy;
236 for (cy = 0; (cy < Height) && !GetPix(x: Width - 1, y: cy); cy++);
237 RightOpen = cy;
238}
239
240void C4Landscape::Clear(bool fClearMapCreator, bool fClearSky)
241{
242 if (fClearMapCreator) { delete pMapCreator; pMapCreator = nullptr; }
243 // clear sky
244 if (fClearSky) Sky.Clear();
245 // clear surfaces, if assigned
246 delete Surface32; Surface32 = nullptr;
247 delete AnimationSurface; AnimationSurface = nullptr;
248 delete Surface8; Surface8 = nullptr;
249 delete Map; Map = nullptr;
250 // clear initial landscape
251 delete[] pInitial; pInitial = nullptr;
252 // clear scan
253 ScanX = 0;
254 Mode = C4LSC_Undefined;
255 // clear pixel count
256 delete[] PixCnt; PixCnt = nullptr;
257 PixCntPitch = 0;
258}
259
260void C4Landscape::Draw(C4FacetEx &cgo, int32_t iPlayer)
261{
262 if (Modulation) Application.DDraw->ActivateBlitModulation(dwWithClr: Modulation);
263 // do relights
264 DoRelights();
265 // blit landscape
266 if (Game.GraphicsSystem.ShowSolidMask)
267 Application.DDraw->Blit8Fast(sfcSource: Surface8, fx: cgo.TargetX, fy: cgo.TargetY, sfcTarget: cgo.Surface, tx: cgo.X, ty: cgo.Y, wdt: cgo.Wdt, hgt: cgo.Hgt);
268 else
269 Application.DDraw->BlitLandscape(sfcSource: Surface32, sfcSource2: AnimationSurface, sfcLiquidAnimation: &Game.GraphicsResource.sfcLiquidAnimation, fx: cgo.TargetX, fy: cgo.TargetY, sfcTarget: cgo.Surface, tx: cgo.X, ty: cgo.Y, wdt: cgo.Wdt, hgt: cgo.Hgt);
270 if (Modulation) Application.DDraw->DeactivateBlitModulation();
271}
272
273int32_t C4Landscape::ChunkyRandom(int32_t &iOffset, int32_t iRange)
274{
275 if (!iRange) return 0;
276 iOffset += 3;
277 return (iOffset ^ MapSeed) % iRange;
278}
279
280void C4Landscape::DrawChunk(int32_t tx, int32_t ty, int32_t wdt, int32_t hgt, int32_t mcol, int32_t iChunkType, int32_t cro)
281{
282 uint8_t top_rough; uint8_t side_rough;
283 // what to do?
284 switch (iChunkType)
285 {
286 case C4M_Flat:
287 Surface8->Box(iX: tx, iY: ty, iX2: tx + wdt, iY2: ty + hgt, iCol: mcol);
288 return;
289 case C4M_TopFlat:
290 top_rough = 0; side_rough = 1;
291 break;
292 case C4M_Smooth:
293 top_rough = 1; side_rough = 1;
294 break;
295 case C4M_Rough:
296 top_rough = 1; side_rough = 2;
297 break;
298 }
299 int vtcs[16];
300 int32_t rx = (std::max)(a: wdt / 2, b: 1);
301
302 vtcs[ 0] = tx - ChunkyRandom(iOffset&: cro, iRange: rx / 2); vtcs[ 1] = ty - ChunkyRandom(iOffset&: cro, iRange: rx / 2 * top_rough);
303 vtcs[ 2] = tx - ChunkyRandom(iOffset&: cro, iRange: rx * side_rough); vtcs[ 3] = ty + hgt / 2;
304 vtcs[ 4] = tx - ChunkyRandom(iOffset&: cro, iRange: rx); vtcs[ 5] = ty + hgt + ChunkyRandom(iOffset&: cro, iRange: rx);
305 vtcs[ 6] = tx + wdt / 2; vtcs[ 7] = ty + hgt + ChunkyRandom(iOffset&: cro, iRange: 2 * rx);
306 vtcs[ 8] = tx + wdt + ChunkyRandom(iOffset&: cro, iRange: rx); vtcs[ 9] = ty + hgt + ChunkyRandom(iOffset&: cro, iRange: rx);
307 vtcs[10] = tx + wdt + ChunkyRandom(iOffset&: cro, iRange: rx * side_rough); vtcs[11] = ty + hgt / 2;
308 vtcs[12] = tx + wdt + ChunkyRandom(iOffset&: cro, iRange: rx / 2); vtcs[13] = ty - ChunkyRandom(iOffset&: cro, iRange: rx / 2 * top_rough);
309 vtcs[14] = tx + wdt / 2; vtcs[15] = ty - ChunkyRandom(iOffset&: cro, iRange: rx * top_rough);
310
311 Surface8->Polygon(iNum: 8, ipVtx: vtcs, iCol: mcol);
312}
313
314void C4Landscape::DrawSmoothOChunk(int32_t tx, int32_t ty, int32_t wdt, int32_t hgt, int32_t mcol, uint8_t flip, int32_t cro)
315{
316 int vtcs[8];
317 int32_t rx = (std::max)(a: wdt / 2, b: 1);
318
319 vtcs[0] = tx; vtcs[1] = ty - ChunkyRandom(iOffset&: cro, iRange: rx / 2);
320 vtcs[2] = tx; vtcs[3] = ty + hgt;
321 vtcs[4] = tx + wdt; vtcs[5] = ty + hgt;
322 vtcs[6] = tx + wdt; vtcs[7] = ty - ChunkyRandom(iOffset&: cro, iRange: rx / 2);
323
324 if (flip)
325 {
326 vtcs[0] = tx + wdt / 2; vtcs[1] = ty + hgt / 3;
327 }
328 else
329 {
330 vtcs[6] = tx + wdt / 2; vtcs[7] = ty + hgt / 3;
331 }
332
333 Surface8->Polygon(iNum: 4, ipVtx: vtcs, iCol: mcol);
334}
335
336void C4Landscape::ChunkOZoom(CSurface8 *sfcMap, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, int32_t iTexture, int32_t iOffX, int32_t iOffY)
337{
338 int32_t iX, iY, iChunkWidth, iChunkHeight, iToX, iToY;
339 int32_t iIFT;
340 uint8_t byMapPixel, byMapPixelBelow;
341 int iMapWidth, iMapHeight;
342 C4Material *pMaterial = Game.TextureMap.GetEntry(iIndex: iTexture)->GetMaterial();
343 if (!pMaterial) return;
344 int32_t iChunkType = pMaterial->MapChunkType;
345 uint8_t byColor = MatTex2PixCol(tex: iTexture);
346 // Get map & landscape size
347 sfcMap->GetSurfaceSize(irX&: iMapWidth, irY&: iMapHeight);
348 // Clip desired map segment to map size
349 iMapX = BoundBy<int32_t>(bval: iMapX, lbound: 0, rbound: iMapWidth - 1); iMapY = BoundBy<int32_t>(bval: iMapY, lbound: 0, rbound: iMapHeight - 1);
350 iMapWdt = BoundBy<int32_t>(bval: iMapWdt, lbound: 0, rbound: iMapWidth - iMapX); iMapHgt = BoundBy<int32_t>(bval: iMapHgt, lbound: 0, rbound: iMapHeight - iMapY);
351 // get chunk size
352 iChunkWidth = MapZoom; iChunkHeight = MapZoom;
353 Surface32->Lock();
354 if (AnimationSurface) AnimationSurface->Lock();
355 // Scan map lines
356 for (iY = iMapY; iY < iMapY + iMapHgt; iY++)
357 {
358 // Landscape target coordinate vertical
359 iToY = iY * iChunkHeight + iOffY;
360 // Scan map line
361 for (iX = iMapX; iX < iMapX + iMapWdt; iX++)
362 {
363 // Map scan line start
364 byMapPixel = sfcMap->GetPix(iX, iY);
365 // Map scan line pixel below
366 byMapPixelBelow = sfcMap->GetPix(iX, iY: iY + 1);
367 // Landscape target coordinate horizontal
368 iToX = iX * iChunkWidth + iOffX;
369 // Here's a chunk of the texture-material to zoom
370 if ((byMapPixel & 127) == iTexture)
371 {
372 // Determine IFT
373 iIFT = 0; if (byMapPixel >= 128) iIFT = IFT;
374 // Draw chunk
375 DrawChunk(tx: iToX, ty: iToY, wdt: iChunkWidth, hgt: iChunkHeight, mcol: byColor + iIFT, iChunkType: pMaterial->MapChunkType, cro: (iX << 2) + iY);
376 }
377 // Other chunk, check for slope smoothers
378 else
379 // Smooth chunk & same texture-material below
380 if ((iChunkType == C4M_Smooth) && (iY < iMapHeight - 1) && ((byMapPixelBelow & 127) == iTexture))
381 {
382 // Same texture-material on left
383 if ((iX > 0) && ((sfcMap->GetPix(iX: iX - 1, iY) & 127) == iTexture))
384 {
385 // Determine IFT
386 iIFT = 0; if (sfcMap->GetPix(iX: iX - 1, iY) >= 128) iIFT = IFT;
387 // Draw smoother
388 DrawSmoothOChunk(tx: iToX, ty: iToY, wdt: iChunkWidth, hgt: iChunkHeight, mcol: byColor + iIFT, flip: 0, cro: (iX << 2) + iY);
389 }
390 // Same texture-material on right
391 if ((iX < iMapWidth - 1) && ((sfcMap->GetPix(iX: iX + 1, iY) & 127) == iTexture))
392 {
393 // Determine IFT
394 iIFT = 0; if (sfcMap->GetPix(iX: iX + 1, iY) >= 128) iIFT = IFT;
395 // Draw smoother
396 DrawSmoothOChunk(tx: iToX, ty: iToY, wdt: iChunkWidth, hgt: iChunkHeight, mcol: byColor + iIFT, flip: 1, cro: (iX << 2) + iY);
397 }
398 }
399 }
400 }
401 Surface32->Unlock();
402 if (AnimationSurface) AnimationSurface->Unlock();
403}
404
405bool C4Landscape::GetTexUsage(CSurface8 *sfcMap, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, uint32_t *dwpTextureUsage)
406{
407 int iX, iY;
408 // No good parameters
409 if (!sfcMap || !dwpTextureUsage) return false;
410 // Clip desired map segment to map size
411 iMapX = BoundBy<int32_t>(bval: iMapX, lbound: 0, rbound: sfcMap->Wdt - 1); iMapY = BoundBy<int32_t>(bval: iMapY, lbound: 0, rbound: sfcMap->Hgt - 1);
412 iMapWdt = BoundBy<int32_t>(bval: iMapWdt, lbound: 0, rbound: sfcMap->Wdt - iMapX); iMapHgt = BoundBy<int32_t>(bval: iMapHgt, lbound: 0, rbound: sfcMap->Hgt - iMapY);
413 // Zero texture usage list
414 for (int32_t cnt = 0; cnt < C4M_MaxTexIndex; cnt++) dwpTextureUsage[cnt] = 0;
415 // Scan map pixels
416 for (iY = iMapY; iY < iMapY + iMapHgt; iY++)
417 for (iX = iMapX; iX < iMapX + iMapWdt; iX++)
418 // Count texture map index only (no IFT)
419 dwpTextureUsage[sfcMap->GetPix(iX, iY) & (IFT - 1)]++;
420 // Done
421 return true;
422}
423
424bool C4Landscape::TexOZoom(CSurface8 *sfcMap, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, uint32_t *dwpTextureUsage, int32_t iToX, int32_t iToY)
425{
426 int32_t iIndex;
427
428 // ChunkOZoom all used textures
429 for (iIndex = 1; iIndex < C4M_MaxTexIndex; iIndex++)
430 if (dwpTextureUsage[iIndex] > 0)
431 {
432 // ChunkOZoom map to landscape
433 ChunkOZoom(sfcMap, iMapX, iMapY, iMapWdt, iMapHgt, iTexture: iIndex, iOffX: iToX, iOffY: iToY);
434 }
435
436 // Done
437 return true;
438}
439
440bool C4Landscape::SkyToLandscape(int32_t iToX, int32_t iToY, int32_t iToWdt, int32_t iToHgt, int32_t iOffX, int32_t iOffY)
441{
442 if (!Surface32->Lock()) return false;
443 // newgfx: simply blit the sky in realtime...
444 Surface32->ClearBoxDw(iX: iToX, iY: iToY, iWdt: iToWdt, iHgt: iToHgt);
445 Surface8->ClearBox8Only(iX: iToX, iY: iToY, iWdt: iToWdt, iHgt: iToHgt);
446 // unlock
447 Surface32->Unlock();
448 // Done
449 return true;
450}
451
452bool C4Landscape::MapToSurface(CSurface8 *sfcMap, 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)
453{
454 // Sky background segment
455 SkyToLandscape(iToX, iToY, iToWdt, iToHgt, iOffX, iOffY);
456
457 // assign clipper
458 Surface8->Clip(iX: iToX, iY: iToY, iX2: iToX + iToWdt - 1, iY2: iToY + iToHgt - 1);
459 Surface32->Clip(iX: iToX, iY: iToY, iX2: iToX + iToWdt - 1, iY2: iToY + iToHgt - 1);
460 if (AnimationSurface) AnimationSurface->Clip(iX: iToX, iY: iToY, iX2: iToX + iToWdt - 1, iY2: iToY + iToHgt - 1);
461 Application.DDraw->NoPrimaryClipper();
462
463 // Enlarge map segment for chunky rim
464 iMapX -= 2; iMapY -= 2; iMapWdt += 4; iMapHgt += 4;
465
466 // Determine texture usage in map segment
467 uint32_t dwTexUsage[C4M_MaxTexIndex + 1];
468 if (!GetTexUsage(sfcMap, iMapX, iMapY, iMapWdt, iMapHgt, dwpTextureUsage: dwTexUsage)) return false;
469 // Texture zoom map to landscape
470 if (!TexOZoom(sfcMap, iMapX, iMapY, iMapWdt, iMapHgt, dwpTextureUsage: dwTexUsage, iToX: iOffX, iToY: iOffY)) return false;
471
472 // remove clipper
473 Surface8->NoClip();
474 Surface32->NoClip();
475 if (AnimationSurface) AnimationSurface->NoClip();
476
477 // success
478 return true;
479}
480
481bool C4Landscape::MapToLandscape(CSurface8 *sfcMap, int32_t iMapX, int32_t iMapY, int32_t iMapWdt, int32_t iMapHgt, int32_t iOffsX, int32_t iOffsY)
482{
483 assert(Surface8); assert(Surface32);
484 // Clip to map/landscape segment
485 int iMapWidth, iMapHeight, iLandscapeWidth, iLandscapeHeight;
486 // Get map & landscape size
487 sfcMap->GetSurfaceSize(irX&: iMapWidth, irY&: iMapHeight);
488 Surface8->GetSurfaceSize(irX&: iLandscapeWidth, irY&: iLandscapeHeight);
489 // Clip map segment to map size
490 iMapX = BoundBy<int32_t>(bval: iMapX, lbound: 0, rbound: iMapWidth - 1); iMapY = BoundBy<int32_t>(bval: iMapY, lbound: 0, rbound: iMapHeight - 1);
491 iMapWdt = BoundBy<int32_t>(bval: iMapWdt, lbound: 0, rbound: iMapWidth - iMapX); iMapHgt = BoundBy<int32_t>(bval: iMapHgt, lbound: 0, rbound: iMapHeight - iMapY);
492 // No segment
493 if (!iMapWdt || !iMapHgt) return true;
494
495 // Get affected landscape rect
496 C4Rect To;
497 To.x = iMapX * MapZoom + iOffsX;
498 To.y = iMapY * MapZoom + iOffsY;
499 To.Wdt = iMapWdt * MapZoom;
500 To.Hgt = iMapHgt * MapZoom;
501
502 Surface32->Lock();
503 if (AnimationSurface) AnimationSurface->Lock();
504 PrepareChange(BoundingBox: To);
505 MapToSurface(sfcMap, iMapX, iMapY, iMapWdt, iMapHgt, iToX: To.x, iToY: To.y, iToWdt: To.Wdt, iToHgt: To.Hgt, iOffX: iOffsX, iOffY: iOffsY);
506 FinishChange(BoundingBox: To);
507 Surface32->Unlock();
508 if (AnimationSurface) AnimationSurface->Unlock();
509 return true;
510}
511
512CSurface8 *C4Landscape::CreateMap()
513{
514 std::int32_t width{0};
515 std::int32_t height{0};
516
517 // Create map surface
518 Game.C4S.Landscape.GetMapSize(rWdt&: width, rHgt&: height, iPlayerNum: Game.Parameters.StartupPlayerCount);
519 auto surfaceMap = std::make_unique<CSurface8>(args&: width, args&: height);
520
521 // Fill sfcMap
522 C4MapCreator MapCreator;
523 MapCreator.Create(sfcMap: surfaceMap.get(),
524 rLScape&: Game.C4S.Landscape, rTexMap&: Game.TextureMap,
525 fLayers: true, iPlayerNum: Game.Parameters.StartupPlayerCount);
526
527 return surfaceMap.release();
528}
529
530CSurface8 *C4Landscape::CreateMapS2(C4Group &ScenFile)
531{
532 // file present?
533 if (!ScenFile.AccessEntry(C4CFN_DynLandscape)) return nullptr;
534
535 // create map creator
536 if (!pMapCreator)
537 pMapCreator = new C4MapCreatorS2(&Game.C4S.Landscape, &Game.TextureMap, &Game.Material, Game.Parameters.StartupPlayerCount);
538
539 // read file
540 pMapCreator->ReadFile(C4CFN_DynLandscape, pGrp: &ScenFile);
541 // render landscape
542 CSurface8 *sfc = pMapCreator->Render(szMapName: nullptr);
543
544 // keep map creator until script callbacks have been done
545 return sfc;
546}
547
548bool C4Landscape::PostInitMap()
549{
550 // map creator present?
551 if (!pMapCreator) return true;
552 // call scripts
553 pMapCreator->ExecuteCallbacks(iMapZoom: MapZoom);
554 // destroy map creator, if not needed later
555 if (!Game.C4S.Landscape.KeepMapCreator) { delete pMapCreator; pMapCreator = nullptr; }
556 // done, success
557 return true;
558}
559
560bool C4Landscape::Init(C4Group &hGroup, bool fOverloadCurrent, bool fLoadSky, bool &rfLoaded, bool fSavegame)
561{
562 // set map seed, if not pre-assigned
563 if (!MapSeed) MapSeed = Random(iRange: 3133700);
564
565 ShadeMaterials = Game.C4S.Landscape.ShadeMaterials;
566
567 // increase max map size, since developers might set a greater one here
568 Game.C4S.Landscape.MapWdt.Max = 10000;
569 Game.C4S.Landscape.MapHgt.Max = 10000;
570
571 // map and landscape must be initialized with fixed random, so runtime joining clients may recreate it
572 // with same seed
573 // after map/landscape creation, the seed must be fixed again, so there's no difference between clients creating
574 // and not creating the map
575 // this, however, would cause syncloss to DebugRecs
576 C4DebugRecOff DBGRECOFF(!!Game.C4S.Landscape.ExactLandscape);
577
578 Game.FixRandom(iSeed: Game.Parameters.RandomSeed);
579
580 // map is like it's loaded for regular gamestart
581 // but it's changed and would have to be saved if a new section is loaded
582 fMapChanged = fOverloadCurrent;
583
584 // don't change landscape mode in runtime joins
585 bool fLandscapeModeSet = (Mode != C4LSC_Undefined);
586
587 Game.SetInitProgress(60);
588 // create map if necessary
589 if (!Game.C4S.Landscape.ExactLandscape)
590 {
591 CSurface8 *sfcMap = nullptr;
592 // Static map from scenario
593 if (hGroup.AccessEntry(C4CFN_Map))
594 if (sfcMap = GroupReadSurface8(hGroup))
595 if (!fLandscapeModeSet) Mode = C4LSC_Static;
596
597 // allow C4CFN_Landscape as map for downwards compatibility
598 if (!sfcMap)
599 if (hGroup.AccessEntry(C4CFN_Landscape))
600 if (sfcMap = GroupReadSurface8(hGroup))
601 {
602 if (!fLandscapeModeSet) Mode = C4LSC_Static;
603 fMapChanged = true;
604 }
605
606 // dynamic map from file
607 if (!sfcMap)
608 if (sfcMap = CreateMapS2(ScenFile&: hGroup))
609 if (!fLandscapeModeSet) Mode = C4LSC_Dynamic;
610
611 // Dynamic map by scenario
612 if (!sfcMap && !fOverloadCurrent)
613 if (sfcMap = CreateMap())
614 if (!fLandscapeModeSet) Mode = C4LSC_Dynamic;
615
616 // No map failure
617 if (!sfcMap)
618 {
619 // no problem if only overloading
620 if (!fOverloadCurrent) return false;
621 if (fLoadSky) if (!Sky.Init(fSavegame)) return false;
622 return true;
623 }
624
625#ifdef DEBUGREC
626 AddDbgRec(RCT_Block, "|---MAP---|", 12);
627 AddDbgRec(RCT_Map, sfcMap->Bits, sfcMap->Pitch * sfcMap->Hgt);
628#endif
629
630 // Store map size and calculate map zoom
631 int iWdt, iHgt;
632 sfcMap->GetSurfaceSize(irX&: iWdt, irY&: iHgt);
633 MapWidth = iWdt; MapHeight = iHgt;
634 MapZoom = Game.C4S.Landscape.MapZoom.Evaluate();
635
636 // Calculate landscape size
637 Width = MapZoom * MapWidth;
638 Height = MapZoom * MapHeight;
639 Width = std::max<int32_t>(a: Width, b: 100);
640 Height = std::max<int32_t>(a: Height, b: 100);
641
642 // if overloading, clear current landscape (and sections, etc.)
643 // must clear, of course, before new sky is eventually read
644 if (fOverloadCurrent) Clear(fClearMapCreator: !Game.C4S.Landscape.KeepMapCreator, fClearSky: fLoadSky);
645
646 // assign new map
647 Map = sfcMap;
648
649 // Sky (might need to know landscape height)
650 if (fLoadSky)
651 {
652 Game.SetInitProgress(70);
653 if (!Sky.Init(fSavegame)) return false;
654 }
655 }
656
657 // Exact landscape from scenario (no map or exact recreation)
658 else
659 {
660 C4DebugRecOff DBGRECOFF;
661 // if overloading, clear current
662 if (fOverloadCurrent) Clear(fClearMapCreator: !Game.C4S.Landscape.KeepMapCreator, fClearSky: fLoadSky);
663 // load it
664 if (!fLandscapeModeSet) Mode = C4LSC_Exact;
665 rfLoaded = true;
666 if (!Load(hGroup, fLoadSky, fSavegame)) return false;
667 }
668
669 // Make pixel maps
670 UpdatePixMaps();
671
672 // progress
673 Game.SetInitProgress(80);
674
675 // mark as new-style
676 Game.C4S.Landscape.NewStyleLandscape = 2;
677
678 // copy noscan-var
679 NoScan = Game.C4S.Landscape.NoScan;
680
681 // Scan settings
682 ScanSpeed = BoundBy(bval: Width / 500, lbound: 2, rbound: 15);
683
684 // create it
685 if (!Game.C4S.Landscape.ExactLandscape)
686 {
687 // map to big surface and sectionize it
688 // Create landscape surface
689 Surface32 = new C4Surface();
690 Surface8 = new CSurface8();
691 if (Config.Graphics.ColorAnimation && Config.Graphics.Shader)
692 AnimationSurface = new C4Surface(Width, Height);
693 if (!Surface32->Create(iWdt: Width, iHgt: Height, fOwnPal: true)
694 || !Surface8->Create(iWdt: Width, iHgt: Height, fOwnPal: true)
695 || (AnimationSurface && !AnimationSurface->Create(iWdt: Width, iHgt: Height))
696 || !Mat2Pal())
697 {
698 delete Surface8; delete Surface32; delete AnimationSurface;
699 Surface8 = nullptr; Surface32 = nullptr; AnimationSurface = nullptr;
700 return false;
701 }
702
703 // Map to landscape
704 if (!MapToLandscape()) return false;
705 }
706 Game.SetInitProgress(87);
707
708#ifdef DEBUGREC
709 AddDbgRec(RCT_Block, "|---LS---|", 11);
710 AddDbgRec(RCT_Ls, Surface8->Bits, Surface8->Pitch * Surface8->Hgt);
711#endif
712
713 // Create pixel count array
714 // We will use 15x17 blocks so the pixel count can't get over 255.
715 int32_t PixCntWidth = (Width + 16) / 17;
716 PixCntPitch = (Height + 14) / 15;
717 PixCnt = new uint8_t[PixCntWidth * PixCntPitch];
718 UpdatePixCnt(Rect: C4Rect(0, 0, Width, Height));
719 ClearMatCount();
720 UpdateMatCnt(Rect: C4Rect(0, 0, Width, Height), fPlus: true);
721
722 // Save initial landscape
723 if (!SaveInitial())
724 return false;
725
726 // Load diff, if existent
727 ApplyDiff(hGroup);
728
729 // enforce first color to be transparent
730 Surface8->EnforceC0Transparency();
731
732 // after map/landscape creation, the seed must be fixed again, so there's no difference between clients creating
733 // and not creating the map
734 Game.FixRandom(iSeed: Game.Parameters.RandomSeed);
735
736 // Success
737 rfLoaded = true;
738 return true;
739}
740
741bool C4Landscape::SetPix(int32_t x, int32_t y, uint8_t npix)
742{
743#ifdef DEBUGREC
744 C4RCSetPix rc;
745 rc.x = x; rc.y = y; rc.clr = npix;
746 AddDbgRec(RCT_SetPix, &rc, sizeof(rc));
747#endif
748 // check bounds
749 if (x < 0 || y < 0 || x >= Width || y >= Height)
750 return false;
751 // no change?
752 if (npix == _GetPix(x, y))
753 return true;
754 // note for relight
755 C4Rect CheckRect(x - 2 * C4LS_MaxLightDistX, y - 2 * C4LS_MaxLightDistY, 4 * C4LS_MaxLightDistX + 1, 4 * C4LS_MaxLightDistY + 1);
756 for (int32_t i = 0; i < C4LS_MaxRelights; i++)
757 if (!Relights[i].Wdt || Relights[i].Overlap(rTarget&: CheckRect) || i + 1 >= C4LS_MaxRelights)
758 {
759 Relights[i].Add(r2: C4Rect(x, y, 1, 1));
760 break;
761 }
762 // set pixel
763 return _SetPix(x, y, npix);
764}
765
766bool C4Landscape::SetPixDw(int32_t x, int32_t y, uint32_t dwPix)
767{
768 if (!Surface32->LockForUpdate(rect: {x, y, 1, 1}))
769 {
770 return false;
771 }
772 // set in surface
773 const auto ret = Surface32->SetPixDw(iX: x, iY: y, dwCol: dwPix);
774 Surface32->Unlock();
775 return ret;
776}
777
778bool C4Landscape::_SetPix(int32_t x, int32_t y, uint8_t npix)
779{
780#ifdef DEBUGREC
781 C4RCSetPix rc;
782 rc.x = x; rc.y = y; rc.clr = npix;
783 AddDbgRec(RCT_SetPix, &rc, sizeof(rc));
784#endif
785 assert(x >= 0 && y >= 0 && x < Width && y < Height);
786 // get and check pixel
787 uint8_t opix = _GetPix(x, y);
788 if (npix == opix) return true;
789 // count pixels
790 if (Pix2Dens[npix])
791 {
792 if (!Pix2Dens[opix]) PixCnt[(y / 15) + (x / 17) * PixCntPitch]++;
793 }
794 else
795 {
796 if (Pix2Dens[opix]) PixCnt[(y / 15) + (x / 17) * PixCntPitch]--;
797 }
798
799 // count material
800 if (!npix || MatValid(mat: Pix2Mat[npix]))
801 {
802 int32_t omat = Pix2Mat[opix], nmat = Pix2Mat[npix];
803 if (opix) MatCount[omat]--;
804 if (npix) MatCount[nmat]++;
805 // count effective material
806 if (omat != nmat)
807 {
808 if (npix && Game.Material.Map[nmat].MinHeightCount)
809 {
810 // Check for material above & below
811 int iMinHeight = Game.Material.Map[nmat].MinHeightCount,
812 iBelow = GetMatHeight(x, y: y + 1, iYDir: +1, iMat: nmat, iMax: iMinHeight),
813 iAbove = GetMatHeight(x, y: y - 1, iYDir: -1, iMat: nmat, iMax: iMinHeight);
814 // Will be above treshold?
815 if (iBelow + iAbove + 1 >= iMinHeight)
816 {
817 int iChange = 1;
818 // Check for heights below threshold
819 if (iBelow < iMinHeight) iChange += iBelow;
820 if (iAbove < iMinHeight) iChange += iAbove;
821 // Change
822 EffectiveMatCount[nmat] += iChange;
823 }
824 }
825 if (opix && Game.Material.Map[omat].MinHeightCount)
826 {
827 // Check for material above & below
828 int iMinHeight = Game.Material.Map[omat].MinHeightCount,
829 iBelow = GetMatHeight(x, y: y + 1, iYDir: +1, iMat: omat, iMax: iMinHeight),
830 iAbove = GetMatHeight(x, y: y - 1, iYDir: -1, iMat: omat, iMax: iMinHeight);
831 // Not already below threshold?
832 if (iBelow + iAbove + 1 >= iMinHeight)
833 {
834 int iChange = 1;
835 // Check for heights that will get below threshold
836 if (iBelow < iMinHeight) iChange += iBelow;
837 if (iAbove < iMinHeight) iChange += iAbove;
838 // Change
839 EffectiveMatCount[omat] -= iChange;
840 }
841 }
842 }
843 }
844
845 // set 8bpp-surface only!
846 Surface8->SetPix(iX: x, iY: y, byCol: npix);
847 // success
848 return true;
849}
850
851bool C4Landscape::_SetPixIfMask(int32_t x, int32_t y, uint8_t npix, uint8_t nMask)
852{
853 // set 8bpp-surface only!
854 if (_GetPix(x, y) == nMask)
855 _SetPix(x, y, npix);
856 // success
857 return true;
858}
859
860bool C4Landscape::CheckInstability(int32_t tx, int32_t ty)
861{
862 int32_t mat = GetMat(x: tx, y: ty);
863 if (MatValid(mat))
864 if (Game.Material.Map[mat].Instable)
865 return Game.MassMover.Create(x: tx, y: ty);
866 return false;
867}
868
869void C4Landscape::CheckInstabilityRange(int32_t tx, int32_t ty)
870{
871 if (!CheckInstability(tx, ty))
872 {
873 CheckInstability(tx, ty: ty - 1);
874 CheckInstability(tx, ty: ty - 2);
875 CheckInstability(tx: tx - 1, ty);
876 CheckInstability(tx: tx + 1, ty);
877 }
878}
879
880bool C4Landscape::ClearPix(int32_t tx, int32_t ty)
881{
882 uint8_t bcol;
883 if (GBackIFT(x: tx, y: ty))
884 bcol = Mat2PixColDefault(mat: MTunnel) + IFT;
885 else
886 bcol = 0;
887 return SetPix(x: tx, y: ty, npix: bcol);
888}
889
890bool C4Landscape::_PathFree(int32_t x, int32_t y, int32_t x2, int32_t y2)
891{
892 x /= 17; y /= 15; x2 /= 17; y2 /= 15;
893 while (x != x2 && y != y2)
894 {
895 if (PixCnt[x * PixCntPitch + y])
896 return false;
897 if (x > x2) x--; else x++;
898 if (y > y2) y--; else y++;
899 }
900 if (x != x2)
901 do
902 {
903 if (PixCnt[x * PixCntPitch + y])
904 return false;
905 if (x > x2) x--; else x++;
906 } while (x != x2);
907 else
908 while (y != y2)
909 {
910 if (PixCnt[x * PixCntPitch + y])
911 return false;
912 if (y > y2) y--; else y++;
913 }
914 return !PixCnt[x * PixCntPitch + y];
915}
916
917int32_t C4Landscape::GetMatHeight(int32_t x, int32_t y, int32_t iYDir, int32_t iMat, int32_t iMax)
918{
919 if (iYDir > 0)
920 {
921 iMax = std::min<int32_t>(a: iMax, b: Height - y);
922 for (int32_t i = 0; i < iMax; i++)
923 if (_GetMat(x, y: y + i) != iMat)
924 return i;
925 }
926 else
927 {
928 iMax = std::min<int32_t>(a: iMax, b: y + 1);
929 for (int32_t i = 0; i < iMax; i++)
930 if (_GetMat(x, y: y - i) != iMat)
931 return i;
932 }
933 return iMax;
934}
935
936int32_t C4Landscape::DigFreePix(int32_t tx, int32_t ty)
937{
938 int32_t mat = GetMat(x: tx, y: ty);
939 if (mat != MNone)
940 if (Game.Material.Map[mat].DigFree)
941 ClearPix(tx, ty);
942 CheckInstabilityRange(tx, ty);
943 return mat;
944}
945
946int32_t C4Landscape::ShakeFreePix(int32_t tx, int32_t ty)
947{
948 int32_t mat = GetMat(x: tx, y: ty);
949 if (mat != MNone)
950 if (Game.Material.Map[mat].DigFree)
951 {
952 ClearPix(tx, ty);
953 Game.PXS.Create(mat, ix: itofix(x: tx), iy: itofix(x: ty));
954 }
955 CheckInstabilityRange(tx, ty);
956 return mat;
957}
958
959int32_t C4Landscape::BlastFreePix(int32_t tx, int32_t ty, int32_t grade, int32_t iBlastSize)
960{
961 int32_t mat = GetMat(x: tx, y: ty);
962 if (MatValid(mat))
963 {
964 // Blast Shift
965 if (Game.Material.Map[mat].BlastShiftTo)
966 {
967 // blast free amount; always blast if 100% is to be blasted away
968 if (Random(iRange: BlastMatCount[mat]) < iBlastSize * grade / 6)
969 SetPix(x: tx, y: ty, npix: MatTex2PixCol(tex: Game.Material.Map[mat].BlastShiftTo) + GBackIFT(x: tx, y: ty));
970 }
971 // Blast Free
972 if (Game.Material.Map[mat].BlastFree) ClearPix(tx, ty);
973 }
974
975 CheckInstabilityRange(tx, ty);
976
977 return mat;
978}
979
980void C4Landscape::DigFree(int32_t tx, int32_t ty, int32_t rad, bool fRequest, C4Object *pByObj)
981{
982 int32_t ycnt, xcnt, iLineWidth, iLineY, iMaterial;
983 // Dig free
984 for (ycnt = -rad; ycnt < rad; ycnt++)
985 {
986 iLineWidth = static_cast<int32_t>(sqrt(x: double(rad * rad - ycnt * ycnt)));
987 iLineY = ty + ycnt;
988 for (xcnt = -iLineWidth; xcnt < iLineWidth + (iLineWidth == 0); xcnt++)
989 if (MatValid(mat: iMaterial = DigFreePix(tx: tx + xcnt, ty: iLineY)))
990 if (pByObj) pByObj->AddMaterialContents(iMaterial, iAmount: 1);
991 // Clear single pixels - left and right
992 DigFreeSinglePix(x: tx - iLineWidth - 1, y: iLineY, dx: -1, dy: 0);
993 DigFreeSinglePix(x: tx + iLineWidth + (iLineWidth == 0), y: iLineY, dx: +1, dy: 0);
994 }
995 // Clear single pixels - up and down
996 DigFreeSinglePix(x: tx, y: ty - rad - 1, dx: 0, dy: -1);
997 for (xcnt = -iLineWidth; xcnt < iLineWidth + (iLineWidth == 0); xcnt++)
998 DigFreeSinglePix(x: tx + xcnt, y: ty + rad, dx: 0, dy: +1);
999 // Dig out material cast
1000 if (!Tick5) if (pByObj) pByObj->DigOutMaterialCast(fRequest);
1001}
1002
1003void C4Landscape::DigFreeRect(int32_t tx, int32_t ty, int32_t wdt, int32_t hgt, bool fRequest, C4Object *pByObj)
1004{
1005 // Dig free pixels
1006 int32_t cx, cy, iMaterial;
1007 for (cx = tx; cx < tx + wdt; cx++)
1008 for (cy = ty; cy < ty + hgt; cy++)
1009 if (MatValid(mat: iMaterial = DigFreePix(tx: cx, ty: cy)))
1010 if (pByObj) pByObj->AddMaterialContents(iMaterial, iAmount: 1);
1011 // Clear single pixels
1012
1013 // Dig out material cast
1014 if (!Tick5) if (pByObj) pByObj->DigOutMaterialCast(fRequest);
1015}
1016
1017void C4Landscape::ShakeFree(int32_t tx, int32_t ty, int32_t rad)
1018{
1019 int32_t ycnt, xcnt, lwdt, dpy;
1020 // Shake free pixels
1021 for (ycnt = rad - 1; ycnt >= -rad; ycnt--)
1022 {
1023 lwdt = static_cast<int32_t>(sqrt(x: double(rad * rad - ycnt * ycnt)));
1024 dpy = ty + ycnt;
1025 for (xcnt = -lwdt; xcnt < lwdt + (lwdt == 0); xcnt++)
1026 ShakeFreePix(tx: tx + xcnt, ty: dpy);
1027 }
1028}
1029
1030void C4Landscape::DigFreeMat(int32_t tx, int32_t ty, int32_t wdt, int32_t hgt, int32_t mat)
1031{
1032 int32_t cx, cy;
1033 if (MatValid(mat))
1034 for (cx = tx; cx < tx + wdt; cx++)
1035 for (cy = ty; cy < ty + hgt; cy++)
1036 if (GetMat(x: cx, y: cy) == mat)
1037 DigFreePix(tx: cx, ty: cy);
1038}
1039
1040void C4Landscape::BlastFree(int32_t tx, int32_t ty, int32_t rad, int32_t grade, int32_t iByPlayer)
1041{
1042 int32_t ycnt, xcnt, lwdt, dpy, mat, cnt;
1043
1044 // Reset material count
1045 ClearBlastMatCount();
1046
1047 // Blast free pixels
1048 // count pixel before, so BlastShiftTo can be evaluated
1049 for (ycnt = -rad; ycnt <= rad; ycnt++)
1050 {
1051 lwdt = static_cast<int32_t>(sqrt(x: double(rad * rad - ycnt * ycnt))); dpy = ty + ycnt;
1052 for (xcnt = -lwdt; xcnt < lwdt + (lwdt == 0); xcnt++)
1053 if (MatValid(mat: mat = GetMat(x: tx + xcnt, y: dpy)))
1054 BlastMatCount[mat]++;
1055 }
1056 // blast pixels
1057 int32_t iBlastSize = rad * rad * 6283 / 2000; // rad^2 * pi
1058 for (ycnt = -rad; ycnt <= rad; ycnt++)
1059 {
1060 lwdt = static_cast<int32_t>(sqrt(x: double(rad * rad - ycnt * ycnt))); dpy = ty + ycnt;
1061 for (xcnt = -lwdt; xcnt < lwdt + (lwdt == 0); xcnt++)
1062 BlastFreePix(tx: tx + xcnt, ty: dpy, grade, iBlastSize);
1063 }
1064
1065 // Evaluate material count
1066 for (cnt = 0; cnt < Game.Material.Num; cnt++)
1067 if (BlastMatCount[cnt])
1068 {
1069 if (Game.Material.Map[cnt].Blast2Object != C4ID_None)
1070 if (Game.Material.Map[cnt].Blast2ObjectRatio != 0)
1071 Game.BlastCastObjects(id: Game.Material.Map[cnt].Blast2Object, pCreator: nullptr,
1072 num: BlastMatCount[cnt] / Game.Material.Map[cnt].Blast2ObjectRatio,
1073 tx, ty, iController: iByPlayer);
1074
1075 if (Game.Material.Map[cnt].Blast2PXSRatio != 0)
1076 Game.PXS.Cast(mat: cnt,
1077 num: BlastMatCount[cnt] / Game.Material.Map[cnt].Blast2PXSRatio,
1078 tx, ty, level: 60);
1079 }
1080}
1081
1082void C4Landscape::DrawMaterialRect(int32_t mat, int32_t tx, int32_t ty, int32_t wdt, int32_t hgt)
1083{
1084 int32_t cx, cy;
1085 for (cy = ty; cy < ty + hgt; cy++)
1086 for (cx = tx; cx < tx + wdt; cx++)
1087 if ((MatDensity(mat) > GetDensity(x: cx, y: cy))
1088 || ((MatDensity(mat) == GetDensity(x: cx, y: cy)) && (MatDigFree(mat) <= MatDigFree(mat: GetMat(x: cx, y: cy)))))
1089 SetPix(x: cx, y: cy, npix: Mat2PixColDefault(mat) + GBackIFT(x: cx, y: cy));
1090}
1091
1092void C4Landscape::RaiseTerrain(int32_t tx, int32_t ty, int32_t wdt)
1093{
1094 int32_t cx, cy;
1095 uint8_t cpix;
1096 for (cx = tx; cx < tx + wdt; cx++)
1097 {
1098 for (cy = ty; (cy + 1 < GBackHgt) && !GBackSolid(x: cx, y: cy + 1); cy++);
1099 if (cy + 1 < GBackHgt) if (cy - ty < 20)
1100 {
1101 cpix = GBackPix(x: cx, y: cy + 1);
1102 if (!MatVehicle(iMat: PixCol2Mat(pixc: cpix)))
1103 while (cy >= ty) { SetPix(x: cx, y: cy, npix: cpix); cy--; }
1104 }
1105 }
1106}
1107
1108int32_t C4Landscape::AreaSolidCount(int32_t x, int32_t y, int32_t wdt, int32_t hgt)
1109{
1110 int32_t cx, cy, ascnt = 0;
1111 for (cy = y; cy < y + hgt; cy++)
1112 for (cx = x; cx < x + wdt; cx++)
1113 if (GBackSolid(x: cx, y: cy))
1114 ascnt++;
1115 return ascnt;
1116}
1117
1118void C4Landscape::FindMatTop(int32_t mat, int32_t &x, int32_t &y)
1119{
1120 int32_t mslide, cslide, tslide; // tslide 0 none 1 left 2 right
1121 bool fLeft, fRight;
1122
1123 if (!MatValid(mat)) return;
1124 mslide = Game.Material.Map[mat].MaxSlide;
1125
1126 do
1127 {
1128 // Find upwards slide
1129 fLeft = true; fRight = true; tslide = 0;
1130 for (cslide = 0; (cslide <= mslide) && (fLeft || fRight); cslide++)
1131 {
1132 // Left
1133 if (fLeft)
1134 if (GetMat(x: x - cslide, y) != mat) fLeft = false;
1135 else if (GetMat(x: x - cslide, y: y - 1) == mat) { tslide = 1; break; }
1136 // Right
1137 if (fRight)
1138 if (GetMat(x: x + cslide, y) != mat) fRight = false;
1139 else if (GetMat(x: x + cslide, y: y - 1) == mat) { tslide = 2; break; }
1140 }
1141
1142 // Slide
1143 if (tslide == 1) { x -= cslide; y--; }
1144 if (tslide == 2) { x += cslide; y--; }
1145 } while (tslide);
1146}
1147
1148int32_t C4Landscape::ExtractMaterial(int32_t fx, int32_t fy)
1149{
1150 int32_t mat = GetMat(x: fx, y: fy);
1151 if (mat == MNone) return MNone;
1152 FindMatTop(mat, x&: fx, y&: fy);
1153 ClearPix(tx: fx, ty: fy);
1154 CheckInstabilityRange(tx: fx, ty: fy);
1155 return mat;
1156}
1157
1158bool C4Landscape::InsertMaterial(int32_t mat, int32_t tx, int32_t ty, int32_t vx, int32_t vy)
1159{
1160 int32_t mdens;
1161 if (!MatValid(mat)) return false;
1162 mdens = MatDensity(mat);
1163 if (!mdens) return true;
1164
1165 // Bounds
1166 if (!Inside<int32_t>(ival: tx, lbound: 0, rbound: Width - 1) || !Inside<int32_t>(ival: ty, lbound: 0, rbound: Height)) return false;
1167
1168 if (Game.C4S.Game.Realism.LandscapePushPull)
1169 {
1170 // Same or higher density?
1171 if (GetDensity(x: tx, y: ty) >= mdens)
1172 // Push
1173 if (!FindMatPathPush(fx&: tx, fy&: ty, mdens, mslide: Game.Material.Map[mat].MaxSlide, liquid: !!Game.Material.Map[mat].Instable))
1174 // Or die
1175 return false;
1176 }
1177 else
1178 {
1179 // Move up above same density
1180 while (mdens == GetDensity(x: tx, y: ty))
1181 {
1182 ty--; if (ty < 0) return false;
1183 // Primitive slide (1)
1184 if (GetDensity(x: tx - 1, y: ty) < mdens) tx--;
1185 if (GetDensity(x: tx + 1, y: ty) < mdens) tx++;
1186 }
1187 // Stuck in higher density
1188 if (GetDensity(x: tx, y: ty) > mdens) return false;
1189 }
1190
1191 // Try slide
1192 while (FindMatSlide(fx&: tx, fy&: ty, ydir: +1, mdens, mslide: Game.Material.Map[mat].MaxSlide))
1193 if (GetDensity(x: tx, y: ty + 1) < mdens)
1194 {
1195 Game.PXS.Create(mat, ix: itofix(x: tx), iy: itofix(x: ty), ixdir: FIXED10(x: vx), iydir: FIXED10(x: vy)); return true;
1196 }
1197
1198 // Try reaction with material below
1199 C4MaterialReaction *pReact; int32_t tmat;
1200 if (pReact = Game.Material.GetReactionUnsafe(iPXSMat: mat, iLandscapeMat: tmat = GetMat(x: tx, y: ty + Sign(GravAccel))))
1201 {
1202 C4Fixed fvx = FIXED10(x: vx), fvy = FIXED10(x: vy);
1203 if ((*pReact->pFunc)(pReact, tx, ty, tx, ty + Sign(GravAccel), fvx, fvy, mat, tmat, meePXSPos, nullptr))
1204 {
1205 // the material to be inserted killed itself in some material reaction below
1206 return true;
1207 }
1208 }
1209
1210 int omat;
1211 if (Game.C4S.Game.Realism.LandscapeInsertThrust)
1212 omat = GetMat(x: tx, y: ty);
1213
1214 // Insert dead material
1215 SetPix(x: tx, y: ty, npix: Mat2PixColDefault(mat) + GBackIFT(x: tx, y: ty));
1216
1217 // Search a position for the old material pixel
1218 if (Game.C4S.Game.Realism.LandscapeInsertThrust && MatValid(mat: omat))
1219 InsertMaterial(mat: omat, tx, ty: ty - 1);
1220
1221 return true;
1222}
1223
1224// Finds the next pixel position moving to desired slide.
1225
1226bool C4Landscape::FindMatPath(int32_t &fx, int32_t &fy, int32_t ydir, int32_t mdens, int32_t mslide)
1227{
1228 int32_t cslide;
1229 bool fLeft = true, fRight = true;
1230
1231 // One downwards
1232 if (GetDensity(x: fx, y: fy + ydir) < mdens) { fy += ydir; return true; }
1233
1234 // Find downwards slide path
1235 for (cslide = 1; (cslide <= mslide) && (fLeft || fRight); cslide++)
1236 {
1237 // Check left
1238 if (fLeft)
1239 if (GetDensity(x: fx - cslide, y: fy) >= mdens) // Left clogged
1240 fLeft = false;
1241 else if (GetDensity(x: fx - cslide, y: fy + ydir) < mdens) // Left slide okay
1242 {
1243 fx--; return true;
1244 }
1245 // Check right
1246 if (fRight)
1247 if (GetDensity(x: fx + cslide, y: fy) >= mdens) // Right clogged
1248 fRight = false;
1249 else if (GetDensity(x: fx + cslide, y: fy + ydir) < mdens) // Right slide okay
1250 {
1251 fx++; return true;
1252 }
1253 }
1254
1255 return false;
1256}
1257
1258// Finds the closest immediate slide position.
1259
1260bool C4Landscape::FindMatSlide(int32_t &fx, int32_t &fy, int32_t ydir, int32_t mdens, int32_t mslide)
1261{
1262 int32_t cslide;
1263 bool fLeft = true, fRight = true;
1264
1265 // One downwards
1266 if (GetDensity(x: fx, y: fy + ydir) < mdens) { fy += ydir; return true; }
1267
1268 // Find downwards slide path
1269 for (cslide = 1; (cslide <= mslide) && (fLeft || fRight); cslide++)
1270 {
1271 // Check left
1272 if (fLeft)
1273 if (GetDensity(x: fx - cslide, y: fy) >= mdens && GetDensity(x: fx - cslide, y: fy + ydir) >= mdens) // Left clogged
1274 fLeft = false;
1275 else if (GetDensity(x: fx - cslide, y: fy + ydir) < mdens) // Left slide okay
1276 {
1277 fx -= cslide; fy += ydir; return true;
1278 }
1279 // Check right
1280 if (fRight)
1281 if (GetDensity(x: fx + cslide, y: fy) >= mdens && GetDensity(x: fx + cslide, y: fy + ydir) >= mdens) // Right clogged
1282 fRight = false;
1283 else if (GetDensity(x: fx + cslide, y: fy + ydir) < mdens) // Right slide okay
1284 {
1285 fx += cslide; fy += ydir; return true;
1286 }
1287 }
1288
1289 return false;
1290}
1291
1292// Find closest point with density below mdens. Note this may return a point outside of the landscape,
1293// Assumption: There are no holes with smaller density inside of material with greater
1294// density.
1295bool C4Landscape::FindMatPathPush(int32_t &fx, int32_t &fy, int32_t mdens, int32_t mslide, bool liquid)
1296{
1297 // Startpoint must be inside landscape
1298 fx = BoundBy<int32_t>(bval: fx, lbound: 0, rbound: Width - 1);
1299 fy = BoundBy<int32_t>(bval: fy, lbound: 0, rbound: Height - 1);
1300 // Range to search, calculate bounds
1301 const int32_t iPushRange = 500;
1302 int32_t left = std::max<int32_t>(a: 0, b: fx - iPushRange), right = std::min<int32_t>(a: Width - 1, b: fx + iPushRange),
1303 top = std::max<int32_t>(a: 0, b: fy - iPushRange), bottom = std::min<int32_t>(a: Height - 1, b: fy + iPushRange);
1304 // Direction constants
1305 const int8_t R = 0, D = 1, L = 2, U = 3;
1306 int8_t dir = 0;
1307 int32_t x = fx, y = fy;
1308 // Get startpoint density
1309 int32_t dens = GetDensity(x: fx, y: fy);
1310 // Smaller density? We're done.
1311 if (dens < mdens)
1312 return true;
1313 // Right density?
1314 else if (dens == mdens)
1315 {
1316 // Find start point for border search
1317 for (int32_t i = 0; ; i++)
1318 if (x - i - 1 < left || GetDensity(x: x - i - 1, y) != mdens)
1319 {
1320 x -= i; dir = L; break;
1321 }
1322 else if (y - i - 1 < top || GetDensity(x, y: y - i - 1) != mdens)
1323 {
1324 y -= i; dir = U; break;
1325 }
1326 else if (x + i + 1 > right || GetDensity(x: x + i + 1, y) != mdens)
1327 {
1328 x += i; dir = R; break;
1329 }
1330 else if (y + i + 1 > bottom || GetDensity(x, y: y + i + 1) != mdens)
1331 {
1332 y += i; dir = D; break;
1333 }
1334 }
1335 // Greater density
1336 else
1337 {
1338 // Try to find a way out
1339 int i = 1;
1340 for (; i < iPushRange; i++)
1341 if (GetDensity(x: x - i, y) <= mdens)
1342 {
1343 x -= i; dir = R; break;
1344 }
1345 else if (GetDensity(x, y: y - i) <= mdens)
1346 {
1347 y -= i; dir = D; break;
1348 }
1349 else if (GetDensity(x: x + i, y) <= mdens)
1350 {
1351 x += i; dir = L; break;
1352 }
1353 else if (GetDensity(x, y: y + i) <= mdens)
1354 {
1355 y += i; dir = U; break;
1356 }
1357 // Not found?
1358 if (i >= iPushRange) return false;
1359 // Done?
1360 if (GetDensity(x, y) < mdens)
1361 {
1362 fx = x; fy = y;
1363 return true;
1364 }
1365 }
1366 // Save startpoint of search
1367 int32_t sx = x, sy = y, sdir = dir;
1368 // Best point so far
1369 bool fGotBest = false; int32_t bx, by, bdist;
1370 // Start searching
1371 do
1372 {
1373 // We should always be in a material of same density
1374 assert(x >= left && y >= top && x <= right && y <= bottom && GetDensity(x, y) == mdens);
1375 // Calc new position
1376 int nx = x, ny = y;
1377 switch (dir)
1378 {
1379 case R: nx++; break;
1380 case D: ny++; break;
1381 case L: nx--; break;
1382 case U: ny--; break;
1383 default: assert(false);
1384 }
1385 // In bounds?
1386 bool fInBounds = (nx >= left && ny >= top && nx <= right && ny <= bottom);
1387 // Get density. Not this performs an SideOpen-check if outside landscape bounds.
1388 int32_t dens = GetDensity(x: nx, y: ny);
1389 // Flow possible?
1390 if (dens < mdens)
1391 {
1392 // Calculate "distance".
1393 int32_t dist = Abs(val: nx - fx) + mslide * (liquid ? fy - ny : Abs(val: fy - ny));
1394 // New best point?
1395 if (!fGotBest || dist < bdist)
1396 {
1397 // Save it
1398 bx = nx; by = ny; bdist = dist; fGotBest = true;
1399 // Adjust borders: We can obviously safely ignore anything at greater distance
1400 top = std::max<int32_t>(a: top, b: fy - dist / mslide - 1);
1401 if (!liquid)
1402 {
1403 bottom = std::min<int32_t>(a: bottom, b: fy + dist / mslide + 1);
1404 left = std::max<int32_t>(a: left, b: fx - dist - 1);
1405 right = std::min<int32_t>(a: right, b: fx + dist + 1);
1406 }
1407 // Set new startpoint
1408 sx = x; sy = y; sdir = dir;
1409 }
1410 }
1411 // Step?
1412 if (fInBounds && dens == mdens)
1413 {
1414 // New point
1415 x = nx; y = ny;
1416 // Turn left
1417 (dir += 3) %= 4;
1418 }
1419 // Otherwise: Turn right
1420 else
1421 ++dir %= 4;
1422 } while (x != sx || y != sy || dir != sdir);
1423 // Nothing found?
1424 if (!fGotBest) return false;
1425 // Return it
1426 fx = bx; fy = by;
1427 return true;
1428}
1429
1430bool C4Landscape::Incinerate(int32_t x, int32_t y)
1431{
1432 int32_t mat = GetMat(x, y);
1433 if (MatValid(mat))
1434 if (Game.Material.Map[mat].Inflammable)
1435 // Not too much FLAMs
1436 if (!Game.FindObject(id: C4Id(str: "FLAM"), iX: x - 4, iY: y - 1, iWdt: 8, iHgt: 20))
1437 if (Game.CreateObject(type: C4Id(str: "FLAM"), pCreator: nullptr, owner: NO_OWNER, x, y))
1438 return true;
1439 return false;
1440}
1441
1442bool C4Landscape::Save(C4Group &hGroup)
1443{
1444 // Save members
1445 if (!Sky.Save(hGroup))
1446 return false;
1447
1448 // Save landscape surface
1449 char szTempLandscape[_MAX_PATH + 1];
1450 SCopy(szSource: Config.AtTempPath(C4CFN_TempLandscape), sTarget: szTempLandscape);
1451 MakeTempFilename(szFileName: szTempLandscape);
1452 if (!Surface8->Save(szFilename: szTempLandscape))
1453 return false;
1454
1455 // Move temp file to group
1456 if (!hGroup.Move(szFile: szTempLandscape, C4CFN_Landscape))
1457 return false;
1458
1459 SCopy(szSource: Config.AtTempPath(C4CFN_TempLandscapePNG), sTarget: szTempLandscape);
1460 MakeTempFilename(szFileName: szTempLandscape);
1461 if (!Surface32->SavePNG(szFilename: szTempLandscape, fSaveAlpha: true, fApplyGamma: false, fSaveOverlayOnly: false))
1462 return false;
1463 if (!hGroup.Move(szFile: szTempLandscape, C4CFN_LandscapePNG)) return false;
1464
1465 if (fMapChanged && Map)
1466 if (!SaveMap(hGroup)) return false;
1467
1468 // save textures (if changed)
1469 if (!SaveTextures(hGroup)) return false;
1470
1471 return true;
1472}
1473
1474bool C4Landscape::SaveDiff(C4Group &hGroup, bool fSyncSave)
1475{
1476 assert(pInitial);
1477 if (!pInitial) return false;
1478
1479 // If it shouldn't be sync-save: Clear all bytes that have not changed
1480 bool fChanged = false;
1481 if (!fSyncSave)
1482 for (int y = 0; y < Height; y++)
1483 for (int x = 0; x < Width; x++)
1484 if (pInitial[y * Width + x] == _GetPix(x, y))
1485 Surface8->SetPix(iX: x, iY: y, byCol: 0xff);
1486 else
1487 fChanged = true;
1488
1489 if (fSyncSave || fChanged)
1490 {
1491 // Save landscape surface
1492 if (!Surface8->Save(szFilename: Config.AtTempPath(C4CFN_TempLandscape)))
1493 return false;
1494
1495 // Move temp file to group
1496 if (!hGroup.Move(szFile: Config.AtTempPath(C4CFN_TempLandscape),
1497 C4CFN_DiffLandscape))
1498 return false;
1499 }
1500
1501 // Restore landscape pixels
1502 if (!fSyncSave)
1503 if (pInitial)
1504 for (int y = 0; y < Height; y++)
1505 for (int x = 0; x < Width; x++)
1506 if (_GetPix(x, y) == 0xff)
1507 Surface8->SetPix(iX: x, iY: y, byCol: pInitial[y * Width + x]);
1508
1509 // Save changed map, too
1510 if (fMapChanged && Map)
1511 if (!SaveMap(hGroup)) return false;
1512
1513 // and textures (if changed)
1514 if (!SaveTextures(hGroup)) return false;
1515
1516 return true;
1517}
1518
1519bool C4Landscape::SaveInitial()
1520{
1521 // Create array
1522 delete[] pInitial;
1523 pInitial = new uint8_t[Width * Height];
1524
1525 // Save material data
1526 for (int y = 0; y < Height; y++)
1527 for (int x = 0; x < Width; x++)
1528 pInitial[y * Width + x] = _GetPix(x, y);
1529
1530 return true;
1531}
1532
1533bool C4Landscape::Load(C4Group &hGroup, bool fLoadSky, bool fSavegame)
1534{
1535 // Load exact landscape from group
1536 if (!hGroup.AccessEntry(C4CFN_Landscape)) return false;
1537 if (!(Surface8 = GroupReadSurfaceOwnPal8(hGroup))) return false;
1538 int iWidth, iHeight;
1539 Surface8->GetSurfaceSize(irX&: iWidth, irY&: iHeight);
1540 Width = iWidth; Height = iHeight;
1541 Surface32 = new C4Surface(Width, Height);
1542 if (Config.Graphics.ColorAnimation && Config.Graphics.Shader)
1543 AnimationSurface = new C4Surface(Width, Height);
1544 // adjust pal
1545 if (!Mat2Pal()) return false;
1546 // load the 32bit-surface, too
1547 size_t iSize;
1548 if (hGroup.AccessEntry(C4CFN_LandscapePNG, iSize: &iSize))
1549 {
1550 const std::unique_ptr<uint8_t []> pPNG(new uint8_t[iSize]);
1551 hGroup.Read(pBuffer: pPNG.get(), iSize);
1552 bool locked = false;
1553 try
1554 {
1555 CPNGFile png(pPNG.get(), iSize);
1556 StdBitmap bmp(png.Width(), png.Height(), png.UsesAlpha());
1557 png.Decode(pixels: bmp.GetBytes());
1558 if (!Surface32->Lock()) throw std::runtime_error("Could not lock surface");
1559 locked = true;
1560 for (int32_t y = 0; y < Height; ++y) for (int32_t x = 0; x < Width; ++x)
1561 Surface32->SetPixDw(iX: x, iY: y, dwCol: bmp.GetPixel(x, y));
1562 }
1563 catch (const std::runtime_error &e)
1564 {
1565 LogNTr(level: spdlog::level::err, fmt: "Could not load 32 bit landscape surface from PNG file: {}", args: e.what());
1566 }
1567 if (locked) Surface32->Unlock();
1568 UpdateAnimationSurface(To: {0, 0, Width, Height});
1569 }
1570 // no PNG: convert old-style landscapes
1571 else if (!Game.C4S.Landscape.NewStyleLandscape)
1572 {
1573 // convert all pixels
1574 for (int32_t y = 0; y < Height; ++y) for (int32_t x = 0; x < Width; ++x)
1575 {
1576 uint8_t byPix = Surface8->GetPix(iX: x, iY: y);
1577 int32_t iMat = PixCol2MatOld(pixc: byPix); uint8_t byIFT = PixColIFTOld(pixc: byPix);
1578 if (byIFT) byIFT = IFT;
1579 // set pixel in 8bpp-surface only, so old-style landscapes won't be screwed up!
1580 Surface8->SetPix(iX: x, iY: y, byCol: Mat2PixColDefault(mat: iMat) + byIFT);
1581 }
1582 // NewStyleLandscape-flag will be set in C4Landscape::Init later
1583 }
1584 // New style landscape first generation: just correct
1585 if (Game.C4S.Landscape.NewStyleLandscape == 1)
1586 {
1587 // convert all pixels
1588 for (int32_t y = 0; y < Height; ++y) for (int32_t x = 0; x < Width; ++x)
1589 {
1590 // get material
1591 uint8_t byPix = Surface8->GetPix(iX: x, iY: y);
1592 int32_t iMat = PixCol2MatOld2(pixc: byPix);
1593 if (MatValid(mat: iMat))
1594 // insert pixel
1595 Surface8->SetPix(iX: x, iY: y, byCol: Mat2PixColDefault(mat: iMat) + (byPix & IFT));
1596 else
1597 Surface8->SetPix(iX: x, iY: y, byCol: 0);
1598 }
1599 }
1600 else
1601 {
1602 // Landscape should be in correct format: Make sure it is!
1603 for (int32_t y = 0; y < Height; ++y) for (int32_t x = 0; x < Width; ++x)
1604 {
1605 uint8_t byPix = Surface8->GetPix(iX: x, iY: y);
1606 int32_t iMat = PixCol2Mat(pixc: byPix);
1607 if (byPix && !MatValid(mat: iMat))
1608 {
1609 LogFatalNTr(fmt: "Landscape loading error at ({}/{}): Pixel value {} not a valid material!", args&: x, args&: y, args&: byPix);
1610 return false;
1611 }
1612 }
1613 }
1614 // Init sky
1615 if (fLoadSky)
1616 {
1617 Game.SetInitProgress(70);
1618 if (!Sky.Init(fSavegame)) return false;
1619 }
1620 // Success
1621 return true;
1622}
1623
1624bool C4Landscape::ApplyDiff(C4Group &hGroup)
1625{
1626 CSurface8 *pDiff;
1627 // Load diff landscape from group
1628 if (!hGroup.AccessEntry(C4CFN_DiffLandscape)) return false;
1629 if (!(pDiff = GroupReadSurfaceOwnPal8(hGroup))) return false;
1630 // convert all pixels: keep if same material; re-set if different material
1631 uint8_t byPix;
1632 for (int32_t y = 0; y < Height; ++y) for (int32_t x = 0; x < Width; ++x)
1633 if (pDiff->GetPix(iX: x, iY: y) != 0xff)
1634 if (Surface8->GetPix(iX: x, iY: y) != (byPix = pDiff->GetPix(iX: x, iY: y)))
1635 // material has changed here: readjust with new texture
1636 SetPix(x, y, npix: byPix);
1637 // done; clear diff
1638 delete pDiff;
1639 return true;
1640}
1641
1642void C4Landscape::Default()
1643{
1644 Mode = C4LSC_Undefined;
1645 Surface8 = nullptr;
1646 Surface32 = nullptr;
1647 AnimationSurface = nullptr;
1648 Map = nullptr;
1649 Width = Height = 0;
1650 MapWidth = MapHeight = MapZoom = 0;
1651 ClearMatCount();
1652 ClearBlastMatCount();
1653 ScanX = 0;
1654 ScanSpeed = 2;
1655 LeftOpen = RightOpen = 0;
1656 TopOpen = BottomOpen = false;
1657 Gravity = FIXED100(x: 20); // == 0.2
1658 MapSeed = 0; NoScan = false;
1659 pMapCreator = nullptr;
1660 Modulation = 0;
1661 fMapChanged = false;
1662 ShadeMaterials = true;
1663}
1664
1665void C4Landscape::ClearBlastMatCount()
1666{
1667 for (int32_t cnt = 0; cnt < C4MaxMaterial; cnt++) BlastMatCount[cnt] = 0;
1668}
1669
1670void C4Landscape::ClearMatCount()
1671{
1672 for (int32_t cnt = 0; cnt < C4MaxMaterial; cnt++) { MatCount[cnt] = 0; EffectiveMatCount[cnt] = 0; }
1673}
1674
1675void C4Landscape::Synchronize()
1676{
1677 ScanX = 0;
1678 ClearBlastMatCount();
1679}
1680
1681namespace
1682{
1683bool ForLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2,
1684 bool(*fnCallback)(int32_t, int32_t, int32_t), int32_t iPar = 0,
1685 int32_t *lastx = nullptr, int32_t *lasty = nullptr)
1686{
1687 int d, dx, dy, aincr, bincr, xincr, yincr, x, y;
1688 if (std::abs(x: x2 - x1) < std::abs(x: y2 - y1))
1689 {
1690 if (y1 > y2) { std::swap(a&: x1, b&: x2); std::swap(a&: y1, b&: y2); }
1691 xincr = (x2 > x1) ? 1 : -1;
1692 dy = y2 - y1; dx = std::abs(x: x2 - x1);
1693 d = 2 * dx - dy; aincr = 2 * (dx - dy); bincr = 2 * dx; x = x1; y = y1;
1694 if (!fnCallback(x, y, iPar))
1695 {
1696 if (lastx) *lastx = x; if (lasty) *lasty = y;
1697 return false;
1698 }
1699 for (y = y1 + 1; y <= y2; ++y)
1700 {
1701 if (d >= 0) { x += xincr; d += aincr; }
1702 else d += bincr;
1703 if (!fnCallback(x, y, iPar))
1704 {
1705 if (lastx) *lastx = x; if (lasty) *lasty = y;
1706 return false;
1707 }
1708 }
1709 }
1710 else
1711 {
1712 if (x1 > x2) { std::swap(a&: x1, b&: x2); std::swap(a&: y1, b&: y2); }
1713 yincr = (y2 > y1) ? 1 : -1;
1714 dx = x2 - x1; dy = std::abs(x: y2 - y1);
1715 d = 2 * dy - dx; aincr = 2 * (dy - dx); bincr = 2 * dy; x = x1; y = y1;
1716 if (!fnCallback(x, y, iPar))
1717 {
1718 if (lastx) *lastx = x; if (lasty) *lasty = y;
1719 return false;
1720 }
1721 for (x = x1 + 1; x <= x2; ++x)
1722 {
1723 if (d >= 0) { y += yincr; d += aincr; }
1724 else d += bincr;
1725 if (!fnCallback(x, y, iPar))
1726 {
1727 if (lastx) *lastx = x; if (lasty) *lasty = y;
1728 return false;
1729 }
1730 }
1731 }
1732 return true;
1733}
1734}
1735
1736bool AboveSemiSolid(int32_t &rx, int32_t &ry) // Nearest free above semi solid
1737{
1738 int32_t cy1 = ry, cy2 = ry;
1739 bool UseUpwardsNextFree = false, UseDownwardsNextSolid = false;
1740
1741 while ((cy1 >= 0) || (cy2 < GBackHgt))
1742 {
1743 // Check upwards
1744 if (cy1 >= 0)
1745 if (GBackSemiSolid(x: rx, y: cy1)) UseUpwardsNextFree = true;
1746 else if (UseUpwardsNextFree) { ry = cy1; return true; }
1747 // Check downwards
1748 if (cy2 < GBackHgt)
1749 if (!GBackSemiSolid(x: rx, y: cy2)) UseDownwardsNextSolid = true;
1750 else if (UseDownwardsNextSolid) { ry = cy2; return true; }
1751 // Advance
1752 cy1--; cy2++;
1753 }
1754
1755 return false;
1756}
1757
1758bool AboveSolid(int32_t &rx, int32_t &ry) // Nearest free directly above solid
1759{
1760 int32_t cy1 = ry, cy2 = ry;
1761
1762 while ((cy1 >= 0) || (cy2 < GBackHgt))
1763 {
1764 // Check upwards
1765 if (cy1 >= 0)
1766 if (!GBackSemiSolid(x: rx, y: cy1))
1767 if (GBackSolid(x: rx, y: cy1 + 1))
1768 {
1769 ry = cy1; return true;
1770 }
1771 // Check downwards
1772 if (cy2 + 1 < GBackHgt)
1773 if (!GBackSemiSolid(x: rx, y: cy2))
1774 if (GBackSolid(x: rx, y: cy2 + 1))
1775 {
1776 ry = cy2; return true;
1777 }
1778 // Advance
1779 cy1--; cy2++;
1780 }
1781
1782 return false;
1783}
1784
1785bool SemiAboveSolid(int32_t &rx, int32_t &ry) // Nearest free/semi above solid
1786{
1787 int32_t cy1 = ry, cy2 = ry;
1788
1789 while ((cy1 >= 0) || (cy2 < GBackHgt))
1790 {
1791 // Check upwards
1792 if (cy1 >= 0)
1793 if (!GBackSolid(x: rx, y: cy1))
1794 if (GBackSolid(x: rx, y: cy1 + 1))
1795 {
1796 ry = cy1; return true;
1797 }
1798 // Check downwards
1799 if (cy2 + 1 < GBackHgt)
1800 if (!GBackSolid(x: rx, y: cy2))
1801 if (GBackSolid(x: rx, y: cy2 + 1))
1802 {
1803 ry = cy2; return true;
1804 }
1805 // Advance
1806 cy1--; cy2++;
1807 }
1808
1809 return false;
1810}
1811
1812bool FindLiquidHeight(int32_t cx, int32_t &ry, int32_t hgt)
1813{
1814 int32_t cy1 = ry, cy2 = ry, rl1 = 0, rl2 = 0;
1815
1816 while ((cy1 >= 0) || (cy2 < GBackHgt))
1817 {
1818 // Check upwards
1819 if (cy1 >= 0)
1820 if (GBackLiquid(x: cx, y: cy1))
1821 {
1822 rl1++; if (rl1 >= hgt) { ry = cy1 + hgt / 2; return true; }
1823 }
1824 else rl1 = 0;
1825 // Check downwards
1826 if (cy2 + 1 < GBackHgt)
1827 if (GBackLiquid(x: cx, y: cy2))
1828 {
1829 rl2++; if (rl2 >= hgt) { ry = cy2 - hgt / 2; return true; }
1830 }
1831 else rl2 = 0;
1832 // Advance
1833 cy1--; cy2++;
1834 }
1835
1836 return false;
1837}
1838
1839// Starting from rx/ry, searches for a width
1840// of solid ground. Returns bottom center
1841// of surface space found.
1842
1843bool FindSolidGround(int32_t &rx, int32_t &ry, int32_t width)
1844{
1845 bool fFound = false;
1846
1847 int32_t cx1, cx2, cy1, cy2, rl1 = 0, rl2 = 0;
1848
1849 for (cx1 = cx2 = rx, cy1 = cy2 = ry; (cx1 > 0) || (cx2 < GBackWdt); cx1--, cx2++)
1850 {
1851 // Left search
1852 if (cx1 >= 0) // Still going
1853 {
1854 if (AboveSolid(rx&: cx1, ry&: cy1)) rl1++; // Run okay
1855 else rl1 = 0; // No run
1856 }
1857 // Right search
1858 if (cx2 < GBackWdt) // Still going
1859 {
1860 if (AboveSolid(rx&: cx2, ry&: cy2)) rl2++; // Run okay
1861 else rl2 = 0; // No run
1862 }
1863 // Check runs
1864 if (rl1 >= width) { rx = cx1 + rl1 / 2; ry = cy1; fFound = true; break; }
1865 if (rl2 >= width) { rx = cx2 - rl2 / 2; ry = cy2; fFound = true; break; }
1866 }
1867
1868 if (fFound) AboveSemiSolid(rx, ry);
1869
1870 return fFound;
1871}
1872
1873bool FindSurfaceLiquid(int32_t &rx, int32_t &ry, int32_t width, int32_t height)
1874{
1875 bool fFound = false;
1876
1877 int32_t cx1, cx2, cy1, cy2, rl1 = 0, rl2 = 0, cnt;
1878 bool lokay;
1879 for (cx1 = cx2 = rx, cy1 = cy2 = ry; (cx1 > 0) || (cx2 < GBackWdt); cx1--, cx2++)
1880 {
1881 // Left search
1882 if (cx1 > 0) // Still going
1883 if (!AboveSemiSolid(rx&: cx1, ry&: cy1)) cx1 = -1; // Abort left
1884 else
1885 {
1886 for (lokay = true, cnt = 0; cnt < height; cnt++) if (!GBackLiquid(x: cx1, y: cy1 + 1 + cnt)) lokay = false;
1887 if (lokay) rl1++; // Run okay
1888 else rl1 = 0; // No run
1889 }
1890 // Right search
1891 if (cx2 < GBackWdt) // Still going
1892 if (!AboveSemiSolid(rx&: cx2, ry&: cy2)) cx2 = GBackWdt; // Abort right
1893 else
1894 {
1895 for (lokay = true, cnt = 0; cnt < height; cnt++) if (!GBackLiquid(x: cx2, y: cy2 + 1 + cnt)) lokay = false;
1896 if (lokay) rl2++; // Run okay
1897 else rl2 = 0; // No run
1898 }
1899 // Check runs
1900 if (rl1 >= width) { rx = cx1 + rl1 / 2; ry = cy1; fFound = true; break; }
1901 if (rl2 >= width) { rx = cx2 - rl2 / 2; ry = cy2; fFound = true; break; }
1902 }
1903
1904 if (fFound) AboveSemiSolid(rx, ry);
1905
1906 return fFound;
1907}
1908
1909bool FindLiquid(int32_t &rx, int32_t &ry, int32_t width, int32_t height)
1910{
1911 int32_t cx1, cx2, cy1, cy2, rl1 = 0, rl2 = 0;
1912
1913 for (cx1 = cx2 = rx, cy1 = cy2 = ry; (cx1 > 0) || (cx2 < GBackWdt); cx1--, cx2++)
1914 {
1915 // Left search
1916 if (cx1 > 0)
1917 if (FindLiquidHeight(cx: cx1, ry&: cy1, hgt: height)) rl1++;
1918 else rl1 = 0;
1919 // Right search
1920 if (cx2 < GBackWdt)
1921 if (FindLiquidHeight(cx: cx2, ry&: cy2, hgt: height)) rl2++;
1922 else rl2 = 0;
1923 // Check runs
1924 if (rl1 >= width) { rx = cx1 + rl1 / 2; ry = cy1; return true; }
1925 if (rl2 >= width) { rx = cx2 - rl2 / 2; ry = cy2; return true; }
1926 }
1927
1928 return false;
1929}
1930
1931// FindLevelGround: Starting from rx/ry, searches for a width
1932// of solid ground. Extreme distances may not
1933// exceed hrange.
1934// Returns bottom center of surface found.
1935
1936bool FindLevelGround(int32_t &rx, int32_t &ry, int32_t width, int32_t hrange)
1937{
1938 bool fFound = false;
1939
1940 int32_t cx1, cx2, cy1, cy2, rh1, rh2, rl1, rl2;
1941
1942 cx1 = cx2 = rx; cy1 = cy2 = ry;
1943 rh1 = cy1; rh2 = cy2;
1944 rl1 = rl2 = 0;
1945
1946 for (cx1--, cx2++; (cx1 > 0) || (cx2 < GBackWdt); cx1--, cx2++)
1947 {
1948 // Left search
1949 if (cx1 > 0) // Still going
1950 if (!AboveSemiSolid(rx&: cx1, ry&: cy1)) cx1 = -1; // Abort left
1951 else if (GBackSolid(x: cx1, y: cy1 + 1) && (Abs(val: cy1 - rh1) < hrange))
1952 rl1++; // Run okay
1953 else
1954 {
1955 rl1 = 0; rh1 = cy1;
1956 } // No run
1957
1958 // Right search
1959 if (cx2 < GBackWdt) // Still going
1960 if (!AboveSemiSolid(rx&: cx2, ry&: cy2)) cx2 = GBackWdt; // Abort right
1961 else if (GBackSolid(x: cx2, y: cy2 + 1) && (Abs(val: cy2 - rh2) < hrange))
1962 rl2++; // Run okay
1963 else
1964 {
1965 rl2 = 0; rh2 = cy2;
1966 } // No run
1967
1968 // Check runs
1969 if (rl1 >= width) { rx = cx1 + rl1 / 2; ry = cy1; fFound = true; break; }
1970 if (rl2 >= width) { rx = cx2 - rl2 / 2; ry = cy2; fFound = true; break; }
1971 }
1972
1973 if (fFound) AboveSemiSolid(rx, ry);
1974
1975 return fFound;
1976}
1977
1978// Starting from rx/ry, searches for a width of solid level
1979// ground with structure clearance (category).
1980// Returns bottom center of surface found.
1981
1982bool FindConSiteSpot(int32_t &rx, int32_t &ry, int32_t wdt, int32_t hgt,
1983 uint32_t category, int32_t hrange)
1984{
1985 bool fFound = false;
1986
1987 // No hrange limit, use standard smooth surface limit
1988 if (hrange == -1) hrange = (std::max)(a: wdt / 4, b: 5);
1989
1990 int32_t cx1, cx2, cy1, cy2, rh1, rh2, rl1, rl2;
1991
1992 // Left offset starting position
1993 cx1 = (std::min)(a: rx + wdt / 2, GBackWdt - 1); cy1 = ry;
1994 // No good: use centered starting position
1995 if (!AboveSemiSolid(rx&: cx1, ry&: cy1)) { cx1 = std::min<int32_t>(a: rx, GBackWdt - 1); cy1 = ry; }
1996 // Right offset starting position
1997 cx2 = (std::max)(a: rx - wdt / 2, b: 0); cy2 = ry;
1998 // No good: use centered starting position
1999 if (!AboveSemiSolid(rx&: cx2, ry&: cy2)) { cx2 = std::min<int32_t>(a: rx, GBackWdt - 1); cy2 = ry; }
2000
2001 rh1 = cy1; rh2 = cy2; rl1 = rl2 = 0;
2002
2003 for (cx1--, cx2++; (cx1 > 0) || (cx2 < GBackWdt); cx1--, cx2++)
2004 {
2005 // Left search
2006 if (cx1 > 0) // Still going
2007 if (!AboveSemiSolid(rx&: cx1, ry&: cy1))
2008 cx1 = -1; // Abort left
2009 else if (GBackSolid(x: cx1, y: cy1 + 1) && (Abs(val: cy1 - rh1) < hrange))
2010 rl1++; // Run okay
2011 else
2012 {
2013 rl1 = 0; rh1 = cy1;
2014 } // No run
2015
2016 // Right search
2017 if (cx2 < GBackWdt) // Still going
2018 if (!AboveSemiSolid(rx&: cx2, ry&: cy2))
2019 cx2 = GBackWdt; // Abort right
2020 else if (GBackSolid(x: cx2, y: cy2 + 1) && (Abs(val: cy2 - rh2) < hrange))
2021 rl2++; // Run okay
2022 else
2023 {
2024 rl2 = 0; rh2 = cy2;
2025 } // No run
2026
2027 // Check runs & object overlap
2028 if (rl1 >= wdt) if (cx1 > 0)
2029 if (!Game.OverlapObject(tx: cx1, ty: cy1 - hgt - 10, wdt, hgt: hgt + 40, category))
2030 {
2031 rx = cx1 + wdt / 2; ry = cy1; fFound = true; break;
2032 }
2033 if (rl2 >= wdt) if (cx2 < GBackWdt)
2034 if (!Game.OverlapObject(tx: cx2 - wdt, ty: cy2 - hgt - 10, wdt, hgt: hgt + 40, category))
2035 {
2036 rx = cx2 - wdt / 2; ry = cy2; fFound = true; break;
2037 }
2038 }
2039
2040 if (fFound) AboveSemiSolid(rx, ry);
2041
2042 return fFound;
2043}
2044
2045// Returns false on any solid pix in path.
2046
2047bool PathFreePix(int32_t x, int32_t y, int32_t par)
2048{
2049 return !GBackSolid(x, y);
2050}
2051
2052bool PathFree(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t *ix, int32_t *iy)
2053{
2054 return ForLine(x1, y1, x2, y2, fnCallback: &PathFreePix, iPar: 0, lastx: ix, lasty: iy);
2055}
2056
2057bool PathFreeIgnoreVehiclePix(int32_t x, int32_t y, int32_t par)
2058{
2059 uint8_t byPix = GBackPix(x, y);
2060 return !byPix || !DensitySolid(dens: Game.Landscape.GetPixMat(byPix)) || Game.Landscape.GetPixMat(byPix) == MVehic;
2061}
2062
2063bool PathFreeIgnoreVehicle(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t *ix, int32_t *iy)
2064{
2065 return ForLine(x1, y1, x2, y2, fnCallback: &PathFreeIgnoreVehiclePix, iPar: 0, lastx: ix, lasty: iy);
2066}
2067
2068int32_t TrajectoryDistance(int32_t iFx, int32_t iFy, C4Fixed iXDir, C4Fixed iYDir, int32_t iTx, int32_t iTy)
2069{
2070 int32_t iClosest = Distance(iX1: iFx, iY1: iFy, iX2: iTx, iY2: iTy);
2071 // Follow free trajectory, take closest point distance
2072 C4Fixed cx = itofix(x: iFx), cy = itofix(x: iFy);
2073 int32_t cdis;
2074 while (Inside(ival: fixtoi(x: cx), lbound: 0, GBackWdt - 1) && Inside(ival: fixtoi(x: cy), lbound: 0, GBackHgt - 1) && !GBackSolid(x: fixtoi(x: cx), y: fixtoi(x: cy)))
2075 {
2076 cdis = Distance(iX1: fixtoi(x: cx), iY1: fixtoi(x: cy), iX2: iTx, iY2: iTy);
2077 if (cdis < iClosest) iClosest = cdis;
2078 cx += iXDir; cy += iYDir; iYDir += GravAccel;
2079 }
2080 return iClosest;
2081}
2082
2083const int32_t C4LSC_Throwing_MaxVertical = 50,
2084 C4LSC_Throwing_MaxHorizontal = 60;
2085
2086bool FindThrowingPosition(int32_t iTx, int32_t iTy, C4Fixed fXDir, C4Fixed fYDir, int32_t iHeight, int32_t &rX, int32_t &rY)
2087{
2088 // Start underneath throwing target
2089 rX = iTx; rY = iTy; // improve: check from overhanging cliff
2090 if (!SemiAboveSolid(rx&: rX, ry&: rY)) return false;
2091
2092 // Target too far above surface
2093 if (!Inside(ival: rY - iTy, lbound: -C4LSC_Throwing_MaxVertical, rbound: +C4LSC_Throwing_MaxVertical)) return false;
2094
2095 // Search in direction according to launch fXDir
2096 int32_t iDir = +1; if (fXDir > 0) iDir = -1;
2097
2098 // Move along surface
2099 for (int32_t cnt = 0; Inside<int32_t>(ival: rX, lbound: 0, GBackWdt - 1) && (cnt <= C4LSC_Throwing_MaxHorizontal); rX += iDir, cnt++)
2100 {
2101 // Adjust to surface
2102 if (!SemiAboveSolid(rx&: rX, ry&: rY)) return false;
2103
2104 // Check trajectory distance
2105 int32_t itjd = TrajectoryDistance(iFx: rX, iFy: rY - iHeight, iXDir: fXDir, iYDir: fYDir, iTx, iTy);
2106
2107 // Hitting range: success
2108 if (itjd <= 2) return true;
2109 }
2110
2111 // Failure
2112 return false;
2113}
2114
2115const int32_t C4LSC_Closest_MaxRange = 200,
2116 C4LSC_Closest_Step = 10;
2117
2118bool FindClosestFree(int32_t &rX, int32_t &rY, int32_t iAngle1, int32_t iAngle2,
2119 int32_t iExcludeAngle1, int32_t iExcludeAngle2)
2120{
2121 int32_t iX, iY;
2122 for (int32_t iR = C4LSC_Closest_Step; iR < C4LSC_Closest_MaxRange; iR += C4LSC_Closest_Step)
2123 for (int32_t iAngle = iAngle1; iAngle < iAngle2; iAngle += C4LSC_Closest_Step)
2124 if (!Inside(ival: iAngle, lbound: iExcludeAngle1, rbound: iExcludeAngle2))
2125 {
2126 iX = rX + fixtoi(x: Sin(fAngle: itofix(x: iAngle)) * iR);
2127 iY = rY - fixtoi(x: Cos(fAngle: itofix(x: iAngle)) * iR);
2128 if (Inside<int32_t>(ival: iX, lbound: 0, GBackWdt - 1))
2129 if (Inside<int32_t>(ival: iY, lbound: 0, GBackHgt - 1))
2130 if (!GBackSemiSolid(x: iX, y: iY))
2131 {
2132 rX = iX; rY = iY; return true;
2133 }
2134 }
2135 return false;
2136}
2137
2138bool ConstructionCheck(C4ID id, int32_t iX, int32_t iY, C4Object *pByObj)
2139{
2140 C4Def *ndef;
2141 char idostr[5];
2142
2143 // Check def
2144 if (!(ndef = C4Id2Def(id)))
2145 {
2146 GetC4IdText(id, sBuf: idostr);
2147 if (pByObj) GameMsgObject(szText: LoadResStr(id: C4ResStrTableKey::IDS_OBJ_UNDEF, args&: idostr).c_str(), pTarget: pByObj, iFCol: FRed);
2148 return false;
2149 }
2150
2151 // Constructable?
2152 if (!ndef->Constructable)
2153 {
2154 if (pByObj) GameMsgObject(szText: LoadResStr(id: C4ResStrTableKey::IDS_OBJ_NOCON, args: ndef->GetName()).c_str(), pTarget: pByObj, iFCol: FRed);
2155 return false;
2156 }
2157
2158 // Check area
2159 int32_t rtx, rty, wdt, hgt;
2160 wdt = ndef->Shape.Wdt; hgt = ndef->Shape.Hgt - ndef->ConSizeOff;
2161 rtx = iX - wdt / 2; rty = iY - hgt;
2162 if (Game.Landscape.AreaSolidCount(x: rtx, y: rty, wdt, hgt) > (wdt * hgt / 20))
2163 {
2164 if (pByObj) GameMsgObject(szText: LoadResStr(id: C4ResStrTableKey::IDS_OBJ_NOROOM), pTarget: pByObj, iFCol: FRed);
2165 return false;
2166 }
2167 if (Game.Landscape.AreaSolidCount(x: rtx, y: rty + hgt, wdt, hgt: 5) < (wdt * 2))
2168 {
2169 if (pByObj) GameMsgObject(szText: LoadResStr(id: C4ResStrTableKey::IDS_OBJ_NOLEVEL), pTarget: pByObj, iFCol: FRed);
2170 return false;
2171 }
2172
2173 // Check other structures
2174 C4Object *other;
2175 if (other = Game.OverlapObject(tx: rtx, ty: rty, wdt, hgt, category: ndef->Category))
2176 {
2177 if (pByObj) GameMsgObject(szText: LoadResStr(id: C4ResStrTableKey::IDS_OBJ_NOOTHER, args: other->GetName()).c_str(), pTarget: pByObj, iFCol: FRed);
2178 return false;
2179 }
2180
2181 return true;
2182}
2183
2184void C4Landscape::ClearRect(int32_t iTx, int32_t iTy, int32_t iWdt, int32_t iHgt)
2185{
2186 C4Rect rt(iTx, iTy, iWdt, iHgt);
2187 PrepareChange(BoundingBox: rt, updateMatCnt: false);
2188 for (int32_t y = iTy; y < iTy + iHgt; y++)
2189 {
2190 for (int32_t x = iTx; x < iTx + iWdt; x++) ClearPix(tx: x, ty: y);
2191 if (Rnd3()) Rnd3();
2192 }
2193 FinishChange(BoundingBox: rt, updateMatAndPixCnt: false);
2194}
2195
2196void C4Landscape::ClearRectDensity(int32_t iTx, int32_t iTy, int32_t iWdt, int32_t iHgt, int32_t iOfDensity)
2197{
2198 int32_t iMinDensity = iOfDensity, iMaxDensity = iOfDensity;
2199 switch (iOfDensity)
2200 {
2201 case C4M_Vehicle: iMaxDensity = 1000; break;
2202 case C4M_Solid: iMaxDensity = C4M_Vehicle - 1; break;
2203 case C4M_Liquid: iMaxDensity = C4M_Solid - 1; break;
2204 case C4M_Background: iMaxDensity = C4M_Liquid - 1; break;
2205 default: break; // min=max as given
2206 }
2207
2208 for (int32_t y = iTy; y < iTy + iHgt; y++)
2209 {
2210 for (int32_t x = iTx; x < iTx + iWdt; x++)
2211 {
2212 if (Inside(ival: GetDensity(x, y), lbound: iMinDensity, rbound: iMaxDensity))
2213 ClearPix(tx: x, ty: y);
2214 }
2215 if (Rnd3()) Rnd3();
2216 }
2217}
2218
2219bool C4Landscape::SaveMap(C4Group &hGroup)
2220{
2221 // No map
2222 if (!Map) return false;
2223
2224 // Create map palette
2225 uint8_t bypPalette[3 * 256];
2226 Game.TextureMap.StoreMapPalette(bypPalette, rMaterials&: Game.Material);
2227
2228 // Save map surface
2229 if (!Map->Save(szFilename: Config.AtTempPath(C4CFN_TempMap), bpPalette: bypPalette))
2230 return false;
2231
2232 // Move temp file to group
2233 if (!hGroup.Move(szFile: Config.AtTempPath(C4CFN_TempMap),
2234 C4CFN_Map))
2235 return false;
2236
2237 // Success
2238 return true;
2239}
2240
2241bool C4Landscape::SaveTextures(C4Group &hGroup)
2242{
2243 // if material-texture-combinations have been added, write the texture map
2244 if (Game.TextureMap.fEntriesAdded)
2245 {
2246 C4Group *pMatGroup = new C4Group();
2247 bool fSuccess = false;
2248 // create local material group
2249 if (!hGroup.FindEntry(C4CFN_Material))
2250 {
2251 // delete previous item at temp path
2252 EraseItem(szItemName: Config.AtTempPath(C4CFN_Material));
2253 // create at temp path
2254 if (pMatGroup->Open(szGroupName: Config.AtTempPath(C4CFN_Material), fCreate: true))
2255 // write to it
2256 if (Game.TextureMap.SaveMap(hGroup&: *pMatGroup, C4CFN_TexMap))
2257 // close (flush)
2258 if (pMatGroup->Close())
2259 // add it
2260 if (hGroup.Move(szFile: Config.AtTempPath(C4CFN_Material), C4CFN_Material))
2261 fSuccess = true;
2262 // temp group must remain for scenario file closure
2263 // it will be deleted when the group is closed
2264 }
2265 else
2266 // simply write it to the local material file
2267 if (pMatGroup->OpenAsChild(pMother: &hGroup, C4CFN_Material))
2268 fSuccess = Game.TextureMap.SaveMap(hGroup&: *pMatGroup, C4CFN_TexMap);
2269 // close material group again
2270 if (pMatGroup->IsOpen()) pMatGroup->Close();
2271 delete pMatGroup;
2272 // fail if unsuccessful
2273 if (!fSuccess) return false;
2274 }
2275 // done, success
2276 return true;
2277}
2278
2279bool C4Landscape::SetMode(int32_t iMode)
2280{
2281 // Invalid mode
2282 if (!Inside<int32_t>(ival: iMode, lbound: C4LSC_Dynamic, rbound: C4LSC_Exact)) return false;
2283 // Set mode
2284 Mode = iMode;
2285 // Done
2286 return true;
2287}
2288
2289bool C4Landscape::MapToLandscape()
2290{
2291 // zoom map to landscape
2292 return MapToLandscape(sfcMap: Map, iMapX: 0, iMapY: 0, iMapWdt: MapWidth, iMapHgt: MapHeight);
2293}
2294
2295bool C4Landscape::GetMapColorIndex(const char *szMaterial, const char *szTexture, bool fIFT, uint8_t &rbyCol)
2296{
2297 // Sky
2298 if (SEqual(szStr1: szMaterial, C4TLS_MatSky))
2299 rbyCol = 0;
2300 // Material-Texture
2301 else
2302 {
2303 if (!(rbyCol = Game.TextureMap.GetIndex(szMaterial, szTexture))) return false;
2304 if (fIFT) rbyCol += IFT;
2305 }
2306 // Found
2307 return true;
2308}
2309
2310bool C4Landscape::DrawBrush(int32_t iX, int32_t iY, int32_t iGrade, const char *szMaterial, const char *szTexture, bool fIFT)
2311{
2312 uint8_t byCol;
2313 switch (Mode)
2314 {
2315 // Dynamic: ignore
2316 case C4LSC_Dynamic:
2317 break;
2318 // Static: draw to map by material-texture-index, chunk-o-zoom to landscape
2319 case C4LSC_Static:
2320 // Get map color index by material-texture
2321 if (!GetMapColorIndex(szMaterial, szTexture, fIFT, rbyCol&: byCol)) return false;
2322 // Draw to map
2323 int32_t iRadius; iRadius = std::max<int32_t>(a: 2 * iGrade / MapZoom, b: 1);
2324 if (iRadius == 1) { if (Map) Map->SetPix(iX: iX / MapZoom, iY: iY / MapZoom, byCol); }
2325 else Map->Circle(x: iX / MapZoom, y: iY / MapZoom, r: iRadius, col: byCol);
2326 // Update landscape
2327 MapToLandscape(sfcMap: Map, iMapX: iX / MapZoom - iRadius - 1, iMapY: iY / MapZoom - iRadius - 1, iMapWdt: 2 * iRadius + 2, iMapHgt: 2 * iRadius + 2);
2328 SetMapChanged();
2329 break;
2330 // Exact: draw directly to landscape by color & pattern
2331 case C4LSC_Exact:
2332 // Set texture pattern & get material color
2333 if (!GetMapColorIndex(szMaterial, szTexture, fIFT, rbyCol&: byCol)) return false;
2334 C4Rect BoundingBox(iX - iGrade - 1, iY - iGrade - 1, iGrade * 2 + 2, iGrade * 2 + 2);
2335 // Draw to landscape
2336 PrepareChange(BoundingBox);
2337 Surface8->Circle(x: iX, y: iY, r: iGrade, col: byCol);
2338 FinishChange(BoundingBox);
2339 break;
2340 }
2341 return true;
2342}
2343
2344uint8_t DrawLineCol;
2345
2346bool C4Landscape::DrawLineLandscape(int32_t iX, int32_t iY, int32_t iGrade)
2347{
2348 Game.Landscape.Surface8->Circle(x: iX, y: iY, r: iGrade, col: DrawLineCol);
2349 return true;
2350}
2351
2352bool DrawLineMap(int32_t iX, int32_t iY, int32_t iRadius)
2353{
2354 if (iRadius == 1) { if (Game.Landscape.Map) Game.Landscape.Map->SetPix(iX, iY, byCol: DrawLineCol); }
2355 else Game.Landscape.Map->Circle(x: iX, y: iY, r: iRadius, col: DrawLineCol);
2356 return true;
2357}
2358
2359bool C4Landscape::DrawLine(int32_t iX1, int32_t iY1, int32_t iX2, int32_t iY2, int32_t iGrade, const char *szMaterial, const char *szTexture, bool fIFT)
2360{
2361 switch (Mode)
2362 {
2363 // Dynamic: ignore
2364 case C4LSC_Dynamic:
2365 break;
2366 // Static: draw to map by material-texture-index, chunk-o-zoom to landscape
2367 case C4LSC_Static:
2368 // Get map color index by material-texture
2369 if (!GetMapColorIndex(szMaterial, szTexture, fIFT, rbyCol&: DrawLineCol)) return false;
2370 // Draw to map
2371 int32_t iRadius; iRadius = std::max<int32_t>(a: 2 * iGrade / MapZoom, b: 1);
2372 iX1 /= MapZoom; iY1 /= MapZoom; iX2 /= MapZoom; iY2 /= MapZoom;
2373 ForLine(x1: iX1, y1: iY1, x2: iX2, y2: iY2, fnCallback: &DrawLineMap, iPar: iRadius);
2374 // Update landscape
2375 int32_t iUpX, iUpY, iUpWdt, iUpHgt;
2376 iUpX = (std::min)(a: iX1, b: iX2) - iRadius - 1; iUpY = (std::min)(a: iY1, b: iY2) - iRadius - 1;
2377 iUpWdt = Abs(val: iX2 - iX1) + 2 * iRadius + 2; iUpHgt = Abs(val: iY2 - iY1) + 2 * iRadius + 2;
2378 MapToLandscape(sfcMap: Map, iMapX: iUpX, iMapY: iUpY, iMapWdt: iUpWdt, iMapHgt: iUpHgt);
2379 SetMapChanged();
2380 break;
2381 // Exact: draw directly to landscape by color & pattern
2382 case C4LSC_Exact:
2383 // Set texture pattern & get material color
2384 if (!GetMapColorIndex(szMaterial, szTexture, fIFT, rbyCol&: DrawLineCol)) return false;
2385 C4Rect BoundingBox(iX1 - iGrade, iY1 - iGrade, iGrade * 2 + 1, iGrade * 2 + 1);
2386 BoundingBox.Add(r2: C4Rect(iX2 - iGrade, iY2 - iGrade, iGrade * 2 + 1, iGrade * 2 + 1));
2387 // Draw to landscape
2388 PrepareChange(BoundingBox);
2389 ForLine(x1: iX1, y1: iY1, x2: iX2, y2: iY2, fnCallback: &DrawLineLandscape, iPar: iGrade);
2390 FinishChange(BoundingBox);
2391 break;
2392 }
2393 return true;
2394}
2395
2396bool C4Landscape::DrawBox(int32_t iX1, int32_t iY1, int32_t iX2, int32_t iY2, int32_t iGrade, const char *szMaterial, const char *szTexture, bool fIFT)
2397{
2398 // get upper-left/lower-right - corners
2399 int32_t iX0 = (std::min)(a: iX1, b: iX2); int32_t iY0 = (std::min)(a: iY1, b: iY2);
2400 iX2 = (std::max)(a: iX1, b: iX2); iY2 = (std::max)(a: iY1, b: iY2); iX1 = iX0; iY1 = iY0;
2401 uint8_t byCol;
2402 switch (Mode)
2403 {
2404 // Dynamic: ignore
2405 case C4LSC_Dynamic:
2406 break;
2407 // Static: draw to map by material-texture-index, chunk-o-zoom to landscape
2408 case C4LSC_Static:
2409 // Get map color index by material-texture
2410 if (!GetMapColorIndex(szMaterial, szTexture, fIFT, rbyCol&: byCol)) return false;
2411 // Draw to map
2412 iX1 /= MapZoom; iY1 /= MapZoom; iX2 /= MapZoom; iY2 /= MapZoom;
2413 Map->Box(iX: iX1, iY: iY1, iX2, iY2, iCol: byCol);
2414 // Update landscape
2415 MapToLandscape(sfcMap: Map, iMapX: iX1 - 1, iMapY: iY1 - 1, iMapWdt: iX2 - iX1 + 3, iMapHgt: iY2 - iY1 + 3);
2416 SetMapChanged();
2417 break;
2418 // Exact: draw directly to landscape by color & pattern
2419 case C4LSC_Exact:
2420 // Set texture pattern & get material color
2421 if (!GetMapColorIndex(szMaterial, szTexture, fIFT, rbyCol&: byCol)) return false;
2422 C4Rect BoundingBox(iX1, iY1, iX2 - iX1 + 1, iY2 - iY1 + 1);
2423 // Draw to landscape
2424 PrepareChange(BoundingBox);
2425 Surface8->Box(iX: iX1, iY: iY1, iX2, iY2, iCol: byCol);
2426 FinishChange(BoundingBox);
2427 break;
2428 }
2429 return true;
2430}
2431
2432bool 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)
2433{
2434 uint8_t byColor;
2435 if (!GetMapColorIndex(szMaterial, szTexture, fIFT: bIFT, rbyCol&: byColor)) return false;
2436
2437 int32_t iMaterial = Game.Material.Get(szMaterial); if (!MatValid(mat: iMaterial)) return false;
2438
2439 C4Rect BoundingBox(tx - 5, ty - 5, wdt + 10, hgt + 10);
2440 PrepareChange(BoundingBox);
2441
2442 // assign clipper
2443 Surface8->Clip(iX: BoundingBox.x, iY: BoundingBox.y, iX2: BoundingBox.x + BoundingBox.Wdt, iY2: BoundingBox.y + BoundingBox.Hgt);
2444 Application.DDraw->NoPrimaryClipper();
2445
2446 // draw all chunks
2447 int32_t x, y;
2448 for (x = 0; x < icntx; x++)
2449 for (y = 0; y < icnty; y++)
2450 DrawChunk(tx: tx + wdt * x / icntx, ty: ty + hgt * y / icnty, wdt: wdt / icntx, hgt: hgt / icnty, mcol: byColor, iChunkType: Game.Material.Map[iMaterial].MapChunkType, cro: Random(iRange: 1000));
2451
2452 // remove clipper
2453 Surface8->NoClip();
2454
2455 FinishChange(BoundingBox);
2456
2457 // success
2458 return true;
2459}
2460
2461bool 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, bool fIFT)
2462{
2463 // get texture
2464 int32_t iMatTex = Game.TextureMap.GetIndexMatTex(szMaterialTexture: szMaterial);
2465 if (!iMatTex) return false;
2466 // prepate pixel count update
2467 C4Rect BoundingBox(iX1, iY1, 1, 1);
2468 BoundingBox.Add(r2: C4Rect(iX2, iY2, 1, 1));
2469 BoundingBox.Add(r2: C4Rect(iX3, iY3, 1, 1));
2470 BoundingBox.Add(r2: C4Rect(iX4, iY4, 1, 1));
2471 // set vertices
2472 int vtcs[8];
2473 vtcs[0] = iX1; vtcs[1] = iY1;
2474 vtcs[2] = iX2; vtcs[3] = iY2;
2475 vtcs[4] = iX3; vtcs[5] = iY3;
2476 vtcs[6] = iX4; vtcs[7] = iY4;
2477 // draw quad
2478 PrepareChange(BoundingBox);
2479 Surface8->Polygon(iNum: 4, ipVtx: vtcs, iCol: MatTex2PixCol(tex: iMatTex) + (fIFT ? IFT : 0));
2480 FinishChange(BoundingBox);
2481 return true;
2482}
2483
2484uint8_t C4Landscape::GetMapIndex(int32_t iX, int32_t iY)
2485{
2486 if (!Map) return 0;
2487 return Map->GetPix(iX, iY);
2488}
2489
2490bool C4Landscape::DoRelights()
2491{
2492 if (!Relights[0].Wdt) return true;
2493
2494 if (!Surface32->Lock()) return false;
2495 if (AnimationSurface)
2496 {
2497 AnimationSurface->Lock();
2498 }
2499
2500 for (int32_t i = 0; i < C4LS_MaxRelights; i++)
2501 {
2502 if (!Relights[i].Wdt)
2503 break;
2504 C4Rect SolidMaskRect = Relights[i];
2505 SolidMaskRect.x -= 2 * C4LS_MaxLightDistX; SolidMaskRect.y -= 2 * C4LS_MaxLightDistY;
2506 SolidMaskRect.Wdt += 4 * C4LS_MaxLightDistX; SolidMaskRect.Hgt += 4 * C4LS_MaxLightDistY;
2507 C4SolidMask *pSolid;
2508 for (pSolid = C4SolidMask::Last; pSolid; pSolid = pSolid->Prev)
2509 {
2510 pSolid->RemoveTemporary(where: SolidMaskRect);
2511 }
2512 Relight(To: Relights[i]);
2513 // Restore Solidmasks
2514 for (pSolid = C4SolidMask::First; pSolid; pSolid = pSolid->Next)
2515 {
2516 pSolid->PutTemporary(where: SolidMaskRect);
2517 }
2518 Relights[i].Default();
2519 C4SolidMask::CheckConsistency();
2520 }
2521
2522 Surface32->Unlock();
2523 if (AnimationSurface) AnimationSurface->Unlock();
2524
2525 return true;
2526}
2527
2528bool C4Landscape::Relight(C4Rect To)
2529{
2530 // Enlarge to relight pixels surrounding a changed one
2531 To.x -= C4LS_MaxLightDistX; To.y -= C4LS_MaxLightDistY;
2532 To.Wdt += 2 * C4LS_MaxLightDistX; To.Hgt += 2 * C4LS_MaxLightDistY;
2533 // Apply lighting
2534 return ApplyLighting(To);
2535}
2536
2537bool C4Landscape::ApplyLighting(C4Rect To)
2538{
2539 // clip to landscape size
2540 To.Intersect(r2: C4Rect(0, 0, GBackWdt, GBackHgt));
2541 // everything clipped?
2542 if (To.Wdt <= 0 || To.Hgt <= 0) return true;
2543
2544 if (!Surface32->LockForUpdate(rect: To)) return false;
2545 Surface32->ClearBoxDw(iX: To.x, iY: To.y, iWdt: To.Wdt, iHgt: To.Hgt);
2546 // do lightning
2547 for (int32_t iX = To.x; iX < To.x + To.Wdt; ++iX)
2548 {
2549 int AboveDensity = 0, BelowDensity = 0;
2550 if (ShadeMaterials)
2551 {
2552 for (int i = 1; i <= 8; ++i)
2553 {
2554 AboveDensity += GetPlacement(x: iX, y: To.y - i - 1);
2555 BelowDensity += GetPlacement(x: iX, y: To.y + i - 1);
2556 }
2557 }
2558
2559 for (int32_t iY = To.y; iY < To.y + To.Hgt; ++iY)
2560 {
2561 // do not move that code into the if (ShadeMaterials) block, as it needs to be run for sky pixels as well
2562 AboveDensity -= GetPlacement(x: iX, y: iY - 9);
2563 AboveDensity += GetPlacement(x: iX, y: iY - 1);
2564 BelowDensity -= GetPlacement(x: iX, y: iY);
2565 BelowDensity += GetPlacement(x: iX, y: iY + 8);
2566
2567 // Normal color
2568 uint32_t dwBackClr = GetClrByTex(iX, iY);
2569
2570 uint8_t pix = _GetPix(x: iX, y: iY);
2571 // Sky
2572 if (!pix)
2573 {
2574 Surface32->SetPixDw(iX, iY, dwCol: dwBackClr);
2575 continue;
2576 }
2577
2578 if (ShadeMaterials)
2579 {
2580 // get density
2581 int iOwnDens = Pix2Place[pix];
2582 if (!iOwnDens) continue;
2583 iOwnDens *= 2;
2584 iOwnDens += GetPlacement(x: iX + 1, y: iY) + GetPlacement(x: iX - 1, y: iY);
2585 iOwnDens /= 4;
2586 // get density of surrounding materials
2587 int iCompareDens = AboveDensity / 8;
2588 if (iOwnDens > iCompareDens)
2589 {
2590 // apply light
2591 LightenClrBy(dst&: dwBackClr, by: (std::min)(a: 30, b: 2 * (iOwnDens - iCompareDens)));
2592 }
2593 else if (iOwnDens < iCompareDens && iOwnDens < 30)
2594 {
2595 DarkenClrBy(dst&: dwBackClr, by: (std::min)(a: 30, b: 2 * (iCompareDens - iOwnDens)));
2596 }
2597 iCompareDens = BelowDensity / 8;
2598 if (iOwnDens > iCompareDens)
2599 {
2600 DarkenClrBy(dst&: dwBackClr, by: (std::min)(a: 30, b: 2 * (iOwnDens - iCompareDens)));
2601 }
2602 }
2603
2604 Surface32->SetPixDw(iX, iY, dwCol: dwBackClr);
2605 }
2606 }
2607 Surface32->Unlock();
2608
2609 return UpdateAnimationSurface(To);
2610}
2611
2612bool C4Landscape::UpdateAnimationSurface(C4Rect To)
2613{
2614 if (!AnimationSurface) return true;
2615
2616 if (!AnimationSurface->LockForUpdate(rect: To)) return false;
2617
2618 AnimationSurface->ClearBoxDw(iX: To.x, iY: To.y, iWdt: To.Wdt, iHgt: To.Hgt);
2619
2620 for (int32_t iX = To.x; iX < To.x + To.Wdt; ++iX)
2621 {
2622 for (int32_t iY = To.y; iY < To.y + To.Hgt; ++iY)
2623 {
2624 AnimationSurface->SetPixDw(iX, iY, dwCol: DensityLiquid(dens: Pix2Dens[_GetPix(x: iX, y: iY)]) ? 255 << 24 : 0);
2625 }
2626 }
2627
2628 AnimationSurface->Unlock();
2629 return true;
2630}
2631
2632uint32_t C4Landscape::GetClrByTex(int32_t iX, int32_t iY)
2633{
2634 // Get pixel and default color
2635 uint8_t pix = _GetPix(x: iX, y: iY);
2636 uint32_t dwPix = Surface8->pPal->GetClr(byCol: pix);
2637 // get texture map entry for pixel
2638 const C4TexMapEntry *pTex;
2639 if (pix && (pTex = Game.TextureMap.GetEntry(iIndex: PixCol2Tex(pixc: pix))))
2640 {
2641 // pattern color
2642 pTex->getPattern().PatternClr(iX, iY, byClr&: pix, dwClr&: dwPix, rPal&: Application.DDraw->Pal);
2643 if (pTex->GetMaterial())
2644 pTex->GetMaterial()->MatPattern.PatternClr(iX, iY, byClr&: pix, dwClr&: dwPix, rPal&: Application.DDraw->Pal);
2645 }
2646 return dwPix;
2647}
2648
2649bool C4Landscape::DrawMap(int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, const char *szMapDef)
2650{
2651 // safety
2652 if (!szMapDef) return false;
2653 // clip to landscape size
2654 if (!ClipRect(rX&: iX, rY&: iY, rWdt&: iWdt, rHgt&: iHgt)) return false;
2655 // get needed map size
2656 int32_t iMapWdt = (iWdt - 1) / MapZoom + 1;
2657 int32_t iMapHgt = (iHgt - 1) / MapZoom + 1;
2658 C4SLandscape FakeLS = Game.C4S.Landscape;
2659 FakeLS.MapWdt.Set(std: iMapWdt, rnd: 0, min: iMapWdt, max: iMapWdt);
2660 FakeLS.MapHgt.Set(std: iMapHgt, rnd: 0, min: iMapHgt, max: iMapHgt);
2661 // create map creator
2662 std::optional<C4MapCreatorS2> mapCreator;
2663 // If KeepMapCreator=1 we copy the existing creator to gain access to the named overlays
2664 if (pMapCreator)
2665 {
2666 mapCreator.emplace(args&: *pMapCreator, args: &FakeLS);
2667 }
2668 else
2669 {
2670 mapCreator.emplace(args: &FakeLS, args: &Game.TextureMap, args: &Game.Material, args&: Game.Parameters.StartupPlayerCount);
2671 }
2672 // read file
2673 mapCreator->ReadScript(szScript: szMapDef);
2674 // render map
2675 CSurface8 *sfcMap = mapCreator->Render(szMapName: nullptr);
2676 if (!sfcMap) return false;
2677 // map it to the landscape
2678 bool fSuccess = MapToLandscape(sfcMap, iMapX: 0, iMapY: 0, iMapWdt, iMapHgt, iOffsX: iX, iOffsY: iY);
2679 // cleanup
2680 delete sfcMap;
2681 // return whether successful
2682 return fSuccess;
2683}
2684
2685bool C4Landscape::DrawDefMap(int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, const char *szMapDef)
2686{
2687 // safety
2688 if (!szMapDef || !pMapCreator) return false;
2689 // clip to landscape size
2690 if (!ClipRect(rX&: iX, rY&: iY, rWdt&: iWdt, rHgt&: iHgt)) return false;
2691 // get needed map size
2692 int32_t iMapWdt = (iWdt - 1) / MapZoom + 1;
2693 int32_t iMapHgt = (iHgt - 1) / MapZoom + 1;
2694 bool fSuccess = false;
2695 // render map
2696 C4MCMap *pMap = pMapCreator->GetMap(szMapName: szMapDef);
2697 if (!pMap) return false;
2698 pMap->SetSize(iWdt: iMapWdt, iHgt: iMapHgt);
2699 CSurface8 *sfcMap = pMapCreator->Render(szMapName: szMapDef);
2700 if (sfcMap)
2701 {
2702 // map to landscape
2703 fSuccess = MapToLandscape(sfcMap, iMapX: 0, iMapY: 0, iMapWdt, iMapHgt, iOffsX: iX, iOffsY: iY);
2704 }
2705 // cleanup
2706 delete sfcMap;
2707 // done
2708 return fSuccess;
2709}
2710
2711bool C4Landscape::ClipRect(int32_t &rX, int32_t &rY, int32_t &rWdt, int32_t &rHgt)
2712{
2713 // clip by bounds
2714 if (rX < 0) { rWdt += rX; rX = 0; }
2715 if (rY < 0) { rHgt += rY; rY = 0; }
2716 int32_t iOver;
2717 iOver = rX + rWdt - Width; if (iOver > 0) { rWdt -= iOver; }
2718 iOver = rY + rHgt - Height; if (iOver > 0) { rHgt -= iOver; }
2719 // anything left inside the bounds?
2720 return rWdt > 0 && rHgt > 0;
2721}
2722
2723bool C4Landscape::ReplaceMapColor(uint8_t iOldIndex, uint8_t iNewIndex)
2724{
2725 // find every occurance of iOldIndex in map; replace it by new index
2726 if (!Map) return false;
2727 int iPitch, iMapWdt, iMapHgt;
2728 uint8_t *pMap = Map->Bits;
2729 iMapWdt = Map->Wdt;
2730 iMapHgt = Map->Hgt;
2731 iPitch = Map->Pitch;
2732 if (!pMap) return false;
2733 for (int32_t y = 0; y < iMapHgt; ++y)
2734 {
2735 for (int32_t x = 0; x < iMapWdt; ++x)
2736 {
2737 if ((*pMap & 0x7f) == iOldIndex)
2738 *pMap = (*pMap & 0x80) + iNewIndex;
2739 ++pMap;
2740 }
2741 pMap += iPitch - iMapWdt;
2742 }
2743 return true;
2744}
2745
2746bool C4Landscape::SetTextureIndex(const char *szMatTex, uint8_t iNewIndex, bool fInsert)
2747{
2748 if (((!szMatTex || !*szMatTex) && !fInsert) || !Inside<int>(ival: iNewIndex, lbound: 0x01, rbound: 0x7f))
2749 {
2750 DebugLog(level: spdlog::level::err, fmt: "Cannot insert new texture {} to index {}: Invalid parameters.", args&: szMatTex, args: static_cast<int>(iNewIndex));
2751 return false;
2752 }
2753 // get last mat index - returns zero for not found (valid for insertion mode)
2754 StdStrBuf Material, Texture;
2755 Material.CopyUntil(szString: szMatTex, cUntil: '-'); Texture.Copy(pnData: SSearch(szString: szMatTex, szIndex: "-"));
2756 uint8_t iOldIndex = (szMatTex && *szMatTex) ? Game.TextureMap.GetIndex(szMaterial: Material.getData(), szTexture: Texture.getData(), fAddIfNotExist: false) : 0;
2757 // insertion mode?
2758 if (fInsert)
2759 {
2760 // there must be room to move up to
2761 uint8_t byLastMoveIndex = C4M_MaxTexIndex - 1;
2762 while (Game.TextureMap.GetEntry(iIndex: byLastMoveIndex))
2763 if (--byLastMoveIndex == iNewIndex)
2764 {
2765 DebugLog(level: spdlog::level::err, fmt: "Cannot insert new texture {} to index {}: No room for insertion.", args&: szMatTex, args: static_cast<int>(iNewIndex));
2766 return false;
2767 }
2768 // then move up all other textures first
2769 // could do this in one loop, but it's just a developement call anyway, so move one index at a time
2770 while (--byLastMoveIndex >= iNewIndex)
2771 if (Game.TextureMap.GetEntry(iIndex: byLastMoveIndex))
2772 {
2773 ReplaceMapColor(iOldIndex: byLastMoveIndex, iNewIndex: byLastMoveIndex + 1);
2774 Game.TextureMap.MoveIndex(byOldIndex: byLastMoveIndex, byNewIndex: byLastMoveIndex + 1);
2775 }
2776 // new insertion desired?
2777 if (szMatTex && *szMatTex)
2778 {
2779 // move from old or create new
2780 if (iOldIndex)
2781 {
2782 ReplaceMapColor(iOldIndex, iNewIndex);
2783 Game.TextureMap.MoveIndex(byOldIndex: iOldIndex, byNewIndex: iNewIndex);
2784 }
2785 else
2786 {
2787 StdStrBuf Material, Texture;
2788 Material.CopyUntil(szString: szMatTex, cUntil: '-'); Texture.Copy(pnData: SSearch(szString: szMatTex, szIndex: "-"));
2789 // new insertion
2790 if (!Game.TextureMap.AddEntry(byIndex: iNewIndex, szMaterial: Material.getData(), szTexture: Texture.getData()))
2791 {
2792 LogNTr(level: spdlog::level::err, fmt: "Cannot insert new texture {} to index {}: Texture map entry error", args&: szMatTex, args&: iNewIndex);
2793 return false;
2794 }
2795 }
2796 }
2797 // done, success
2798 return true;
2799 }
2800 else
2801 {
2802 // new index must not be occupied
2803 const C4TexMapEntry *pOld;
2804 if ((pOld = Game.TextureMap.GetEntry(iIndex: iNewIndex)) && !pOld->isNull())
2805 {
2806 DebugLog(level: spdlog::level::err, fmt: "Cannot move texture {} to index {}: Index occupied by {}-{}.", args&: szMatTex, args: static_cast<int>(iNewIndex), args: pOld->GetMaterialName(), args: pOld->GetTextureName());
2807 return false;
2808 }
2809 // must only move existing textures
2810 if (!iOldIndex)
2811 {
2812 DebugLog(level: spdlog::level::err, fmt: "Cannot move texture {} to index {}: Texture not found.", args&: szMatTex, args&: iNewIndex);
2813 return false;
2814 }
2815 // update map
2816 ReplaceMapColor(iOldIndex, iNewIndex);
2817 // change to new index in texmap
2818 Game.TextureMap.MoveIndex(byOldIndex: iOldIndex, byNewIndex: iNewIndex);
2819 // done, success
2820 return true;
2821 }
2822}
2823
2824void C4Landscape::HandleTexMapUpdate()
2825{
2826 // Pixel maps must be update
2827 UpdatePixMaps();
2828 // Update landscape palette
2829 Mat2Pal();
2830}
2831
2832void C4Landscape::UpdatePixMaps()
2833{
2834 int32_t i;
2835 for (i = 0; i < 256; i++) Pix2Mat[i] = PixCol2Mat(pixc: i);
2836 for (i = 0; i < 256; i++) Pix2Dens[i] = MatDensity(mat: Pix2Mat[i]);
2837 for (i = 0; i < 256; i++) Pix2Place[i] = MatValid(mat: Pix2Mat[i]) ? Game.Material.Map[Pix2Mat[i]].Placement : 0;
2838 Pix2Place[0] = 0;
2839}
2840
2841bool C4Landscape::Mat2Pal()
2842{
2843 if (!Surface8) return false;
2844 // set landscape pal
2845 int32_t tex, rgb;
2846 for (tex = 0; tex < C4M_MaxTexIndex; tex++)
2847 {
2848 const C4TexMapEntry *pTex = Game.TextureMap.GetEntry(iIndex: tex);
2849 if (!pTex || pTex->isNull())
2850 continue;
2851 // colors
2852 for (rgb = 0; rgb < 3; rgb++)
2853 Surface8->pPal->Colors[MatTex2PixCol(tex) * 3 + rgb]
2854 = Surface8->pPal->Colors[(MatTex2PixCol(tex) + IFT) * 3 + rgb]
2855 = pTex->GetMaterial()->Color[rgb];
2856 // alpha
2857 Surface8->pPal->Alpha[MatTex2PixCol(tex)] = pTex->GetMaterial()->Alpha[0];
2858 Surface8->pPal->Alpha[MatTex2PixCol(tex) + IFT] = pTex->GetMaterial()->Alpha[C4M_ColsPerMat];
2859 }
2860 // success
2861 return true;
2862}
2863
2864void C4Landscape::PrepareChange(C4Rect BoundingBox, const bool updateMatCnt)
2865{
2866 // move solidmasks out of the way
2867 C4Rect SolidMaskRect = BoundingBox;
2868 SolidMaskRect.x -= 2 * C4LS_MaxLightDistX; SolidMaskRect.y -= 2 * C4LS_MaxLightDistY;
2869 SolidMaskRect.Wdt += 4 * C4LS_MaxLightDistX; SolidMaskRect.Hgt += 4 * C4LS_MaxLightDistY;
2870 for (C4SolidMask *pSolid = C4SolidMask::Last; pSolid; pSolid = pSolid->Prev)
2871 {
2872 pSolid->RemoveTemporary(where: SolidMaskRect);
2873 }
2874 if (updateMatCnt) UpdateMatCnt(Rect: BoundingBox, fPlus: false);
2875}
2876
2877void C4Landscape::FinishChange(C4Rect BoundingBox, const bool updateMatAndPixCnt)
2878{
2879 // relight
2880 Relight(To: BoundingBox);
2881 if (updateMatAndPixCnt) UpdateMatCnt(Rect: BoundingBox, fPlus: true);
2882 // Restore Solidmasks
2883 C4Rect SolidMaskRect = BoundingBox;
2884 SolidMaskRect.x -= 2 * C4LS_MaxLightDistX; SolidMaskRect.y -= 2 * C4LS_MaxLightDistY;
2885 SolidMaskRect.Wdt += 4 * C4LS_MaxLightDistX; SolidMaskRect.Hgt += 4 * C4LS_MaxLightDistY;
2886 for (C4SolidMask *pSolid = C4SolidMask::First; pSolid; pSolid = pSolid->Next)
2887 {
2888 pSolid->Repair(where: SolidMaskRect);
2889 }
2890 if (updateMatAndPixCnt) UpdatePixCnt(Rect: BoundingBox);
2891 C4SolidMask::CheckConsistency();
2892}
2893
2894void C4Landscape::UpdatePixCnt(const C4Rect &Rect, bool fCheck)
2895{
2896 int32_t PixCntWidth = (Width + 16) / 17;
2897 for (int32_t y = std::max<int32_t>(a: 0, b: Rect.y / 15); y < std::min<int32_t>(a: PixCntPitch, b: (Rect.y + Rect.Hgt + 14) / 15); y++)
2898 for (int32_t x = std::max<int32_t>(a: 0, b: Rect.x / 17); x < std::min<int32_t>(a: PixCntWidth, b: (Rect.x + Rect.Wdt + 16) / 17); x++)
2899 {
2900 int iCnt = 0;
2901 for (int32_t x2 = x * 17; x2 < std::min<int32_t>(a: x * 17 + 17, b: Width); x2++)
2902 for (int32_t y2 = y * 15; y2 < std::min<int32_t>(a: y * 15 + 15, b: Height); y2++)
2903 if (_GetDensity(x: x2, y: y2))
2904 iCnt++;
2905 if (fCheck)
2906 assert(iCnt == PixCnt[x * PixCntPitch + y]);
2907 PixCnt[x * PixCntPitch + y] = iCnt;
2908 }
2909}
2910
2911void C4Landscape::UpdateMatCnt(C4Rect Rect, bool fPlus)
2912{
2913 Rect.Intersect(r2: C4Rect(0, 0, Width, Height));
2914 if (!Rect.Hgt || !Rect.Wdt) return;
2915 // Multiplicator for changes
2916 const int32_t iMul = fPlus ? +1 : -1;
2917 // Count pixels
2918 for (int32_t x = 0; x < Rect.Wdt; x++)
2919 {
2920 int iHgt = 0;
2921 int32_t y;
2922 for (y = 1; y < Rect.Hgt; y++)
2923 {
2924 int32_t iMat = _GetMat(x: Rect.x + x, y: Rect.y + y - 1);
2925 // Same material? Count it.
2926 if (iMat == _GetMat(x: Rect.x + x, y: Rect.y + y))
2927 iHgt++;
2928 else
2929 {
2930 if (iMat >= 0)
2931 {
2932 // Normal material counting
2933 MatCount[iMat] += iMul * (iHgt + 1);
2934 // Effective material counting enabled?
2935 if (int32_t iMinHgt = Game.Material.Map[iMat].MinHeightCount)
2936 {
2937 // First chunk? Add any material above when checking chunk height
2938 int iAddedHeight = 0;
2939 if (Rect.y && iHgt + 1 == y)
2940 iAddedHeight = GetMatHeight(x: Rect.x + x, y: Rect.y - 1, iYDir: -1, iMat, iMax: iMinHgt);
2941 // Check the chunk height
2942 if (iHgt + 1 + iAddedHeight >= iMinHgt)
2943 {
2944 EffectiveMatCount[iMat] += iMul * (iHgt + 1);
2945 if (iAddedHeight < iMinHgt)
2946 EffectiveMatCount[iMat] += iMul * iAddedHeight;
2947 }
2948 }
2949 }
2950 // Next chunk of material
2951 iHgt = 0;
2952 }
2953 }
2954 // Check last pixel
2955 int32_t iMat = _GetMat(x: Rect.x + x, y: Rect.y + Rect.Hgt - 1);
2956 if (iMat >= 0)
2957 {
2958 // Normal material counting
2959 MatCount[iMat] += iMul * (iHgt + 1);
2960 // Minimum height counting?
2961 if (int32_t iMinHgt = Game.Material.Map[iMat].MinHeightCount)
2962 {
2963 int iAddedHeight1 = 0, iAddedHeight2 = 0;
2964 // Add any material above for chunk size check
2965 if (Rect.y && iHgt + 1 == Rect.Hgt)
2966 iAddedHeight1 = GetMatHeight(x: Rect.x + x, y: Rect.y - 1, iYDir: -1, iMat, iMax: iMinHgt);
2967 // Add any material below for chunk size check
2968 if (Rect.y + y < Height)
2969 iAddedHeight2 = GetMatHeight(x: Rect.x + x, y: Rect.y + Rect.Hgt, iYDir: 1, iMat, iMax: iMinHgt);
2970 // Chunk tall enough?
2971 if (iHgt + 1 + iAddedHeight1 + iAddedHeight2 >= Game.Material.Map[iMat].MinHeightCount)
2972 {
2973 EffectiveMatCount[iMat] += iMul * (iHgt + 1);
2974 if (iAddedHeight1 < iMinHgt)
2975 EffectiveMatCount[iMat] += iMul * iAddedHeight1;
2976 if (iAddedHeight2 < iMinHgt)
2977 EffectiveMatCount[iMat] += iMul * iAddedHeight2;
2978 }
2979 }
2980 }
2981 }
2982}
2983
2984void C4Landscape::CompileFunc(StdCompiler *pComp)
2985{
2986 pComp->Value(rStruct: mkNamingAdapt(rValue&: MapSeed, szName: "MapSeed", rDefault: 0));
2987 pComp->Value(rStruct: mkNamingAdapt(rValue&: LeftOpen, szName: "LeftOpen", rDefault: 0));
2988 pComp->Value(rStruct: mkNamingAdapt(rValue&: RightOpen, szName: "RightOpen", rDefault: 0));
2989 pComp->Value(rStruct: mkNamingAdapt(rValue&: TopOpen, szName: "TopOpen", rDefault: false));
2990 pComp->Value(rStruct: mkNamingAdapt(rValue&: BottomOpen, szName: "BottomOpen", rDefault: false));
2991 pComp->Value(rStruct: mkNamingAdapt(rValue: mkCastIntAdapt(rValue&: Gravity), szName: "Gravity", rDefault: FIXED100(x: 20)));
2992 pComp->Value(rStruct: mkNamingAdapt(rValue&: Modulation, szName: "MatModulation", rDefault: 0U));
2993 pComp->Value(rStruct: mkNamingAdapt(rValue&: Mode, szName: "Mode", rDefault: C4LSC_Undefined));
2994}
2995
2996void C4Landscape::RemoveUnusedTexMapEntries()
2997{
2998 // check usage in landscape
2999 bool fTexUsage[128];
3000 int32_t iMatTex;
3001 for (iMatTex = 0; iMatTex < 128; ++iMatTex) fTexUsage[iMatTex] = false;
3002 for (int32_t y = 0; y < Height; ++y)
3003 for (int32_t x = 0; x < Width; ++x)
3004 fTexUsage[Surface8->GetPix(iX: x, iY: y) & 0x7f] = true;
3005 // check usage by materials
3006 for (int32_t iMat = 0; iMat < Game.Material.Num; ++iMat)
3007 {
3008 C4Material *pMat = Game.Material.Map + iMat;
3009 if (pMat->BlastShiftTo >= 0) fTexUsage[pMat->BlastShiftTo & 0x7f] = true;
3010 if (pMat->BelowTempConvertTo >= 0) fTexUsage[pMat->BelowTempConvertTo & 0x7f] = true;
3011 if (pMat->AboveTempConvertTo >= 0) fTexUsage[pMat->AboveTempConvertTo & 0x7f] = true;
3012 if (pMat->DefaultMatTex >= 0) fTexUsage[pMat->DefaultMatTex & 0x7f] = true;
3013 }
3014 // remove unused
3015 for (iMatTex = 1; iMatTex < C4M_MaxTexIndex; ++iMatTex)
3016 if (!fTexUsage[iMatTex])
3017 Game.TextureMap.RemoveEntry(iIndex: iMatTex);
3018 // flag rewrite
3019 Game.TextureMap.fEntriesAdded = true;
3020}
3021