Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Core/GameEngine/Include/Common/AudioRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ struct AudioRequest : public MemoryPoolObject
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( AudioRequest, "AudioRequest" )

public:
AudioEventRTS* releasePendingEvent();

RequestType m_request;
union
{
Expand Down
20 changes: 20 additions & 0 deletions Core/GameEngine/Include/Common/RandomValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,24 @@ extern void InitRandom( UnsignedInt seed );
extern UnsignedInt GetGameLogicRandomSeed(); ///< Get the seed (used for replays)
extern UnsignedInt GetGameLogicRandomSeedCRC();///< Get the seed (used for CRCs)

struct RandomValueClass
{
virtual Int GetRandomValueInt( Int lo, Int hi, const char *file, Int line ) const = 0;
virtual Real GetRandomValueReal( Real lo, Real hi, const char *file, Int line ) const = 0;
};
struct LogicRandomValueClass final : RandomValueClass
{
virtual Int GetRandomValueInt( Int lo, Int hi, const char *file, Int line ) const override;
virtual Real GetRandomValueReal( Real lo, Real hi, const char *file, Int line ) const override;
};
struct ClientRandomValueClass final : RandomValueClass
{
virtual Int GetRandomValueInt( Int lo, Int hi, const char *file, Int line ) const override;
virtual Real GetRandomValueReal( Real lo, Real hi, const char *file, Int line ) const override;
};

// use these macros to access the random value functions
#define RandomValueInt(randomValueClass, lo, hi) randomValueClass.GetRandomValueInt( lo, hi, __FILE__, __LINE__ )
#define RandomValueReal(randomValueClass, lo, hi) randomValueClass.GetRandomValueReal( lo, hi, __FILE__, __LINE__ )

//--------------------------------------------------------------------------------------------------------------
16 changes: 14 additions & 2 deletions Core/GameEngine/Include/GameClient/ParticleSys.h
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,8 @@ class ParticleSystemManager : public SubsystemInterface,
virtual void reset() override; ///< reset the manager and all particle systems
virtual void update() override; ///< update all particle systems

virtual Bool isDummy() const { return false; }

virtual Int getOnScreenParticleCount() = 0; ///< returns the number of particles on screen
virtual void setOnScreenParticleCount(int count);

Expand All @@ -761,8 +763,7 @@ class ParticleSystemManager : public SubsystemInterface,
ParticleSystemTemplate *newTemplate( const AsciiString &name );

/// given a template, instantiate a particle system
ParticleSystem *createParticleSystem( const ParticleSystemTemplate *sysTemplate,
Bool createSlaves = TRUE );
ParticleSystem *createParticleSystem( const ParticleSystemTemplate *sysTemplate, Bool createSlaves = TRUE );

/** given a template, instantiate a particle system.
if attachTo is not null, attach the particle system to the given object.
Expand Down Expand Up @@ -835,13 +836,24 @@ class ParticleSystemManager : public SubsystemInterface,
ParticleSystemIDMap m_systemMap; ///< a hash map of all particle systems
};


// TheSuperHackers @feature bobtista 31/01/2026
// ParticleSystemManager that does nothing. Used for Headless Mode.
// Generally does not load particle system templates. Certainly does not create particle systems.
class ParticleSystemManagerDummy : public ParticleSystemManager
{
public:
#if RETAIL_COMPATIBLE_CRC
// Must not overload init to keep loading the particle system templates,
// which are unfortunately needed to preserve the correct logic crc.
#else
virtual void init() override {}
virtual void reset() override {}
#endif
// GeneralsX @bugfix fbraz 04/05/2026 Prevent headless replay from entering full particle update path.
virtual void update() override {}

virtual Bool isDummy() const override { return true; }
virtual Int getOnScreenParticleCount() override { return 0; }
virtual void doParticles(RenderInfoClass &rinfo) override {}
virtual void queueParticleRender() override {}
Expand Down
15 changes: 15 additions & 0 deletions Core/GameEngine/Source/Common/Audio/AudioRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,20 @@

AudioRequest::~AudioRequest()
{
if (m_usePendingEvent)
{
delete m_pendingEvent;
}
}

AudioEventRTS* AudioRequest::releasePendingEvent()
{
if (m_usePendingEvent)
{
m_usePendingEvent = false;
AudioEventRTS* event = m_pendingEvent;
m_pendingEvent = nullptr;
return event;
}
return nullptr;
}
21 changes: 21 additions & 0 deletions Core/GameEngine/Source/Common/RandomValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,24 @@ Real GameLogicRandomVariable::getValue() const
return 0.0f;
}
}


Int LogicRandomValueClass::GetRandomValueInt( Int lo, Int hi, const char *file, Int line ) const
{
return GetGameLogicRandomValue(lo, hi, file, line);
}

Real LogicRandomValueClass::GetRandomValueReal( Real lo, Real hi, const char *file, Int line ) const
{
return GetGameLogicRandomValueReal(lo, hi, file, line);
}

Int ClientRandomValueClass::GetRandomValueInt( Int lo, Int hi, const char *file, Int line ) const
{
return GetGameClientRandomValue(lo, hi, file, line);
}

Real ClientRandomValueClass::GetRandomValueReal( Real lo, Real hi, const char *file, Int line ) const
{
return GetGameClientRandomValueReal(lo, hi, file, line);
}
2 changes: 0 additions & 2 deletions Core/GameEngine/Source/Common/ReplaySimulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ int ReplaySimulation::simulateReplaysInThisProcess(const std::vector<AsciiString
UnsignedInt totalTimeSec = TheRecorder->getPlaybackFrameCount() / LOGICFRAMES_PER_SECOND;
while (TheRecorder->isPlaybackInProgress())
{
TheGameClient->updateHeadless();

const int progressFrameInterval = 10*60*LOGICFRAMES_PER_SECOND;
if (TheGameLogic->getFrame() != 0 && TheGameLogic->getFrame() % progressFrameInterval == 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,7 @@ static ParticleSystem* createParticleSystem( Drawable *draw )
AsciiString templateName;
templateName.format("BeaconSmoke%6.6X", (0xffffff & obj->getIndicatorColor()));
const ParticleSystemTemplate *particleTemplate = TheParticleSystemManager->findTemplate( templateName );

DEBUG_ASSERTCRASH(particleTemplate, ("Could not find particle system %s", templateName.str()));

DEBUG_ASSERTCRASH(TheParticleSystemManager->isDummy() || particleTemplate, ("Could not find particle system %s", templateName.str()));
if (particleTemplate)
{
system = TheParticleSystemManager->createParticleSystem( particleTemplate );
Expand All @@ -107,7 +105,7 @@ static ParticleSystem* createParticleSystem( Drawable *draw )
{// THis this will whip up a new particle system to match the house color provided
templateName.format("BeaconSmokeFFFFFF");
const ParticleSystemTemplate *failsafeTemplate = TheParticleSystemManager->findTemplate( templateName );
DEBUG_ASSERTCRASH(failsafeTemplate, ("Doh, this is bad \n I Could not even find the white particle system to make a failsafe system out of."));
DEBUG_ASSERTCRASH(TheParticleSystemManager->isDummy() || failsafeTemplate, ("Doh, this is bad \n I Could not even find the white particle system to make a failsafe system out of."));
system = TheParticleSystemManager->createParticleSystem( failsafeTemplate );
if (system)
{
Expand Down
2 changes: 1 addition & 1 deletion Core/GameEngine/Source/GameClient/FXList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ class ParticleSystemFXNugget : public FXNugget
}

const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate(m_name);
DEBUG_ASSERTCRASH(tmp, ("ParticleSystem %s not found",m_name.str()));
DEBUG_ASSERTCRASH(TheParticleSystemManager->isDummy() || tmp, ("ParticleSystem %s not found",m_name.str()));
if (tmp)
{
for (Int i = 0; i < m_count; i++ )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ class MilesAudioManager : public AudioManager
void initSamplePools();
void processRequest( AudioRequest *req );

void playAudioEvent( AudioEventRTS *event );
void playAudioEvent( AudioRequest* req );
void stopAudioEvent( AudioHandle handle );
void pauseAudioEvent( AudioHandle handle );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -656,8 +656,12 @@ void MilesAudioManager::pauseAmbient( Bool shouldPause )
}

//-------------------------------------------------------------------------------------------------
void MilesAudioManager::playAudioEvent( AudioEventRTS *event )
void MilesAudioManager::playAudioEvent( AudioRequest* req )
{
DEBUG_ASSERTCRASH(req->m_usePendingEvent && req->m_pendingEvent, ("audio request was expected to contain a valid audio event"));

AudioEventRTS* event = req->m_pendingEvent;

#ifdef INTENSIVE_AUDIO_DEBUG
DEBUG_LOG(("MILES (%d) - Processing play request: %d (%s)", TheGameLogic->getFrame(), event->getPlayingHandle(), event->getEventName().str()));
#endif
Expand Down Expand Up @@ -713,7 +717,7 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event )
}

// Put this on here, so that the audio event RTS will be cleaned up regardless.
audio->m_audioEventRTS = event;
audio->m_audioEventRTS = event = req->releasePendingEvent();
audio->m_stream = stream;
audio->m_type = PAT_Stream;

Expand Down Expand Up @@ -782,7 +786,7 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event )
sample3D = nullptr;
}
// Push it onto the list of playing things
audio->m_audioEventRTS = event;
audio->m_audioEventRTS = event = req->releasePendingEvent();
audio->m_3DSample = sample3D;
audio->m_file = nullptr;
audio->m_type = PAT_3DSample;
Expand Down Expand Up @@ -853,7 +857,7 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event )
}

// Push it onto the list of playing things
audio->m_audioEventRTS = event;
audio->m_audioEventRTS = event = req->releasePendingEvent();
audio->m_sample = sample;
audio->m_file = nullptr;
audio->m_type = PAT_Sample;
Expand Down Expand Up @@ -2934,7 +2938,7 @@ void MilesAudioManager::processRequest( AudioRequest *req )
{
case AR_Play:
{
playAudioEvent(req->m_pendingEvent);
playAudioEvent(req);
break;
}
case AR_Pause:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ static Int getRiverVertexDiffuse(W3DShroud *shroud, Real x, Real y, Real shadeR,
(Int)(shadeR * shroudScale),
(Int)(shadeG * shroudScale),
(Int)(shadeB * shroudScale),
(diffuse >> 24) & 0xff);
((diffuse >> 24) & 0xff) * shroudScale);
}

void doSkyBoxSet(Bool startDraw)
Expand Down Expand Up @@ -933,9 +933,11 @@ void WaterRenderObjClass::ReAcquireResources()
tex t1 \n\
tex t2 \n\
tex t3\n\
mul r0,v0,t0 ; blend vertex color into t0. \n\
mul r0.rgb, v0, t0 ; blend vertex color into t0. \n\
mov r0.a, t0 ; keep vertex alpha from fading the base water. \n\
mul r1, t1, t2 ; mul\n\
add r0.rgb, r0, t3\n\
add r1.rgb, r1, t3\n\
mul r1.rgb, r1, v0.a\n\
+mul r0.a, r0, t3\n\
add r0.rgb, r0, r1\n";
hr = D3DXAssembleShader( shader, strlen(shader), 0, nullptr, &compiledShader, nullptr);
Expand Down
6 changes: 3 additions & 3 deletions Generals/Code/GameEngine/Include/Common/Geometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

#include "Lib/BaseType.h"
#include "Common/AsciiString.h"
#include "Common/RandomValue.h"
#include "Common/Snapshot.h"

class INI;
Expand Down Expand Up @@ -171,9 +172,8 @@ class GeometryInfo : public Snapshot
/// get the 2d bounding box
void get2DBounds(const Coord3D& geomCenter, Real angle, Region2D& bounds ) const;

/// note that the pt is generated using game logic random, not game client random!
void makeRandomOffsetWithinFootprint(Coord3D& pt) const;
void makeRandomOffsetOnPerimeter(Coord3D& pt) const; //Chooses a random point on the extent border.
void makeRandomOffsetWithinFootprint(Coord3D& pt, const RandomValueClass& random = LogicRandomValueClass()) const;
void makeRandomOffsetOnPerimeter(Coord3D& pt) const; ///< Chooses a random point on the extent border. Uses game logic random!

void clipPointToFootprint(const Coord3D& geomCenter, Coord3D& ptToClip) const;

Expand Down
2 changes: 0 additions & 2 deletions Generals/Code/GameEngine/Include/GameClient/GameClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ class GameClient : public SubsystemInterface,

void step(); ///< Do one fixed time step

void updateHeadless();

void addDrawableToLookupTable( Drawable *draw ); ///< add drawable ID to hash lookup table
void removeDrawableFromLookupTable( Drawable *draw ); ///< remove drawable ID from hash lookup table

Expand Down
14 changes: 7 additions & 7 deletions Generals/Code/GameEngine/Source/Common/System/Geometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ Bool GeometryInfo::isPointInFootprint(const Coord3D& geomCenter, const Coord3D&
}

//=============================================================================
void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const
void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt, const RandomValueClass& random) const
{
switch(m_type)
{
Expand All @@ -390,14 +390,14 @@ void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const
Real distSqr;
do
{
pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius);
pt.y = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius);
pt.x = RandomValueReal(random, -m_majorRadius, m_majorRadius);
pt.y = RandomValueReal(random, -m_majorRadius, m_majorRadius);
pt.z = 0.0f;
distSqr = sqr(pt.x) + sqr(pt.y);
} while (distSqr > maxDistSqr);
#else
Real radius = GameLogicRandomValueReal(0.0f, m_boundingCircleRadius);
Real angle = GameLogicRandomValueReal(-PI, PI);
Real radius = RandomValueReal(random, 0.0f, m_boundingCircleRadius);
Real angle = RandomValueReal(random, -PI, PI);
pt.x = radius * Cos(angle);
pt.y = radius * Sin(angle);
pt.z = 0.0f;
Expand All @@ -407,8 +407,8 @@ void GeometryInfo::makeRandomOffsetWithinFootprint(Coord3D& pt) const

case GEOMETRY_BOX:
{
pt.x = GameLogicRandomValueReal(-m_majorRadius, m_majorRadius);
pt.y = GameLogicRandomValueReal(-m_minorRadius, m_minorRadius);
pt.x = RandomValueReal(random, -m_majorRadius, m_majorRadius);
pt.y = RandomValueReal(random, -m_minorRadius, m_minorRadius);
pt.z = 0.0f;
break;
}
Expand Down
9 changes: 0 additions & 9 deletions Generals/Code/GameEngine/Source/GameClient/GameClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -750,15 +750,6 @@ void GameClient::step()
TheDisplay->step();
}

void GameClient::updateHeadless()
{
// TheSuperHackers @info helmutbuhler 03/05/2025 bobtista 02/02/2026
// Update particles to prevent accumulation in headless mode. Particles are generated
// during GameLogic and only cleaned up during rendering. update() lets particles finish
// their lifecycle naturally instead of abruptly removing them with reset().
TheParticleSystemManager->update();
}

Bool GameClient::isMovieAbortRequested()
{
if (TheGameEngine)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ void TransitionDamageFX::onDelete()
/** Given an FXLoc info struct, return the effect position that we are supposed to use.
* The position is local to to the object */
//-------------------------------------------------------------------------------------------------
static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw )
static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw, const RandomValueClass &random = LogicRandomValueClass() )
{

DEBUG_ASSERTCRASH( locInfo, ("getLocalEffectPos: locInfo is null") );
Expand Down Expand Up @@ -290,7 +290,7 @@ static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw )
return locInfo->loc;

// pick one of the bone positions
Int pick = GameLogicRandomValue( 0, boneCount - 1 );
Int pick = RandomValueInt( random, 0, boneCount - 1 );
return positions[ pick ];

}
Expand Down Expand Up @@ -387,14 +387,19 @@ void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo,
if( lastDamageInfo == nullptr ||
getDamageTypeFlag( modData->m_damageParticleTypes, lastDamageInfo->in.m_damageType ) )
{
#if RETAIL_COMPATIBLE_CRC
// TheSuperHackers @fix The particle system is now decoupled from the logic crc
// and the side effects on the logic random seed values are preserved for retail compatibility.
getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw, LogicRandomValueClass() );
#endif

// create a new particle system based on the template provided
ParticleSystem* pSystem = TheParticleSystemManager->createParticleSystem( pSystemT );
if( pSystem )
{

// get the what is the position we're going to played the effect at
pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw );
pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw, ClientRandomValueClass() );

//
// set position on system given any bone position provided, the bone position is
Expand Down
Loading
Loading