From 9f1e69dc4e04be93dc04d64347037a617661ea42 Mon Sep 17 00:00:00 2001 From: zhaog100 Date: Mon, 23 Mar 2026 07:10:00 +0800 Subject: [PATCH 1/2] [BOUNTY #2434] Fix GLA Cash Bounty using incorrect value for China Red Guard When a unit is produced in quantity via QuantityModifier (e.g. China Red Guards come in pairs for $300 total), the BuildCost on the template reflects the total cost for the whole batch, not the per-unit cost. Cash Bounty was using this total cost for each individual unit, resulting in double the expected bounty per Red Guard killed. Fix by looking up the victim's producer and checking its QuantityModifiers at bounty time. If the unit was produced in a batch, divide the template cost by the production quantity to get the correct per-unit cost. Before: Level 1 Cash Bounty gives $15 per Red Guard (5% of $300) After: Level 1 Cash Bounty gives $7 per Red Guard (5% of $150) Closes #2434 --- .../GameEngine/Source/Common/RTS/Player.cpp | 34 +++++++++++++++++++ .../GameEngine/Source/Common/RTS/Player.cpp | 33 ++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp index cca2ee3ebc..c299577b39 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,39 @@ 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) + { + ProductionUpdateInterface* pu = producer->getProductionUpdateInterface(); + if (pu) + { + const ProductionUpdateModuleData* pud = pu->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(victim->getTemplate())) + { + if (it->m_quantity > 1) + { + costToBuild = costToBuild / it->m_quantity; + } + break; + } + } + } + } + } + #if RETAIL_COMPATIBLE_CRC Int bounty = REAL_TO_INT_CEIL(costToBuild * m_cashBountyPercent); #else diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index 69d44b5edc..6d33bf3aeb 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -2429,6 +2429,39 @@ 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) + { + ProductionUpdateInterface* pu = producer->getProductionUpdateInterface(); + if (pu) + { + const ProductionUpdateModuleData* pud = pu->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(victim->getTemplate())) + { + if (it->m_quantity > 1) + { + costToBuild = costToBuild / it->m_quantity; + } + break; + } + } + } + } + } + #if RETAIL_COMPATIBLE_CRC Int bounty = REAL_TO_INT_CEIL(costToBuild * m_cashBountyPercent); #else From bd97266c597d31ce4402ca9ba29080f11016f55e Mon Sep 17 00:00:00 2001 From: zhaog100 Date: Mon, 23 Mar 2026 15:59:02 +0800 Subject: [PATCH 2/2] fix: move quantity modifier lookup into ProductionUpdate::getQuantityForTemplate() The previous code called getProductionUpdateModuleData() on a ProductionUpdateInterface*, but that method is private in the ProductionUpdate class (generated by MAKE_STANDARD_MODULE_DATA_MACRO_ABC). VC6 compiler correctly rejects this. Extract the lookup into a new public method on ProductionUpdate and use static_cast instead of dynamic_cast (VC6 has limited RTTI support). --- .../GameLogic/Module/ProductionUpdate.h | 3 +++ .../GameEngine/Source/Common/RTS/Player.cpp | 17 ++++------------- .../Object/Update/ProductionUpdate.cpp | 18 ++++++++++++++++++ .../GameLogic/Module/ProductionUpdate.h | 3 +++ .../GameEngine/Source/Common/RTS/Player.cpp | 17 ++++------------- .../Object/Update/ProductionUpdate.cpp | 18 ++++++++++++++++++ 6 files changed, 50 insertions(+), 26 deletions(-) diff --git a/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h b/Generals/Code/GameEngine/Include/GameLogic/Module/ProductionUpdate.h index 6ca26ace2d..141e4ced74 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 c299577b39..f7807744ba 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -2041,22 +2041,13 @@ void Player::doBountyForKill(const Object* killer, const Object* victim) Object* producer = TheGameLogic->findObjectByID(producerID); if (producer) { - ProductionUpdateInterface* pu = producer->getProductionUpdateInterface(); + ProductionUpdate* pu = dynamic_cast(producer->getProductionUpdateInterface()); if (pu) { - const ProductionUpdateModuleData* pud = pu->getProductionUpdateModuleData(); - const std::vector& modifiers = pud->m_quantityModifiers; - for (std::vector::const_iterator it = modifiers.begin(); it != modifiers.end(); ++it) + Int quantity = pu->getQuantityForTemplate(victim->getTemplate()); + if (quantity > 1) { - const ThingTemplate* modTemplate = TheThingFactory->findTemplate(it->m_templateName); - if (modTemplate && modTemplate->isEquivalentTo(victim->getTemplate())) - { - if (it->m_quantity > 1) - { - costToBuild = costToBuild / it->m_quantity; - } - break; - } + costToBuild = costToBuild / quantity; } } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp index ac7ef9765c..d561605843 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 2f89b4c969..6f0b3af2a2 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 6d33bf3aeb..6a7d84e1be 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -2441,22 +2441,13 @@ void Player::doBountyForKill(const Object* killer, const Object* victim) Object* producer = TheGameLogic->findObjectByID(producerID); if (producer) { - ProductionUpdateInterface* pu = producer->getProductionUpdateInterface(); + ProductionUpdate* pu = dynamic_cast(producer->getProductionUpdateInterface()); if (pu) { - const ProductionUpdateModuleData* pud = pu->getProductionUpdateModuleData(); - const std::vector& modifiers = pud->m_quantityModifiers; - for (std::vector::const_iterator it = modifiers.begin(); it != modifiers.end(); ++it) + Int quantity = pu->getQuantityForTemplate(victim->getTemplate()); + if (quantity > 1) { - const ThingTemplate* modTemplate = TheThingFactory->findTemplate(it->m_templateName); - if (modTemplate && modTemplate->isEquivalentTo(victim->getTemplate())) - { - if (it->m_quantity > 1) - { - costToBuild = costToBuild / it->m_quantity; - } - break; - } + costToBuild = costToBuild / quantity; } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp index e540ac948d..d129214b19 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; +}