Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class SpecialPowerModuleInterface

virtual Bool isModuleForPower( const SpecialPowerTemplate *specialPowerTemplate ) const = 0;
virtual Bool isReady() const = 0;
// This is the althernate way to one-at-a-time BlackLotus' specials; we'll keep it commented her until Dustin decides, or until 12/10/02
// This is the alternate way to one-at-a-time BlackLotus' specials; we'll keep it commented her until Dustin decides, or until 12/10/02
// virtual Bool isBusy() const = 0;
virtual Real getPercentReady() const = 0;
virtual UnsignedInt getReadyFrame() const = 0;
Expand All @@ -70,6 +70,7 @@ class SpecialPowerModuleInterface

//If the special power launches a construction site, we need to know the final product for placement purposes.
virtual const ThingTemplate* getReferenceThingTemplate() const = 0;
virtual void onConstructionCompleted() = 0;
};

//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -162,8 +163,12 @@ class SpecialPowerModule : public BehaviorModule,
//If the special power launches a construction site, we need to know the final product for placement purposes.
virtual const ThingTemplate* getReferenceThingTemplate() const { return nullptr; }

virtual void onConstructionCompleted();

protected:

void init();
void initCountdown();
Bool initiateIntentToDoSpecialPower( const Object *targetObj, const Coord3D *targetPos, const Waypoint *way, UnsignedInt commandOptions );
void triggerSpecialPower( const Coord3D *location );
void createViewObject( const Coord3D *location );
Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ class Object : public Thing, public Snapshot
Bool isAirborneTarget() const { return m_status.test( OBJECT_STATUS_AIRBORNE_TARGET ); } ///< Our locomotor will control marking us as a valid target for anti air weapons or not
Bool isUsingAirborneLocomotor() const; ///< returns true if the current locomotor is an airborne one

void onConstructionCompleted();

/// central place for us to put any additional capture logic
void onCapture( Player *oldOwner, Player *newOwner );

Expand Down
7 changes: 3 additions & 4 deletions GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ void AIPlayer::onStructureProduced( Object *factory, Object *bldg )

info->setUnderConstruction(false);
bldg->updateObjValuesFromMapProperties(&d);
// clear the under construction status
bldg->clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_UNDER_CONSTRUCTION, OBJECT_STATUS_RECONSTRUCTING ) );

bldg->onConstructionCompleted();

// UnderConstruction just cleared, so update our upgrades
bldg->updateUpgradeModules();
Expand Down Expand Up @@ -470,8 +470,7 @@ Object *AIPlayer::buildStructureNow(const ThingTemplate *bldgPlan, BuildListInfo
info->setObjectID( bldg->getID() );
info->setObjectTimestamp( TheGameLogic->getFrame()+1 ); // has to be non-zero, so just add 1.

// clear the under construction status
bldg->clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_UNDER_CONSTRUCTION, OBJECT_STATUS_RECONSTRUCTING ) );
bldg->onConstructionCompleted();

// UnderConstruction just cleared, so update our upgrades
bldg->updateUpgradeModules();
Expand Down
29 changes: 29 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4565,6 +4565,35 @@ void Object::removeUpgrade( const UpgradeTemplate *upgradeT )
}
}

void Object::onConstructionCompleted()
{
if (!testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION))
{
// Was already signaled completed.
DEBUG_ASSERTCRASH(!testStatus(OBJECT_STATUS_RECONSTRUCTING), ("Unexpected status"));
return;
}

DEBUG_ASSERTCRASH(
getConstructionPercent() == (Real)CONSTRUCTION_COMPLETE ||
getConstructionPercent() >= 100.0f,
("Is the construction really completed yet?"));

// clear the under construction status
clearStatus(MAKE_OBJECT_STATUS_MASK2(
OBJECT_STATUS_UNDER_CONSTRUCTION,
OBJECT_STATUS_RECONSTRUCTING
));

// tell all special powers that the construction has completed
for(BehaviorModule** m = getBehaviorModules(); *m; ++m)
{
SpecialPowerModuleInterface* sp = (*m)->getSpecialPower();
if (sp)
sp->onConstructionCompleted();
}
}

//-------------------------------------------------------------------------------------------------
/** Central point for onCapture logic */
//-------------------------------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ void OCLSpecialPower::doSpecialPowerAtLocation( const Coord3D *loc, Real angle,
SpecialPowerModule::doSpecialPowerAtLocation( &targetCoord, angle, commandOptions );

#if RETAIL_COMPATIBLE_CRC
// TheSuperHackers @info we need to leave early if we are in the MissileLauncherBuildingUpdate crash fix codepath
if (m_availableOnFrame == 0xFFFFFFFF)
// TheSuperHackers @info Leave early if we are in the special power crash fix code path
if (m_availableOnFrame == ~0u)
return;
#endif

