diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h b/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h index 6ca26ace2d5..141e4ced749 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h @@ -195,6 +195,9 @@ class ProductionUpdate : public UpdateModule, public ProductionUpdateInterface, virtual DieModuleInterface* getDie() override { return this; } static ProductionUpdateInterface *getProductionUpdateInterfaceFromObject( Object *obj ); + /// @return the quantity modifier for the given thing template, or 1 if none found + Int getQuantityForTemplate(const ThingTemplate* thingTemplate) const; + virtual CanMakeType canQueueCreateUnit( const ThingTemplate *unitType ) const override; virtual CanMakeType canQueueUpgrade( const UpgradeTemplate *upgrade ) const override; diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp index cca2ee3ebc7..f7807744ba8 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -59,6 +59,7 @@ #include "Common/PlayerList.h" #include "Common/PlayerTemplate.h" #include "Common/ProductionPrerequisite.h" +#include "GameLogic/Module/ProductionUpdate.h" #include "Common/Radar.h" #include "Common/ResourceGatheringManager.h" #include "Common/Team.h" @@ -2028,6 +2029,30 @@ void Player::doBountyForKill(const Object* killer, const Object* victim) return; Int costToBuild = victim->getTemplate()->calcCostToBuild(victim->getControllingPlayer()); + + // TheSuperHackers @bugfix zhaog100 23/03/2026 + // When a unit is produced in quantity (e.g. China Red Guards come in pairs), + // the BuildCost on the template reflects the total cost for the whole batch, + // not the per-unit cost. Look up the producer's quantity modifier to get the + // correct per-unit bounty value. + ObjectID producerID = victim->getProducerID(); + if (producerID != INVALID_ID) + { + Object* producer = TheGameLogic->findObjectByID(producerID); + if (producer) + { + ProductionUpdate* pu = dynamic_cast(producer->getProductionUpdateInterface()); + if (pu) + { + Int quantity = pu->getQuantityForTemplate(victim->getTemplate()); + if (quantity > 1) + { + costToBuild = costToBuild / quantity; + } + } + } + } + #if RETAIL_COMPATIBLE_CRC Int bounty = REAL_TO_INT_CEIL(costToBuild * m_cashBountyPercent); #else diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp index ac7ef9765c5..d561605843d 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp @@ -1395,3 +1395,21 @@ void ProductionUpdate::loadPostProcess() UpdateModule::loadPostProcess(); } + +// ------------------------------------------------------------------------------------------------ +/** Get the production quantity modifier for the given thing template. */ +// ------------------------------------------------------------------------------------------------ +Int ProductionUpdate::getQuantityForTemplate(const ThingTemplate* thingTemplate) const +{ + const ProductionUpdateModuleData* pud = getProductionUpdateModuleData(); + const std::vector& modifiers = pud->m_quantityModifiers; + for (std::vector::const_iterator it = modifiers.begin(); it != modifiers.end(); ++it) + { + const ThingTemplate* modTemplate = TheThingFactory->findTemplate(it->m_templateName); + if (modTemplate && modTemplate->isEquivalentTo(thingTemplate)) + { + return it->m_quantity; + } + } + return 1; +} diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h index 2f89b4c969a..6f0b3af2a2d 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h @@ -200,6 +200,9 @@ class ProductionUpdate : public UpdateModule, public ProductionUpdateInterface, virtual DieModuleInterface* getDie() override { return this; } static ProductionUpdateInterface *getProductionUpdateInterfaceFromObject( Object *obj ); + /// @return the quantity modifier for the given thing template, or 1 if none found + Int getQuantityForTemplate(const ThingTemplate* thingTemplate) const; + virtual CanMakeType canQueueCreateUnit( const ThingTemplate *unitType ) const override; virtual CanMakeType canQueueUpgrade( const UpgradeTemplate *upgrade ) const override; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index 69d44b5edc1..6a7d84e1be7 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -2429,6 +2429,30 @@ void Player::doBountyForKill(const Object* killer, const Object* victim) return; Int costToBuild = victim->getTemplate()->calcCostToBuild(victim->getControllingPlayer()); + + // TheSuperHackers @bugfix zhaog100 23/03/2026 + // When a unit is produced in quantity (e.g. China Red Guards come in pairs), + // the BuildCost on the template reflects the total cost for the whole batch, + // not the per-unit cost. Look up the producer's quantity modifier to get the + // correct per-unit bounty value. + ObjectID producerID = victim->getProducerID(); + if (producerID != INVALID_ID) + { + Object* producer = TheGameLogic->findObjectByID(producerID); + if (producer) + { + ProductionUpdate* pu = dynamic_cast(producer->getProductionUpdateInterface()); + if (pu) + { + Int quantity = pu->getQuantityForTemplate(victim->getTemplate()); + if (quantity > 1) + { + costToBuild = costToBuild / quantity; + } + } + } + } + #if RETAIL_COMPATIBLE_CRC Int bounty = REAL_TO_INT_CEIL(costToBuild * m_cashBountyPercent); #else diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp index e540ac948d9..d129214b19d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp @@ -1399,3 +1399,21 @@ void ProductionUpdate::loadPostProcess() UpdateModule::loadPostProcess(); } + +// ------------------------------------------------------------------------------------------------ +/** Get the production quantity modifier for the given thing template. */ +// ------------------------------------------------------------------------------------------------ +Int ProductionUpdate::getQuantityForTemplate(const ThingTemplate* thingTemplate) const +{ + const ProductionUpdateModuleData* pud = getProductionUpdateModuleData(); + const std::vector& modifiers = pud->m_quantityModifiers; + for (std::vector::const_iterator it = modifiers.begin(); it != modifiers.end(); ++it) + { + const ThingTemplate* modTemplate = TheThingFactory->findTemplate(it->m_templateName); + if (modTemplate && modTemplate->isEquivalentTo(thingTemplate)) + { + return it->m_quantity; + } + } + return 1; +}