1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2001, Sven2
6 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// newgfx particle system for smoke, sparks, ...
19
20#include <C4Include.h>
21#include <C4Particles.h>
22
23#include <C4Physics.h>
24#include <C4Object.h>
25#include <C4Random.h>
26#include <C4Game.h>
27#include <C4Components.h>
28#include <C4Wrappers.h>
29
30void C4ParticleDefCore::CompileFunc(StdCompiler *pComp)
31{
32 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(Name), szName: "Name", rDefault: ""));
33 pComp->Value(rStruct: mkNamingAdapt(rValue&: MaxCount, szName: "MaxCount", rDefault: C4Px_MaxParticle));
34 pComp->Value(rStruct: mkNamingAdapt(rValue&: MinLifetime, szName: "MinLifetime", rDefault: 0));
35 pComp->Value(rStruct: mkNamingAdapt(rValue&: MaxLifetime, szName: "MaxLifetime", rDefault: 0));
36 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(InitFn), szName: "InitFn", rDefault: ""));
37 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(ExecFn), szName: "ExecFn", rDefault: ""));
38 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(CollisionFn), szName: "CollisionFn", rDefault: ""));
39 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(DrawFn), szName: "DrawFn", rDefault: ""));
40 pComp->Value(rStruct: mkNamingAdapt(rValue&: GfxFace, szName: "Face"));
41 pComp->Value(rStruct: mkNamingAdapt(rValue&: YOff, szName: "YOff", rDefault: 0));
42 pComp->Value(rStruct: mkNamingAdapt(rValue&: Delay, szName: "Delay", rDefault: 0));
43 pComp->Value(rStruct: mkNamingAdapt(rValue&: Repeats, szName: "Repeats", rDefault: 0));
44 pComp->Value(rStruct: mkNamingAdapt(rValue&: Reverse, szName: "Reverse", rDefault: 0));
45 pComp->Value(rStruct: mkNamingAdapt(rValue&: FadeOutLen, szName: "FadeOutLen", rDefault: 0));
46 pComp->Value(rStruct: mkNamingAdapt(rValue&: FadeOutDelay, szName: "FadeOutDelay", rDefault: 0));
47 pComp->Value(rStruct: mkNamingAdapt(rValue&: RByV, szName: "RByV", rDefault: 0));
48 pComp->Value(rStruct: mkNamingAdapt(rValue&: GravityAcc, szName: "GravityAcc", rDefault: 0));
49 pComp->Value(rStruct: mkNamingAdapt(rValue&: WindDrift, szName: "WindDrift", rDefault: 0));
50 pComp->Value(rStruct: mkNamingAdapt(rValue&: VertexCount, szName: "VertexCount", rDefault: 0));
51 pComp->Value(rStruct: mkNamingAdapt(rValue&: VertexY, szName: "VertexY", rDefault: 0));
52 pComp->Value(rStruct: mkNamingAdapt(rValue&: Additive, szName: "Additive", rDefault: 0));
53 pComp->Value(rStruct: mkNamingAdapt(rValue&: AlphaFade, szName: "AlphaFade", rDefault: 0));
54 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: Parallaxity, default_: 100), szName: "Parallaxity"));
55 pComp->Value(rStruct: mkNamingAdapt(rValue&: Attach, szName: "Attach", rDefault: 0));
56}
57
58C4ParticleDefCore::C4ParticleDefCore() :
59 MaxCount(C4Px_MaxParticle),
60 MinLifetime(0), MaxLifetime(0),
61 YOff(0),
62 Reverse(0), Repeats(0), Delay(0),
63 FadeOutLen(0), FadeOutDelay(0),
64 RByV(0),
65 Placement(0),
66 GravityAcc(0),
67 VertexCount(0), VertexY(0),
68 Additive(0),
69 Attach(0),
70 AlphaFade(0)
71{
72 GfxFace.Default();
73 Parallaxity[0] = Parallaxity[1] = 100;
74}
75
76bool C4ParticleDefCore::Compile(const char *szSource, const char *szName)
77{
78 return CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct: mkNamingAdapt(rValue&: *this, szName: "Particle"),
79 SrcBuf: StdStrBuf::MakeRef(str: szSource), szName);
80}
81
82C4ParticleDef::C4ParticleDef() :
83 C4ParticleDefCore(),
84 InitProc(&fxStdInit),
85 ExecProc(&fxStdExec),
86 DrawProc(&fxStdDraw),
87 CollisionProc(nullptr),
88 Count(0)
89{
90 // zero fields
91 Gfx.Default();
92 // link into list
93 if (!ParticleSystem.pDef0)
94 {
95 pPrev = nullptr;
96 ParticleSystem.pDef0 = this;
97 }
98 else
99 (pPrev = ParticleSystem.pDefL)->pNext = this;
100 ParticleSystem.pDefL = this;
101 pNext = nullptr;
102}
103
104C4ParticleDef::~C4ParticleDef()
105{
106 // clear
107 Clear();
108 // unlink from list
109 if (pPrev) pPrev->pNext = pNext; else ParticleSystem.pDef0 = pNext;
110 if (pNext) pNext->pPrev = pPrev; else ParticleSystem.pDefL = pPrev;
111}
112
113void C4ParticleDef::Clear()
114{
115 Name.Clear();
116}
117
118bool C4ParticleDef::Load(C4Group &rGrp)
119{
120 // store file
121 Filename.Copy(Buf2: rGrp.GetFullName());
122 // load
123 char *pSource;
124 if (rGrp.LoadEntry(C4CFN_ParticleCore, lpbpBuf: &pSource, ipSize: nullptr, iAppendZeros: 1))
125 {
126 if (!Compile(szSource: pSource, szName: Filename.getData()))
127 {
128 DebugLog(level: spdlog::level::err, fmt: "invalid particle def at '{}'", args: rGrp.GetFullName().getData());
129 delete[] pSource; return false;
130 }
131 delete[] pSource;
132 // load graphics
133 if (!Gfx.Load(hGroup&: rGrp, C4CFN_DefGraphicsPNG))
134 {
135 DebugLog(level: spdlog::level::err, fmt: "particle {} has no valid graphics defined", args: Name.getData());
136 return false;
137 }
138 // set facet, if assigned - otherwise, assume full surface
139 if (GfxFace.Wdt) Gfx.Set(nsfc: Gfx.Surface, nx: GfxFace.x, ny: GfxFace.y, nwdt: GfxFace.Wdt, nhgt: GfxFace.Hgt, ntx: GfxFace.tx, nty: GfxFace.ty);
140 // set phase num
141 int32_t Q; Gfx.GetPhaseNum(rX&: Length, rY&: Q);
142 if (!Length)
143 {
144 DebugLog(level: spdlog::level::err, fmt: "invalid facet for particle '{}'", args: Name.getData());
145 return false;
146 }
147 // case fadeout from length
148 if (FadeOutLen)
149 {
150 Length = std::max<int32_t>(a: Length - FadeOutLen, b: 1);
151 if (!FadeOutDelay) FadeOutDelay = 1;
152 }
153 // if phase num is 1, no reverse is allowed
154 if (Length == 1) Reverse = 0;
155 // calc aspect
156 Aspect = static_cast<float>(Gfx.Wdt) / Gfx.Hgt;
157 // get proc pointers
158 if (!(InitProc = ParticleSystem.GetProc(szName: InitFn.getData())))
159 {
160 DebugLog(level: spdlog::level::err, fmt: "init proc for particle '{}' not found: '{}'", args: Name.getData(), args: InitFn.getData());
161 return false;
162 }
163 if (!(ExecProc = ParticleSystem.GetProc(szName: ExecFn.getData())))
164 {
165 DebugLog(level: spdlog::level::err, fmt: "exec proc for particle '{}' not found: '{}'", args: Name.getData(), args: ExecFn.getData());
166 return false;
167 }
168 if (CollisionFn && CollisionFn[0]) if (!(CollisionProc = ParticleSystem.GetProc(szName: CollisionFn.getData())))
169 {
170 DebugLog(level: spdlog::level::err, fmt: "collision proc for particle '{}' not found: '{}'", args: Name.getData(), args: CollisionFn.getData());
171 return false;
172 }
173 if (!(DrawProc = ParticleSystem.GetDrawProc(szName: DrawFn.getData())))
174 {
175 DebugLog(level: spdlog::level::err, fmt: "draw proc for particle '{}' not found: '{}'", args: Name.getData(), args: DrawFn.getData());
176 return false;
177 }
178 // particle overloading
179 C4ParticleDef *pDefOverload;
180 if (pDefOverload = ParticleSystem.GetDef(szName: Name.getData(), pExclude: this))
181 {
182 if (Config.Graphics.VerboseObjectLoading >= 1)
183 {
184 Log(id: C4ResStrTableKey::IDS_PRC_DEFOVERLOAD, args: pDefOverload->Name.getData(), args: "<particle>");
185 }
186 delete pDefOverload;
187 }
188 // success
189 return true;
190 }
191 return false;
192}
193
194bool C4ParticleDef::Reload()
195{
196 // no file?
197 if (!Filename[0]) return false;
198 // open group
199 C4Group hGroup;
200 if (!hGroup.Open(szGroupName: Filename.getData())) return false;
201 // reset class
202 Clear();
203 // load
204 return Load(rGrp&: hGroup);
205}
206
207void C4Particle::MoveList(C4ParticleList &rFrom, C4ParticleList &rTo)
208{
209 // remove from current list
210 if (pPrev)
211 pPrev->pNext = pNext;
212 else
213 // is it the first item in the list? then set this to the next one
214 if (rFrom.pFirst == this) rFrom.pFirst = pNext;
215 if (pNext) pNext->pPrev = pPrev;
216 // add to the other list - insert before first
217 if (pNext = rTo.pFirst) pNext->pPrev = this;
218 rTo.pFirst = this; pPrev = nullptr;
219}
220
221C4ParticleChunk::C4ParticleChunk()
222{
223 // zero linked list
224 pNext = nullptr;
225 // zero buffer
226 Clear();
227}
228
229C4ParticleChunk::~C4ParticleChunk()
230{
231 // list stuff done by C4ParticleSystem
232}
233
234void C4ParticleChunk::Clear()
235{
236 // note that this method is called in ctor with uninitialized data!
237 // simply clear mem - this won't adjust any counts!
238 std::memset(s: Data, c: 0, n: sizeof(Data));
239 // init list
240 C4Particle *pPrt = Data;
241 for (int32_t i = 0; i < C4Px_BufSize; ++i)
242 {
243 pPrt->pPrev = pPrt - 1;
244 pPrt->pNext = pPrt + 1;
245 ++pPrt;
246 }
247 Data[0].pPrev = Data[C4Px_BufSize - 1].pNext = nullptr;
248}
249
250void C4ParticleList::Exec(C4Object *pObj)
251{
252 // execute all particles
253 C4Particle *pPrtNext = pFirst, *pPrt;
254 while (pPrt = pPrtNext)
255 {
256 // get next now, because destruction could corrupt the list
257 pPrtNext = pPrt->pNext;
258 // execute it
259 if (!pPrt->pDef->ExecProc(pPrt, pObj))
260 {
261 // sorry, life is over for you :P
262 --pPrt->pDef->Count;
263 pPrt->MoveList(rFrom&: *this, rTo&: Game.Particles.FreeParticles);
264 }
265 }
266 // done
267}
268
269void C4ParticleList::Draw(C4FacetEx &cgo, C4Object *pObj)
270{
271 // draw all particles
272 for (C4Particle *pPrt = pFirst; pPrt; pPrt = pPrt->pNext)
273 pPrt->pDef->DrawProc(pPrt, cgo, pObj);
274 // done
275}
276
277void C4ParticleList::Clear()
278{
279 // remove all particles
280 C4Particle *pPrtNext = pFirst, *pPrt;
281 while (pPrt = pPrtNext)
282 {
283 // get next now, because destruction could corrupt the list
284 pPrtNext = pPrt->pNext;
285 // sorry, life is over for you :P
286 --pPrt->pDef->Count;
287 pPrt->MoveList(rFrom&: *this, rTo&: Game.Particles.FreeParticles);
288 }
289}
290
291int32_t C4ParticleList::Remove(C4ParticleDef *pOfDef)
292{
293 int32_t iNumRemoved = 0;
294 // check all particles for def
295 C4Particle *pPrtNext = pFirst, *pPrt;
296 while (pPrt = pPrtNext)
297 {
298 // get next now, because destruction could corrupt the list
299 pPrtNext = pPrt->pNext;
300 // execute it
301 if (!pOfDef || pPrt->pDef == pOfDef)
302 {
303 // sorry, life is over for you :P
304 --pPrt->pDef->Count;
305 pPrt->MoveList(rFrom&: *this, rTo&: Game.Particles.FreeParticles);
306 }
307 }
308 // done
309 return iNumRemoved;
310}
311
312C4ParticleSystem::C4ParticleSystem()
313{
314 // zero fields
315 pDef0 = pDefL = nullptr;
316 pSmoke = nullptr;
317 pBlast = nullptr;
318 pFSpark = nullptr;
319 pFire1 = nullptr;
320 pFire2 = nullptr;
321}
322
323C4ParticleSystem::~C4ParticleSystem()
324{
325 // clean up
326 Clear();
327}
328
329C4ParticleChunk *C4ParticleSystem::AddChunk()
330{
331 // add another chunk
332 C4ParticleChunk *pNewChnk = new C4ParticleChunk();
333 pNewChnk->pNext = Chunk.pNext;
334 Chunk.pNext = pNewChnk;
335 // register into free-particle-list
336 if (pNewChnk->Data[C4Px_BufSize - 1].pNext = FreeParticles.pFirst)
337 FreeParticles.pFirst->pPrev = &pNewChnk->Data[C4Px_BufSize - 1];
338 FreeParticles.pFirst = &pNewChnk->Data[0];
339 // return it
340 return pNewChnk;
341}
342
343void C4ParticleSystem::ClearParticles()
344{
345 // clear particle lists
346 C4ObjectLink *pLnk;
347 for (pLnk = Game.Objects.First; pLnk; pLnk = pLnk->Next)
348 pLnk->Obj->FrontParticles.pFirst = pLnk->Obj->BackParticles.pFirst = nullptr;
349 for (pLnk = Game.Objects.InactiveObjects.First; pLnk; pLnk = pLnk->Next)
350 pLnk->Obj->FrontParticles.pFirst = pLnk->Obj->BackParticles.pFirst = nullptr;
351 GlobalParticles.pFirst = nullptr;
352 // reset chunks
353 C4ParticleChunk *pNextChnk = Chunk.pNext, *pChnk;
354 while (pChnk = pNextChnk)
355 {
356 pNextChnk = pChnk->pNext;
357 delete pChnk;
358 }
359 Chunk.pNext = nullptr;
360 Chunk.Clear();
361 FreeParticles.pFirst = Chunk.Data;
362 // adjust counts
363 for (C4ParticleDef *pDef = pDef0; pDef; pDef = pDef->pNext)
364 pDef->Count = 0;
365}
366
367void C4ParticleSystem::Clear()
368{
369 // clear particles first
370 ClearParticles();
371 // clear defs
372 while (pDef0) delete pDef0;
373 // clear system particles
374 pSmoke = pBlast = pFSpark = pFire1 = pFire2 = nullptr;
375 // done
376}
377
378C4Particle *C4ParticleSystem::Create(C4ParticleDef *pOfDef,
379 float x, float y,
380 float xdir, float ydir,
381 float a, int32_t b, C4ParticleList *pPxList,
382 C4Object *pObj)
383{
384 // safety
385 if (!pOfDef) return nullptr;
386 // default to global list
387 if (!pPxList) pPxList = &GlobalParticles;
388 // check count
389 int32_t MaxCount = pOfDef->MaxCount * (Config.Graphics.SmokeLevel + 20) / 150;
390 int32_t iRoom = MaxCount - pOfDef->Count;
391 if (iRoom <= 0) return nullptr;
392 // reduce creation if limit is nearly reached
393 if (iRoom < (MaxCount >> 1))
394 if (SafeRandom(range: iRoom) < SafeRandom(range: MaxCount)) return nullptr;
395 // get free particle
396 if (!FreeParticles.pFirst) AddChunk();
397 C4Particle *pPrt = FreeParticles.pFirst;
398 if (!pPrt) return nullptr;
399 // set values
400 pPrt->x = x; pPrt->y = y;
401 pPrt->xdir = xdir; pPrt->ydir = ydir;
402 pPrt->a = a; pPrt->b = b;
403 pPrt->pDef = pOfDef;
404 if (pPrt->pDef->Attach && pObj != nullptr)
405 {
406 pPrt->x -= pObj->x;
407 pPrt->y -= pObj->y;
408 }
409 // call initialization
410 if (!pOfDef->InitProc(pPrt, pObj))
411 // failed :(
412 return nullptr;
413 // count particle
414 ++pOfDef->Count;
415 // more to desired list
416 pPrt->MoveList(rFrom&: Game.Particles.FreeParticles, rTo&: *pPxList);
417 // return newly created particle
418 return pPrt;
419}
420
421bool C4ParticleSystem::Cast(C4ParticleDef *pOfDef, int32_t iAmount,
422 float x, float y, int32_t level,
423 float a0, uint32_t b0, float a1, uint32_t b1, C4ParticleList *pPxList, C4Object *pObj)
424{
425 // safety
426 if (!pOfDef) return false;
427 // get range for a and b
428 int32_t iA0 = static_cast<int32_t>(a0 * 100), iA1 = static_cast<int32_t>(a1 * 100);
429 if (iA1 < iA0) std::swap(a&: iA0, b&: iA1);
430 int32_t iAd = iA1 - iA0 + 1;
431 if (b1 < b0) { uint32_t dwX = b0; b0 = b1; b1 = dwX; }
432 uint32_t db = b1 - b0;
433 uint8_t db1 = uint8_t(db >> 24), db2 = uint8_t(db >> 16), db3 = uint8_t(db >> 8), db4 = uint8_t(db);
434 // create them
435 for (int32_t i = iAmount; i > 0; --i)
436 Create(pOfDef, x, y,
437 xdir: static_cast<int32_t>(SafeRandom(range: level + 1) - level / 2) / 10.0f,
438 ydir: static_cast<int32_t>(SafeRandom(range: level + 1) - level / 2) / 10.0f,
439 a: static_cast<int32_t>(iA0 + SafeRandom(range: iAd)) / 100.0f,
440 b: b0 + (SafeRandom(range: db1) << 24) + (SafeRandom(range: db2) << 16) + (SafeRandom(range: db3) << 8) + SafeRandom(range: db4), pPxList, pObj);
441 // success
442 return true;
443}
444
445C4ParticleProc C4ParticleSystem::GetProc(const char *szName)
446{
447 // seek in map
448 for (int32_t i = 0; C4ParticleProcMap[i].Name[0]; ++i)
449 if (SEqual(szStr1: C4ParticleProcMap[i].Name, szStr2: szName))
450 return C4ParticleProcMap[i].Proc;
451 // nothing found...
452 return nullptr;
453}
454
455C4ParticleDrawProc C4ParticleSystem::GetDrawProc(const char *szName)
456{
457 // seek in map
458 for (int32_t i = 0; C4ParticleDrawProcMap[i].Name[0]; ++i)
459 if (SEqual(szStr1: C4ParticleDrawProcMap[i].Name, szStr2: szName))
460 return C4ParticleDrawProcMap[i].Proc;
461 // nothing found...
462 return nullptr;
463}
464
465C4ParticleDef *C4ParticleSystem::GetDef(const char *szName, C4ParticleDef *pExclude)
466{
467 // seek list
468 for (C4ParticleDef *pDef = pDef0; pDef; pDef = pDef->pNext)
469 if (pDef != pExclude && pDef->Name == szName)
470 return pDef;
471 // nothing found
472 return nullptr;
473}
474
475void C4ParticleSystem::SetDefParticles()
476{
477 // get smoke
478 pSmoke = GetDef(szName: "Smoke");
479 // get blast
480 pBlast = GetDef(szName: "Blast");
481 pFSpark = GetDef(szName: "FSpark");
482 // get fire, if fire particles are desired
483 if (Config.Graphics.FireParticles)
484 {
485 pFire1 = GetDef(szName: "Fire");
486 pFire2 = GetDef(szName: "Fire2");
487 }
488 else
489 pFire1 = pFire2 = nullptr;
490 // if fire is drawn w/o background fct: unload fire face if both fire particles are assigned
491 // but this is not done here
492}
493
494int32_t C4ParticleSystem::Push(C4ParticleDef *pOfDef, float dxdir, float dydir)
495{
496 int32_t iNumPushed = 0;
497 // go through all particle chunks
498 for (C4ParticleChunk *pChnk = &Chunk; pChnk; pChnk = pChnk->pNext)
499 {
500 // go through all particles
501 C4Particle *pPrt = pChnk->Data; int32_t i = C4Px_BufSize;
502 while (i--)
503 {
504 // def fits?
505 if (!pOfDef || pPrt->pDef == pOfDef)
506 {
507 // push it!
508 pPrt->xdir += dxdir;
509 pPrt->ydir += dydir;
510 // count pushed
511 ++iNumPushed;
512 }
513 // next particle
514 ++pPrt;
515 }
516 }
517 // done
518 return iNumPushed;
519}
520
521bool fxSmokeInit(C4Particle *pPrt, C4Object *pTarget)
522{
523 // init lifetime
524 pPrt->life = pPrt->pDef->MinLifetime;
525 int32_t iLD = pPrt->pDef->MaxLifetime - pPrt->pDef->MinLifetime;
526 if (iLD) pPrt->life += SafeRandom(range: iLD);
527 // use high-word of life to store init-status
528 pPrt->life |= (pPrt->life / 17) << 16;
529 // set kind - ydir is unused anyway; set last kind reeeaaally seldom
530 pPrt->ydir = static_cast<float>(SafeRandom(range: 15)) + SafeRandom(range: 300) / 299;
531 // set color
532 if (!pPrt->b) pPrt->b = 0xff4b4b4b; else pPrt->b |= 0xff000000;
533 // always OK
534 return true;
535}
536
537bool fxSmokeExec(C4Particle *pPrt, C4Object *pTarget)
538{
539 // lifetime
540 if (!--pPrt->life) return false;
541 bool fBuilding = !!(pPrt->life & 0x7fff0000);
542 // still building?
543 if (fBuilding)
544 {
545 // decrease init-time
546 pPrt->life -= 0x010000;
547 // increase color value
548 pPrt->b -= 0x10000000;
549 // if full-grown, adjust to lifetime
550 if (!(pPrt->life & 0x7fff0000))
551 pPrt->b = (pPrt->b & 0xffffff) | ((255 - pPrt->life) << 24);
552 }
553 // color change
554 uint32_t dwClr = pPrt->b;
555 pPrt->b = (LightenClrBy(dst&: dwClr, by: 1) & 0xffffff) | std::min<int32_t>(a: (dwClr >> 24) + 1, b: 255) << 24;
556 // wind to float
557 if (!(pPrt->b % 12) || fBuilding)
558 {
559 pPrt->xdir = 0.025f * Game.Weather.GetWind(x: int32_t(pPrt->x), y: int32_t(pPrt->y));
560 if (pPrt->xdir < -2.0f) pPrt->xdir = -2.0f; else if (pPrt->xdir > 2.0f) pPrt->xdir = 2.0f;
561 pPrt->xdir += 0.1f * SafeRandom(range: 41) - 2.0f;
562 }
563 // float
564 if (GBackSolid(x: int32_t(pPrt->x), y: int32_t(pPrt->y - pPrt->a)))
565 {
566 // if stuck, decay; otherwise, move down
567 if (!GBackSolid(x: int32_t(pPrt->x), y: int32_t(pPrt->y))) pPrt->y += 0.4f; else pPrt->a -= 2;
568 }
569 else
570 --pPrt->y;
571 pPrt->x += pPrt->xdir;
572 // increase in size
573 pPrt->a *= 1.01f;
574 // done, keep
575 return true;
576}
577
578void fxSmokeDraw(C4Particle *pPrt, C4FacetEx &cgo, C4Object *pTarget)
579{
580 C4ParticleDef *pDef = pPrt->pDef;
581 // apply parallaxity to target pos
582 int32_t tx = cgo.TargetX * pDef->Parallaxity[0] / 100;
583 int32_t ty = cgo.TargetY * pDef->Parallaxity[1] / 100;
584 // check if it's in screen range
585 if (!Inside(ival: pPrt->x, lbound: tx - pPrt->a, rbound: tx + cgo.Wdt + pPrt->a)) return;
586 if (!Inside(ival: pPrt->y, lbound: ty - pPrt->a, rbound: ty + cgo.Hgt + pPrt->a)) return;
587 // get pos
588 int32_t cx = int32_t(pPrt->x) + cgo.X - tx;
589 int32_t cy = int32_t(pPrt->y) + cgo.Y - ty;
590 // get phase by particle index
591 int32_t i = static_cast<int32_t>(pPrt->ydir);
592 int32_t ipx = i / 4;
593 int32_t ipy = i % 4;
594 // draw at pos
595 Application.DDraw->ActivateBlitModulation(dwWithClr: pPrt->b);
596 pDef->Gfx.DrawX(sfcTarget: cgo.Surface, iX: int32_t(cx - pPrt->a), iY: int32_t(cy - pPrt->a), iWdt: int32_t(pPrt->a * 2), iHgt: int32_t(pPrt->a * 2), iPhaseX: ipx, iPhaseY: ipy);
597 Application.DDraw->DeactivateBlitModulation();
598}
599
600bool fxStdInit(C4Particle *pPrt, C4Object *pTarget)
601{
602 if (pPrt->pDef->Delay)
603 // delay given: lifetime starts at zero
604 pPrt->life = 0;
605 else
606 // init lifetime as phase
607 pPrt->life = SafeRandom(range: pPrt->pDef->Length);
608 // default color
609 if (!pPrt->b) pPrt->b = 0xffffff;
610 // always OK
611 return true;
612}
613
614bool fxStdExec(C4Particle *pPrt, C4Object *pTarget)
615{
616 float dx = pPrt->x, dy = pPrt->y;
617 float dxdir = pPrt->xdir, dydir = pPrt->ydir;
618 // rel. position & movement
619 if (pPrt->pDef->Attach && pTarget != nullptr)
620 {
621 dx += pTarget->x;
622 dy += pTarget->y;
623 dxdir += fixtof(x: pTarget->xdir);
624 dydir += fixtof(x: pTarget->ydir);
625 }
626
627 // move
628 if (pPrt->xdir || pPrt->ydir)
629 {
630 if (pPrt->pDef->VertexCount && GBackSolid(x: int32_t(dx + pPrt->xdir), y: int32_t(dy + pPrt->ydir + pPrt->pDef->VertexY * pPrt->a / 100.0f)))
631 {
632 // collision
633 if (pPrt->pDef->CollisionProc)
634 if (!pPrt->pDef->CollisionProc(pPrt, pTarget)) return false;
635 }
636 else if (pPrt->pDef->RByV != 2)
637 {
638 pPrt->x += pPrt->xdir;
639 pPrt->y += pPrt->ydir;
640 }
641 else
642 {
643 // With RByV=2, the V is only used for rotation, not for movement
644 }
645 }
646 // apply gravity
647 if (pPrt->pDef->GravityAcc) pPrt->ydir += fixtof(GravAccel * pPrt->pDef->GravityAcc) / 100.0f;
648 // apply WindDrift
649 if (pPrt->pDef->WindDrift && !GBackSolid(x: int32_t(dx), y: int32_t(dy)))
650 {
651 // Air speed: Wind plus some random
652 int32_t iWind = GBackWind(x: int32_t(dx), y: int32_t(dy));
653 float txdir = iWind / 15.0f;
654 float tydir = 0;
655
656 // Air friction, based on WindDrift.
657 int32_t iWindDrift = (std::max)(a: pPrt->pDef->WindDrift - 20, b: 0);
658 pPrt->xdir += ((txdir - dxdir) * iWindDrift) / 800;
659 pPrt->ydir += ((tydir - dydir) * iWindDrift) / 800;
660 }
661 // fade out
662 int32_t iFade = pPrt->pDef->AlphaFade;
663 if (iFade < 0) if (Game.FrameCounter % -iFade == 0) iFade = 1; else iFade = 0;
664 if (iFade)
665 {
666 uint32_t dwClr = pPrt->b;
667 int32_t iAlpha = dwClr >> 24;
668 iAlpha += pPrt->pDef->AlphaFade;
669 if (iAlpha >= 0xff) return false;
670 pPrt->b = (dwClr & 0xffffff) | (iAlpha << 24);
671 }
672 // if delay is given, advance lifetime
673 if (pPrt->pDef->Delay)
674 {
675 if (pPrt->life < 0)
676 {
677 // decay
678 return pPrt->life-- >= -pPrt->pDef->FadeOutLen * pPrt->pDef->FadeOutDelay;
679 }
680 ++pPrt->life;
681 // check if still alive
682 int32_t iPhase = pPrt->life / pPrt->pDef->Delay;
683 int32_t length = pPrt->pDef->Length - pPrt->pDef->Reverse;
684 if (iPhase >= length * pPrt->pDef->Repeats + pPrt->pDef->Reverse)
685 {
686 // do fadeout, if assigned
687 if (!pPrt->pDef->FadeOutLen) return false;
688 pPrt->life = -1;
689 }
690 return true;
691 }
692 // outside landscape range?
693 bool kp;
694 if (dxdir > 0) kp = (dx - pPrt->a < GBackWdt); else kp = (dx + pPrt->a > 0);
695 if (dydir > 0) kp = kp && (dy - pPrt->a < GBackHgt); else kp = kp && (dy + pPrt->a > pPrt->pDef->YOff);
696 return kp;
697}
698
699bool fxBounce(C4Particle *pPrt, C4Object *pTarget)
700{
701 // reverse xdir/ydir
702 pPrt->xdir = -pPrt->xdir;
703 pPrt->ydir = -pPrt->ydir;
704 return true;
705}
706
707bool fxBounceY(C4Particle *pPrt, C4Object *pTarget)
708{
709 // reverse ydir only
710 pPrt->ydir = -pPrt->ydir;
711 return true;
712}
713
714bool fxStop(C4Particle *pPrt, C4Object *pTarget)
715{
716 // zero xdir/ydir
717 pPrt->xdir = pPrt->ydir = 0;
718 return true;
719}
720
721bool fxDie(C4Particle *pPrt, C4Object *pTarget)
722{
723 // DIEEEEEE
724 return false;
725}
726
727void fxStdDraw(C4Particle *pPrt, C4FacetEx &cgo, C4Object *pTarget)
728{
729 // get def
730 C4ParticleDef *pDef = pPrt->pDef;
731 // apply parallaxity to target pos
732 int32_t tx = cgo.TargetX * pDef->Parallaxity[0] / 100;
733 int32_t ty = cgo.TargetY * pDef->Parallaxity[1] / 100;
734
735 float dx = pPrt->x, dy = pPrt->y;
736 float dxdir = pPrt->xdir, dydir = pPrt->ydir;
737 // relative position & movement
738 if (pPrt->pDef->Attach && pTarget != nullptr)
739 {
740 dx += pTarget->x;
741 dy += pTarget->y;
742 dxdir += fixtof(x: pTarget->xdir);
743 dydir += fixtof(x: pTarget->ydir);
744 }
745
746 // check if it's in screen range
747 if (!Inside(ival: dx, lbound: tx - pPrt->a, rbound: tx + cgo.Wdt + pPrt->a)) return;
748 if (!Inside(ival: dy, lbound: ty - pPrt->a, rbound: ty + cgo.Hgt + pPrt->a)) return;
749 // get pos
750 int32_t cgox = cgo.X - tx, cgoy = cgo.Y - ty;
751 int32_t cx = int32_t(dx + cgox);
752 int32_t cy = int32_t(dy + cgoy);
753 // get phase
754 int32_t iPhase = pPrt->life;
755 if (pDef->Delay)
756 if (iPhase >= 0)
757 {
758 iPhase /= pDef->Delay;
759 int32_t length = pDef->Length;
760 if (pDef->Reverse)
761 {
762 --length; iPhase %= length * 2;
763 if (iPhase > length) iPhase = length * 2 + 1 - iPhase;
764 }
765 else iPhase %= length;
766 }
767 else iPhase = (iPhase + 1) / -pDef->FadeOutDelay + pDef->Length;
768 // get rotation
769 int32_t r = 0;
770 if ((pDef->RByV == 1) || (pDef->RByV == 2)) // rotation by direction
771 r = Angle(iX1: 0, iY1: 0, iX2: static_cast<int32_t>(dxdir * 10.0f), iY2: static_cast<int32_t>(dydir * 10.0f)) * 100;
772 if (pDef->RByV == 3) // random rotation - currently a pseudo random rotation by x/y position
773 r = (static_cast<int32_t>(pPrt->x * 23 + pPrt->y * 12) % 360) * 100;
774 // draw at pos
775 Application.DDraw->ActivateBlitModulation(dwWithClr: pPrt->b);
776 Application.DDraw->StorePrimaryClipper();
777 Application.DDraw->SubPrimaryClipper(iX1: cgox, iY1: cgoy + pDef->YOff, iX2: 100000, iY2: 100000);
778 if (pDef->Additive) lpDDraw->SetBlitMode(C4GFXBLIT_ADDITIVE);
779 int32_t iDrawWdt = int32_t(pPrt->a);
780 int32_t iDrawHgt = int32_t(pDef->Aspect * iDrawWdt);
781 if (r)
782 pDef->Gfx.DrawXR(sfcTarget: cgo.Surface, iX: cx - iDrawWdt, iY: cy - iDrawHgt, iWdt: iDrawWdt * 2, iHgt: iDrawHgt * 2, iPhaseX: iPhase, iPhaseY: 0, r);
783 else
784 pDef->Gfx.DrawX(sfcTarget: cgo.Surface, iX: cx - iDrawWdt, iY: cy - iDrawHgt, iWdt: iDrawWdt * 2, iHgt: iDrawHgt * 2, iPhaseX: iPhase, iPhaseY: 0);
785 Application.DDraw->ResetBlitMode();
786 Application.DDraw->RestorePrimaryClipper();
787 Application.DDraw->DeactivateBlitModulation();
788}
789
790C4ParticleProcRec C4ParticleProcMap[] =
791{
792 { .Name: "SmokeInit", .Proc: fxSmokeInit },
793 { .Name: "SmokeExec", .Proc: fxSmokeExec },
794 { .Name: "StdInit", .Proc: fxStdInit },
795 { .Name: "StdExec", .Proc: fxStdExec },
796 { .Name: "Bounce", .Proc: fxBounce },
797 { .Name: "BounceY", .Proc: fxBounceY },
798 { .Name: "Stop", .Proc: fxStop },
799 { .Name: "Die", .Proc: fxDie },
800 { .Name: "", .Proc: nullptr }
801};
802
803C4ParticleDrawProcRec C4ParticleDrawProcMap[] =
804{
805 { .Name: "Smoke", .Proc: fxSmokeDraw },
806 { .Name: "Std", .Proc: fxStdDraw },
807 { .Name: "", .Proc: nullptr }
808};
809