diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h b/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h index b0754eee327..e5d800fc40a 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; @@ -209,7 +209,7 @@ class ProductionUpdate : public UpdateModule, public ProductionUpdateInterface, virtual UnsignedInt countUnitTypeInQueue( const ThingTemplate *unitType ) const; ///< count number of units with matching unit type in the production queue virtual Bool queueCreateUnit( const ThingTemplate *unitType, ProductionID productionID ); ///< queue unit to be produced - virtual void cancelUnitCreate( ProductionID productionID ); ///< cancel construction of unit with matching production ID + virtual void cancelUnitCreate( ProductionID productionID, Bool forceCancel = FALSE ); ///< cancel construction of unit with matching production ID virtual void cancelAllUnitsOfType( const ThingTemplate *unitType); ///< cancel all production of type unitType virtual void cancelAndRefundAllProduction(); ///< 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 ac7ef9765c5..968766351c2 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp @@ -457,7 +457,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 @@ -469,8 +469,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 ); @@ -692,10 +700,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 } } @@ -1140,7 +1155,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 a3e3b5c01e5..7d45241efd8 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; ///< count number of units with matching unit type in the production queue virtual Bool queueCreateUnit( const ThingTemplate *unitType, ProductionID productionID ); ///< queue unit to be produced - virtual void cancelUnitCreate( ProductionID productionID ); ///< cancel construction of unit with matching production ID + virtual void cancelUnitCreate( ProductionID productionID, Bool forceCancel = FALSE ); ///< cancel construction of unit with matching production ID virtual void cancelAllUnitsOfType( const ThingTemplate *unitType); ///< cancel all production of type unitType virtual void cancelAndRefundAllProduction(); ///< 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