diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BoneFXUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BoneFXUpdate.h index 8aea676986..a2a9f72075 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BoneFXUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BoneFXUpdate.h @@ -243,6 +243,10 @@ class BoneFXUpdate : public UpdateModule virtual UpdateSleepTime update(); +#if !PRESERVE_RETAIL_BEHAVIOR + void setInactive() { m_inactive = TRUE; } +#endif + protected: virtual void onObjectCreated(); @@ -273,4 +277,8 @@ class BoneFXUpdate : public UpdateModule BodyDamageType m_curBodyState; Bool m_bonesResolved[BODYDAMAGETYPE_COUNT]; Bool m_active; + +#if !PRESERVE_RETAIL_BEHAVIOR + Bool m_inactive; +#endif }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/PartitionManager.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/PartitionManager.h index 627f5494d5..287e445a3e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/PartitionManager.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/PartitionManager.h @@ -304,6 +304,13 @@ class PartitionCell : public Snapshot // not MPO: allocated in an array Short m_cellX; ///< x-coord of this cell within the Partition Mgr coords (NOT in world coords) Short m_cellY; ///< y-coord of this cell within the Partition Mgr coords (NOT in world coords) +#if !PRESERVE_RETAIL_BEHAVIOR + typedef std::pair WeaponNameDurationPair; + typedef std::vector WeaponNameDurationVec; + + WeaponNameDurationVec m_weaponNameDurationVec; +#endif + public: // Note, we allocate these in arrays, thus we must have a default ctor (and NOT descend from MPO) @@ -359,6 +366,10 @@ class PartitionCell : public Snapshot // not MPO: allocated in an array // intended only for CellAndObjectIntersection. void friend_removeFromCellList(CellAndObjectIntersection *coi); + +#if !PRESERVE_RETAIL_BEHAVIOR + Bool checkWeaponNameStack(const AsciiString& weaponName, Int maxCount, UnsignedInt duration); +#endif }; //===================================== @@ -1528,6 +1539,10 @@ class PartitionManager : public SubsystemInterface, public Snapshot // If saveToFog is false, then we are writing STORE_PERMENANT_REVEAL void storeFoggedCells(ShroudStatusStoreRestore &outPartitionStore, Bool storeToFog) const; void restoreFoggedCells(const ShroudStatusStoreRestore &inPartitionStore, Bool restoreToFog); + +#if !PRESERVE_RETAIL_BEHAVIOR + Bool checkWeaponNameStackAtCell(const Coord3D *pos, const AsciiString& weaponName, Int maxCount, UnsignedInt duration); +#endif }; // ----------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h index 3afebed868..2cb5fa8817 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h @@ -464,6 +464,13 @@ class WeaponTemplate : public MemoryPoolObject Bool isPlayFXWhenStealthed() const { return m_playFXWhenStealthed; } Bool getDieOnDetonate() const { return m_dieOnDetonate; } +#if !PRESERVE_RETAIL_BEHAVIOR + UnsignedInt getFXRenderingInterval() const { return m_fxRenderingInterval; } + UnsignedInt getFXMaxStackInterval() const { return m_fxMaxStackInterval; } + Int getFXMaxStackPerLoc() const { return m_fxMaxStackPerLoc; } + Bool getCheckFireFXForFXMaxStack() const { return m_checkFireFXForFXMaxStack; } +#endif + Bool shouldProjectileCollideWith( const Object* projectileLauncher, const Object* projectile, @@ -563,6 +570,13 @@ class WeaponTemplate : public MemoryPoolObject UnsignedInt m_suspendFXDelay; ///< The fx can be suspended for any delay, in frames, then they will execute as normal Bool m_dieOnDetonate; +#if !PRESERVE_RETAIL_BEHAVIOR + UnsignedInt m_fxRenderingInterval; + UnsignedInt m_fxMaxStackInterval; + Int m_fxMaxStackPerLoc; + Bool m_checkFireFXForFXMaxStack; +#endif + mutable HistoricWeaponDamageList m_historicDamage; mutable UnsignedInt m_historicDamageTriggerId; }; @@ -766,6 +780,11 @@ class Weapon : public MemoryPoolObject, void setClipPercentFull(Real percent, Bool allowReduction); UnsignedInt getSuspendFXFrame( void ) const { return m_suspendFXFrame; } +#if !PRESERVE_RETAIL_BEHAVIOR + UnsignedInt getLastFXShotFrame( void ) const { return m_lastFXFireFrame; } + void setLastFXShotFrame(UnsignedInt frame) { m_lastFXFireFrame = frame; } +#endif + protected: Weapon(const WeaponTemplate* tmpl, WeaponSlotType wslot); @@ -811,6 +830,10 @@ class Weapon : public MemoryPoolObject, Bool m_pitchLimited; Bool m_leechWeaponRangeActive; ///< This weapon has unlimited range until attack state is aborted! +#if !PRESERVE_RETAIL_BEHAVIOR + UnsignedInt m_lastFXFireFrame; ///< frame a shot was last fired on with FX Rendered +#endif + // setter function for status that should not be used outside this class void setStatus( WeaponStatus status) { m_status = status; } }; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index e5f48c1213..0a8d56e876 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -1250,6 +1250,10 @@ PartitionCell::PartitionCell() // default threat value is 0 m_threatValue[i] = 0; } + +#if !PRESERVE_RETAIL_BEHAVIOR + m_weaponNameDurationVec.clear(); +#endif } //----------------------------------------------------------------------------- @@ -1499,6 +1503,48 @@ void PartitionCell::validateCoiList() } #endif +#if !PRESERVE_RETAIL_BEHAVIOR +//----------------------------------------------------------------------------- +Bool PartitionCell::checkWeaponNameStack(const AsciiString& weaponName, Int maxCount, UnsignedInt duration) +{ + Int count = 0; + UnsignedInt now = TheGameLogic->getFrame(); + for(WeaponNameDurationVec::iterator it = m_weaponNameDurationVec.begin(); it != m_weaponNameDurationVec.end(); /* empty */) + { + // Delete the element if it has exceeded the current frame + if (it->second <= now) + { + it = m_weaponNameDurationVec.erase(it); + continue; + } + + // Add it to count if it matches the name + if(it->first == weaponName) + count++; + + ++it; + } + + if(count < maxCount) + { + // Add it to vector if it has not reached maxCount + WeaponNameDurationPair stack; + stack.first = weaponName; + stack.second = now + duration; + + m_weaponNameDurationVec.push_back(stack); + + // return FALSE if not yet reached Max Count + return FALSE; + } + else + { + // return TRUE if reached Max Count + return TRUE; + } +} +#endif + // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ @@ -4593,6 +4639,21 @@ Bool PartitionManager::isClearLineOfSightTerrain(const Object* obj, const Coord3 #endif } +#if !PRESERVE_RETAIL_BEHAVIOR +//----------------------------------------------------------------------------- +Bool PartitionManager::checkWeaponNameStackAtCell(const Coord3D *pos, const AsciiString& weaponName, Int maxCount, UnsignedInt duration) +{ + Int cellX, cellY; + worldToCell(pos->x, pos->y, &cellX, &cellY); + PartitionCell* cell = getCellAt(cellX, cellY); // might be null if off the edge + DEBUG_ASSERTCRASH(cell != NULL, ("off the map")); + if (cell) + return cell->checkWeaponNameStack(weaponName, maxCount, duration); + else + return FALSE; +} +#endif + // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp index d2bd3ef3d1..31449e75df 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp @@ -90,6 +90,9 @@ BoneFXUpdate::BoneFXUpdate( Thing *thing, const ModuleData* moduleData ) : Updat } m_particleSystemIDs.clear(); m_active = FALSE; +#if !PRESERVE_RETAIL_BEHAVIOR + m_inactive = FALSE; +#endif m_curBodyState = BODY_PRISTINE; } @@ -292,6 +295,11 @@ UpdateSleepTime BoneFXUpdate::update( void ) const BoneFXUpdateModuleData *d = getBoneFXUpdateModuleData(); Int now = TheGameLogic->getFrame(); +#if !PRESERVE_RETAIL_BEHAVIOR + if(m_inactive == TRUE) + return UPDATE_SLEEP_NONE; +#endif + if (m_active == FALSE) { initTimes(); m_active = TRUE; @@ -569,7 +577,11 @@ void BoneFXUpdate::xfer( Xfer *xfer ) { // version +#if !RETAIL_COMPATIBLE_XFER_SAVE && !PRESERVE_RETAIL_BEHAVIOR + XferVersion currentVersion = 2; +#else XferVersion currentVersion = 1; +#endif XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); @@ -646,6 +658,11 @@ void BoneFXUpdate::xfer( Xfer *xfer ) // active xfer->xferBool( &m_active ); +#if !PRESERVE_RETAIL_BEHAVIOR + if( version >= 2) + xfer->xferBool( &m_inactive ); +#endif + } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProjectileStreamUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProjectileStreamUpdate.cpp index 96bb28994c..6cc5e23408 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProjectileStreamUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProjectileStreamUpdate.cpp @@ -91,7 +91,10 @@ void ProjectileStreamUpdate::addProjectile( ObjectID sourceID, ObjectID newID, O { // Changed targets, insert a hole to break the stream m_projectileIDs[ m_nextFreeIndex ] = INVALID_ID; - m_nextFreeIndex = (m_nextFreeIndex + 1) % MAX_PROJECTILE_STREAM; + + // TheSuperHackers @ performance IamInnocent 03/01/26 - Refractor the usage of Modulo to Comparison + if(++m_nextFreeIndex >= MAX_PROJECTILE_STREAM) + m_nextFreeIndex -= MAX_PROJECTILE_STREAM; // And mark this as our new target m_targetObject = victimID; @@ -106,7 +109,8 @@ void ProjectileStreamUpdate::addProjectile( ObjectID sourceID, ObjectID newID, O { // New position, so insert hole m_projectileIDs[ m_nextFreeIndex ] = INVALID_ID; - m_nextFreeIndex = (m_nextFreeIndex + 1) % MAX_PROJECTILE_STREAM; + if(++m_nextFreeIndex >= MAX_PROJECTILE_STREAM) + m_nextFreeIndex -= MAX_PROJECTILE_STREAM; // And mark this as our new target m_targetPosition = (*victimPos); @@ -122,7 +126,8 @@ void ProjectileStreamUpdate::addProjectile( ObjectID sourceID, ObjectID newID, O // Keep track of the id in a circular array m_projectileIDs[ m_nextFreeIndex ] = newID; - m_nextFreeIndex = (m_nextFreeIndex + 1) % MAX_PROJECTILE_STREAM; + if(++m_nextFreeIndex >= MAX_PROJECTILE_STREAM) + m_nextFreeIndex -= MAX_PROJECTILE_STREAM; DEBUG_ASSERTCRASH( m_nextFreeIndex != m_firstValidIndex, ("Need to increase the allowed number of simultaneous particles in ProjectileStreamUpdate.") ); } @@ -131,7 +136,8 @@ void ProjectileStreamUpdate::cullFrontOfList() while( (m_firstValidIndex != m_nextFreeIndex) && (TheGameLogic->findObjectByID( m_projectileIDs[m_firstValidIndex] ) == NULL) ) { // Chew off the front if they are gone. Don't chew on the middle, as bad ones there are just a break in the chain - m_firstValidIndex = (m_firstValidIndex + 1) % MAX_PROJECTILE_STREAM; + if(++m_nextFreeIndex >= MAX_PROJECTILE_STREAM) + m_nextFreeIndex -= MAX_PROJECTILE_STREAM; } } @@ -194,7 +200,8 @@ void ProjectileStreamUpdate::getAllPoints( Vector3 *points, Int *count ) points[pointCount].Z = 0; } - pointIndex = (pointIndex + 1) % MAX_PROJECTILE_STREAM; + if(++pointIndex >= MAX_PROJECTILE_STREAM) + pointIndex -= MAX_PROJECTILE_STREAM; pointCount++; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 473f284ab4..c9d1acfbe3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -76,6 +76,10 @@ #include "GameLogic/Module/PhysicsUpdate.h" #include "GameLogic/TerrainLogic.h" +#if !PRESERVE_RETAIL_BEHAVIOR +#include "GameLogic/Module/BoneFXUpdate.h" +#endif + #define RATIONALIZE_ATTACK_RANGE #define ATTACK_RANGE_IS_2D @@ -238,6 +242,15 @@ const FieldParse WeaponTemplate::TheWeaponTemplateFieldParseTable[] = { "ContinueAttackRange", INI::parseReal, NULL, offsetof(WeaponTemplate, m_continueAttackRange) }, { "SuspendFXDelay", INI::parseDurationUnsignedInt, NULL, offsetof(WeaponTemplate, m_suspendFXDelay) }, { "MissileCallsOnDie", INI::parseBool, NULL, offsetof(WeaponTemplate, m_dieOnDetonate) }, + +#if !PRESERVE_RETAIL_BEHAVIOR + // TheSuperHackers @performance IamInnocent 03/01/2026 - Tweaks to Limit FX Rendering for Performance Improvisation + { "FXRenderingInterval", INI::parseDurationUnsignedInt, NULL, offsetof( WeaponTemplate, m_fxRenderingInterval ) }, + { "FXMaxStackInterval", INI::parseDurationUnsignedInt, NULL, offsetof( WeaponTemplate, m_fxMaxStackInterval ) }, + { "FXMaxStackPerLoc", INI::parseInt, NULL, offsetof( WeaponTemplate, m_fxMaxStackPerLoc ) }, + { "CheckFireFXForFXMaxStack", INI::parseBool, NULL, offsetof( WeaponTemplate, m_checkFireFXForFXMaxStack ) }, +#endif + { NULL, NULL, NULL, 0 } }; @@ -323,6 +336,13 @@ WeaponTemplate::WeaponTemplate() : m_nextTemplate(NULL) m_suspendFXDelay = 0; m_dieOnDetonate = FALSE; +#if !PRESERVE_RETAIL_BEHAVIOR + m_fxRenderingInterval = 0; + m_fxMaxStackInterval = 0; + m_fxMaxStackPerLoc = 0; + m_checkFireFXForFXMaxStack = FALSE; +#endif + m_historicDamageTriggerId = 0; } @@ -893,6 +913,22 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate } } +#if !PRESERVE_RETAIL_BEHAVIOR + Bool renderWeaponFX = TRUE; + if( getFXRenderingInterval() > 0 && !isProjectileDetonation ) + { + if(TheGameLogic->getFrame() >= firingWeapon->getLastFXShotFrame() + getFXRenderingInterval()) + { + firingWeapon->setLastFXShotFrame(TheGameLogic->getFrame()); + renderWeaponFX = TRUE; + } + else + { + renderWeaponFX = FALSE; + } + } +#endif + // call this even if FXList is null, because this also handles stuff like Gun Barrel Recoil if (sourceObj && sourceObj->getDrawable()) { @@ -910,11 +946,24 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate VeterancyLevel v = sourceObj->getVeterancyLevel(); const FXList* fx = isProjectileDetonation ? getProjectileDetonateFX(v) : getFireFX(v); +#if PRESERVE_RETAIL_BEHAVIOR if ( TheGameLogic->getFrame() < firingWeapon->getSuspendFXFrame() ) +#else + if ( TheGameLogic->getFrame() < firingWeapon->getSuspendFXFrame() || !renderWeaponFX ) +#endif fx = NULL; Bool handled; +#if !PRESERVE_RETAIL_BEHAVIOR + if( getFXMaxStackPerLoc() > 0 && fx && (isProjectileDetonation || getCheckFireFXForFXMaxStack()) ) + { + // Check if there is any FX Stack to apply for the Weapon at Loc. Returns TRUE if stack reaches MaxStackCount + if(ThePartitionManager->checkWeaponNameStackAtCell(&targetPos, getName(), getFXMaxStackPerLoc(), getFXMaxStackInterval() ? getFXMaxStackInterval() : getDelayBetweenShots(bonus))) + fx = NULL; + } +#endif + // TheSuperHackers @todo: Remove hardcoded KINDOF_MINE check and apply PlayFXWhenStealthed = Yes to the mine weapons instead. if (!sourceObj->isLogicallyVisible() // if user watching cannot see us @@ -1139,6 +1188,18 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate { pui->projectileLaunchAtObjectOrPosition(victimObj, &projectileDestination, sourceObj, wslot, specificBarrelToUse, this, m_projectileExhausts[v]); } + +#if !PRESERVE_RETAIL_BEHAVIOR + if( !renderWeaponFX ) + { + static NameKeyType key_BoneFXUpdate = NAMEKEY("BoneFXUpdate"); + BoneFXUpdate *bfxu = (BoneFXUpdate *)projectile->findUpdateModule(key_BoneFXUpdate); + if (bfxu != NULL) + { + bfxu->setInactive(); + } + } +#endif } else { @@ -1837,6 +1898,10 @@ Weapon::Weapon(const WeaponTemplate* tmpl, WeaponSlotType wslot) m_numShotsForCurBarrel = m_template->getShotsPerBarrel(); m_lastFireFrame = 0; m_suspendFXFrame = TheGameLogic->getFrame() + m_template->getSuspendFXDelay(); + +#if !PRESERVE_RETAIL_BEHAVIOR + m_lastFXFireFrame = 0; +#endif } //------------------------------------------------------------------------------------------------- @@ -1858,6 +1923,10 @@ Weapon::Weapon(const Weapon& that) this->m_numShotsForCurBarrel = m_template->getShotsPerBarrel(); this->m_lastFireFrame = 0; this->m_suspendFXFrame = that.getSuspendFXFrame(); + +#if !PRESERVE_RETAIL_BEHAVIOR + this->m_lastFXFireFrame = 0; +#endif } //------------------------------------------------------------------------------------------------- @@ -1881,6 +1950,10 @@ Weapon& Weapon::operator=(const Weapon& that) this->m_suspendFXFrame = that.getSuspendFXFrame(); this->m_numShotsForCurBarrel = m_template->getShotsPerBarrel(); this->m_projectileStreamID = INVALID_ID; + +#if !PRESERVE_RETAIL_BEHAVIOR + this->m_lastFXFireFrame = 0; +#endif } return *this; } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DProjectileStreamDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DProjectileStreamDraw.cpp index 6aa69be1c0..72f0b6dce1 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DProjectileStreamDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DProjectileStreamDraw.cpp @@ -132,6 +132,10 @@ void W3DProjectileStreamDraw::doDrawModule(const Matrix3D* ) static NameKeyType key_ProjectileStreamUpdate = NAMEKEY("ProjectileStreamUpdate"); ProjectileStreamUpdate* update = (ProjectileStreamUpdate*)me->findUpdateModule(key_ProjectileStreamUpdate); + // Requires ProjectileStreamUpdate for getting Vector Points + if(!update) + return; + const W3DProjectileStreamDrawModuleData *data = getW3DProjectileStreamDrawModuleData(); Vector3 allPoints[MAX_PROJECTILE_STREAM]; @@ -140,7 +144,6 @@ void W3DProjectileStreamDraw::doDrawModule(const Matrix3D* ) update->getAllPoints( allPoints, &pointsUsed ); Vector3 stagingPoints[MAX_PROJECTILE_STREAM]; - Vector3 zeroVector(0, 0, 0); Int linesMade = 0; Int currentMasterPoint = 0; @@ -159,7 +162,10 @@ void W3DProjectileStreamDraw::doDrawModule(const Matrix3D* ) // I'll keep doing this until I run out of valid points. while( currentMasterPoint < pointsUsed ) { - while( currentMasterPoint < pointsUsed && allPoints[currentMasterPoint] != zeroVector ) + // TheSuperHackers @ performance IamInnocent 03/01/26 - Changes ProjectileStreamDraw Update Requirement Vector from being equal zero Vector to any Vector above near zero. + while( currentMasterPoint < pointsUsed && + (fabs(allPoints[currentMasterPoint].X) > WWMATH_EPSILON || fabs(allPoints[currentMasterPoint].Y) > WWMATH_EPSILON || fabs(allPoints[currentMasterPoint].Z) > WWMATH_EPSILON) + ) { // While I am not looking at a bad point (off edge or zero) stagingPoints[currentStagingPoint] = allPoints[currentMasterPoint];// copy to the staging