diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h b/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h index b0754eee32..e35b422f11 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h @@ -158,7 +158,8 @@ 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; + // TheSuperHackers @info arcticdolphin 08/03/2026 Introduced forceCancel param (used to bypass partial-production refund guard) + virtual void cancelUnitCreate( ProductionID productionID, Bool forceCancel = FALSE ) = 0; virtual void cancelAllUnitsOfType( const ThingTemplate *unitType) = 0; virtual void cancelAndRefundAllProduction() = 0; @@ -208,8 +209,9 @@ class ProductionUpdate : public UpdateModule, public ProductionUpdateInterface, virtual Bool isUpgradeInQueue( const UpgradeTemplate *upgrade ) const; ///< is the upgrade in our production queue already 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 Bool queueCreateUnit( const ThingTemplate *unitType, ProductionID productionID ); ///< queue unit to be produced + // TheSuperHackers @info arcticdolphin 08/03/2026 Introduced forceCancel param (used to bypass partial-production refund guard) + 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 ac7ef9765c..ca62d60031 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp @@ -457,7 +457,8 @@ Bool ProductionUpdate::queueCreateUnit( const ThingTemplate *unitType, Productio //------------------------------------------------------------------------------------------------- /** Cancel the construction of the unit with the matching production ID */ //------------------------------------------------------------------------------------------------- -void ProductionUpdate::cancelUnitCreate( ProductionID productionID ) +// TheSuperHackers @info arcticdolphin 08/03/2026 Introduced forceCancel param (used to bypass partial-production refund guard) +void ProductionUpdate::cancelUnitCreate( ProductionID productionID, Bool forceCancel ) { // search for the production entry in our queue @@ -469,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 Do not cancel if units were already produced from this entry + 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,7 +702,8 @@ UpdateSleepTime ProductionUpdate::update() if (!production->getProductionObject()->isKindOf(KINDOF_DOZER)) { - cancelUnitCreate(production->getProductionID()); + // TheSuperHackers @info arcticdolphin 08/03/2026 Introduced forceCancel param (used to bypass partial-production refund guard) + cancelUnitCreate(production->getProductionID(), TRUE); return UPDATE_SLEEP_NONE; } @@ -1140,7 +1150,8 @@ void ProductionUpdate::cancelAndRefundAllProduction() if( m_productionQueue ) { if( m_productionQueue->getProductionType() == PRODUCTION_UNIT ) - cancelUnitCreate( m_productionQueue->getProductionID() ); + // TheSuperHackers @info arcticdolphin 08/03/2026 Introduced forceCancel param (used to bypass partial-production refund guard) + 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 a3e3b5c01e..301cdb6287 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h @@ -158,7 +158,8 @@ 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; + // TheSuperHackers @info arcticdolphin 08/03/2026 Introduced forceCancel param (used to bypass partial-production refund guard) + virtual void cancelUnitCreate( ProductionID productionID, Bool forceCancel = FALSE ) = 0; virtual void cancelAllUnitsOfType( const ThingTemplate *unitType) = 0; virtual void cancelAndRefundAllProduction() = 0; @@ -214,7 +215,8 @@ 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 + // TheSuperHackers @info arcticdolphin 08/03/2026 Introduced forceCancel param (used to bypass partial-production refund guard) + 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 e540ac948d..2b1e2c9c99 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp @@ -458,7 +458,8 @@ Bool ProductionUpdate::queueCreateUnit( const ThingTemplate *unitType, Productio //------------------------------------------------------------------------------------------------- /** Cancel the construction of the unit with the matching production ID */ //------------------------------------------------------------------------------------------------- -void ProductionUpdate::cancelUnitCreate( ProductionID productionID ) +// TheSuperHackers @info arcticdolphin 08/03/2026 Introduced forceCancel param (used to bypass partial-production refund guard) +void ProductionUpdate::cancelUnitCreate( ProductionID productionID, Bool forceCancel ) { // search for the production entry in our queue @@ -470,8 +471,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 Do not cancel if units were already produced from this entry + 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 ); @@ -694,7 +703,8 @@ UpdateSleepTime ProductionUpdate::update() if (!production->getProductionObject()->isKindOf(KINDOF_DOZER)) { - cancelUnitCreate(production->getProductionID()); + // TheSuperHackers @info arcticdolphin 08/03/2026 Introduced forceCancel param (used to bypass partial-production refund guard) + cancelUnitCreate(production->getProductionID(), TRUE); return UPDATE_SLEEP_NONE; } @@ -1145,7 +1155,8 @@ void ProductionUpdate::cancelAndRefundAllProduction() if( m_productionQueue ) { if( m_productionQueue->getProductionType() == PRODUCTION_UNIT ) - cancelUnitCreate( m_productionQueue->getProductionID() ); + // TheSuperHackers @info arcticdolphin 08/03/2026 Introduced forceCancel param (used to bypass partial-production refund guard) + cancelUnitCreate( m_productionQueue->getProductionID(), TRUE ); else if( m_productionQueue->getProductionType() == PRODUCTION_UPGRADE ) cancelUpgrade( m_productionQueue->getProductionUpgrade() ); else