Expand Down Expand Up @@ -244,6 +244,12 @@ void OCLSpecialPower::doSpecialPower( UnsignedInt commandOptions )
// call the base class action cause we are *EXTENDING* functionality
SpecialPowerModule::doSpecialPowerAtLocation( &creationCoord, INVALID_ANGLE, commandOptions );

#if RETAIL_COMPATIBLE_CRC
// TheSuperHackers @info Leave early if we are in the special power crash fix code path
if (m_availableOnFrame == ~0u)
return;
#endif

const ObjectCreationList* ocl = findOCL();
ObjectCreationList::create( ocl, getObject(), &creationCoord, &creationCoord, false );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,7 @@ SpecialPowerModuleData::SpecialPowerModuleData()
SpecialPowerModule::SpecialPowerModule( Thing *thing, const ModuleData *moduleData )
: BehaviorModule( thing, moduleData )
{

#if RETAIL_COMPATIBLE_CRC
m_availableOnFrame = 0;
#else
m_availableOnFrame = 0xFFFFFFFF;
#endif
m_pausedCount = 0;
m_pausedOnFrame = 0;
m_pausedPercent = 0.0f;
Expand All @@ -111,24 +106,44 @@ SpecialPowerModule::SpecialPowerModule( Thing *thing, const ModuleData *moduleDa
// if we're pre-built, start counting down
if( !getObject()->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
{
//A sharedNSync special only startPowerRecharges when first scienced or when executed,
//Since a new module with same SPTemplates may construct at any time.
if ( getSpecialPowerTemplate()->isSharedNSync() == FALSE )
startPowerRecharge();
init();
}
else
{
#if RETAIL_COMPATIBLE_CRC
initCountdown();
#else
// TheSuperHackers @bugfix The Special Power will not be available until construction is done.
m_availableOnFrame = ~0u;
#endif
}
}

void SpecialPowerModule::init()
{
//A sharedNSync special only startPowerRecharges when first scienced or when executed,
//Since a new module with same SPTemplates may construct at any time.
if ( getSpecialPowerTemplate()->isSharedNSync() == FALSE )
startPowerRecharge();

initCountdown();
}

void SpecialPowerModule::initCountdown()
{
// WE USED TO DO THE POLL-EVERYBODY-AND-VOTE-ON-WHO-TO-SYNC-TO THING HERE,
// BUT NO MORE, NOW IT IS HANDLED IN PLAYER

// Some Special powers need to be activated by an Upgrade, so prevent the timer from going until then
const SpecialPowerModuleData *md = (const SpecialPowerModuleData *)moduleData;
const SpecialPowerModuleData *md = getSpecialPowerModuleData();
if( md->m_startsPaused )
pauseCountdown( TRUE );

resolveSpecialPower();

// Now, if we find that we have just come into being,
// but there is already a science granted for our shared superweapon,
// lets make sure TheIngameUI knows about our public timer
// lets make sure TheInGameUI knows about our public timer
// add this weapon to the UI if it has a public timer for all to see
if( m_pausedCount == 0 &&
getSpecialPowerTemplate()->isSharedNSync() == TRUE &&
Expand All @@ -142,7 +157,6 @@ SpecialPowerModule::SpecialPowerModule( Thing *thing, const ModuleData *moduleDa
getSpecialPowerModuleData()->m_specialPowerTemplate );
}


}

//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -381,6 +395,17 @@ Bool SpecialPowerModule::isScriptOnly() const
return modData->m_scriptedSpecialPowerOnly;
}

//-------------------------------------------------------------------------------------------------
void SpecialPowerModule::onConstructionCompleted()
{
#if !RETAIL_COMPATIBLE_CRC
DEBUG_ASSERTCRASH(m_availableOnFrame == ~0u,
("Unexpected state. Function must be called only after OBJECT_STATUS_UNDER_CONSTRUCTION was removed"));

m_availableOnFrame = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be m_availableOnFrame = TheGameLogic->getFrame(); ?

@xezon xezon Oct 11, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has been a long time and I do not remember if this was intentional. I tend to believe it was intentionally 0. We can try TheGameLogic->getFrame() and see if that works.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using zero or the current frame at this point should work either way, when init is called it will call initCountdown(); which will set m_availableOnFrame into the future where it needs to be for the countdown to work.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main thing to prevent the bugged behaviour is making sure that the m_availableOnFrame is set into the far future while a building is still under construciton or a unit is not ready yet etc.

This then prevents the exploit that can crash the game.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not be enough to just return false with initiateIntentToDoSpecialPower?

init();
#endif
}

//-------------------------------------------------------------------------------------------------
/** A special power has been used ... start the recharge process by computing the frame
Expand Down Expand Up @@ -429,6 +454,13 @@ void SpecialPowerModule::startPowerRecharge()
//-------------------------------------------------------------------------------------------------
Bool SpecialPowerModule::initiateIntentToDoSpecialPower( const Object *targetObj, const Coord3D *targetPos, const Waypoint *way, UnsignedInt commandOptions )
{
#if !RETAIL_COMPATIBLE_CRC
if( getObject()->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) )
{
return false;
}
#endif

Bool valid = false;
// tell our update modules that we intend to do this special power.
for( BehaviorModule** u = getObject()->getBehaviorModules(); *u; ++u )
Expand Down Expand Up @@ -456,26 +488,20 @@ Bool SpecialPowerModule::initiateIntentToDoSpecialPower( const Object *targetObj
}

#if RETAIL_COMPATIBLE_CRC
// TheSuperHackers @info we need to leave early if we are in the MissileLauncherBuildingUpdate crash fix codepath
if (m_availableOnFrame == 0xFFFFFFFF)
// TheSuperHackers @info Leave early if we are in the special power crash fix code path
if (m_availableOnFrame == ~0u)
{
DEBUG_ASSERTCRASH(!valid, ("Using MissileLauncherBuildingUpdate escape path when valid is set to true"));
DEBUG_ASSERTCRASH(!valid, ("Using special power escape path when valid is set to true"));
return false;
}
#endif

getObject()->getControllingPlayer()->getAcademyStats()->recordSpecialPowerUsed( getSpecialPowerModuleData()->m_specialPowerTemplate );
//If we depend on our update module to trigger the special power, make sure we have the appropriate update module!
DEBUG_ASSERTCRASH(valid || !getSpecialPowerModuleData()->m_updateModuleStartsAttack,
("Object does not contain a special power module to execute. Did you forget to add it to the object INI?"));

//If we depend on our update module to trigger the special power, make sure we have the
//appropriate update module!
if( !valid && getSpecialPowerModuleData()->m_updateModuleStartsAttack )
{
DEBUG_CRASH( ("Object does not contain a special power module to execute. Did you forget to add it to the object INI?"));
//DEBUG_CRASH(( "Object does not contain special power module (%s) to execute. Did you forget to add it to the object INI?",
// command->m_specialPower->getName().str() ));
}

return valid;
getObject()->getControllingPlayer()->getAcademyStats()->recordSpecialPowerUsed( getSpecialPowerModuleData()->m_specialPowerTemplate );
return true;
}

//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -679,15 +705,16 @@ void SpecialPowerModule::doSpecialPower( UnsignedInt commandOptions )

//This tells the update module that we want to do our special power. The update modules
//will then start processing each frame.
initiateIntentToDoSpecialPower( nullptr, nullptr, nullptr, commandOptions );

//Only trigger the special power immediately if the updatemodule doesn't start the attack.
//An example of a case that wouldn't trigger immediately is for a unit that needs to
//close to range before firing the special attack. A case that would trigger immediately
//is the napalm strike. If we don't call this now, it's up to the update module to do so.
if( !getSpecialPowerModuleData()->m_updateModuleStartsAttack )
if (initiateIntentToDoSpecialPower( nullptr, nullptr, nullptr, commandOptions ))
{
triggerSpecialPower( nullptr );// Location-less trigger
//Only trigger the special power immediately if the update module doesn't start the attack.
//An example of a case that wouldn't trigger immediately is for a unit that needs to
//close to range before firing the special attack. A case that would trigger immediately
//is the napalm strike. If we don't call this now, it's up to the update module to do so.
if( !getSpecialPowerModuleData()->m_updateModuleStartsAttack )
{
triggerSpecialPower( nullptr );// Location-less trigger
}
}
}

Expand All @@ -701,15 +728,16 @@ void SpecialPowerModule::doSpecialPowerAtObject( Object *obj, UnsignedInt comman

//This tells the update module that we want to do our special power. The update modules
//will then start processing each frame.
initiateIntentToDoSpecialPower( obj, nullptr, nullptr, commandOptions );

//Only trigger the special power immediately if the updatemodule doesn't start the attack.
//An example of a case that wouldn't trigger immediately is for a unit that needs to
//close to range before firing the special attack. A case that would trigger immediately
//is the napalm strike. If we don't call this now, it's up to the update module to do so.
if( !getSpecialPowerModuleData()->m_updateModuleStartsAttack )
if (initiateIntentToDoSpecialPower( obj, nullptr, nullptr, commandOptions ))
{
triggerSpecialPower( obj->getPosition() );
//Only trigger the special power immediately if the update module doesn't start the attack.
//An example of a case that wouldn't trigger immediately is for a unit that needs to
//close to range before firing the special attack. A case that would trigger immediately
//is the napalm strike. If we don't call this now, it's up to the update module to do so.
if( !getSpecialPowerModuleData()->m_updateModuleStartsAttack )
{
triggerSpecialPower( obj->getPosition() );
}
}
}

Expand All @@ -723,21 +751,16 @@ void SpecialPowerModule::doSpecialPowerAtLocation( const Coord3D *loc, Real angl

//This tells the update module that we want to do our special power. The update modules
//will then start processing each frame.
initiateIntentToDoSpecialPower( nullptr, loc, nullptr, commandOptions );

#if RETAIL_COMPATIBLE_CRC
// TheSuperHackers @info we need to leave early if we are in the MissileLauncherBuildingUpdate crash fix codepath
if (m_availableOnFrame == 0xFFFFFFFF)
return;
#endif

//Only trigger the special power immediately if the updatemodule doesn't start the attack.
//An example of a case that wouldn't trigger immediately is for a unit that needs to
//close to range before firing the special attack. A case that would trigger immediately
//is the napalm strike. If we don't call this now, it's up to the update module to do so.
if( !getSpecialPowerModuleData()->m_updateModuleStartsAttack )
if (initiateIntentToDoSpecialPower( nullptr, loc, nullptr, commandOptions ))
{
triggerSpecialPower( loc );
//Only trigger the special power immediately if the update module doesn't start the attack.
//An example of a case that wouldn't trigger immediately is for a unit that needs to
//close to range before firing the special attack. A case that would trigger immediately
//is the napalm strike. If we don't call this now, it's up to the update module to do so.
if( !getSpecialPowerModuleData()->m_updateModuleStartsAttack )
{
triggerSpecialPower( loc );
}
}
}

Expand All @@ -751,15 +774,16 @@ void SpecialPowerModule::doSpecialPowerUsingWaypoints( const Waypoint *way, Unsi

//This tells the update module that we want to do our special power. The update modules
//will then start processing each frame.
initiateIntentToDoSpecialPower( nullptr, nullptr, way, commandOptions );

//Only trigger the special power immediately if the updatemodule doesn't start the attack.
//An example of a case that wouldn't trigger immediately is for a unit that needs to
//close to range before firing the special attack. A case that would trigger immediately
//is the napalm strike. If we don't call this now, it's up to the update module to do so.
if( !getSpecialPowerModuleData()->m_updateModuleStartsAttack )
if (initiateIntentToDoSpecialPower( nullptr, nullptr, way, commandOptions ))
{
triggerSpecialPower( nullptr );// This type doesn't create view objects
//Only trigger the special power immediately if the update module doesn't start the attack.
//An example of a case that wouldn't trigger immediately is for a unit that needs to
//close to range before firing the special attack. A case that would trigger immediately
//is the napalm strike. If we don't call this now, it's up to the update module to do so.
if( !getSpecialPowerModuleData()->m_updateModuleStartsAttack )
{
triggerSpecialPower( nullptr );// This type doesn't create view objects
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,9 +553,7 @@ StateReturnType DozerActionDoActionState::update()
if( goalObject->getConstructionPercent() >= 100.0f )
{

// clear the under construction status
goalObject->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNDER_CONSTRUCTION ) );
goalObject->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_RECONSTRUCTING ) );
goalObject->onConstructionCompleted();

// stop playing the construction sound!
dozerAI->finishBuildingSound();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,15 @@ void BattlePlanUpdate::onObjectCreated()
//-------------------------------------------------------------------------------------------------
Bool BattlePlanUpdate::initiateIntentToDoSpecialPower(const SpecialPowerTemplate *specialPowerTemplate, const Object *targetObj, const Coord3D *targetPos, const Waypoint *way, UnsignedInt commandOptions )
{
#if RETAIL_COMPATIBLE_CRC
// TheSuperHackers @bugfix Mauller 29/06/2025 prevent a game crash when told to launch before ready to do so
Comment thread
Skyaero42 marked this conversation as resolved.
if (!m_specialPowerModule) {
Object* us = getObject();
us->getSpecialPowerModule(specialPowerTemplate)->setReadyFrame(0xFFFFFFFF);
return FALSE;
}
#endif

if( m_specialPowerModule->getSpecialPowerTemplate() != specialPowerTemplate )
{
//Check to make sure our modules are connected.
Expand Down
Loading
Loading