diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt index 9f495b545f0..c0c85c3c6e4 100644 --- a/Core/GameEngine/CMakeLists.txt +++ b/Core/GameEngine/CMakeLists.txt @@ -290,6 +290,7 @@ set(GAMEENGINE_SRC # Include/GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h # Include/GameLogic/Module/CostModifierUpgrade.h # Include/GameLogic/Module/CountermeasuresBehavior.h +# Include/GameLogic/Module/BattlePlanBonusBehavior.h # Include/GameLogic/Module/CrateCollide.h # Include/GameLogic/Module/CreateCrateDie.h # Include/GameLogic/Module/CreateModule.h @@ -852,6 +853,7 @@ set(GAMEENGINE_SRC # Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp # Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp # Source/GameLogic/Object/Behavior/CountermeasuresBehavior.cpp +# Source/GameLogic/Object/Behavior/BattlePlanBonusBehavior.cpp # Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp # Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp # Source/GameLogic/Object/Behavior/FireWeaponWhenDeadBehavior.cpp diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl index 49c4f6fe416..f7e02d7c8a9 100644 --- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl +++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl @@ -108,6 +108,7 @@ static PoolSizeRec PoolSizes[] = { "GrantStealthBehavior", 4096, 32 }, { "NeutronBlastBehavior", 4096, 32 }, { "CountermeasuresBehavior", 256, 32 }, + { "BattlePlanBonusBehavior", 256, 32 }, { "BaseRegenerateUpdate", 128, 32 }, { "BoneFXDamage", 64, 32 }, { "BoneFXUpdate", 64, 32 }, diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 6ef78dbac79..e0dde9186b8 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -293,6 +293,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/ProductionTimeModifierUpgrade.h Include/GameLogic/Module/UnitProductionBonusUpgrade.h Include/GameLogic/Module/CountermeasuresBehavior.h + Include/GameLogic/Module/BattlePlanBonusBehavior.h Include/GameLogic/Module/CrateCollide.h Include/GameLogic/Module/CreateCrateDie.h Include/GameLogic/Module/CreateModule.h @@ -867,6 +868,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Behavior/BridgeTowerBehavior.cpp Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp Source/GameLogic/Object/Behavior/CountermeasuresBehavior.cpp + Source/GameLogic/Object/Behavior/BattlePlanBonusBehavior.cpp Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Player.h b/GeneralsMD/Code/GameEngine/Include/Common/Player.h index b05ee7daa96..debdf33071e 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Player.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Player.h @@ -706,7 +706,7 @@ class Player : public Snapshot this call externally, you probably don't... you should probably be using grantScience() instead. */ - Bool addScience(ScienceType science); + Bool addScience(ScienceType science, Bool playerAction = FALSE); public: Int getSkillPoints() const { return m_skillPoints; } @@ -740,7 +740,7 @@ class Player : public Snapshot attempt to purchase the science, but use prereqs, and charge points. return true if successful. */ - Bool attemptToPurchaseScience(ScienceType science); + Bool attemptToPurchaseScience(ScienceType science, Bool playerAction = FALSE); /** grant the science, ignore prereqs & charge no points, @@ -753,6 +753,8 @@ class Player : public Snapshot /** return true attemptToPurchaseScience() would succeed for this science. */ Bool isCapableOfPurchasingScience(ScienceType science) const; + const BattlePlanBonuses* getBattlePlanBonuses(void) const { return m_battlePlanBonuses; } + protected: // snapshot methods diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Science.h b/GeneralsMD/Code/GameEngine/Include/Common/Science.h index a94ed801bde..bca5cff0324 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Science.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Science.h @@ -63,6 +63,7 @@ class ScienceInfo : public Overridable ScienceVec m_prereqSciences; Int m_sciencePurchasePointCost; Bool m_grantable; + std::vector m_grantedUpgradeNames; ScienceInfo() : m_science(SCIENCE_INVALID), @@ -91,6 +92,8 @@ class ScienceStore : public SubsystemInterface Bool isScienceGrantable(ScienceType st) const; + Bool getGrantedUpgradeNames(ScienceType st, std::vector& grantedUpgradeNames) const; + Bool getNameAndDescription(ScienceType st, UnicodeString& name, UnicodeString& description) const; Bool playerHasPrereqsForScience(const Player* player, ScienceType st) const; diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Upgrade.h b/GeneralsMD/Code/GameEngine/Include/Common/Upgrade.h index 132d22b3811..bb8585a4bfd 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Upgrade.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Upgrade.h @@ -182,6 +182,8 @@ class UpgradeTemplate : public MemoryPoolObject const AudioEventRTS* getUnitSpecificSound() const { return &m_unitSpecificSound; } AcademyClassificationType getAcademyClassificationType() const { return m_academyClassificationType; } + const Bool isSilentCompletion(void) const { return m_silentCompletion; } + /// inventory pictures void cacheButtonImage(); const Image* getButtonImage() const { return m_buttonImage; } @@ -212,6 +214,8 @@ class UpgradeTemplate : public MemoryPoolObject AudioEventRTS m_unitSpecificSound; ///< Secondary sound played when upgrade researched. AcademyClassificationType m_academyClassificationType; ///< A value used by the academy to evaluate advice based on what players do. + Bool m_silentCompletion; ///< hide notification and audio when this upgrade is completed. + UpgradeTemplate *m_next; ///< next UpgradeTemplate *m_prev; ///< prev diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/ArmorSet.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/ArmorSet.h index 1d50ec28520..c2151e31227 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/ArmorSet.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/ArmorSet.h @@ -45,6 +45,7 @@ class INI; // has all condition flags set to zero. enum ArmorSetType CPP_11(: Int) { + ARMORSET_NONE = -1, // The access and use of this enum has the bit shifting built in, so this is a 0,1,2,3,4,5 enum ARMORSET_VETERAN = 0, ARMORSET_ELITE = 1, diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattlePlanBonusBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattlePlanBonusBehavior.h new file mode 100644 index 00000000000..12cd2d0d45b --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattlePlanBonusBehavior.h @@ -0,0 +1,178 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: BattlePlanBonusBehavior.h ///////////////////////////////////////////////////////////////////////// +// Author: Andi W, September 2025 +// Desc: Behavior to react to battlePlans being applied or removed to this object +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __BATTLEPLANBONUSBEHAVIOR_H_ +#define __BATTLEPLANBONUSBEHAVIOR_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// + +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/UpgradeModule.h" +#include "GameLogic/Module/BattlePlanUpdate.h" + +enum BattlePlanStatus CPP_11(: Int); +enum WeaponBonusConditionType CPP_11(: Int); +enum WeaponSetType CPP_11(: Int); +enum ArmorSetType CPP_11(: Int); +enum ObjectStatusTypes CPP_11(: Int); + +enum { BATTLE_PLAN_COUNT = PLANSTATUS_COUNT-1 }; + +//------------------------------------------------------------------------------------------------- +class BattlePlanBonusBehaviorModuleData : public BehaviorModuleData +{ +public: + UpgradeMuxData m_upgradeMuxData; + + Bool m_initiallyActive; // Apply upgrade immediately (Does this make sense?) + Bool m_overrideGlobal; // Do not apply effects from global BattlePlan bonus + Bool m_shouldParalyze; // Paralyze this unit when applying BattlePlans + + WeaponBonusConditionType m_weaponBonusEntries[BATTLE_PLAN_COUNT]; + WeaponSetType m_weaponSetFlagEntries[BATTLE_PLAN_COUNT]; + ArmorSetType m_armorSetFlagEntries[BATTLE_PLAN_COUNT]; + Real m_armorDamageScalarEntries[BATTLE_PLAN_COUNT]; + Real m_sightRangeScalarEntries[BATTLE_PLAN_COUNT]; + //Real m_movementSpeedScalarEntries[BATTLE_PLAN_COUNT]; + ObjectStatusTypes m_statusToSetEntries[BATTLE_PLAN_COUNT]; + ObjectStatusTypes m_statusToClearEntries[BATTLE_PLAN_COUNT]; + + //std::vector> m_weaponBonusEntries; + //std::vector> m_weaponSetFlagEntries; + //std::vector> m_armorSetFlagEntries; + //std::vector> m_armorDamageScalarEntries; + //std::vector> m_sightRangeScalarEntries; + //std::vector> m_movementSpeedScalarEntries; + + BattlePlanBonusBehaviorModuleData(); + + static void buildFieldParse(MultiIniFieldParse& p); + +private: + static void parseBPWeaponBonus(INI* ini, void* instance, void* store, const void* userData); + static void parseBPWeaponSetFlag(INI* ini, void* instance, void* store, const void* userData); + static void parseBPArmorSetFlag(INI* ini, void* instance, void* store, const void* userData); + static void parseBPArmorDamageScalar(INI* ini, void* instance, void* store, const void* userData); + static void parseBPSightRangeScalar(INI* ini, void* instance, void* store, const void* userData); + //static void parseBPMovementSpeedScalar(INI* ini, void* instance, void* store, const void* userData); + static void parseBPStatusToSet(INI* ini, void* instance, void* store, const void* userData); + static void parseBPStatusToClear(INI* ini, void* instance, void* store, const void* userData); +}; +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class BattlePlanBonusBehaviorInterface +{ +public: + virtual void applyBonus(const BattlePlanBonuses* bonus, bool checkIsValid = TRUE) = 0; + //virtual void removeBonus(const BattlePlanBonuses* bonus) = 0; + virtual Bool shouldParalyze() const = 0; + virtual Bool isOverrideGlobalBonus() const = 0; + virtual Bool isConflicting() const = 0; + virtual Bool isAnyEffectApplied() const = 0; + virtual void removeActiveEffects() = 0; +}; +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class BattlePlanBonusBehavior : public BehaviorModule, public UpgradeMux, public BattlePlanBonusBehaviorInterface +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(BattlePlanBonusBehavior, "BattlePlanBonusBehavior") + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(BattlePlanBonusBehavior, BattlePlanBonusBehaviorModuleData) + +private: + //UnsignedInt m_triggerFrame; + //// UnsignedInt m_shotsLeft; + //// TODO: Which weaponslot + //Bool m_triggerCompleted; + +public: + + BattlePlanBonusBehavior(Thing* thing, const ModuleData* moduleData); + // virtual destructor prototype provided by memory pool declaration + + // module methods + static Int getInterfaceMask() { return BehaviorModule::getInterfaceMask() | MODULEINTERFACE_UPGRADE; } + + // BehaviorModule + virtual UpgradeModuleInterface* getUpgrade() { return this; } + virtual BattlePlanBonusBehaviorInterface* getBattlePlanBonusBehaviorInterface() { return this; } + +protected: + // BattlePlan stuff + + void applyBonus(const BattlePlanBonuses*, bool checkIsValid = TRUE); + //void removeBonus(const BattlePlanBonuses*); + Bool shouldParalyze() const; + Bool isOverrideGlobalBonus() const; + + Bool isConflicting() const; + Bool isAnyEffectApplied() const; + void removeActiveEffects(); + + // Upgrade stuff + + virtual void upgradeImplementation(); + + virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const + { + getBattlePlanBonusBehaviorModuleData()->m_upgradeMuxData.getUpgradeActivationMasks(activation, conflicting); + } + + virtual void performUpgradeFX() + { + getBattlePlanBonusBehaviorModuleData()->m_upgradeMuxData.performUpgradeFX(getObject()); + } + + virtual void processUpgradeRemoval() + { + // I can't take it any more. Let the record show that I think the UpgradeMux multiple inheritence is CRAP. + getBattlePlanBonusBehaviorModuleData()->m_upgradeMuxData.muxDataProcessUpgradeRemoval(getObject()); + } + + virtual Bool requiresAllActivationUpgrades() const + { + return getBattlePlanBonusBehaviorModuleData()->m_upgradeMuxData.m_requiresAllTriggers; + } + + inline Bool isUpgradeActive() const { return isAlreadyUpgraded(); } + + virtual Bool isSubObjectsUpgrade() { return false; } + +private: + Bool isEffectValid() const; + void addBonusForType(BattlePlanStatus plan); + void removeBonusForType(BattlePlanStatus plan); + + Bool m_effectApplied[BATTLE_PLAN_COUNT]; +}; + +#endif // __BattlePlanBonusBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattlePlanUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattlePlanUpdate.h index 7edae5d51e1..4b1b4c24d31 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattlePlanUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BattlePlanUpdate.h @@ -98,12 +98,26 @@ enum TransitionStatus CPP_11(: Int) TRANSITIONSTATUS_PACKING, }; +#ifdef DEFINE_BATTLEPLANSTATUS_NAMES +static const char* TheBattlePlanStatusNames[] = +{ + "NONE", + "BOMBARDMENT", + "HOLD_THE_LINE", + "SEARCH_AND_DESTROY", + + NULL +}; +#endif + enum BattlePlanStatus CPP_11(: Int) { PLANSTATUS_NONE, PLANSTATUS_BOMBARDMENT, PLANSTATUS_HOLDTHELINE, PLANSTATUS_SEARCHANDDESTROY, + + PLANSTATUS_COUNT }; class BattlePlanBonuses : public MemoryPoolObject diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BehaviorModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BehaviorModule.h index 2653fcedf5e..f5dacd3dd34 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BehaviorModule.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BehaviorModule.h @@ -81,7 +81,8 @@ class DamageInfo; class ParticleSystemTemplate; class StealthUpdate; class SpyVisionUpdate; - +// ----------------- +class BattlePlanBonusBehaviorInterface; //------------------------------------------------------------------------------------------------- class BehaviorModuleData : public ModuleData @@ -140,6 +141,8 @@ class BehaviorModuleInterface virtual CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() = 0; virtual const CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() const = 0; + virtual BattlePlanBonusBehaviorInterface* getBattlePlanBonusBehaviorInterface() = 0; + }; //------------------------------------------------------------------------------------------------- @@ -194,6 +197,7 @@ class BehaviorModule : public ObjectModule, public BehaviorModuleInterface virtual SpawnBehaviorInterface* getSpawnBehaviorInterface() { return NULL; } virtual CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() { return NULL; } virtual const CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() const { return NULL; } + virtual BattlePlanBonusBehaviorInterface* getBattlePlanBonusBehaviorInterface() { return NULL; }; protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h index 4f398c34b63..857852d302b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h @@ -39,6 +39,7 @@ class Thing; enum StealthLookType CPP_11(: Int); enum EvaMessage CPP_11(: Int); +enum WeaponSetType CPP_11(: Int); class FXList; enum @@ -155,6 +156,8 @@ class StealthUpdate : public UpdateModule Bool isGrantedBySpecialPower( void ) { return getStealthUpdateModuleData()->m_grantedBySpecialPower; } Bool isTemporaryGrant() { return m_framesGranted > 0; } + inline void setStealthLevelOverride(UnsignedInt stealthLevel) { m_stealthLevelOverride = stealthLevel; } + protected: StealthLookType calcStealthedStatusForPlayer(const Object* obj, const Player* player); @@ -175,6 +178,8 @@ class StealthUpdate : public UpdateModule Real m_pulsePhaseRate; Real m_pulsePhase; + UnsignedInt m_stealthLevelOverride; //Override stealth conditions via upgrade + //Disguise only members Int m_disguiseAsPlayerIndex; //The player team we are wanting to disguise as (might not actually be disguised yet). const ThingTemplate *m_disguiseAsTemplate; //The disguise template (might not actually be using it yet) @@ -187,7 +192,6 @@ class StealthUpdate : public UpdateModule // runtime xfer members (does not need saving) Bool m_xferRestoreDisguise; //Tells us we need to restore our disguise WeaponSetType m_requiresWeaponSetType; - }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpgrade.h index 9142c9b2e1c..8a84e035bbc 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpgrade.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpgrade.h @@ -38,14 +38,27 @@ // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// class Thing; -//------------------------------------------------------------------------------------------------- -/** The default die module */ +//----------------------------------------------------------------------------- +class StealthUpgradeModuleData : public UpgradeModuleData +{ +public: + UnsignedInt m_stealthLevel; ///< override stealthLevel of the stealthUpdate module (=StealthForbiddenConditions) + Bool m_enableStealth; ///< Enable or Disable stealth + + StealthUpgradeModuleData() + { + m_enableStealth = TRUE; + m_stealthLevel = 0; + } + + static void buildFieldParse(MultiIniFieldParse& p); +}; //------------------------------------------------------------------------------------------------- class StealthUpgrade : public UpgradeModule { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( StealthUpgrade, "StealthUpgrade" ) - MAKE_STANDARD_MODULE_MACRO( StealthUpgrade ); + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( StealthUpgrade, StealthUpgradeModuleData); public: diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index d412b3efb92..d5d64a5d347 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -46,6 +46,8 @@ #define DEFINE_SCIENCE_AVAILABILITY_NAMES +#define NO_DEBUG_CRC + #include "Common/ActionManager.h" #include "Common/BuildAssistant.h" #include "Common/CRCDebug.h" @@ -97,6 +99,7 @@ #include "GameLogic/Module/SupplyTruckAIUpdate.h" #include "GameLogic/Module/BattlePlanUpdate.h" #include "GameLogic/Module/ProductionUpdate.h" +#include "GameLogic/Module/BattlePlanBonusBehavior.h" #include "GameLogic/VictoryConditions.h" #include "GameNetwork/GameInfo.h" @@ -2701,7 +2704,7 @@ void Player::resetSciences() //============================================================================= /// returns TRUE if sciences were gained/lost. -Bool Player::addScience(ScienceType science) +Bool Player::addScience(ScienceType science, Bool playerAction/* = FALSE*/) { if (hasScience(science)) return false; @@ -2710,6 +2713,57 @@ Bool Player::addScience(ScienceType science) m_sciences.push_back(science); + // Grant Upgrades + std::vector upgrades; + TheScienceStore->getGrantedUpgradeNames(science, upgrades); + for (AsciiString upgradeName : upgrades) { + const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(upgradeName); + if (!upgradeTemplate) + { + DEBUG_LOG(("Player::addScience - can't find upgrade template %s.", upgradeName.str())); + continue; + } + + if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) + { + DEBUG_LOG(("Player::addScience - Granting upgrade %s.", upgradeName.str())); + addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); + + // Only show audio and visuals if this was a manual player command + if (playerAction && !upgradeTemplate->getDisplayNameLabel().isEmpty() && !upgradeTemplate->isSilentCompletion()) { + getAcademyStats()->recordUpgrade(upgradeTemplate, FALSE); + // print a message to the local player + if (isLocalPlayer()) + { + UnicodeString msg; + UnicodeString format = TheGameText->fetch("UPGRADE:UpgradeComplete"); + UnicodeString upgradeName = TheGameText->fetch(upgradeTemplate->getDisplayNameLabel().str()); + + msg.format(format.str(), upgradeName.str()); + TheInGameUI->message(msg); + + //Play the sound for the upgrade, because we just built it! + AudioEventRTS sound = *upgradeTemplate->getResearchCompleteSound(); + if (TheAudio->isValidAudioEvent(&sound)) + { + //We have a custom upgrade complete sound. + sound.setPlayerIndex(getPlayerIndex()); + TheAudio->addAudioEvent(&sound); + } + else + { + //Use a generic EVA event. + TheEva->setShouldPlay(EVA_UpgradeComplete); + } + } + } + } + else + { + DEBUG_LOG(("Player::addScience - Cannot grant OBJECT type upgrade %s.", upgradeName.str())); + } + } + // 'wake up' any special powers controlled by, well, stuff for (PlayerTeamList::iterator it = m_playerTeamPrototypes.begin(); it != m_playerTeamPrototypes.end(); ++it) @@ -2767,7 +2821,7 @@ void Player::addSciencePurchasePoints(Int delta) } //============================================================================= -Bool Player::attemptToPurchaseScience(ScienceType science) +Bool Player::attemptToPurchaseScience(ScienceType science, Bool playerAction/* = FALSE*/) { if (!isCapableOfPurchasingScience(science)) { @@ -2777,7 +2831,7 @@ Bool Player::attemptToPurchaseScience(ScienceType science) Int cost = TheScienceStore->getSciencePurchaseCost(science); addSciencePurchasePoints(-cost); - addScience(science); + addScience(science, playerAction); getAcademyStats()->recordGeneralsPointsSpent( cost ); @@ -3690,30 +3744,51 @@ static void localApplyBattlePlanBonusesToObject( Object *obj, void *userData ) Object *objectToValidate = obj; Object *objectToModify = obj; - DEBUG_LOG(("localApplyBattlePlanBonusesToObject() - looking at object %d (%s)", + /*DEBUG_LOG(("localApplyBattlePlanBonusesToObject() - looking at object %d (%s)", (objectToValidate)?objectToValidate->getID():INVALID_ID, - (objectToValidate)?objectToValidate->getTemplate()->getName().str():"")); + (objectToValidate)?objectToValidate->getTemplate()->getName().str():""));*/ //First check if the obj is a projectile -- if so split the //object so that the producer is validated, not the projectile. Bool isProjectile = obj->isKindOf( KINDOF_PROJECTILE ); - if( isProjectile ) - { - objectToValidate = TheGameLogic->findObjectByID( obj->getProducerID() ); - DEBUG_LOG(("Object is a projectile - looking at object %d (%s) instead", - (objectToValidate)?objectToValidate->getID():INVALID_ID, - (objectToValidate)?objectToValidate->getTemplate()->getName().str():"")); - } + + // Note AW: Shouldn't projectiles just gain the launcher's weapon bonus anyways? + // This doesn't really fit with the whole BattlePlanBonusBehavior idea. And I don't think it is needed. + + //if( isProjectile ) + //{ + // objectToValidate = TheGameLogic->findObjectByID( obj->getProducerID() ); + // /*DEBUG_LOG(("Object is a projectile - looking at object %d (%s) instead", + // (objectToValidate)?objectToValidate->getID():INVALID_ID, + // (objectToValidate)?objectToValidate->getTemplate()->getName().str():""));*/ + //} if( objectToValidate && objectToValidate->isAnyKindOf( bonus->m_validKindOf ) ) { - DEBUG_LOG(("Is valid kindof")); + //DEBUG_LOG(("Is valid kindof")); if( !objectToValidate->isAnyKindOf( bonus->m_invalidKindOf ) ) { - DEBUG_LOG(("Is not invalid kindof")); + //DEBUG_LOG(("Is not invalid kindof")); //Quite the trek eh? Now we can apply the bonuses! - if( !isProjectile ) + + if (!isProjectile) { - DEBUG_LOG(("Is not projectile. Armor scalar is %g", bonus->m_armorScalar)); + // ------------------------ + // Check Modules + for (BehaviorModule** b = objectToModify->getBehaviorModules(); *b; ++b) + { + BattlePlanBonusBehaviorInterface* bpbi = (*b)->getBattlePlanBonusBehaviorInterface(); + if (bpbi) { + bpbi->applyBonus(bonus); + if (bpbi->isOverrideGlobalBonus()) { + DEBUG_LOG(("### PLAYER localApplyBattlePlanBonusesToObject - OVERRIDE!")); + return; + } + } + } + + // ------------------------ + + //DEBUG_LOG(("Is not projectile. Armor scalar is %g", bonus->m_armorScalar)); //Really important to not apply certain bonuses like health augmentation to projectiles! if( bonus->m_armorScalar != 1.0f ) { @@ -3723,7 +3798,7 @@ static void localApplyBattlePlanBonusesToObject( Object *obj, void *userData ) bonus->m_armorScalar, AS_INT(bonus->m_armorScalar), objectToModify->getID(), objectToModify->getTemplate()->getDisplayName().str(), objectToModify->getControllingPlayer()->getPlayerIndex())); - DEBUG_LOG(("After apply, armor scalar is %g", body->getDamageScalar())); + //DEBUG_LOG(("After apply, armor scalar is %g", body->getDamageScalar())); } if( bonus->m_sightRangeScalar != 1.0f ) { @@ -3778,9 +3853,18 @@ void Player::removeBattlePlanBonusesForObject( Object *obj ) const *bonus = *m_battlePlanBonuses; bonus->m_armorScalar = 1.0f / __max( bonus->m_armorScalar, 0.01f ); bonus->m_sightRangeScalar = 1.0f / __max( bonus->m_sightRangeScalar, 0.01f ); - bonus->m_bombardment = -ALL_PLANS; //Safe to remove as it clears the weapon bonus flag - bonus->m_searchAndDestroy = -ALL_PLANS; //Safe to remove as it clears the weapon bonus flag - bonus->m_holdTheLine = -ALL_PLANS; //Safe to remove as it clears the weapon bonus flag + + //bonus->m_bombardment = -ALL_PLANS; //Safe to remove as it clears the weapon bonus flag + //bonus->m_searchAndDestroy = -ALL_PLANS; //Safe to remove as it clears the weapon bonus flag + //bonus->m_holdTheLine = -ALL_PLANS; //Safe to remove as it clears the weapon bonus flag + + // Update AW: We need use these variables now to track which plan should be added/removed + if (bonus->m_bombardment > 0) + bonus->m_bombardment = -1; + if (bonus->m_searchAndDestroy > 0) + bonus->m_searchAndDestroy = -1; + if (bonus->m_holdTheLine > 0) + bonus->m_holdTheLine = -1; DUMPBATTLEPLANBONUSES(bonus, this, obj); localApplyBattlePlanBonusesToObject( obj, bonus ); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Science.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Science.cpp index bf018c27bcc..d31cc284db8 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Science.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Science.cpp @@ -170,6 +170,7 @@ const ScienceInfo* ScienceStore::findScienceInfo(ScienceType st) const { "IsGrantable", INI::parseBool, NULL, offsetof( ScienceInfo, m_grantable ) }, { "DisplayName", INI::parseAndTranslateLabel, NULL, offsetof( ScienceInfo, m_name) }, { "Description", INI::parseAndTranslateLabel, NULL, offsetof( ScienceInfo, m_description) }, + { "GrantUpgrades", INI::parseAsciiStringVector, NULL, offsetof( ScienceInfo, m_grantedUpgradeNames) }, { 0, 0, 0, 0 } }; @@ -256,6 +257,21 @@ Bool ScienceStore::isScienceGrantable(ScienceType st) const } } +//----------------------------------------------------------------------------- +Bool ScienceStore::getGrantedUpgradeNames(ScienceType st, /*out*/ std::vector& grantedUpgradeNames) const +{ + const ScienceInfo* si = findScienceInfo(st); + if (si) + { + grantedUpgradeNames = si->m_grantedUpgradeNames; + return NULL; + } + else + { + return NULL; + } +} + //----------------------------------------------------------------------------- Bool ScienceStore::getNameAndDescription(ScienceType st, UnicodeString& name, UnicodeString& description) const { diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/Upgrade.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/Upgrade.cpp index 20d1702fcc5..a8c5a76bdb9 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/Upgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/Upgrade.cpp @@ -122,6 +122,7 @@ const FieldParse UpgradeTemplate::m_upgradeFieldParseTable[] = { "ResearchSound", INI::parseAudioEventRTS, NULL, offsetof( UpgradeTemplate, m_researchSound ) }, { "UnitSpecificSound", INI::parseAudioEventRTS, NULL, offsetof( UpgradeTemplate, m_unitSpecificSound ) }, { "AcademyClassify", INI::parseIndexList, TheAcademyClassificationTypeNames, offsetof( UpgradeTemplate, m_academyClassificationType ) }, + { "SilentCompletion", INI::parseBool, NULL, offsetof(UpgradeTemplate, m_silentCompletion) }, { NULL, NULL, NULL, 0 } // keep this last }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 3db7e5df038..573b53c2879 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -50,6 +50,7 @@ #include "GameLogic/Module/BridgeScaffoldBehavior.h" #include "GameLogic/Module/BridgeTowerBehavior.h" #include "GameLogic/Module/CountermeasuresBehavior.h" +#include "GameLogic/Module/BattlePlanBonusBehavior.h" #include "GameLogic/Module/DumbProjectileBehavior.h" #include "GameLogic/Module/FreeFallProjectileBehavior.h" #include "GameLogic/Module/InstantDeathBehavior.h" @@ -341,6 +342,7 @@ void ModuleFactory::init( void ) addModule( BridgeScaffoldBehavior ); addModule( BridgeTowerBehavior ); addModule( CountermeasuresBehavior ); + addModule( BattlePlanBonusBehavior ); addModule( DumbProjectileBehavior ); addModule( FreeFallProjectileBehavior ); addModule( PhysicsBehavior ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BattlePlanBonusBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BattlePlanBonusBehavior.cpp new file mode 100644 index 00000000000..4e173ba41de --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BattlePlanBonusBehavior.cpp @@ -0,0 +1,510 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: BattlePlanBonusBehavior.cpp /////////////////////////////////////////////////////////////////////// +// Author: +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#define DEFINE_BATTLEPLANSTATUS_NAMES +#define DEFINE_WEAPONBONUSCONDITION_NAMES + +#include "Common/INI.h" +#include "Common/Xfer.h" +#include "Common/Player.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Module/BattlePlanBonusBehavior.h" +#include "GameLogic/Module/BattlePlanUpdate.h" +#include "GameLogic/Object.h" +#include "GameLogic/Weapon.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/AIUpdate.h" +//#include "Common/ObjectStatusTypes.h" + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +BattlePlanBonusBehaviorModuleData::BattlePlanBonusBehaviorModuleData() +{ + m_initiallyActive = false; + for (UnsignedInt i = 0; i < BATTLE_PLAN_COUNT; i++) { + m_weaponBonusEntries[i] = WEAPONBONUSCONDITION_INVALID; + m_weaponSetFlagEntries[i] = WEAPONSET_NONE; + m_armorSetFlagEntries[i] = ARMORSET_NONE; + m_armorDamageScalarEntries[i] = 1.0; + m_sightRangeScalarEntries[i] = 1.0; + //m_movementSpeedScalarEntries[i] = 1.0; + m_statusToSetEntries[i] = OBJECT_STATUS_NONE; + m_statusToClearEntries[i] = OBJECT_STATUS_NONE; + } +} +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehaviorModuleData::parseBPWeaponBonus(INI* ini, void* instance, void* store, const void* userData) +{ + BattlePlanBonusBehaviorModuleData* self = (BattlePlanBonusBehaviorModuleData*)instance; + BattlePlanStatus plan = (BattlePlanStatus)INI::scanIndexList(ini->getNextToken(), TheBattlePlanStatusNames); + if ((plan) == PLANSTATUS_NONE) + return; + + self->m_weaponBonusEntries[plan-1] = (WeaponBonusConditionType)INI::scanIndexList(ini->getNextToken(), TheWeaponBonusNames); +} +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehaviorModuleData::parseBPWeaponSetFlag(INI* ini, void* instance, void* store, const void* userData) +{ + BattlePlanBonusBehaviorModuleData* self = (BattlePlanBonusBehaviorModuleData*)instance; + BattlePlanStatus plan = (BattlePlanStatus)INI::scanIndexList(ini->getNextToken(), TheBattlePlanStatusNames); + if ((plan) == PLANSTATUS_NONE) + return; + + self->m_weaponSetFlagEntries[plan - 1] = (WeaponSetType)INI::scanIndexList(ini->getNextToken(), WeaponSetFlags::getBitNames()); +} +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehaviorModuleData::parseBPArmorSetFlag(INI* ini, void* instance, void* store, const void* userData) +{ + BattlePlanBonusBehaviorModuleData* self = (BattlePlanBonusBehaviorModuleData*)instance; + BattlePlanStatus plan = (BattlePlanStatus)INI::scanIndexList(ini->getNextToken(), TheBattlePlanStatusNames); + if ((plan) == PLANSTATUS_NONE) + return; + + self->m_armorSetFlagEntries[plan - 1] = (ArmorSetType)INI::scanIndexList(ini->getNextToken(), ArmorSetFlags::getBitNames()); +} +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehaviorModuleData::parseBPArmorDamageScalar(INI* ini, void* instance, void* store, const void* userData) +{ + BattlePlanBonusBehaviorModuleData* self = (BattlePlanBonusBehaviorModuleData*)instance; + BattlePlanStatus plan = (BattlePlanStatus)INI::scanIndexList(ini->getNextToken(), TheBattlePlanStatusNames); + if ((plan) == PLANSTATUS_NONE) + return; + + self->m_armorDamageScalarEntries[plan - 1] = INI::scanReal(ini->getNextToken()); +} +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehaviorModuleData::parseBPSightRangeScalar(INI* ini, void* instance, void* store, const void* userData) +{ + BattlePlanBonusBehaviorModuleData* self = (BattlePlanBonusBehaviorModuleData*)instance; + BattlePlanStatus plan = (BattlePlanStatus)INI::scanIndexList(ini->getNextToken(), TheBattlePlanStatusNames); + if ((plan) == PLANSTATUS_NONE) + return; + + self->m_sightRangeScalarEntries[plan - 1] = INI::scanReal(ini->getNextToken()); +} +//------------------------------------------------------------------------------------------------- +//void BattlePlanBonusBehaviorModuleData::parseBPMovementSpeedScalar(INI* ini, void* instance, void* store, const void* userData) +//{ +// BattlePlanBonusBehaviorModuleData* self = (BattlePlanBonusBehaviorModuleData*)instance; +// BattlePlanStatus plan = (BattlePlanStatus)INI::scanIndexList(ini->getNextToken(), TheBattlePlanStatusNames); +// if ((plan) == PLANSTATUS_NONE) +// return; +// +// self->m_movementSpeedScalarEntries[plan - 1] = INI::scanReal(ini->getNextToken()); +//} +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehaviorModuleData::parseBPStatusToSet(INI* ini, void* instance, void* store, const void* userData) +{ + BattlePlanBonusBehaviorModuleData* self = (BattlePlanBonusBehaviorModuleData*)instance; + BattlePlanStatus plan = (BattlePlanStatus)INI::scanIndexList(ini->getNextToken(), TheBattlePlanStatusNames); + if ((plan) == PLANSTATUS_NONE) + return; + + self->m_statusToSetEntries[plan - 1] = (ObjectStatusTypes)INI::scanIndexList(ini->getNextToken(), ObjectStatusMaskType::getBitNames()); +} +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehaviorModuleData::parseBPStatusToClear(INI* ini, void* instance, void* store, const void* userData) +{ + BattlePlanBonusBehaviorModuleData* self = (BattlePlanBonusBehaviorModuleData*)instance; + BattlePlanStatus plan = (BattlePlanStatus)INI::scanIndexList(ini->getNextToken(), TheBattlePlanStatusNames); + if ((plan) == PLANSTATUS_NONE) + return; + + self->m_statusToClearEntries[plan - 1] = (ObjectStatusTypes)INI::scanIndexList(ini->getNextToken(), ObjectStatusMaskType::getBitNames()); +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void BattlePlanBonusBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + static const FieldParse dataFieldParse[] = + { + { "StartsActive", INI::parseBool, NULL, offsetof(BattlePlanBonusBehaviorModuleData, m_initiallyActive) }, + { "OverrideGlobalBonus", INI::parseBool, NULL, offsetof(BattlePlanBonusBehaviorModuleData, m_overrideGlobal) }, + { "ShouldParalyzeOnPlanChange", INI::parseBool, NULL, offsetof(BattlePlanBonusBehaviorModuleData, m_shouldParalyze) }, + + { "WeaponBonus", parseBPWeaponBonus, NULL, 0 }, + { "WeaponSet", parseBPWeaponSetFlag, NULL, 0 }, + { "ArmorSet", parseBPArmorSetFlag, NULL, 0 }, + { "ArmorDamageScalar", parseBPArmorDamageScalar, NULL, 0 }, + { "SightRangeScalar", parseBPSightRangeScalar, NULL, 0 }, + //{ "MovementSpeedScalar", parseBPMovementSpeedScalar, NULL, 0 }, + { "StatusToSet", parseBPStatusToSet, NULL, 0 }, + { "StatusToClear", parseBPStatusToClear, NULL, 0 }, + + { 0, 0, 0, 0 } + }; + + BehaviorModuleData::buildFieldParse(p); + p.add(dataFieldParse); + p.add(UpgradeMuxData::getFieldParse(), offsetof(BattlePlanBonusBehaviorModuleData, m_upgradeMuxData)); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +BattlePlanBonusBehavior::BattlePlanBonusBehavior(Thing* thing, const ModuleData* moduleData) : BehaviorModule(thing, moduleData) +{ + if (getBattlePlanBonusBehaviorModuleData()->m_initiallyActive) + { + giveSelfUpgrade(); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +BattlePlanBonusBehavior::~BattlePlanBonusBehavior(void) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehavior::upgradeImplementation(void) +{ + // DEBUG_LOG(("### BattlePlanBonusBehavior(%s)::upgradeImplementation 0", KEYNAME(getModuleTagNameKey()).str())); + + // Get other BattlePlanBonusBehavior and remove their active effects if they are conflicting + Object* obj = getObject(); + + for (BehaviorModule** b = obj->getBehaviorModules(); *b; ++b) + { + BattlePlanBonusBehaviorInterface* bpbi = (*b)->getBattlePlanBonusBehaviorInterface(); + if (bpbi && bpbi != this) { + if (bpbi->isConflicting() && bpbi->isAnyEffectApplied()) { + DEBUG_LOG(("### BattlePlanBonusBehavior(%s)::upgradeImplementation 1. Try to remove effects of conflicting module", KEYNAME(getModuleTagNameKey()).str())); + bpbi->removeActiveEffects(); + } + } + } + + + // Note: this doesn't handle the Regular bonus being applied/removed Instead of Applying our bonus, we should instead just reapply all. + Player* player = obj->getControllingPlayer(); + const BattlePlanBonuses* bonus = player->getBattlePlanBonuses(); + if (bonus) { + player->removeBattlePlanBonusesForObject(obj); + player->applyBattlePlanBonusesForObject(obj); + + // We still need to apply our bonus, because at this point we do not count as Upgraded + // Check for valid KindOf. This shouldn't be needed unless you screwed up your INI, but just to be safe + if (player->doesObjectQualifyForBattlePlan(obj)) { + applyBonus(bonus, FALSE); + } + } + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehavior::applyBonus(const BattlePlanBonuses* bonus, bool checkIsValid /* = True*/) +{ + //DEBUG_LOG(("---\n")); + //DEBUG_LOG(("### BattlePlanBonusBehavior(%s)::applyBonus", KEYNAME(getModuleTagNameKey()).str())); + //DEBUG_LOG(("- m_bombardment = %d", bonus->m_bombardment)); + //DEBUG_LOG(("- m_holdTheLine = %d", bonus->m_holdTheLine)); + //DEBUG_LOG(("- m_searchAndDestroy = %d", bonus->m_searchAndDestroy)); + //DEBUG_LOG(("---\n")); + + + //const BattlePlanBonusBehaviorModuleData* d = getBattlePlanBonusBehaviorModuleData(); + if (!checkIsValid || isEffectValid()) { + + //DEBUG_LOG(("### BattlePlanBonusBehavior(%s)::applyBonus - valid", KEYNAME(getModuleTagNameKey()).str())); + + //Object* obj = getObject(); + + if (bonus->m_bombardment >= 1) { + addBonusForType(PLANSTATUS_BOMBARDMENT); + } + else if (bonus->m_bombardment <= -1) { + removeBonusForType(PLANSTATUS_BOMBARDMENT); + } + + if (bonus->m_holdTheLine >= 1) { + addBonusForType(PLANSTATUS_HOLDTHELINE); + } + else if (bonus->m_holdTheLine <= -1) { + removeBonusForType(PLANSTATUS_HOLDTHELINE); + } + + if (bonus->m_searchAndDestroy >= 1) { + addBonusForType(PLANSTATUS_SEARCHANDDESTROY); + } + else if (bonus->m_searchAndDestroy <= -1) { + removeBonusForType(PLANSTATUS_SEARCHANDDESTROY); + } + } + /*else { + DEBUG_LOG(("### BattlePlanBonusBehavior(%s)::applyBonus - Not Valid.", KEYNAME(getModuleTagNameKey()).str())); + }*/ + DEBUG_LOG(("---\n")); +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehavior::addBonusForType(BattlePlanStatus plan) +{ + // DEBUG_LOG(("### BattlePlanBonusBehavior(%s)::addBonusForType %d", KEYNAME(getModuleTagNameKey()).str(), plan)); + const BattlePlanBonusBehaviorModuleData* d = getBattlePlanBonusBehaviorModuleData(); + Object* obj = getObject(); + int idx = plan - 1; + + //Sanity check: + DEBUG_ASSERTCRASH(!m_effectApplied[idx], ("BattlePlanBonusBehavior::addBonusForType -- Bonus is currently active!")); + + if (idx > BATTLE_PLAN_COUNT) + return; + if (d->m_weaponBonusEntries[idx] != -1) { + obj->setWeaponBonusCondition(d->m_weaponBonusEntries[idx]); + } + if (d->m_weaponSetFlagEntries[idx] != -1) { + obj->setWeaponSetFlag(d->m_weaponSetFlagEntries[idx]); + } + if (d->m_armorSetFlagEntries[idx] != -1) { + BodyModuleInterface* body = obj->getBodyModule(); + if (body) + body->setArmorSetFlag(d->m_armorSetFlagEntries[idx]); + } + if (d->m_armorDamageScalarEntries[idx] != 1.0) { + BodyModuleInterface* body = obj->getBodyModule(); + if (body) + body->applyDamageScalar(d->m_armorDamageScalarEntries[idx]); + } + if (d->m_sightRangeScalarEntries[idx] != 1.0) { + obj->setVisionRange(obj->getVisionRange() * d->m_sightRangeScalarEntries[idx]); + obj->setShroudClearingRange(obj->getShroudClearingRange() * d->m_sightRangeScalarEntries[idx]); + } + /*if (d->m_movementSpeedScalarEntries[idx] != 1.0) { + AIUpdateInterface* ai = obj->getAI(); + if (ai) { + Locomotor* loco = ai->getCurLocomotor(); + loco->applySpeedMultiplier(d->m_movementSpeedScalarEntries[idx]); + } + }*/ + if (d->m_statusToSetEntries[idx] != -1) { + obj->setStatus(MAKE_OBJECT_STATUS_MASK(d->m_statusToSetEntries[idx])); + } + if (d->m_statusToClearEntries[idx] != -1) { + obj->setStatus(MAKE_OBJECT_STATUS_MASK(d->m_statusToClearEntries[idx]), FALSE); + } + + m_effectApplied[idx] = TRUE; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehavior::removeBonusForType(BattlePlanStatus plan) +{ + // DEBUG_LOG(("### BattlePlanBonusBehavior(%s)::removeBonusForType %d", KEYNAME(getModuleTagNameKey()).str(), plan)); + + const BattlePlanBonusBehaviorModuleData* d = getBattlePlanBonusBehaviorModuleData(); + Object* obj = getObject(); + int idx = plan - 1; + + DEBUG_ASSERTCRASH(m_effectApplied[idx], ("BattlePlanBonusBehavior::removeBonusForType -- Bonus is currently NOT active!")); + + if (idx > BATTLE_PLAN_COUNT) + return; + if (d->m_weaponBonusEntries[idx] != -1) { + obj->clearWeaponBonusCondition(d->m_weaponBonusEntries[idx]); + } + if (d->m_weaponSetFlagEntries[idx] != -1) { + obj->clearWeaponSetFlag(d->m_weaponSetFlagEntries[idx]); + } + if (d->m_armorSetFlagEntries[idx] != -1) { + BodyModuleInterface* body = obj->getBodyModule(); + if (body) + body->clearArmorSetFlag(d->m_armorSetFlagEntries[idx]); + } + if (d->m_armorDamageScalarEntries[idx] != 1.0) { + BodyModuleInterface* body = obj->getBodyModule(); + if (body) + body->applyDamageScalar(1.0f / __max(d->m_armorDamageScalarEntries[idx], 0.01f)); + } + if (d->m_sightRangeScalarEntries[idx] != 1.0) { + Real sightRangeScalar = 1.0f / __max(d->m_sightRangeScalarEntries[idx], 0.01f); + obj->setVisionRange(obj->getVisionRange() * sightRangeScalar); + obj->setShroudClearingRange(obj->getShroudClearingRange() * sightRangeScalar); + } + /*if (d->m_movementSpeedScalarEntries[idx] != 1.0) { + AIUpdateInterface* ai = obj->getAI(); + if (ai) { + Locomotor* loco = ai->getCurLocomotor(); + loco->applySpeedMultiplier(d->m_movementSpeedScalarEntries[idx]); + } + }*/ + if (d->m_statusToSetEntries[idx] != -1) { + obj->setStatus(MAKE_OBJECT_STATUS_MASK(d->m_statusToSetEntries[idx]), FALSE); + } + if (d->m_statusToClearEntries[idx] != -1) { + obj->setStatus(MAKE_OBJECT_STATUS_MASK(d->m_statusToClearEntries[idx])); + } + + m_effectApplied[idx] = FALSE; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool BattlePlanBonusBehavior::shouldParalyze(void) const +{ + // DEBUG_LOG(("### BattlePlanBonusBehavior(%s)::shouldParalyze ?", KEYNAME(getModuleTagNameKey()).str())); + if (isEffectValid()) { + Bool shouldParalyze = getBattlePlanBonusBehaviorModuleData()->m_shouldParalyze; + // DEBUG_LOG(("### BattlePlanBonusBehavior(%s)::shouldParalyze = %d!!!", KEYNAME(getModuleTagNameKey()).str(), shouldParalyze)); + return shouldParalyze; + } + return TRUE; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool BattlePlanBonusBehavior::isOverrideGlobalBonus(void) const +{ + if (isEffectValid()) { + return getBattlePlanBonusBehaviorModuleData()->m_overrideGlobal; + } + return FALSE; +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool BattlePlanBonusBehavior::isConflicting(void) const +{ + // Check for conflicting upgrades + UpgradeMaskType activation, conflicting; + getUpgradeActivationMasks(activation, conflicting); + const Object* obj = getObject(); + if (obj->getObjectCompletedUpgradeMask().testForAny(conflicting)) + { + return TRUE; + } + if (obj->getControllingPlayer() && obj->getControllingPlayer()->getCompletedUpgradeMask().testForAny(conflicting)) + { + return TRUE; + } + return FALSE; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void BattlePlanBonusBehavior::removeActiveEffects(void) +{ + // DEBUG_LOG(("### BattlePlanBonusBehavior(%s)::removeActiveEffects 0", KEYNAME(getModuleTagNameKey()).str())); + + for (int i = 0; i < BATTLE_PLAN_COUNT; i++) { + if (m_effectApplied[i]) { + BattlePlanStatus plan = (BattlePlanStatus)(i + 1); + removeBonusForType(plan); + } + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool BattlePlanBonusBehavior::isEffectValid(void) const +{ + return isUpgradeActive() && !isConflicting(); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool BattlePlanBonusBehavior::isAnyEffectApplied(void) const +{ + for (int i = 0; i < BATTLE_PLAN_COUNT; i++) { + if (m_effectApplied[i]) { + return TRUE; + } + } + return FALSE; +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void BattlePlanBonusBehavior::crc(Xfer* xfer) +{ + + // extend base class + BehaviorModule::crc(xfer); + + // extend upgrade mux + UpgradeMux::upgradeMuxCRC(xfer); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ + // ------------------------------------------------------------------------------------------------ +void BattlePlanBonusBehavior::xfer(Xfer* xfer) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + BehaviorModule::xfer(xfer); + + // extend upgrade mux + UpgradeMux::upgradeMuxXfer(xfer); + + //// trigger frame + //xfer->xferUnsignedInt(&m_triggerFrame); + + // m_effectApplied + //xfer->xferBool(&m_effectApplied); + // This is now an array + //if (xfer->getXferMode() == XFER_SAVE) + //{ + for (int i = 0; i < BATTLE_PLAN_COUNT; i++) { + xfer->xferBool(&m_effectApplied[i]); + } + //} + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void BattlePlanBonusBehavior::loadPostProcess(void) +{ + + // extend base class + BehaviorModule::loadPostProcess(); + + // extend upgrade mux + UpgradeMux::upgradeMuxLoadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ParkingPlaceBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ParkingPlaceBehavior.cpp index b9ebcd3e1e7..ffddb8bc8a3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ParkingPlaceBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ParkingPlaceBehavior.cpp @@ -765,7 +765,7 @@ UpdateSleepTime ParkingPlaceBehavior::update() // Check if Damage Scalar is upgraded: - if (!m_damageScalarUpgradeApplied) { + if (!m_damageScalarUpgradeApplied && d->m_damageScalarUpgradeTrigger.isNotEmpty()) { Player* player = getObject()->getControllingPlayer(); const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(d->m_damageScalarUpgradeTrigger); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp index 31e4988270c..a250c1328c1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp @@ -60,6 +60,7 @@ #include "GameLogic/Module/ActiveBody.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/StealthDetectorUpdate.h" +#include "GameLogic/Module/BattlePlanBonusBehavior.h" //------------------------------------------------------------------------------------------------- @@ -719,7 +720,18 @@ static void paralyzeTroop( Object *obj, void *userData ) { if( !obj->isAnyKindOf( data->m_invalidMemberKindOf ) ) { - obj->setDisabledUntil( DISABLED_PARALYZED, TheGameLogic->getFrame() + data->m_battlePlanParalyzeFrames ); + Bool shouldParalyze = true; + // Check Modules + for (BehaviorModule** b = obj->getBehaviorModules(); *b; ++b) + { + BattlePlanBonusBehaviorInterface* bpbi = (*b)->getBattlePlanBonusBehaviorInterface(); + if (bpbi && !bpbi->shouldParalyze()) { + shouldParalyze = false; + } + } + + if (shouldParalyze) + obj->setDisabledUntil( DISABLED_PARALYZED, TheGameLogic->getFrame() + data->m_battlePlanParalyzeFrames ); } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp index 04ebe32fcc6..73d49e1e155 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionUpdate.cpp @@ -906,7 +906,7 @@ UpdateSleepTime ProductionUpdate::update( void ) us->getID()); // print a message to the local player, if it wants one - if( us->isLocallyControlled() && !upgrade->getDisplayNameLabel().isEmpty() ) + if( us->isLocallyControlled() && !upgrade->getDisplayNameLabel().isEmpty() && !upgrade->isSilentCompletion() ) { UnicodeString msg; UnicodeString format = TheGameText->fetch( "UPGRADE:UpgradeComplete" ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp index fdf36d31792..330120a189d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StealthUpdate.cpp @@ -148,6 +148,8 @@ StealthUpdate::StealthUpdate( Thing *thing, const ModuleData* moduleData ) : Upd m_nextBlackMarketCheckFrame = 0; m_framesGranted = 0; + m_stealthLevelOverride = 0; + if( data->m_innateStealth ) { //Giving innate stealth units this status bit allows other code to easily check the status bit. @@ -257,6 +259,9 @@ Bool StealthUpdate::allowedToStealth( Object *stealthOwner ) const UnsignedInt now = TheGameLogic->getFrame(); UnsignedInt flags = data->m_stealthLevel; + if (m_stealthLevelOverride > 0) + flags = m_stealthLevelOverride; + if( self != stealthOwner ) { //Extract the rules from the rider's stealthupdate module data instead @@ -1206,6 +1211,8 @@ void StealthUpdate::xfer( Xfer *xfer ) xfer->xferUnsignedInt( &m_framesGranted ); } + xfer->xferUnsignedInt( &m_stealthLevelOverride ); + } // end xfer // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/StealthUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/StealthUpgrade.cpp index 7077c360fa6..81f9edbf811 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/StealthUpgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/StealthUpgrade.cpp @@ -30,11 +30,28 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#define DEFINE_STEALTHLEVEL_NAMES + #include "Common/Xfer.h" +#include "GameLogic/Module/StealthUpdate.h" #include "GameLogic/Module/StealthUpgrade.h" #include "GameLogic/Module/SpawnBehavior.h" #include "GameLogic/Object.h" +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void StealthUpgradeModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + UpgradeModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "EnableStealth", INI::parseBool, NULL, offsetof(StealthUpgradeModuleData, m_enableStealth) }, + { "OverrideStealthForbiddenConditions", INI::parseBitString32, TheStealthLevelNames, offsetof(StealthUpgradeModuleData, m_stealthLevel) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); +} //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- StealthUpgrade::StealthUpgrade( Thing *thing, const ModuleData* moduleData ) : UpgradeModule( thing, moduleData ) @@ -48,19 +65,30 @@ StealthUpgrade::~StealthUpgrade( void ) } //------------------------------------------------------------------------------------------------- -void StealthUpgrade::upgradeImplementation( ) +void StealthUpgrade::upgradeImplementation() { + + const StealthUpgradeModuleData* d = getStealthUpgradeModuleData(); + + if (d->m_stealthLevel > 0) { + StealthUpdate* stealth = getObject()->getStealth(); + if (stealth) { // we should always have a stealth update module + stealth->setStealthLevelOverride(d->m_stealthLevel); + } + return; // Note AW: There should be no reason to enable/disable stealth if you change the stealthLevel + } + // The logic that does the stealthupdate will notice this and start stealthing - Object *me = getObject(); - me->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_CAN_STEALTH ) ); + Object* me = getObject(); + me->setStatus(MAKE_OBJECT_STATUS_MASK(OBJECT_STATUS_CAN_STEALTH), d->m_enableStealth); //Grant stealth to spawns if applicable. - if( me->isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) + if (me->isKindOf(KINDOF_SPAWNS_ARE_THE_WEAPONS)) { - SpawnBehaviorInterface *sbInterface = me->getSpawnBehaviorInterface(); - if( sbInterface ) + SpawnBehaviorInterface* sbInterface = me->getSpawnBehaviorInterface(); + if (sbInterface) { - sbInterface->giveSlavesStealthUpgrade( TRUE ); + sbInterface->giveSlavesStealthUpgrade(d->m_enableStealth); } } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 58a792d7f7c..79ac3333bc8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -2765,7 +2765,7 @@ Bool Weapon::privateFireWeapon( WeaponBonus bonus; computeBonus(sourceObj, extraBonusFlags, bonus); - // debug_printWeaponBonus(&bonus, m_template->getName()); + debug_printWeaponBonus(&bonus, m_template->getName()); DEBUG_ASSERTCRASH(getStatus() != OUT_OF_AMMO, ("Hmm, firing weapon that is OUT_OF_AMMO")); DEBUG_ASSERTCRASH(getStatus() == READY_TO_FIRE, ("Hmm, Weapon is firing more often than should be possible")); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index a98cb50533d..473911146cf 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -2045,7 +2045,7 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) if( science == SCIENCE_INVALID || thisPlayer == NULL ) break; - thisPlayer->attemptToPurchaseScience(science); + thisPlayer->attemptToPurchaseScience(science, TRUE); break; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp index 26f8ad3e3cd..4b24a345cdd 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp @@ -93,19 +93,31 @@ void W3DOverlordAircraftDraw::doDrawModule(const Matrix3D* transformMtx) const ContainedItemsList* addOns = me->getContain()->getAddOnList(); for (ContainedItemsList::const_iterator it = addOns->begin(); it != addOns->end(); it++) { Drawable* riderDraw = (*it)->getDrawable(); - riderDraw->setColorTintEnvelope(*getDrawable()->getColorTintEnvelope()); - riderDraw->notifyDrawableDependencyCleared(); - riderDraw->draw(NULL); + if (riderDraw) + { + TintEnvelope* env = getDrawable()->getColorTintEnvelope(); + if (env) + riderDraw->setColorTintEnvelope(*env); + + riderDraw->notifyDrawableDependencyCleared(); + riderDraw->draw(NULL);// What the hell? This param isn't used for anything + } } } else if (me->getContain()->friend_getRider() && me->getContain()->friend_getRider()->getDrawable()) { Drawable* riderDraw = me->getContain()->friend_getRider()->getDrawable(); - riderDraw->setColorTintEnvelope(*getDrawable()->getColorTintEnvelope()); + if (riderDraw) + { + TintEnvelope* env = getDrawable()->getColorTintEnvelope(); + if (env) + riderDraw->setColorTintEnvelope(*env); - riderDraw->notifyDrawableDependencyCleared(); - riderDraw->draw(NULL);// What the hell? This param isn't used for anything + riderDraw->notifyDrawableDependencyCleared(); + riderDraw->draw(NULL);// What the hell? This param isn't used for anything + } + DEBUG_ASSERTCRASH(riderDraw, ("OverlordAircraftDraw finds no rider's drawable")); } } }