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/* Pixel Sprite system for tiny bits of moving material */
18
19#include <C4Include.h>
20#include <C4PXS.h>
21
22#include <C4Physics.h>
23#include <C4Random.h>
24#include <C4Wrappers.h>
25
26static const C4Fixed WindDrift_Factor = itofix(x: 1, prec: 800);
27
28void C4PXS::Execute()
29{
30#ifdef DEBUGREC_PXS
31 {
32 C4RCExecPXS rc;
33 rc.x = x; rc.y = y; rc.iMat = Mat;
34 rc.pos = 0;
35 AddDbgRec(RCT_ExecPXS, &rc, sizeof(rc));
36 }
37#endif
38 int32_t inmat;
39
40 // Safety
41 if (!MatValid(mat: Mat))
42 {
43 Deactivate(); return;
44 }
45
46 // Out of bounds
47 if ((x < 0) || (x >= GBackWdt) || (y < -10) || (y >= GBackHgt))
48 {
49 Deactivate(); return;
50 }
51
52 // Material conversion
53 int32_t iX = fixtoi(x), iY = fixtoi(x: y);
54 inmat = GBackMat(x: iX, y: iY);
55 C4MaterialReaction *pReact = Game.Material.GetReactionUnsafe(iPXSMat: Mat, iLandscapeMat: inmat);
56 if (pReact && (*pReact->pFunc)(pReact, iX, iY, iX, iY, xdir, ydir, Mat, inmat, meePXSPos, nullptr))
57 {
58 Deactivate(); return;
59 }
60
61 // Gravity
62 ydir += GravAccel;
63
64 if (GBackDensity(x: iX, y: iY + 1) < Game.Material.Map[Mat].Density)
65 {
66 // Air speed: Wind plus some random
67 int32_t iWind = GBackWind(x: iX, y: iY);
68 C4Fixed txdir = itofix(x: iWind, prec: 15) + FIXED256(x: Random(iRange: 1200) - 600);
69 C4Fixed tydir = FIXED256(x: Random(iRange: 1200) - 600);
70
71 // Air friction, based on WindDrift. MaxSpeed is ignored.
72 int32_t iWindDrift = (std::max)(a: Game.Material.Map[Mat].WindDrift - 20, b: 0);
73 xdir += ((txdir - xdir) * iWindDrift) * WindDrift_Factor;
74 ydir += ((tydir - ydir) * iWindDrift) * WindDrift_Factor;
75 }
76
77 C4Fixed ctcox = x + xdir;
78 C4Fixed ctcoy = y + ydir;
79
80 int32_t iToX = fixtoi(x: ctcox), iToY = fixtoi(x: ctcoy);
81
82 // In bounds?
83 if (Inside<int32_t>(ival: iToX, lbound: 0, GBackWdt - 1) && Inside<int32_t>(ival: iToY, lbound: 0, GBackHgt - 1))
84 // Check path
85 if (Game.Landscape._PathFree(x: iX, y: iY, x2: iToX, y2: iToY))
86 {
87 x = ctcox; y = ctcoy;
88 return;
89 }
90
91 // Test path to target position
92 bool fStopMovement = false;
93 do
94 {
95 // Step
96 int32_t inX = iX + Sign(val: iToX - iX), inY = iY + Sign(val: iToY - iY);
97 // Contact?
98 inmat = GBackMat(x: inX, y: inY);
99 C4MaterialReaction *pReact = Game.Material.GetReactionUnsafe(iPXSMat: Mat, iLandscapeMat: inmat);
100 if (pReact)
101 if ((*pReact->pFunc)(pReact, iX, iY, inX, inY, xdir, ydir, Mat, inmat, meePXSMove, &fStopMovement))
102 {
103 // destructive contact
104 Deactivate();
105 return;
106 }
107 else
108 {
109 // no destructive contact, but speed or position changed: Stop moving for now
110 if (fStopMovement)
111 {
112 x = itofix(x: iX); y = itofix(x: iY);
113 return;
114 }
115 // there was a reaction func, but it didn't do anything - continue movement
116 }
117 iX = inX; iY = inY;
118 } while (iX != iToX || iY != iToY);
119
120 // No contact? Free movement
121 x = ctcox; y = ctcoy;
122#ifdef DEBUGREC_PXS
123 {
124 C4RCExecPXS rc;
125 rc.x = x; rc.y = y; rc.iMat = Mat;
126 rc.pos = 1;
127 AddDbgRec(RCT_ExecPXS, &rc, sizeof(rc));
128 }
129#endif
130 return;
131}
132
133void C4PXS::Deactivate()
134{
135#ifdef DEBUGREC_PXS
136 C4RCExecPXS rc;
137 rc.x = x; rc.y = y; rc.iMat = Mat;
138 rc.pos = 2;
139 AddDbgRec(RCT_ExecPXS, &rc, sizeof(rc));
140#endif
141 Mat = MNone;
142 Game.PXS.Delete(pPXS: this);
143}
144
145C4PXSSystem::C4PXSSystem()
146{
147 Default();
148}
149
150C4PXSSystem::~C4PXSSystem()
151{
152 Clear();
153}
154
155void C4PXSSystem::Default()
156{
157 Count = 0;
158 for (unsigned int cnt = 0; cnt < PXSMaxChunk; cnt++)
159 {
160 Chunk[cnt] = nullptr;
161 iChunkPXS[cnt] = 0;
162 }
163}
164
165void C4PXSSystem::Clear()
166{
167 for (unsigned int cnt = 0; cnt < PXSMaxChunk; cnt++)
168 {
169 delete[] Chunk[cnt];
170 Chunk[cnt] = nullptr;
171 iChunkPXS[cnt] = 0;
172 }
173}
174
175C4PXS *C4PXSSystem::New()
176{
177 unsigned int cnt, cnt2;
178 C4PXS *pxp;
179 // Check chunks for available space
180 for (cnt = 0; cnt < PXSMaxChunk; cnt++)
181 {
182 // Create new chunk if necessary
183 if (!Chunk[cnt])
184 {
185 Chunk[cnt] = new C4PXS[PXSChunkSize];
186 iChunkPXS[cnt] = 0;
187 }
188 // Check this chunk for space
189 if (iChunkPXS[cnt] < PXSChunkSize)
190 for (cnt2 = 0, pxp = Chunk[cnt]; cnt2 < PXSChunkSize; cnt2++, pxp++)
191 if (pxp->Mat == MNone)
192 {
193 // count theam
194 iChunkPXS[cnt]++;
195 return pxp;
196 }
197 }
198 return nullptr;
199}
200
201bool C4PXSSystem::Create(int32_t mat, C4Fixed ix, C4Fixed iy, C4Fixed ixdir, C4Fixed iydir)
202{
203 C4PXS *pxp;
204 if (!MatValid(mat)) return false;
205 if (!(pxp = New())) return false;
206 pxp->Mat = mat;
207 pxp->x = ix; pxp->y = iy;
208 pxp->xdir = ixdir; pxp->ydir = iydir;
209 return true;
210}
211
212void C4PXSSystem::Execute()
213{
214 // Execute all chunks
215 Count = 0;
216 for (unsigned int cchunk = 0; cchunk < PXSMaxChunk; cchunk++)
217 if (Chunk[cchunk])
218 // empty chunk?
219 if (!iChunkPXS[cchunk])
220 {
221 delete[] Chunk[cchunk]; Chunk[cchunk] = nullptr;
222 }
223 else
224 {
225 // Execute chunk pxs, check for empty
226 C4PXS *pxp = Chunk[cchunk];
227 for (unsigned int cnt2 = 0; cnt2 < PXSChunkSize; cnt2++, pxp++)
228 if (pxp->Mat != MNone)
229 {
230 pxp->Execute();
231 Count++;
232 }
233 }
234}
235
236void C4PXSSystem::Draw(C4FacetEx &cgo)
237{
238 // Draw PXS in this region
239 C4Rect VisibleRect(cgo.TargetX, cgo.TargetY, cgo.Wdt, cgo.Hgt);
240 VisibleRect.Enlarge(iBy: 20);
241
242 // First pass: draw old-style PXS (lines/pixels)
243 int32_t cgox = cgo.X - cgo.TargetX, cgoy = cgo.Y - cgo.TargetY;
244 unsigned int cnt;
245 for (cnt = 0; cnt < PXSMaxChunk; cnt++)
246 if (Chunk[cnt] && iChunkPXS[cnt])
247 {
248 C4PXS *pxp = Chunk[cnt];
249 for (unsigned int cnt2 = 0; cnt2 < PXSChunkSize; cnt2++, pxp++)
250 if (pxp->Mat != MNone && VisibleRect.Contains(iX: fixtoi(x: pxp->x), iY: fixtoi(x: pxp->y)))
251 {
252 C4Material *pMat = &Game.Material.Map[pxp->Mat];
253 if (pMat->PXSFace.Surface && Config.Graphics.PXSGfx)
254 continue;
255 // old-style: unicolored pixels or lines
256 uint32_t dwMatClr = Game.Landscape.GetPal()->GetClr(byCol: Mat2PixColDefault(mat: pxp->Mat));
257 if (fixtoi(x: pxp->xdir) || fixtoi(x: pxp->ydir))
258 {
259 // lines for stuff that goes whooosh!
260 int len = fixtoi(x: Abs(val: pxp->xdir) + Abs(val: pxp->ydir));
261 dwMatClr = uint32_t(std::max<int>(a: dwMatClr >> 24, b: 195 - (195 - (dwMatClr >> 24)) / len)) << 24 | (dwMatClr & 0xffffff);
262 Application.DDraw->DrawLineDw(sfcTarget: cgo.Surface,
263 x1: fixtof(x: pxp->x - pxp->xdir) + cgox, y1: fixtof(x: pxp->y - pxp->ydir) + cgoy,
264 x2: fixtof(x: pxp->x) + cgox, y2: fixtof(x: pxp->y) + cgoy,
265 dwClr: dwMatClr);
266 }
267 else
268 // single pixels for slow stuff
269 Application.DDraw->DrawPix(sfcDest: cgo.Surface, tx: fixtof(x: pxp->x) + cgox, ty: fixtof(x: pxp->y) + cgoy, dwCol: dwMatClr);
270 }
271 }
272
273 // PXS graphics disabled?
274 if (!Config.Graphics.PXSGfx)
275 return;
276
277 // Second pass: draw new-style PXS (graphics)
278 for (cnt = 0; cnt < PXSMaxChunk; cnt++)
279 if (Chunk[cnt] && iChunkPXS[cnt])
280 {
281 C4PXS *pxp = Chunk[cnt];
282 for (unsigned int cnt2 = 0; cnt2 < PXSChunkSize; cnt2++, pxp++)
283 if (pxp->Mat != MNone && VisibleRect.Contains(iX: fixtoi(x: pxp->x), iY: fixtoi(x: pxp->y)))
284 {
285 C4Material *pMat = &Game.Material.Map[pxp->Mat];
286 if (!pMat->PXSFace.Surface)
287 continue;
288 // new-style: graphics
289 int32_t pnx, pny;
290 pMat->PXSFace.GetPhaseNum(rX&: pnx, rY&: pny);
291 int32_t fcWdt = pMat->PXSFace.Wdt; int32_t fcWdtH = (std::max)(a: fcWdt / 3, b: 1);
292 // calculate draw width and tile to use (random-ish)
293 int32_t z = 1 + ((cnt2 / std::max<int32_t>(a: pnx * pny, b: 1)) ^ 341) % pMat->PXSGfxSize;
294 pny = (cnt2 / pnx) % pny; pnx = cnt2 % pnx;
295 // draw
296 Application.DDraw->ActivateBlitModulation(dwWithClr: (std::min)(a: (fcWdtH - z) * 16, b: 255) << 24 | 0xffffff);
297 pMat->PXSFace.DrawX(sfcTarget: cgo.Surface, iX: fixtoi(x: pxp->x) + cgox + z * pMat->PXSGfxRt.tx / fcWdt, iY: fixtoi(x: pxp->y) + cgoy + z * pMat->PXSGfxRt.ty / fcWdt, iWdt: z, iHgt: z * pMat->PXSFace.Hgt / fcWdt, iPhaseX: pnx, iPhaseY: pny);
298 Application.DDraw->DeactivateBlitModulation();
299 }
300 }
301}
302
303void C4PXSSystem::Cast(int32_t mat, int32_t num, int32_t tx, int32_t ty, int32_t level)
304{
305 int32_t cnt;
306 for (cnt = 0; cnt < num; cnt++)
307 {
308 // force argument evaluation order
309 const auto r2 = Random(iRange: level + 1);
310 const auto r1 = Random(iRange: level + 1);
311 Create(mat,
312 ix: itofix(x: tx), iy: itofix(x: ty),
313 ixdir: itofix(x: r1 - level / 2) / 10,
314 iydir: itofix(x: r2 - level) / 10);
315 }
316}
317
318bool C4PXSSystem::Save(C4Group &hGroup)
319{
320 unsigned int cnt;
321
322 // Check used chunk count
323 int32_t iChunks = 0;
324 for (cnt = 0; cnt < PXSMaxChunk; cnt++)
325 if (Chunk[cnt] && iChunkPXS[cnt])
326 iChunks++;
327 if (!iChunks)
328 {
329 hGroup.Delete(C4CFN_PXS);
330 return true;
331 }
332
333 // Save chunks to temp file
334 CStdFile hTempFile;
335 if (!hTempFile.Create(szFileName: Config.AtTempPath(C4CFN_TempPXS)))
336 return false;
337 int32_t iNumFormat = 1;
338 if (!hTempFile.Write(pBuffer: &iNumFormat, iSize: sizeof(iNumFormat)))
339 return false;
340 for (cnt = 0; cnt < PXSMaxChunk; cnt++)
341 if (Chunk[cnt]) // must save all chunks in order to keep order consistent on all clients
342 if (!hTempFile.Write(pBuffer: Chunk[cnt], iSize: PXSChunkSize * sizeof(C4PXS)))
343 return false;
344
345 if (!hTempFile.Close())
346 return false;
347
348 // Move temp file to group
349 if (!hGroup.Move(szFile: Config.AtTempPath(C4CFN_TempPXS),
350 C4CFN_PXS))
351 return false;
352
353 return true;
354}
355
356bool C4PXSSystem::Load(C4Group &hGroup)
357{
358 // load new
359 size_t iBinSize, iChunkNum, cnt2;
360 size_t iChunkSize = PXSChunkSize * sizeof(C4PXS);
361 if (!hGroup.AccessEntry(C4CFN_PXS, iSize: &iBinSize)) return false;
362 // clear previous
363 Clear();
364 // using C4Fixed or float?
365 int32_t iNumForm = 1;
366 if (iBinSize % iChunkSize == 4)
367 {
368 if (!hGroup.Read(pBuffer: &iNumForm, iSize: sizeof(iNumForm))) return false;
369 if (!Inside<int32_t>(ival: iNumForm, lbound: 1, rbound: 2)) return false;
370 iBinSize -= 4;
371 }
372 // old pxs-files have no tag for the number format
373 else if (iBinSize % iChunkSize != 0) return false;
374 // calc chunk count
375 iChunkNum = iBinSize / iChunkSize;
376 if (iChunkNum > PXSMaxChunk) return false;
377 for (uint32_t cnt = 0; cnt < iChunkNum; cnt++)
378 {
379 Chunk[cnt] = new C4PXS[PXSChunkSize];
380 if (!hGroup.Read(pBuffer: Chunk[cnt], iSize: iChunkSize)) return false;
381 // count the PXS, Peter!
382 // convert num format, if neccessary
383 C4PXS *pxp; iChunkPXS[cnt] = 0;
384 for (cnt2 = 0, pxp = Chunk[cnt]; cnt2 < PXSChunkSize; cnt2++, pxp++)
385 if (pxp->Mat != MNone)
386 {
387 ++iChunkPXS[cnt];
388 // convert number format
389 if (iNumForm == 2) { FLOAT_TO_FIXED(pVal: &pxp->x); FLOAT_TO_FIXED(pVal: &pxp->y); FLOAT_TO_FIXED(pVal: &pxp->xdir); FLOAT_TO_FIXED(pVal: &pxp->ydir); }
390 }
391 }
392 return true;
393}
394
395void C4PXSSystem::Synchronize()
396{
397 Count = 0;
398}
399
400void C4PXSSystem::SyncClearance()
401{
402 // consolidate chunks; remove empty chunks
403 C4PXS **pDestChunk = Chunk;
404 int32_t iDestChunk = 0;
405 for (unsigned int cnt = 0; cnt < PXSMaxChunk; cnt++)
406 {
407 if (Chunk[cnt] && iChunkPXS[cnt])
408 {
409 *pDestChunk++ = Chunk[cnt];
410 iChunkPXS[iDestChunk++] = iChunkPXS[cnt];
411 }
412 else
413 {
414 delete[] Chunk[cnt];
415 Chunk[cnt] = nullptr;
416 }
417 }
418}
419
420void C4PXSSystem::Delete(C4PXS *pPXS)
421{
422 // find chunk
423 unsigned int cnt;
424 for (cnt = 0; cnt < PXSMaxChunk; cnt++)
425 if (Chunk[cnt] && iChunkPXS[cnt])
426 if (pPXS >= Chunk[cnt] && pPXS < Chunk[cnt] + PXSChunkSize)
427 break;
428 // decrease pxs counter
429 if (cnt < PXSMaxChunk)
430 iChunkPXS[cnt]--;
431}
432