/* ** Command & Conquer Generals(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: PropagandaTowerBehavior.cpp ////////////////////////////////////////////////////////////// // Author: Colin Day, August 2002 // Desc: Behavior module for PropagandaTower /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" #include "Common/GameState.h" #include "Common/Player.h" #include "Common/PlayerList.h" #include "Common/Upgrade.h" #include "Common/Xfer.h" #include "GameClient/Drawable.h" #include "GameClient/FXList.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/Weapon.h" #include "GameLogic/Module/PropagandaTowerBehavior.h" #include "GameLogic/Module/BodyModule.h" // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// enum ObjectID; // ------------------------------------------------------------------------------------------------ /** This class is used to track objects as they exit our area of influence */ // ------------------------------------------------------------------------------------------------ class ObjectTracker : public MemoryPoolObject { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ObjectTracker, "ObjectTracker" ); public: ObjectTracker( void ) { objectID = INVALID_ID; next = NULL; } ObjectID objectID; ObjectTracker *next; }; ObjectTracker::~ObjectTracker( void ) { } /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ PropagandaTowerBehaviorModuleData::PropagandaTowerBehaviorModuleData( void ) { m_scanRadius = 1.0f; m_scanDelayInFrames = 100; m_autoHealPercentPerSecond = 0.01f; m_upgradedAutoHealPercentPerSecond = 0.02f; m_pulseFX = NULL; m_upgradeRequired = NULL; m_upgradedPulseFX = NULL; } // end PropagandaTowerBehaviorModuleData // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ /*static*/ void PropagandaTowerBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p ) { UpdateModuleData::buildFieldParse( p ); static const FieldParse dataFieldParse[] = { { "Radius", INI::parseReal, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_scanRadius ) }, { "DelayBetweenUpdates", INI::parseDurationUnsignedInt, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_scanDelayInFrames ) }, { "HealPercentEachSecond", INI::parsePercentToReal, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_autoHealPercentPerSecond ) }, { "UpgradedHealPercentEachSecond", INI::parsePercentToReal,NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradedAutoHealPercentPerSecond ) }, { "PulseFX", INI::parseFXList, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_pulseFX ) }, { "UpgradeRequired", INI::parseAsciiString, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradeRequired ) }, { "UpgradedPulseFX", INI::parseFXList, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradedPulseFX ) }, { 0, 0, 0, 0 } }; p.add( dataFieldParse ); } // end buildFieldParse /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ PropagandaTowerBehavior::PropagandaTowerBehavior( Thing *thing, const ModuleData *modData ) : UpdateModule( thing, modData ) { //Added By Sadullah Nader //Initializations inserted m_lastScanFrame = 0; // m_insideList = NULL; setWakeFrame( getObject(), UPDATE_SLEEP_NONE ); } // end PropagandaTowerBehavior // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ PropagandaTowerBehavior::~PropagandaTowerBehavior( void ) { } // end ~PropagandaTowerBehavior // ------------------------------------------------------------------------------------------------ /** Module is being deleted */ // ------------------------------------------------------------------------------------------------ void PropagandaTowerBehavior::onDelete( void ) { // remove any benefits from anybody in our area of influence removeAllInfluence(); } // end onDelete // ------------------------------------------------------------------------------------------------ /** Resolve */ // ------------------------------------------------------------------------------------------------ void PropagandaTowerBehavior::onObjectCreated( void ) { const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData(); // convert module upgrade name to a pointer m_upgradeRequired = TheUpgradeCenter->findUpgrade( modData->m_upgradeRequired ); } // end onObjectCreated // ------------------------------------------------------------------------------------------------ void PropagandaTowerBehavior::onCapture( Player *oldOwner, Player *newOwner ) { // We don't function for the neutral player. if( newOwner == ThePlayerList->getNeutralPlayer() ) { removeAllInfluence(); setWakeFrame( getObject(), UPDATE_SLEEP_FOREVER ); } else setWakeFrame( getObject(), UPDATE_SLEEP_NONE ); } // ------------------------------------------------------------------------------------------------ /** The update callback */ // ------------------------------------------------------------------------------------------------ UpdateSleepTime PropagandaTowerBehavior::update( void ) { /// @todo srj use SLEEPY_UPDATE here const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData(); //Sep 27, 2002 (Kris): Added this code to prevent the tower from working while under construction. Object *self = getObject(); if( BitTest( self->getStatusBits(), OBJECT_STATUS_UNDER_CONSTRUCTION ) ) return UPDATE_SLEEP_NONE; if( self->testStatus(OBJECT_STATUS_SOLD) ) { removeAllInfluence(); return UPDATE_SLEEP_FOREVER; } if( self->isEffectivelyDead() ) return UPDATE_SLEEP_FOREVER; if( self->isDisabled() ) { // We need to let go of everyone if we are EMPd or underpowered or yadda, but not if we are only held DisabledMaskType allButHeld = MAKE_DISABLED_MASK( DISABLED_HELD ); FLIP_DISABLEDMASK(allButHeld); if( TEST_DISABLEDMASK_ANY(self->getDisabledFlags(), allButHeld) ) { removeAllInfluence(); return UPDATE_SLEEP_NONE; } } if( self->getContainedBy() && self->getContainedBy()->getContainedBy() ) { // If our container is contained, we turn the heck off. Seems like a weird specific check, but all of // attacking is guarded by the same check in isPassengersAllowedToFire. We similarly work in a container, // but not in a double container. removeAllInfluence(); return UPDATE_SLEEP_NONE; } // if it's not time to scan, nothing to do UnsignedInt currentFrame = TheGameLogic->getFrame(); if( currentFrame - m_lastScanFrame >= modData->m_scanDelayInFrames ) { // do a scan doScan(); m_lastScanFrame = currentFrame; } // end if // go through any objects in our area of influence and do the effect logic on them Object *obj; ObjectTracker *curr = NULL, *prev = NULL, *next = NULL; for( curr = m_insideList; curr; curr = next ) { // get the next link next = curr->next; // find this object obj = TheGameLogic->findObjectByID( curr->objectID ); if ((obj) && (obj->isKindOf(KINDOF_SCORE) || obj->isKindOf(KINDOF_SCORE_CREATE) || obj->isKindOf(KINDOF_SCORE_DESTROY) || obj->isKindOf(KINDOF_MP_COUNT_FOR_VICTORY))) { // give any bonus to this object effectLogic( obj, TRUE, getPropagandaTowerBehaviorModuleData() ); // record this element as the previous one found in the list prev = curr; } // end if else { // // actual object wasn't found, remove this entry from our inside list so we don't // have to search through it again // if( prev ) prev->next = curr->next; else m_insideList = curr->next; curr->deleteInstance(); } // end else } // end for, curr return UPDATE_SLEEP_NONE; } // end update // ------------------------------------------------------------------------------------------------ /** The death callback */ // ------------------------------------------------------------------------------------------------ void PropagandaTowerBehavior::onDie( const DamageInfo *damageInfo ) { // remove any benefits from anybody in our area of influence removeAllInfluence(); } // end onDie /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ /** Grant or remove effect to this object */ // ------------------------------------------------------------------------------------------------ void PropagandaTowerBehavior::effectLogic( Object *obj, Bool giving, const PropagandaTowerBehaviorModuleData *modData ) { Bool effectUpgraded = getObject()->getControllingPlayer()->hasUpgradeComplete( m_upgradeRequired ); // if giving the effect if( giving ) { if ( obj->hasAnyDamageWeapon() == TRUE ) { if( obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC ) == FALSE ) obj->setWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC ); if (effectUpgraded) { if (obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL ) == FALSE) obj->setWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL ); } } // hasdamageweapon // grant health to this object as well BodyModuleInterface *body = obj->getBodyModule(); if( body ) { Real healthPercent; if(effectUpgraded) healthPercent = modData->m_upgradedAutoHealPercentPerSecond; else healthPercent = modData->m_autoHealPercentPerSecond; Real amount = healthPercent / LOGICFRAMES_PER_SECOND * body->getMaxHealth(); // Dustin wants the healing effect not to stack from multiple propaganda towers... // To accomplish this, I'll give every object a single healing-sender (ID) // Any given healing recipient (object) can only receive healing from one particular healing sender // and cannot change healing senders until the previous one expires (its scandelay) // obj->attemptHealing(amount, getObject()); // the regular way to give healing... obj->attemptHealingFromSoleBenefactor( amount, getObject(), modData->m_scanDelayInFrames );//the non-stacking way } // end if } // end if else { // taking effect away obj->clearWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC ); obj->clearWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL ); } // end else } // end effectLogic // ------------------------------------------------------------------------------------------------ /** Remove all influence from objects we've given bonuses to */ // ------------------------------------------------------------------------------------------------ void PropagandaTowerBehavior::removeAllInfluence( void ) { ObjectTracker *o; // go through all objects we've given bonuses to and remove them Object *obj; for( o = m_insideList; o; o = o->next ) { obj = TheGameLogic->findObjectByID( o->objectID ); if( obj ) effectLogic( obj, FALSE, getPropagandaTowerBehaviorModuleData() ); } // end for // delete the list of objects under our influence while( m_insideList ) { o = m_insideList->next; m_insideList->deleteInstance(); m_insideList = o; } // end while } // end removeAllInfluence // ------------------------------------------------------------------------------------------------ /** Do a scan */ // ------------------------------------------------------------------------------------------------ void PropagandaTowerBehavior::doScan( void ) { const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData(); Object *us = getObject(); ObjectTracker *newInsideList = NULL; // The act of scanning is when we play our effect Bool upgradePresent = FALSE; if( m_upgradeRequired ) { // see if we have the upgrade switch( m_upgradeRequired->getUpgradeType() ) { // ------------------------------------------------------------------------------------------ case UPGRADE_TYPE_PLAYER: { Player *player = us->getControllingPlayer(); upgradePresent = player->hasUpgradeComplete( m_upgradeRequired ); break; } // end player upgrade // ------------------------------------------------------------------------------------------ case UPGRADE_TYPE_OBJECT: { upgradePresent = us->hasUpgrade( m_upgradeRequired ); break; } // end object upgrade // ------------------------------------------------------------------------------------------ default: { DEBUG_CRASH(( "PropagandaTowerBehavior::doScan - Unknown upgrade type '%d'\n", m_upgradeRequired->getUpgradeType() )); break; } // end default } // end switch } // end if // play the right pulse if( upgradePresent == TRUE ) FXList::doFXObj( modData->m_upgradedPulseFX, us ); else FXList::doFXObj( modData->m_pulseFX, us ); // setup scan filters PartitionFilterRelationship relationship( us, PartitionFilterRelationship::ALLOW_ALLIES ); PartitionFilterAlive filterAlive; PartitionFilterSameMapStatus filterMapStatus(us); PartitionFilterAcceptByKindOf filterOutBuildings(KINDOFMASK_NONE, MAKE_KINDOF_MASK(KINDOF_STRUCTURE)); PartitionFilter *filters[] = { &relationship, &filterAlive, &filterMapStatus, &filterOutBuildings, NULL }; // scan objects in our region ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( us->getPosition(), modData->m_scanRadius, FROM_CENTER_2D, filters ); MemoryPoolObjectHolder hold( iter ); Object *obj; ObjectTracker *newEntry; for( obj = iter->first(); obj; obj = iter->next() ) { // ignore ourselves, as a tower we're not interesting anyway if( obj == us ) continue; // record this object as being in the new "in list" newEntry = newInstance(ObjectTracker); newEntry->objectID = obj->getID(); newEntry->next = newInsideList; newInsideList = newEntry; } // end for obj // // now that we have a list of objects that are in our area of influence, look through // the objects that were last recorded as in our area of influence and remove any // bonus we've given them (if they are within the area of effect of another tower it's // OK, they'll get the bonus back again when that tower does a scan which won't be too long // for( ObjectTracker *curr = m_insideList; curr; curr = curr->next ) { // find this entry in the new list ObjectTracker *o = NULL; for( o = newInsideList; o; o = o->next ) if( o->objectID == curr->objectID ) break; // if entry wasn't there, remove the bonus from this object if( o == NULL ) { obj = TheGameLogic->findObjectByID( curr->objectID ); if( obj ) effectLogic( obj, FALSE, modData ); } // end if } // end for // delete the inside list we have recoreded ObjectTracker *next; while( m_insideList ) { next = m_insideList->next; m_insideList->deleteInstance(); m_insideList = next; } // end while // set the new inside list to the one we're recording m_insideList = newInsideList; } // end doScan // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void PropagandaTowerBehavior::crc( Xfer *xfer ) { // extend base class UpdateModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void PropagandaTowerBehavior::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class UpdateModule::xfer( xfer ); // last scan frame xfer->xferUnsignedInt( &m_lastScanFrame ); // inside list tracking ObjectTracker *trackerEntry; UnsignedShort insideCount = 0; for( trackerEntry = m_insideList; trackerEntry; trackerEntry = trackerEntry->next ) insideCount++; xfer->xferUnsignedShort( &insideCount ); if( xfer->getXferMode() == XFER_SAVE ) { // write all entries for( trackerEntry = m_insideList; trackerEntry; trackerEntry = trackerEntry->next ) { // object id xfer->xferObjectID( &trackerEntry->objectID ); } // end for } // end if, save else { // sanity if( m_insideList != NULL ) { DEBUG_CRASH(( "PropagandaTowerBehavior::xfer - m_insideList should be empty but is not\n" )); throw SC_INVALID_DATA; } // end if // read all entries for( UnsignedShort i = 0; i < insideCount; ++i ) { // allocate new tracker entry and tie to list trackerEntry = newInstance(ObjectTracker); trackerEntry->next = m_insideList; m_insideList = trackerEntry; // read object id xfer->xferObjectID( &trackerEntry->objectID ); } // end for i } // end else, load } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void PropagandaTowerBehavior::loadPostProcess( void ) { // extend base class UpdateModule::loadPostProcess(); } // end loadPostProcess