forked from Mirrors/openclonk
852 lines
23 KiB
C++
852 lines
23 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2002, 2004-2005 Sven Eberhardt
|
|
* Copyright (c) 2005 Tobias Zwick
|
|
* Copyright (c) 2005-2006, 2008 Günther Brammer
|
|
* Copyright (c) 2008 Peter Wortmann
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
|
|
*
|
|
* Portions might be copyrighted by other authors who have contributed
|
|
* to OpenClonk.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
* See isc_license.txt for full license and disclaimer.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender.
|
|
* See clonk_trademark_license.txt for full license.
|
|
*/
|
|
// newgfx particle system for smoke, sparks, ...
|
|
|
|
#include <C4Include.h>
|
|
#include <C4Particles.h>
|
|
|
|
#ifndef BIG_C4INCLUDE
|
|
#include <C4Physics.h>
|
|
#include <C4Object.h>
|
|
#include <C4Random.h>
|
|
#include <C4Game.h>
|
|
#include <C4Components.h>
|
|
#include <C4Log.h>
|
|
#include <C4Weather.h>
|
|
#include <C4GameObjects.h>
|
|
#endif
|
|
|
|
void C4ParticleDefCore::CompileFunc(StdCompiler * pComp)
|
|
{
|
|
pComp->Value(mkNamingAdapt(toC4CStrBuf(Name), "Name", ""));
|
|
pComp->Value(mkNamingAdapt(MaxCount, "MaxCount", C4Px_MaxParticle));
|
|
pComp->Value(mkNamingAdapt(MinLifetime, "MinLifetime", 0));
|
|
pComp->Value(mkNamingAdapt(MaxLifetime, "MaxLifetime", 0));
|
|
pComp->Value(mkNamingAdapt(toC4CStrBuf(InitFn), "InitFn", ""));
|
|
pComp->Value(mkNamingAdapt(toC4CStrBuf(ExecFn), "ExecFn", ""));
|
|
pComp->Value(mkNamingAdapt(toC4CStrBuf(CollisionFn),"CollisionFn",""));
|
|
pComp->Value(mkNamingAdapt(toC4CStrBuf(DrawFn), "DrawFn", ""));
|
|
pComp->Value(mkNamingAdapt(GfxFace, "Face"));
|
|
pComp->Value(mkNamingAdapt(YOff, "YOff", 0));
|
|
pComp->Value(mkNamingAdapt(Delay, "Delay", 0));
|
|
pComp->Value(mkNamingAdapt(Repeats, "Repeats", 0));
|
|
pComp->Value(mkNamingAdapt(Reverse, "Reverse", 0));
|
|
pComp->Value(mkNamingAdapt(FadeOutLen, "FadeOutLen", 0));
|
|
pComp->Value(mkNamingAdapt(FadeOutDelay, "FadeOutDelay",0));
|
|
pComp->Value(mkNamingAdapt(RByV, "RByV", 0));
|
|
pComp->Value(mkNamingAdapt(GravityAcc, "GravityAcc", 0));
|
|
pComp->Value(mkNamingAdapt(WindDrift, "WindDrift", 0));
|
|
pComp->Value(mkNamingAdapt(VertexCount, "VertexCount", 0));
|
|
pComp->Value(mkNamingAdapt(VertexY, "VertexY", 0));
|
|
pComp->Value(mkNamingAdapt(Additive, "Additive", 0));
|
|
pComp->Value(mkNamingAdapt(AlphaFade, "AlphaFade", 0));
|
|
pComp->Value(mkNamingAdapt(FadeDelay, "FadeDelay", 0));
|
|
pComp->Value(mkNamingAdapt(mkArrayAdaptDM(Parallaxity,100),"Parallaxity"));
|
|
pComp->Value(mkNamingAdapt(Attach, "Attach", 0));
|
|
}
|
|
|
|
C4ParticleDefCore::C4ParticleDefCore():
|
|
MaxCount(C4Px_MaxParticle),
|
|
MinLifetime(0),MaxLifetime(0),
|
|
YOff(0),
|
|
Reverse(0),Repeats(0),Delay(0),
|
|
FadeOutLen(0),FadeOutDelay(0),
|
|
RByV(0),
|
|
Placement(0),
|
|
GravityAcc(0),
|
|
VertexCount(0),VertexY(0),
|
|
Additive(0),
|
|
Attach(0),
|
|
AlphaFade(0),
|
|
FadeDelay(0)
|
|
{
|
|
GfxFace.Default();
|
|
Parallaxity[0] = Parallaxity[1] = 100;
|
|
}
|
|
|
|
bool C4ParticleDefCore::Compile(char *szSource, const char *szName)
|
|
{
|
|
return CompileFromBuf_LogWarn<StdCompilerINIRead>(mkNamingAdapt(*this, "Particle"),
|
|
StdStrBuf(szSource), szName);
|
|
}
|
|
|
|
C4ParticleDef::C4ParticleDef():
|
|
C4ParticleDefCore(),
|
|
InitProc(&fxStdInit),
|
|
ExecProc(&fxStdExec),
|
|
DrawProc(&fxStdDraw),
|
|
CollisionProc(NULL),
|
|
Count(0)
|
|
{
|
|
// zero fields
|
|
Gfx.Default();
|
|
// link into list
|
|
if (!ParticleSystem.pDef0)
|
|
{
|
|
pPrev=NULL;
|
|
ParticleSystem.pDef0=this;
|
|
}
|
|
else
|
|
(pPrev=ParticleSystem.pDefL)->pNext=this;
|
|
ParticleSystem.pDefL=this;
|
|
pNext=NULL;
|
|
}
|
|
|
|
C4ParticleDef::~C4ParticleDef()
|
|
{
|
|
// clear
|
|
Clear();
|
|
// unlink from list
|
|
if (pPrev) pPrev->pNext=pNext; else ParticleSystem.pDef0=pNext;
|
|
if (pNext) pNext->pPrev=pPrev; else ParticleSystem.pDefL=pPrev;
|
|
}
|
|
|
|
void C4ParticleDef::Clear()
|
|
{
|
|
Name.Clear();
|
|
}
|
|
|
|
bool C4ParticleDef::Load(C4Group &rGrp)
|
|
{
|
|
// store file
|
|
Filename.Copy(rGrp.GetFullName());
|
|
// load
|
|
char *pSource;
|
|
if (rGrp.LoadEntry(C4CFN_ParticleCore,&pSource,NULL,1))
|
|
{
|
|
if (!Compile(pSource, Filename.getData()))
|
|
{
|
|
DebugLogF("invalid particle def at '%s'", rGrp.GetFullName().getData());
|
|
delete [] pSource; return false;
|
|
}
|
|
delete [] pSource;
|
|
// load graphics
|
|
if (!Gfx.Load(rGrp, C4CFN_DefGraphicsPNG))
|
|
{
|
|
DebugLogF("particle %s has no valid graphics defined", Name.getData());
|
|
return false;
|
|
}
|
|
// set facet, if assigned - otherwise, assume full surface
|
|
if (GfxFace.Wdt) Gfx.Set(Gfx.Surface, GfxFace.x, GfxFace.y, GfxFace.Wdt, GfxFace.Hgt, GfxFace.tx, GfxFace.ty);
|
|
// set phase num
|
|
int32_t Q; Gfx.GetPhaseNum(Length, Q);
|
|
if (!Length)
|
|
{
|
|
DebugLogF("invalid facet for particle '%s'", Name.getData());
|
|
return false;
|
|
}
|
|
// case fadeout from length
|
|
if (FadeOutLen)
|
|
{
|
|
Length=Max<int32_t>(Length-FadeOutLen, 1);
|
|
if (!FadeOutDelay) FadeOutDelay=1;
|
|
}
|
|
// if phase num is 1, no reverse is allowed
|
|
if (Length==1) Reverse=0;
|
|
// calc aspect
|
|
Aspect=(float) Gfx.Wdt/Gfx.Hgt;
|
|
// get proc pointers
|
|
if (!(InitProc=ParticleSystem.GetProc(InitFn.getData())))
|
|
{
|
|
DebugLogF("init proc for particle '%s' not found: '%s'", Name.getData(), InitFn.getData());
|
|
return false;
|
|
}
|
|
if (!(ExecProc=ParticleSystem.GetProc(ExecFn.getData())))
|
|
{
|
|
DebugLogF("exec proc for particle '%s' not found: '%s'", Name.getData(), ExecFn.getData());
|
|
return false;
|
|
}
|
|
if (CollisionFn && CollisionFn[0]) if (!(CollisionProc=ParticleSystem.GetProc(CollisionFn.getData())))
|
|
{
|
|
DebugLogF("collision proc for particle '%s' not found: '%s'", Name.getData(), CollisionFn.getData());
|
|
return false;
|
|
}
|
|
if (!(DrawProc=ParticleSystem.GetDrawProc(DrawFn.getData())))
|
|
{
|
|
DebugLogF("draw proc for particle '%s' not found: '%s'", Name.getData(), DrawFn.getData());
|
|
return false;
|
|
}
|
|
// particle overloading
|
|
C4ParticleDef *pDefOverload;
|
|
if (pDefOverload=ParticleSystem.GetDef(Name.getData(), this))
|
|
{
|
|
if (Config.Graphics.VerboseObjectLoading>=1)
|
|
{ char ostr[250]; sprintf(ostr,LoadResStr("IDS_PRC_DEFOVERLOAD"),pDefOverload->Name.getData(),"<particle>"); Log(ostr); }
|
|
delete pDefOverload;
|
|
}
|
|
// success
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool C4ParticleDef::Reload()
|
|
{
|
|
// no file?
|
|
if (!Filename[0]) return false;
|
|
// open group
|
|
C4Group hGroup;
|
|
if (!hGroup.Open(Filename.getData())) return false;
|
|
// reset class
|
|
Clear();
|
|
// load
|
|
return Load(hGroup);
|
|
}
|
|
|
|
void C4Particle::MoveList(C4ParticleList &rFrom, C4ParticleList &rTo)
|
|
{
|
|
// remove from current list
|
|
if (pPrev)
|
|
pPrev->pNext=pNext;
|
|
else
|
|
// is it the first item in the list? then set this to the next one
|
|
if (rFrom.pFirst == this) rFrom.pFirst=pNext;
|
|
if (pNext) pNext->pPrev=pPrev;
|
|
// add to the other list - insert before first
|
|
if (pNext = rTo.pFirst) pNext->pPrev = this;
|
|
rTo.pFirst = this; pPrev = NULL;
|
|
}
|
|
|
|
C4ParticleChunk::C4ParticleChunk()
|
|
{
|
|
// zero linked list
|
|
pNext=NULL;
|
|
// zero buffer
|
|
Clear();
|
|
}
|
|
|
|
C4ParticleChunk::~C4ParticleChunk()
|
|
{
|
|
// list stuff done by C4ParticleSystem
|
|
}
|
|
|
|
void C4ParticleChunk::Clear()
|
|
{
|
|
// note that this method is called in ctor with uninitialized data!
|
|
// simply clear mem - this won't adjust any counts!
|
|
ZeroMemory(Data, sizeof(Data));
|
|
// init list
|
|
C4Particle *pPrt=Data;
|
|
for (int32_t i=0; i<C4Px_BufSize; ++i)
|
|
{
|
|
pPrt->pPrev=pPrt-1;
|
|
pPrt->pNext=pPrt+1;
|
|
++pPrt;
|
|
}
|
|
Data[0].pPrev=Data[C4Px_BufSize-1].pNext=NULL;
|
|
// all free
|
|
iNumFree = C4Px_BufSize;
|
|
}
|
|
|
|
void C4ParticleList::Exec(C4Object *pObj)
|
|
{
|
|
// execute all particles
|
|
C4Particle *pPrtNext=pFirst, *pPrt;
|
|
while (pPrt = pPrtNext)
|
|
{
|
|
// get next now, because destruction could corrupt the list
|
|
pPrtNext=pPrt->pNext;
|
|
// execute it
|
|
if (!pPrt->pDef->ExecProc(pPrt,pObj))
|
|
{
|
|
// sorry, life is over for you :P
|
|
--pPrt->pDef->Count;
|
|
pPrt->MoveList(*this, ::Particles.FreeParticles);
|
|
}
|
|
}
|
|
// done
|
|
}
|
|
|
|
void C4ParticleList::Draw(C4TargetFacet &cgo, C4Object *pObj)
|
|
{
|
|
// draw all particles
|
|
for (C4Particle *pPrt=pFirst; pPrt; pPrt=pPrt->pNext)
|
|
pPrt->pDef->DrawProc(pPrt, cgo, pObj);
|
|
// done
|
|
}
|
|
|
|
void C4ParticleList::Clear()
|
|
{
|
|
// remove all particles
|
|
C4Particle *pPrtNext=pFirst, *pPrt;
|
|
while (pPrt = pPrtNext)
|
|
{
|
|
// get next now, because destruction could corrupt the list
|
|
pPrtNext=pPrt->pNext;
|
|
// sorry, life is over for you :P
|
|
--pPrt->pDef->Count;
|
|
pPrt->MoveList(*this, ::Particles.FreeParticles);
|
|
}
|
|
}
|
|
|
|
int32_t C4ParticleList::Remove(C4ParticleDef *pOfDef)
|
|
{
|
|
int32_t iNumRemoved=0;
|
|
// check all particles for def
|
|
C4Particle *pPrtNext=pFirst, *pPrt;
|
|
while (pPrt = pPrtNext)
|
|
{
|
|
// get next now, because destruction could corrupt the list
|
|
pPrtNext=pPrt->pNext;
|
|
// execute it
|
|
if (!pOfDef || pPrt->pDef == pOfDef)
|
|
{
|
|
// sorry, life is over for you :P
|
|
--pPrt->pDef->Count;
|
|
pPrt->MoveList(*this, ::Particles.FreeParticles);
|
|
}
|
|
}
|
|
// done
|
|
return iNumRemoved;
|
|
}
|
|
|
|
C4ParticleSystem::C4ParticleSystem()
|
|
{
|
|
// zero fields
|
|
pDef0=pDefL=NULL;
|
|
pSmoke=NULL;
|
|
pBlast=NULL;
|
|
pFSpark=NULL;
|
|
pFire1=NULL;
|
|
pFire2=NULL;
|
|
}
|
|
|
|
C4ParticleSystem::~C4ParticleSystem()
|
|
{
|
|
// clean up
|
|
Clear();
|
|
}
|
|
|
|
C4ParticleChunk *C4ParticleSystem::AddChunk()
|
|
{
|
|
// add another chunk
|
|
C4ParticleChunk *pNewChnk=new C4ParticleChunk();
|
|
pNewChnk->pNext = Chunk.pNext;
|
|
Chunk.pNext = pNewChnk;
|
|
// register into free-particle-list
|
|
if (pNewChnk->Data[C4Px_BufSize-1].pNext = FreeParticles.pFirst)
|
|
FreeParticles.pFirst->pPrev = &pNewChnk->Data[C4Px_BufSize-1];
|
|
FreeParticles.pFirst = &pNewChnk->Data[0];
|
|
// return it
|
|
return pNewChnk;
|
|
}
|
|
|
|
void C4ParticleSystem::PruneChunks()
|
|
{
|
|
// check all chunks, but not the first
|
|
// that cannot be removed anyway
|
|
C4ParticleChunk *pChnk = Chunk.pNext, *pChnkNext, **ppChnkPrev;
|
|
ppChnkPrev = &Chunk.pNext;
|
|
do
|
|
{
|
|
pChnkNext = pChnk->pNext;
|
|
// chunk empty?
|
|
if (pChnk->iNumFree == C4Px_BufSize)
|
|
{
|
|
// move out all particles
|
|
C4ParticleList tmp;
|
|
for (int32_t i=0; i<C4Px_BufSize; ++i)
|
|
pChnk->Data[i].MoveList(FreeParticles, tmp);
|
|
// and remove the chunk
|
|
*ppChnkPrev = pChnkNext;
|
|
delete pChnk;
|
|
}
|
|
else
|
|
{
|
|
// keep this chunk
|
|
ppChnkPrev = &pChnk->pNext;
|
|
}
|
|
}
|
|
while (pChnk = pChnkNext);
|
|
}
|
|
|
|
void C4ParticleSystem::ClearParticles()
|
|
{
|
|
// clear particle lists
|
|
C4ObjectLink *pLnk;
|
|
for (pLnk = ::Objects.First; pLnk; pLnk = pLnk->Next)
|
|
pLnk->Obj->FrontParticles.pFirst = pLnk->Obj->BackParticles.pFirst = NULL;
|
|
for (pLnk = ::Objects.InactiveObjects.First; pLnk; pLnk = pLnk->Next)
|
|
pLnk->Obj->FrontParticles.pFirst = pLnk->Obj->BackParticles.pFirst = NULL;
|
|
GlobalParticles.pFirst = NULL;
|
|
// reset chunks
|
|
C4ParticleChunk *pNextChnk=Chunk.pNext, *pChnk;
|
|
while (pChnk = pNextChnk)
|
|
{
|
|
pNextChnk = pChnk->pNext;
|
|
delete pChnk;
|
|
}
|
|
Chunk.pNext = NULL;
|
|
Chunk.Clear();
|
|
FreeParticles.pFirst = Chunk.Data;
|
|
// adjust counts
|
|
for (C4ParticleDef *pDef=pDef0; pDef; pDef=pDef->pNext)
|
|
pDef->Count=0;
|
|
}
|
|
|
|
void C4ParticleSystem::Clear()
|
|
{
|
|
// clear particles first
|
|
ClearParticles();
|
|
// clear defs
|
|
while (pDef0) delete pDef0;
|
|
// clear system particles
|
|
pSmoke=pBlast=pFSpark=pFire1=pFire2=NULL;
|
|
// done
|
|
}
|
|
|
|
C4Particle *C4ParticleSystem::Create(C4ParticleDef *pOfDef,
|
|
float x, float y,
|
|
float xdir, float ydir,
|
|
float a, int32_t b, C4ParticleList *pPxList,
|
|
C4Object *pObj)
|
|
{
|
|
// safety
|
|
if (!pOfDef) return NULL;
|
|
// default to global list
|
|
if (!pPxList) pPxList = &GlobalParticles;
|
|
// check count
|
|
int32_t MaxCount=pOfDef->MaxCount*(Config.Graphics.SmokeLevel+20)/150;
|
|
int32_t iRoom=MaxCount-pOfDef->Count;
|
|
if (iRoom<=0) return NULL;
|
|
// reduce creation if limit is nearly reached
|
|
if (iRoom<(MaxCount>>1))
|
|
if (SafeRandom(iRoom)<SafeRandom(MaxCount)) return NULL;
|
|
// get free particle
|
|
if (!FreeParticles.pFirst) AddChunk();
|
|
C4Particle *pPrt = FreeParticles.pFirst;
|
|
if (!pPrt) return NULL;
|
|
// set values
|
|
pPrt->x=x; pPrt->y=y;
|
|
pPrt->xdir=xdir; pPrt->ydir=ydir;
|
|
pPrt->a=a; pPrt->b=b;
|
|
pPrt->pDef = pOfDef;
|
|
if(pPrt->pDef->Attach && pObj != NULL) {
|
|
pPrt->x -= pObj->GetX();
|
|
pPrt->y -= pObj->GetY();
|
|
}
|
|
// call initialization
|
|
if (!pOfDef->InitProc(pPrt,pObj))
|
|
// failed :(
|
|
return NULL;
|
|
// count particle
|
|
++pOfDef->Count;
|
|
// more to desired list
|
|
pPrt->MoveList(::Particles.FreeParticles, *pPxList);
|
|
// return newly created particle
|
|
return pPrt;
|
|
}
|
|
|
|
bool C4ParticleSystem::Cast(C4ParticleDef *pOfDef, int32_t iAmount,
|
|
float x, float y, int32_t level,
|
|
float a0, DWORD b0, float a1, DWORD b1, C4ParticleList *pPxList, C4Object *pObj)
|
|
{
|
|
// safety
|
|
if (!pOfDef) return false;
|
|
// get range for a and b
|
|
int32_t iA0=(int32_t)(a0*100),iA1=(int32_t)(a1*100);
|
|
if (iA1<iA0) Swap(iA0, iA1);
|
|
int32_t iAd=iA1-iA0+1;
|
|
if (b1<b0) { DWORD dwX=b0; b0=b1; b1=dwX; }
|
|
DWORD db=b1-b0;
|
|
BYTE db1=BYTE(db>>24), db2=BYTE(db>>16), db3=BYTE(db>>8), db4=BYTE(db);
|
|
// create them
|
|
for (int32_t i=iAmount; i > 0; --i)
|
|
Create(pOfDef, x, y,
|
|
(float)(SafeRandom(level+1)-level/2)/10.0f,
|
|
(float)(SafeRandom(level+1)-level/2)/10.0f,
|
|
(float)(iA0+SafeRandom(iAd))/100.0f,
|
|
b0+(SafeRandom(db1)<<24)+(SafeRandom(db2)<<16)+(SafeRandom(db3)<<8)+SafeRandom(db4), pPxList, pObj);
|
|
// success
|
|
return true;
|
|
}
|
|
|
|
C4ParticleProc C4ParticleSystem::GetProc(const char *szName)
|
|
{
|
|
// seek in map
|
|
for (int32_t i=0; C4ParticleProcMap[i].Name[0]; ++i)
|
|
if (SEqual(C4ParticleProcMap[i].Name, szName))
|
|
return C4ParticleProcMap[i].Proc;
|
|
// nothing found...
|
|
return NULL;
|
|
}
|
|
|
|
C4ParticleDrawProc C4ParticleSystem::GetDrawProc(const char *szName)
|
|
{
|
|
// seek in map
|
|
for (int32_t i=0; C4ParticleDrawProcMap[i].Name[0]; ++i)
|
|
if (SEqual(C4ParticleDrawProcMap[i].Name, szName))
|
|
return C4ParticleDrawProcMap[i].Proc;
|
|
// nothing found...
|
|
return NULL;
|
|
}
|
|
|
|
C4ParticleDef *C4ParticleSystem::GetDef(const char *szName, C4ParticleDef *pExclude)
|
|
{
|
|
// seek list
|
|
for (C4ParticleDef *pDef=pDef0; pDef; pDef=pDef->pNext)
|
|
if (pDef != pExclude && pDef->Name == szName)
|
|
return pDef;
|
|
// nothing found
|
|
return NULL;
|
|
}
|
|
|
|
void C4ParticleSystem::SetDefParticles()
|
|
{
|
|
// get smoke
|
|
pSmoke=GetDef("Smoke");
|
|
// get blast
|
|
pBlast=GetDef("Blast");
|
|
pFSpark=GetDef("FSpark");
|
|
// get fire, if fire particles are desired
|
|
if (Config.Graphics.FireParticles)
|
|
{
|
|
pFire1=GetDef("Fire");
|
|
pFire2=GetDef("Fire2");
|
|
}
|
|
else
|
|
pFire1=pFire2=NULL;
|
|
// if fire is drawn w/o background fct: unload fire face if both fire particles are assigned
|
|
// but this is not done here
|
|
//if (IsFireParticleLoaded())
|
|
// ::GraphicsResource.fctFire.Clear();
|
|
}
|
|
|
|
int32_t C4ParticleSystem::Push(C4ParticleDef *pOfDef, float dxdir, float dydir)
|
|
{
|
|
int32_t iNumPushed=0;
|
|
// go through all particle chunks
|
|
for (C4ParticleChunk *pChnk=&Chunk; pChnk; pChnk=pChnk->pNext)
|
|
{
|
|
// go through all particles
|
|
C4Particle *pPrt = pChnk->Data; int32_t i=C4Px_BufSize;
|
|
while (i--)
|
|
{
|
|
// def fits?
|
|
if (!pOfDef || pPrt->pDef==pOfDef)
|
|
{
|
|
// push it!
|
|
pPrt->xdir+=dxdir;
|
|
pPrt->ydir+=dydir;
|
|
// count pushed
|
|
++iNumPushed;
|
|
}
|
|
// next particle
|
|
++pPrt;
|
|
}
|
|
}
|
|
// done
|
|
return iNumPushed;
|
|
}
|
|
|
|
bool fxSmokeInit(C4Particle *pPrt, C4Object *pTarget)
|
|
{
|
|
// init lifetime
|
|
pPrt->life=pPrt->pDef->MinLifetime;
|
|
int32_t iLD=pPrt->pDef->MaxLifetime-pPrt->pDef->MinLifetime;
|
|
if (iLD) pPrt->life += SafeRandom(iLD);
|
|
// use high-word of life to store init-status
|
|
pPrt->life |= (pPrt->life/17)<<16;
|
|
// set kind - ydir is unused anyway; set last kind reeeaaally seldom
|
|
pPrt->ydir=(float) SafeRandom(15)+SafeRandom(300)/299;
|
|
// set color
|
|
if (!pPrt->b) pPrt->b=0xff4b4b4b; else pPrt->b|=0xff000000;
|
|
// always OK
|
|
return true;
|
|
}
|
|
|
|
bool fxSmokeExec(C4Particle *pPrt, C4Object *pTarget)
|
|
{
|
|
// lifetime
|
|
if (!--pPrt->life) return false;
|
|
bool fBuilding = !!(pPrt->life&0x7fff0000);
|
|
// still building?
|
|
if (fBuilding)
|
|
{
|
|
// decrease init-time
|
|
pPrt->life-=0x010000;
|
|
// increase color value
|
|
pPrt->b-=0x10000000;
|
|
// if full-grown, adjust to lifetime
|
|
if (!(pPrt->life&0x7fff0000))
|
|
pPrt->b=(pPrt->b&0xffffff)|((255-pPrt->life)<<24);
|
|
}
|
|
// color change
|
|
DWORD dwClr = pPrt->b;
|
|
pPrt->b = (LightenClrBy(dwClr, 1)&0xffffff) | Min<int32_t>((dwClr>>24)+1, 255)<<24;
|
|
// wind to float
|
|
if (!(pPrt->b%12) || fBuilding)
|
|
{
|
|
pPrt->xdir=0.025f*::Weather.GetWind(int32_t(pPrt->x),int32_t(pPrt->y));
|
|
if (pPrt->xdir<-2.0f) pPrt->xdir=-2.0f; else if (pPrt->xdir>2.0f) pPrt->xdir=2.0f;
|
|
pPrt->xdir+=0.1f*SafeRandom(41)-2.0f;
|
|
}
|
|
// float
|
|
if (GBackSolid(int32_t(pPrt->x), int32_t(pPrt->y-pPrt->a)))
|
|
{
|
|
// if stuck, decay; otherwise, move down
|
|
if (!GBackSolid(int32_t(pPrt->x), int32_t(pPrt->y))) pPrt->y+=0.4f; else pPrt->a-=2;
|
|
}
|
|
else
|
|
--pPrt->y;
|
|
pPrt->x+=pPrt->xdir;
|
|
// increase in size
|
|
pPrt->a *= 1.01f;
|
|
// done, keep
|
|
return true;
|
|
}
|
|
|
|
void fxSmokeDraw(C4Particle *pPrt, C4TargetFacet &cgo, C4Object *pTarget)
|
|
{
|
|
C4ParticleDef *pDef = pPrt->pDef;
|
|
// apply parallaxity to target pos
|
|
int32_t tx=cgo.TargetX*pDef->Parallaxity[0]/100;
|
|
int32_t ty=cgo.TargetY*pDef->Parallaxity[1]/100;
|
|
// check if it's in screen range
|
|
if (!Inside(pPrt->x, tx-pPrt->a, tx+cgo.Wdt+pPrt->a)) return;
|
|
if (!Inside(pPrt->y, ty-pPrt->a, ty+cgo.Hgt+pPrt->a)) return;
|
|
// get pos
|
|
int32_t cx=int32_t(pPrt->x)+cgo.X-tx;
|
|
int32_t cy=int32_t(pPrt->y)+cgo.Y-ty;
|
|
// get phase by particle index
|
|
int32_t i=(int32_t) pPrt->ydir;
|
|
int32_t ipx=i/4;
|
|
int32_t ipy=i%4;
|
|
// draw at pos
|
|
Application.DDraw->ActivateBlitModulation(pPrt->b);
|
|
pDef->Gfx.DrawX(cgo.Surface, int32_t(cx-pPrt->a), int32_t(cy-pPrt->a), int32_t(pPrt->a*2), int32_t(pPrt->a*2), ipx, ipy);
|
|
Application.DDraw->DeactivateBlitModulation();
|
|
}
|
|
|
|
bool fxStdInit(C4Particle *pPrt, C4Object *pTarget)
|
|
{
|
|
if (pPrt->pDef->Delay)
|
|
// delay given: lifetime starts at zero
|
|
pPrt->life=0;
|
|
else
|
|
// init lifetime as phase
|
|
pPrt->life=SafeRandom(pPrt->pDef->Length);
|
|
// default color
|
|
if (!pPrt->b) pPrt->b=0xffffff;
|
|
// always OK
|
|
return true;
|
|
}
|
|
|
|
bool fxStdExec(C4Particle *pPrt, C4Object *pTarget)
|
|
{
|
|
|
|
float dx = pPrt->x, dy = pPrt->y;
|
|
float dxdir = pPrt->xdir, dydir = pPrt->ydir;
|
|
// rel. position & movement
|
|
if(pPrt->pDef->Attach && pTarget != NULL)
|
|
{
|
|
dx += pTarget->GetX();
|
|
dy += pTarget->GetY();
|
|
dxdir += fixtof(pTarget->xdir);
|
|
dydir += fixtof(pTarget->ydir);
|
|
}
|
|
|
|
// move
|
|
if (pPrt->xdir || pPrt->ydir)
|
|
{
|
|
if (pPrt->pDef->VertexCount && GBackSolid(int32_t( dx + pPrt->xdir),int32_t( dy + pPrt->ydir + pPrt->pDef->VertexY* pPrt->a/100.0f) ))
|
|
{
|
|
// collision
|
|
if (pPrt->pDef->CollisionProc)
|
|
if (!pPrt->pDef->CollisionProc(pPrt,pTarget)) return false;
|
|
}
|
|
else if (pPrt->pDef->RByV != 2)
|
|
{
|
|
pPrt->x += pPrt->xdir;
|
|
pPrt->y += pPrt->ydir;
|
|
}
|
|
else
|
|
{
|
|
// With RByV=2, the V is only used for rotation, not for movement
|
|
}
|
|
}
|
|
// apply gravity
|
|
if (pPrt->pDef->GravityAcc) pPrt->ydir+=fixtof(GravAccel * pPrt->pDef->GravityAcc)/100.0f;
|
|
// apply WindDrift
|
|
if(pPrt->pDef->WindDrift && !GBackSolid(int32_t(dx), int32_t(dy)))
|
|
{
|
|
// Air speed: Wind plus some random
|
|
int32_t iWind = GBackWind(int32_t(dx), int32_t(dy));
|
|
//FIXED txdir = itofix(iWind, 15) + FIXED256(Random(1200) - 600);
|
|
float txdir = iWind / 15.0f;
|
|
//FIXED tydir = FIXED256(Random(1200) - 600);
|
|
float tydir = 0;
|
|
|
|
// Air friction, based on WindDrift.
|
|
int32_t iWindDrift = Max(pPrt->pDef->WindDrift - 20, 0);
|
|
pPrt->xdir += ((txdir - dxdir) * iWindDrift) / 800;
|
|
pPrt->ydir += ((tydir - dydir) * iWindDrift) / 800;
|
|
}
|
|
// fade out
|
|
int32_t iFade = pPrt->pDef->AlphaFade;
|
|
if (iFade < 0) if (Game.FrameCounter % -iFade == 0) iFade = 1; else iFade = 0;
|
|
if (iFade)
|
|
{
|
|
if(pPrt->pDef->FadeDelay == 0 || Game.FrameCounter % pPrt->pDef->FadeDelay == 0)
|
|
{
|
|
DWORD dwClr=pPrt->b;
|
|
int32_t iAlpha=dwClr>>24;
|
|
iAlpha+=pPrt->pDef->AlphaFade;
|
|
if (iAlpha>=0xff) return false;
|
|
pPrt->b=(dwClr&0xffffff) | (iAlpha<<24);
|
|
}
|
|
}
|
|
// if delay is given, advance lifetime
|
|
if (pPrt->pDef->Delay)
|
|
{
|
|
if (pPrt->life<0)
|
|
{
|
|
// decay
|
|
return pPrt->life-->=-pPrt->pDef->FadeOutLen*pPrt->pDef->FadeOutDelay;
|
|
}
|
|
++pPrt->life;
|
|
// check if still alive
|
|
int32_t iPhase=pPrt->life/pPrt->pDef->Delay;
|
|
int32_t length=pPrt->pDef->Length-pPrt->pDef->Reverse;
|
|
if (iPhase>=length*pPrt->pDef->Repeats+pPrt->pDef->Reverse)
|
|
{
|
|
// do fadeout, if assigned
|
|
if (!pPrt->pDef->FadeOutLen) return false;
|
|
pPrt->life=-1;
|
|
}
|
|
return true;
|
|
}
|
|
// outside landscape range?
|
|
bool kp;
|
|
if (dxdir>0) kp=(dx-pPrt->a<GBackWdt); else kp=(dx+pPrt->a>0);
|
|
if (dydir>0) kp=kp&&(dy-pPrt->a<GBackHgt); else kp=kp&&(dy+pPrt->a>pPrt->pDef->YOff);
|
|
return kp;
|
|
}
|
|
|
|
bool fxBounce(C4Particle *pPrt, C4Object *pTarget)
|
|
{
|
|
// reverse xdir/ydir
|
|
pPrt->xdir=-pPrt->xdir;
|
|
pPrt->ydir=-pPrt->ydir;
|
|
return true;
|
|
}
|
|
|
|
bool fxBounceY(C4Particle *pPrt, C4Object *pTarget)
|
|
{
|
|
// reverse ydir only
|
|
pPrt->ydir=-pPrt->ydir;
|
|
return true;
|
|
}
|
|
|
|
bool fxStop(C4Particle *pPrt, C4Object *pTarget)
|
|
{
|
|
// zero xdir/ydir
|
|
pPrt->xdir=pPrt->ydir=0;
|
|
return true;
|
|
}
|
|
|
|
bool fxDie(C4Particle *pPrt, C4Object *pTarget)
|
|
{
|
|
// DIEEEEEE
|
|
return false;
|
|
}
|
|
|
|
void fxStdDraw(C4Particle *pPrt, C4TargetFacet &cgo, C4Object *pTarget)
|
|
{
|
|
// get def
|
|
C4ParticleDef *pDef = pPrt->pDef;
|
|
// apply parallaxity to target pos
|
|
int32_t tx=cgo.TargetX*pDef->Parallaxity[0]/100;
|
|
int32_t ty=cgo.TargetY*pDef->Parallaxity[1]/100;
|
|
|
|
float dx = pPrt->x, dy = pPrt->y;
|
|
float dxdir = pPrt->xdir, dydir = pPrt->ydir;
|
|
// relative position & movement
|
|
if(pPrt->pDef->Attach && pTarget != NULL)
|
|
{
|
|
dx += pTarget->GetX();
|
|
dy += pTarget->GetY();
|
|
dxdir += fixtof(pTarget->xdir);
|
|
dydir += fixtof(pTarget->ydir);
|
|
}
|
|
|
|
// check if it's in screen range
|
|
if (!Inside(dx, tx-pPrt->a, tx+cgo.Wdt+pPrt->a)) return;
|
|
if (!Inside(dy, ty-pPrt->a, ty+cgo.Hgt+pPrt->a)) return;
|
|
// get pos
|
|
int32_t cgox=cgo.X-tx,cgoy=cgo.Y-ty;
|
|
int32_t cx=int32_t(dx+cgox);
|
|
int32_t cy=int32_t(dy+cgoy);
|
|
// get phase
|
|
int32_t iPhase=pPrt->life;
|
|
if (pDef->Delay)
|
|
if (iPhase >= 0)
|
|
{
|
|
iPhase/=pDef->Delay;
|
|
int32_t length=pDef->Length;
|
|
if (pDef->Reverse)
|
|
{
|
|
--length; iPhase%=length*2;
|
|
if (iPhase>length) iPhase=length*2+1-iPhase;
|
|
}
|
|
else iPhase%=length;
|
|
}
|
|
else iPhase=(iPhase+1)/-pDef->FadeOutDelay+pDef->Length;
|
|
// get rotation
|
|
int32_t r=0;
|
|
if ((pDef->RByV==1) || (pDef->RByV==2)) // rotation by direction
|
|
r=Angle(0,0, (int32_t) (dxdir*10.0f), (int32_t) (dydir*10.0f))*100;
|
|
if (pDef->RByV==3) // random rotation - currently a pseudo random rotation by x/y position
|
|
r = (((int32_t)(pPrt->x*23 + pPrt->y*12)) % 360) * 100;
|
|
// draw at pos
|
|
Application.DDraw->ActivateBlitModulation(pPrt->b);
|
|
Application.DDraw->StorePrimaryClipper();
|
|
Application.DDraw->SubPrimaryClipper(cgox, cgoy+pDef->YOff, 100000, 100000);
|
|
if (pDef->Additive) lpDDraw->SetBlitMode(C4GFXBLIT_ADDITIVE);
|
|
int32_t iDrawWdt=int32_t(pPrt->a);
|
|
int32_t iDrawHgt=int32_t(pDef->Aspect*iDrawWdt);
|
|
if (r)
|
|
pDef->Gfx.DrawXR(cgo.Surface, cx-iDrawWdt, cy-iDrawHgt, iDrawWdt*2, iDrawHgt*2, iPhase, 0, r);
|
|
else
|
|
pDef->Gfx.DrawX(cgo.Surface, cx-iDrawWdt, cy-iDrawHgt, iDrawWdt*2, iDrawHgt*2, iPhase, 0);
|
|
Application.DDraw->ResetBlitMode();
|
|
Application.DDraw->RestorePrimaryClipper();
|
|
Application.DDraw->DeactivateBlitModulation();
|
|
}
|
|
|
|
C4ParticleProcRec C4ParticleProcMap[] = {
|
|
{ "SmokeInit", fxSmokeInit },
|
|
{ "SmokeExec", fxSmokeExec },
|
|
{ "StdInit", fxStdInit },
|
|
{ "StdExec", fxStdExec },
|
|
{ "Bounce", fxBounce },
|
|
{ "BounceY", fxBounceY },
|
|
{ "Stop", fxStop },
|
|
{ "Die", fxDie },
|
|
{ "", 0 } };
|
|
|
|
C4ParticleDrawProcRec C4ParticleDrawProcMap[] = {
|
|
{ "Smoke", fxSmokeDraw },
|
|
{ "Std", fxStdDraw },
|
|
{ "", 0 } };
|
|
|
|
C4ParticleSystem Particles;
|