/* =========================================================================== Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . =========================================================================== */ #if !defined(FX_UTIL_H_INC) #include "FxUtil.h" #endif #include "../qcommon/sstring.h" typedef sstring_t fxString_t; #include "../game/genericparser2.h" #include "qcommon/safe/string.h" #include #ifndef FX_SCHEDULER_H_INC #define FX_SCHEDULER_H_INC #define FX_FILE_PATH "effects" #define FX_MAX_TRACE_DIST WORLD_SIZE #define FX_MAX_EFFECTS 150 // how many effects the system can store #define FX_MAX_EFFECT_COMPONENTS 24 // how many primitives an effect can hold, this should be plenty #define FX_MAX_PRIM_NAME 32 //----------------------------------------------- // These are spawn flags for primitiveTemplates //----------------------------------------------- #define FX_ORG_ON_SPHERE 0x00001 // Pretty dang expensive, calculates a point on a sphere/ellipsoid #define FX_AXIS_FROM_SPHERE 0x00002 // Can be used in conjunction with org_on_sphere to cause particles to move out // from the center of the sphere #define FX_ORG_ON_CYLINDER 0x00004 // calculate point on cylinder/disk #define FX_ORG2_FROM_TRACE 0x00010 #define FX_TRACE_IMPACT_FX 0x00020 // if trace impacts, we should play one of the specified impact fx files #define FX_ORG2_IS_OFFSET 0x00040 // template specified org2 should be the offset from a trace endpos or // passed in org2. You might use this to lend a random flair to the endpos. // Note: this is done pre-trace, so you may have to specify large numbers for this #define FX_CHEAP_ORG_CALC 0x00100 // Origin is calculated relative to passed in axis unless this is on. #define FX_CHEAP_ORG2_CALC 0x00200 // Origin2 is calculated relative to passed in axis unless this is on. #define FX_VEL_IS_ABSOLUTE 0x00400 // Velocity isn't relative to passed in axis with this flag on. #define FX_ACCEL_IS_ABSOLUTE 0x00800 // Acceleration isn't relative to passed in axis with this flag on. #define FX_RAND_ROT_AROUND_FWD 0x01000 // Randomly rotates up and right around forward vector #define FX_EVEN_DISTRIBUTION 0x02000 // When you have a delay, it normally picks a random time to play. When // this flag is on, it generates an even time distribution #define FX_RGB_COMPONENT_INTERP 0x04000 // Picks a color on the line defined by RGB min & max, default is to pick color in cube defined by min & max #define FX_AFFECTED_BY_WIND 0x10000 // we take into account our wind vector when we spawn in #define FX_SND_LESS_ATTENUATION 0x20000 // attenuate sounds less //----------------------------------------------------------------- // // CMediaHandles // // Primitive templates might want to use a list of sounds, shaders // or models to get a bit more variation in their effects. // //----------------------------------------------------------------- class CMediaHandles { private: std::vector mMediaList; public: void AddHandle( int item ) { mMediaList.push_back( item ); } int GetHandle() { if (mMediaList.size()==0) {return 0;} else {return mMediaList[irand(0,(int)mMediaList.size()-1)];} } void operator=(const CMediaHandles &that ); }; //----------------------------------------------------------------- // // CFxRange // // Primitive templates typically use this class to define each of // its members. This is done to make it easier to create effects // with a desired range of characteristics. // //----------------------------------------------------------------- class CFxRange { private: float mMin; float mMax; public: CFxRange() {mMin=0; mMax=0;} inline void SetRange(float min,float max) {mMin=min; mMax=max;} inline void SetMin(float min) {mMin=min;} inline void SetMax(float max) {mMax=max;} inline float GetMax() const {return mMax;} inline float GetMin() const {return mMin;} inline float GetVal(float percent) const {if(mMin == mMax){return mMin;} return (mMin + (mMax - mMin) * percent);} inline float GetVal() const {if(mMin == mMax){return mMin;} return flrand(mMin, mMax);} inline int GetRoundedVal() const {if(mMin == mMax){return mMin;} return (int)(flrand(mMin, mMax) + 0.5f);} inline void ForceRange(float min,float max) {if(mMin < min){mMin=min;} if(mMin > max){mMin=max;} if(mMax < min){mMax=min;} if(mMax > max){mMax=max;}} inline void Sort() {if(mMin > mMax){float temp = mMin; mMin=mMax;mMax=temp;}} void operator=(const CFxRange &that) {mMin=that.mMin; mMax=that.mMax;} bool operator==(const CFxRange &rhs) const { return ((mMin == rhs.mMin) && (mMax == rhs.mMax)); } }; //---------------------------- // Supported primitive types //---------------------------- enum EPrimType { None = 0, Particle, // sprite Line, Tail, // comet-like tail thing Cylinder, Emitter, // emits effects as it moves, can also attach a chunk Sound, Decal, // projected onto architecture OrientedParticle, Electricity, FxRunner, Light, CameraShake, ScreenFlash }; //----------------------------------------------------------------- // // CPrimitiveTemplate // // The primitive template is used to spawn 1 or more fx primitives // with the range of characteristics defined by the template. // // As such, I just made this one huge shared class knowing that // there won't be many of them in memory at once, and we won't // be dynamically creating and deleting them mid-game. Also, // note that not every primitive type will use all of these fields. // //----------------------------------------------------------------- class CPrimitiveTemplate { public: // These kinds of things should not even be allowed to be accessed publicly bool mCopy; int mRefCount; // For a copy of a primitive...when we figure out how many items we want to spawn, // we'll store that here and then decrement us for each we actually spawn. When we // hit zero, we are no longer used and so we can just free ourselves char mName[FX_MAX_PRIM_NAME]; EPrimType mType; CFxRange mSpawnDelay; CFxRange mSpawnCount; CFxRange mLife; int mCullRange; CMediaHandles mMediaHandles; CMediaHandles mImpactFxHandles; CMediaHandles mDeathFxHandles; CMediaHandles mEmitterFxHandles; CMediaHandles mPlayFxHandles; int mFlags; // These need to get passed on to the primitive int mSpawnFlags; // These are only used to control spawning, but never get passed to prims. vec3_t mMin; vec3_t mMax; CFxRange mOrigin1X; CFxRange mOrigin1Y; CFxRange mOrigin1Z; CFxRange mOrigin2X; CFxRange mOrigin2Y; CFxRange mOrigin2Z; CFxRange mRadius; // spawn on sphere/ellipse/disk stuff. CFxRange mHeight; CFxRange mWindModifier; CFxRange mRotation; CFxRange mRotationDelta; CFxRange mAngle1; CFxRange mAngle2; CFxRange mAngle3; CFxRange mAngle1Delta; CFxRange mAngle2Delta; CFxRange mAngle3Delta; CFxRange mVelX; CFxRange mVelY; CFxRange mVelZ; CFxRange mAccelX; CFxRange mAccelY; CFxRange mAccelZ; CFxRange mGravity; CFxRange mDensity; CFxRange mVariance; CFxRange mRedStart; CFxRange mGreenStart; CFxRange mBlueStart; CFxRange mRedEnd; CFxRange mGreenEnd; CFxRange mBlueEnd; CFxRange mRGBParm; CFxRange mAlphaStart; CFxRange mAlphaEnd; CFxRange mAlphaParm; CFxRange mSizeStart; CFxRange mSizeEnd; CFxRange mSizeParm; CFxRange mSize2Start; CFxRange mSize2End; CFxRange mSize2Parm; CFxRange mLengthStart; CFxRange mLengthEnd; CFxRange mLengthParm; CFxRange mTexCoordS; CFxRange mTexCoordT; CFxRange mElasticity; private: // Lower level parsing utilities bool ParseVector( const gsl::cstring_view& val, vec3_t min, vec3_t max ); bool ParseFloat( const gsl::cstring_view& val, float& min, float& max ); bool ParseGroupFlags( const gsl::cstring_view& val, int& flags ); // Base key processing // Note that these all have their own parse functions in case it becomes important to do certain kinds // of validation specific to that type. bool ParseMin( const gsl::cstring_view& val ); bool ParseMax( const gsl::cstring_view& val ); bool ParseDelay( const gsl::cstring_view& val ); bool ParseCount( const gsl::cstring_view& val ); bool ParseLife( const gsl::cstring_view& val ); bool ParseElasticity( const gsl::cstring_view& val ); bool ParseFlags( const gsl::cstring_view& val ); bool ParseSpawnFlags( const gsl::cstring_view& val ); bool ParseOrigin1( const gsl::cstring_view& val ); bool ParseOrigin2( const gsl::cstring_view& val ); bool ParseRadius( const gsl::cstring_view& val ); bool ParseHeight( const gsl::cstring_view& val ); bool ParseWindModifier( const gsl::cstring_view& val ); bool ParseRotation( const gsl::cstring_view& val ); bool ParseRotationDelta( const gsl::cstring_view& val ); bool ParseAngle( const gsl::cstring_view& val ); bool ParseAngleDelta( const gsl::cstring_view& val ); bool ParseVelocity( const gsl::cstring_view& val ); bool ParseAcceleration( const gsl::cstring_view& val ); bool ParseGravity( const gsl::cstring_view& val ); bool ParseDensity( const gsl::cstring_view& val ); bool ParseVariance( const gsl::cstring_view& val ); /// Case insensitive map from cstring_view to Value template< typename Value > using StringViewIMap = std::map< gsl::cstring_view, Value, Q::CStringViewILess >; using ParseMethod = bool ( CPrimitiveTemplate::* )( const gsl::cstring_view& ); // Group type processing bool ParseGroup( const CGPGroup& grp, const StringViewIMap< ParseMethod >& parseMethods, gsl::czstring name ); bool ParseRGB( const CGPGroup& grp ); bool ParseAlpha( const CGPGroup& grp ); bool ParseSize( const CGPGroup& grp ); bool ParseSize2( const CGPGroup& grp ); bool ParseLength( const CGPGroup& grp ); bool ParseModels( const CGPProperty& grp ); bool ParseShaders( const CGPProperty& grp ); bool ParseSounds( const CGPProperty& grp ); bool ParseImpactFxStrings( const CGPProperty& grp ); bool ParseDeathFxStrings( const CGPProperty& grp ); bool ParseEmitterFxStrings( const CGPProperty& grp ); bool ParsePlayFxStrings( const CGPProperty& grp ); // Group keys bool ParseRGBStart( const gsl::cstring_view& val ); bool ParseRGBEnd( const gsl::cstring_view& val ); bool ParseRGBParm( const gsl::cstring_view& val ); bool ParseRGBFlags( const gsl::cstring_view& val ); bool ParseAlphaStart( const gsl::cstring_view& val ); bool ParseAlphaEnd( const gsl::cstring_view& val ); bool ParseAlphaParm( const gsl::cstring_view& val ); bool ParseAlphaFlags( const gsl::cstring_view& val ); bool ParseSizeStart( const gsl::cstring_view& val ); bool ParseSizeEnd( const gsl::cstring_view& val ); bool ParseSizeParm( const gsl::cstring_view& val ); bool ParseSizeFlags( const gsl::cstring_view& val ); bool ParseSize2Start( const gsl::cstring_view& val ); bool ParseSize2End( const gsl::cstring_view& val ); bool ParseSize2Parm( const gsl::cstring_view& val ); bool ParseSize2Flags( const gsl::cstring_view& val ); bool ParseLengthStart( const gsl::cstring_view& val ); bool ParseLengthEnd( const gsl::cstring_view& val ); bool ParseLengthParm( const gsl::cstring_view& val ); bool ParseLengthFlags( const gsl::cstring_view& val ); public: CPrimitiveTemplate(); CPrimitiveTemplate( const CPrimitiveTemplate& rhs ); ~CPrimitiveTemplate() {}; bool ParsePrimitive( const CGPGroup& grp ); void operator=(const CPrimitiveTemplate &that); }; // forward declaration struct SEffectTemplate; // Effects are built of one or more primitives struct SEffectTemplate { bool mInUse; bool mCopy; char mEffectName[MAX_QPATH]; // is this extraneous?? int mPrimitiveCount; int mRepeatDelay; CPrimitiveTemplate *mPrimitives[FX_MAX_EFFECT_COMPONENTS]; bool operator == (const char * name) const { return !Q_stricmp( mEffectName, name ); } void operator=(const SEffectTemplate &that); }; template class PoolAllocator { public: PoolAllocator() : pool (new T[N]) , freeAndAllocated (new int[N]) , numFree (N) , highWatermark (0) { for ( int i = 0; i < N; i++ ) { freeAndAllocated[i] = i; } } T *Alloc() { if ( numFree == 0 ) { return NULL; } T *ptr = new (&pool[freeAndAllocated[0]]) T; std::rotate (freeAndAllocated, freeAndAllocated + 1, freeAndAllocated + N); numFree--; highWatermark = Q_max(highWatermark, N - numFree); return ptr; } void TransferTo ( PoolAllocator& allocator ) { allocator.freeAndAllocated = freeAndAllocated; allocator.highWatermark = highWatermark; allocator.numFree = numFree; allocator.pool = pool; highWatermark = 0; numFree = N; freeAndAllocated = NULL; pool = NULL; } bool OwnsPtr ( const T *ptr ) const { return ptr >= pool && ptr < (pool + N); } void Free ( T *ptr ) { for ( int i = numFree; i < N; i++ ) { T *p = &pool[freeAndAllocated[i]]; if ( p == ptr ) { if ( i > numFree ) { std::rotate (freeAndAllocated + numFree, freeAndAllocated + i, freeAndAllocated + i + 1); } p->~T(); numFree++; break; } } } int GetHighWatermark() const { return highWatermark; } ~PoolAllocator() { for ( int i = numFree; i < N; i++ ) { T *p = &pool[freeAndAllocated[i]]; p->~T(); } delete [] freeAndAllocated; delete [] pool; } private: PoolAllocator ( const PoolAllocator& ); PoolAllocator& operator = ( const PoolAllocator& ); T *pool; // The first 'numFree' elements are the indexes of the free slots. // The remaining elements are the indexes of the allocated slots. int *freeAndAllocated; int numFree; int highWatermark; }; template class PagedPoolAllocator { public: PagedPoolAllocator () : numPages (1) , pages (new PoolAllocator[1]()) { } T *Alloc () { T *ptr = NULL; for ( int i = 0; i < numPages && ptr == NULL; i++ ) { ptr = pages[i].Alloc (); } if ( ptr == NULL ) { PoolAllocator *newPages = new PoolAllocator[numPages + 1] (); for ( int i = 0; i < numPages; i++ ) { pages[i].TransferTo (newPages[i]); } delete[] pages; pages = newPages; ptr = pages[numPages].Alloc (); if ( ptr == NULL ) { return NULL; } numPages++; } return ptr; } void Free ( T *ptr ) { for ( int i = 0; i < numPages; i++ ) { if ( pages[i].OwnsPtr (ptr) ) { pages[i].Free (ptr); break; } } } int GetHighWatermark () const { int total = 0; for ( int i = 0; i < numPages; i++ ) { total += pages[i].GetHighWatermark (); } return total; } ~PagedPoolAllocator () { delete[] pages; } private: int numPages; PoolAllocator *pages; }; //----------------------------------------------------------------- // // CFxScheduler // // The scheduler not only handles requests to play an effect, it // tracks the request throughout its life if necessary, creating // any of the delayed components as needed. // //----------------------------------------------------------------- // needs to be in global space now (loadsave stuff) #define MAX_LOOPED_FX 32 // We hold a looped effect here struct SLoopedEffect { int mId; // effect id int mBoltInfo; // used to determine which bolt on the ghoul2 model we should be attaching this effect to int mNextTime; //time to render again int mLoopStopTime; //time to die bool mPortalEffect; // rww - render this before skyportals, and not in the normal world view. bool mIsRelative; // bolt this puppy on keep it updated void sg_export( ojk::SavedGameHelper& saved_game) const { saved_game.write(mId); saved_game.write(mBoltInfo); saved_game.write(mNextTime); saved_game.write(mLoopStopTime); saved_game.write(mPortalEffect); saved_game.write(mIsRelative); saved_game.skip(2); } void sg_import( ojk::SavedGameHelper& saved_game) { saved_game.read(mId); saved_game.read(mBoltInfo); saved_game.read(mNextTime); saved_game.read(mLoopStopTime); saved_game.read(mPortalEffect); saved_game.read(mIsRelative); saved_game.skip(2); } }; class CFxScheduler { private: // We hold a scheduled effect here struct SScheduledEffect { CPrimitiveTemplate *mpTemplate; // primitive template int mStartTime; char mModelNum; // uset to determine which ghoul2 model we want to bolt this effect to char mBoltNum; // used to determine which bolt on the ghoul2 model we should be attaching this effect to short mEntNum; // used to determine which entity this ghoul model is attached to. short mClientID; // FIXME: redundant. this is used for muzzle bolts, merge into normal bolting bool mPortalEffect; // rww - render this before skyportals, and not in the normal world view. bool mIsRelative; // bolt this puppy on keep it updated vec3_t mOrigin; vec3_t mAxis[3]; }; /* Looped Effects get stored and reschedule at mRepeatRate */ // must be in sync with gLoopedEffectArray[MAX_LOOPED_FX]! // SLoopedEffect mLoopedEffectArray[MAX_LOOPED_FX]; int ScheduleLoopedEffect( int id, int boltInfo, bool isPortal, int iLoopTime, bool isRelative ); void AddLoopedEffects( ); // this makes looking up the index based on the string name much easier typedef std::map TEffectID; typedef std::list TScheduledEffect; // Effects SEffectTemplate mEffectTemplates[FX_MAX_EFFECTS]; TEffectID mEffectIDs; // if you only have the unique effect name, you'll have to use this to get the ID. // List of scheduled effects that will need to be created at the correct time. TScheduledEffect mFxSchedule; PagedPoolAllocator mScheduledEffectsPool; // Private function prototypes SEffectTemplate *GetNewEffectTemplate( int *id, const char *file ); void AddPrimitiveToEffect( SEffectTemplate *fx, CPrimitiveTemplate *prim ); int ParseEffect( const char *file, const CGPGroup& base ); void CreateEffect( CPrimitiveTemplate *fx, const vec3_t origin, vec3_t axis[3], int lateTime, int clientID = -1, int modelNum = -1, int boltNum = -1 ); void CreateEffect( CPrimitiveTemplate *fx, int clientID, int lateTime ); public: CFxScheduler(); void LoadSave_Read(); void LoadSave_Write(); void FX_CopeWithAnyLoadedSaveGames(); int RegisterEffect( const char *file, bool bHasCorrectPath = false ); // handles pre-caching // Nasty overloaded madness void PlayEffect( int id, vec3_t org, bool isPortal = false ); // uses a default up axis void PlayEffect( int id, vec3_t org, vec3_t fwd, bool isPortal = false ); // builds arbitrary perp. right vector, does a cross product to define up void PlayEffect( int id, vec3_t origin, vec3_t axis[3], const int boltInfo=-1, const int entNum=-1, bool isPortal = false, int iLoopTime = false, bool isRelative = false ); void PlayEffect( const char *file, vec3_t org, bool isPortal = false ); // uses a default up axis void PlayEffect( const char *file, vec3_t org, vec3_t fwd, bool isPortal = false ); // builds arbitrary perp. right vector, does a cross product to define up void PlayEffect( const char *file, vec3_t origin, vec3_t axis[3], const int boltInfo, const int entNum, bool isPortal = false, int iLoopTime = false, bool isRelative = false ); //for muzzle void PlayEffect( const char *file, int clientID, bool isPortal = false ); void StopEffect( const char *file, const int boltInfo, bool isPortal = false ); //find a scheduled Looping effect with these parms and kill it void AddScheduledEffects( bool portal ); // call once per CGame frame [rww ammendment - twice now actually, but first only renders portal effects] int NumScheduledFx() { return (int)mFxSchedule.size(); } void Clean(bool bRemoveTemplates = true, int idToPreserve = 0); // clean out the system // FX Override functions SEffectTemplate *GetEffectCopy( int fxHandle, int *newHandle ); SEffectTemplate *GetEffectCopy( const char *file, int *newHandle ); CPrimitiveTemplate *GetPrimitiveCopy( SEffectTemplate *effectCopy, const char *componentName ); }; //------------------- // The one and only //------------------- extern CFxScheduler theFxScheduler; #endif // FX_SCHEDULER_H_INC