diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h b/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h index 3bcfe1b13c1..a2e33faa406 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h @@ -158,7 +158,7 @@ class ProductionUpdateInterface virtual UnsignedInt countUnitTypeInQueue( const ThingTemplate *unitType ) const = 0; virtual Bool queueCreateUnit( const ThingTemplate *unitType, ProductionID productionID ) = 0; - virtual void cancelUnitCreate( ProductionID productionID ) = 0; + virtual void cancelUnitCreate( ProductionID productionID, Bool forceCancel = FALSE ) = 0; virtual void cancelAllUnitsOfType( const ThingTemplate *unitType) = 0; virtual void cancelAndRefundAllProduction() = 0; @@ -214,7 +214,7 @@ class ProductionUpdate : public UpdateModule, public ProductionUpdateInterface, virtual UnsignedInt countUnitTypeInQueue( const ThingTemplate *unitType ) const override; ///< count number of units with matching unit type in the production queue virtual Bool queueCreateUnit( const ThingTemplate *unitType, ProductionID productionID ) override; ///< queue unit to be produced - virtual void cancelUnitCreate( ProductionID productionID ) override; ///< cancel construction of unit with matching production ID + virtual void cancelUnitCreate( ProductionID productionID, Bool forceCancel = FALSE ) override; ///< cancel construction of unit with matching production ID virtual void cancelAllUnitsOfType( const ThingTemplate *unitType) override; ///< cancel all production of type unitType virtual void cancelAndRefundAllProduction() override; ///< cancel and refund anything in the production queue diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp index ea5a60488e3..a1e63f90065 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp @@ -458,7 +458,7 @@ Bool ProductionUpdate::queueCreateUnit( const ThingTemplate *unitType, Productio //------------------------------------------------------------------------------------------------- /** Cancel the construction of the unit with the matching production ID */ //------------------------------------------------------------------------------------------------- -void ProductionUpdate::cancelUnitCreate( ProductionID productionID ) +void ProductionUpdate::cancelUnitCreate( ProductionID productionID, Bool forceCancel ) { // search for the production entry in our queue @@ -470,8 +470,16 @@ void ProductionUpdate::cancelUnitCreate( ProductionID productionID ) if( production->m_productionID == productionID ) { - // give the player the cost of the object back Player *player = getObject()->getControllingPlayer(); +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix arcticdolphin 08/03/2026 Prevent cancel once units are produced to avoid free unit exploit + if( !forceCancel && production->getProductionQuantityRemaining() < production->getProductionQuantity() ) + { + return; + } +#endif + + // give the player the cost of the object back Money *money = player->getMoney(); money->deposit( production->m_objectToProduce->calcCostToBuild( player ), TRUE, FALSE ); @@ -693,10 +701,17 @@ UpdateSleepTime ProductionUpdate::update() // Don't cancel dozers in the queue. jba. if (!production->getProductionObject()->isKindOf(KINDOF_DOZER)) { - +#if RETAIL_COMPATIBLE_CRC cancelUnitCreate(production->getProductionID()); return UPDATE_SLEEP_NONE; - +#else + // TheSuperHackers @bugfix arcticdolphin 13/03/2026 Let partial production finish naturally when script-disallowed. + if( production->getProductionQuantityRemaining() == production->getProductionQuantity() ) + { + cancelUnitCreate(production->getProductionID()); + return UPDATE_SLEEP_NONE; + } +#endif } } @@ -1141,7 +1156,7 @@ void ProductionUpdate::cancelAndRefundAllProduction() if( m_productionQueue ) { if( m_productionQueue->getProductionType() == PRODUCTION_UNIT ) - cancelUnitCreate( m_productionQueue->getProductionID() ); + cancelUnitCreate( m_productionQueue->getProductionID(), TRUE ); else if( m_productionQueue->getProductionType() == PRODUCTION_UPGRADE ) cancelUpgrade( m_productionQueue->getProductionUpgrade() ); else diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h index 2f89b4c969a..a59074b3d6d 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h @@ -158,7 +158,7 @@ class ProductionUpdateInterface virtual UnsignedInt countUnitTypeInQueue( const ThingTemplate *unitType ) const = 0; virtual Bool queueCreateUnit( const ThingTemplate *unitType, ProductionID productionID ) = 0; - virtual void cancelUnitCreate( ProductionID productionID ) = 0; + virtual void cancelUnitCreate( ProductionID productionID, Bool forceCancel = FALSE ) = 0; virtual void cancelAllUnitsOfType( const ThingTemplate *unitType) = 0; virtual void cancelAndRefundAllProduction() = 0; @@ -214,7 +214,7 @@ class ProductionUpdate : public UpdateModule, public ProductionUpdateInterface, virtual UnsignedInt countUnitTypeInQueue( const ThingTemplate *unitType ) const override; ///< count number of units with matching unit type in the production queue virtual Bool queueCreateUnit( const ThingTemplate *unitType, ProductionID productionID ) override; ///< queue unit to be produced - virtual void cancelUnitCreate( ProductionID productionID ) override; ///< cancel construction of unit with matching production ID + virtual void cancelUnitCreate( ProductionID productionID, Bool forceCancel = FALSE ) override; ///< cancel construction of unit with matching production ID virtual void cancelAllUnitsOfType( const ThingTemplate *unitType) override; ///< cancel all production of type unitType virtual void cancelAndRefundAllProduction() override; ///< cancel and refund anything in the production queue diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp index e540ac948d9..51be75356e1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp @@ -458,7 +458,7 @@ Bool ProductionUpdate::queueCreateUnit( const ThingTemplate *unitType, Productio //------------------------------------------------------------------------------------------------- /** Cancel the construction of the unit with the matching production ID */ //------------------------------------------------------------------------------------------------- -void ProductionUpdate::cancelUnitCreate( ProductionID productionID ) +void ProductionUpdate::cancelUnitCreate( ProductionID productionID, Bool forceCancel ) { // search for the production entry in our queue @@ -470,8 +470,16 @@ void ProductionUpdate::cancelUnitCreate( ProductionID productionID ) if( production->m_productionID == productionID ) { - // give the player the cost of the object back Player *player = getObject()->getControllingPlayer(); +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix arcticdolphin 08/03/2026 Prevent cancel once units are produced to avoid free unit exploit + if( !forceCancel && production->getProductionQuantityRemaining() < production->getProductionQuantity() ) + { + return; + } +#endif + + // give the player the cost of the object back Money *money = player->getMoney(); money->deposit( production->m_objectToProduce->calcCostToBuild( player ), TRUE, FALSE ); @@ -693,10 +701,17 @@ UpdateSleepTime ProductionUpdate::update() // Don't cancel dozers in the queue. jba. if (!production->getProductionObject()->isKindOf(KINDOF_DOZER)) { - +#if RETAIL_COMPATIBLE_CRC cancelUnitCreate(production->getProductionID()); return UPDATE_SLEEP_NONE; - +#else + // TheSuperHackers @bugfix arcticdolphin 13/03/2026 Let partial production finish naturally when script-disallowed. + if( production->getProductionQuantityRemaining() == production->getProductionQuantity() ) + { + cancelUnitCreate(production->getProductionID()); + return UPDATE_SLEEP_NONE; + } +#endif } } @@ -1145,7 +1160,7 @@ void ProductionUpdate::cancelAndRefundAllProduction() if( m_productionQueue ) { if( m_productionQueue->getProductionType() == PRODUCTION_UNIT ) - cancelUnitCreate( m_productionQueue->getProductionID() ); + cancelUnitCreate( m_productionQueue->getProductionID(), TRUE ); else if( m_productionQueue->getProductionType() == PRODUCTION_UPGRADE ) cancelUpgrade( m_productionQueue->getProductionUpgrade() ); else