/* ** 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: HordeUpdate.cpp //////////////////////////////////////////////////////////////////////// // Author: Steven Johnson, Feb 2002 // Desc: HordeUpdate module /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #define DEFINE_HORDEACTION_NAMES #include "Common/Player.h" #include "Common/ThingTemplate.h" #include "Common/Upgrade.h" #include "Common/Xfer.h" #include "Common/BitFlagsIO.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/Weapon.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/HordeUpdate.h" #include "GameClient/Drawable.h" //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif static HordeUpdateInterface* getHUI(Object* obj) { for( BehaviorModule** u = obj->getBehaviorModules(); *u; ++u ) { HordeUpdateInterface* hui = (*u)->getHordeUpdateInterface(); if( hui ) return hui; } return NULL; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- class PartitionFilterHordeMember : public PartitionFilter { private: Object* m_obj; const HordeUpdateModuleData* m_data; public: PartitionFilterHordeMember(Object* obj, const HordeUpdateModuleData* data) : m_obj(obj), m_data(data) { } #if defined(_DEBUG) || defined(_INTERNAL) virtual const char* debugGetName() { return "PartitionFilterHordeMember"; } #endif virtual Bool allow(Object *objOther) { // must be exact same type as us (well, maybe) if (m_data->m_exactMatch && m_obj->getTemplate() != objOther->getTemplate()) return false; // can only horde with other horde-able things HordeUpdateInterface *hui = getHUI(objOther); if( !hui ) return false; // must match the kindof flags (if any) if (!objOther->isKindOfMulti(m_data->m_kindof, KINDOFMASK_NONE)) return false; // must be our ally (well, maybe) if (m_data->m_alliesOnly && m_obj->getRelationship(objOther) != ALLIES) return false; // doh if (m_obj->isOffMap() != objOther->isOffMap()) return false; // guess it's horde-worthy! return true; } }; //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- const Int DEFAULT_UPDATE_RATE = LOGICFRAMES_PER_SECOND; //------------------------------------------------------------------------------------------------- HordeUpdateModuleData::HordeUpdateModuleData() : m_updateRate(DEFAULT_UPDATE_RATE), m_minCount(0), m_minDist(0.0f), m_rubOffRadius(20.0f), m_alliesOnly(true), m_exactMatch(false), m_allowedNationalism(TRUE), m_action(HORDEACTION_HORDE) { } //------------------------------------------------------------------------------------------------- /*static*/ void HordeUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { ModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { { "UpdateRate", INI::parseDurationUnsignedInt, NULL, offsetof(HordeUpdateModuleData, m_updateRate) }, { "KindOf", KindOfMaskType::parseFromINI, NULL, offsetof(HordeUpdateModuleData, m_kindof) }, { "Count", INI::parseInt, NULL, offsetof(HordeUpdateModuleData, m_minCount) }, { "Radius", INI::parseReal, NULL, offsetof(HordeUpdateModuleData, m_minDist) }, { "RubOffRadius", INI::parseReal, NULL, offsetof(HordeUpdateModuleData, m_rubOffRadius) }, { "AlliesOnly", INI::parseBool, NULL, offsetof(HordeUpdateModuleData, m_alliesOnly) }, { "ExactMatch", INI::parseBool, NULL, offsetof(HordeUpdateModuleData, m_exactMatch) }, { "Action", INI::parseIndexList, TheHordeActionTypeNames, offsetof(HordeUpdateModuleData, m_action) }, { "FlagSubObjectNames", INI::parseAsciiStringVector, NULL, offsetof(HordeUpdateModuleData, m_flagSubObjNames) }, { "AllowedNationalism", INI::parseBool, NULL, offsetof(HordeUpdateModuleData, m_allowedNationalism) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- HordeUpdate::HordeUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) { m_inHorde = FALSE; m_hasFlag = FALSE; m_lastHordeRefreshFrame = TheGameLogic->getFrame(); // Added By Sadullah Nader // Initializations missing and needed m_trueHordeMember = FALSE; // UnsignedInt delay = getHordeUpdateModuleData()->m_updateRate; setWakeFrame(getObject(), UPDATE_SLEEP(GameLogicRandomValue(1, delay))); } //------------------------------------------------------------------------------------------------- HordeUpdate::~HordeUpdate() { } //------------------------------------------------------------------------------------------------- Bool HordeUpdate::isAllowedNationalism() const { const HordeUpdateModuleData *data = getHordeUpdateModuleData(); return data->m_allowedNationalism; } //------------------------------------------------------------------------------------------------- /** @todo I think we should model the horde list ... so we can do all this without doing * all this scanning, plus we can give exactly 1 flag to the right person in the * center of the horde which I think would look better (CBD) * * Redesign occurred 10/15, where the flags have been removed replaced by a teraindecal for each horde member * Thank You for reading, MLorenzen */ // ------------------------------------------------------------------------------------------------ void HordeUpdate::joinOrLeaveHorde(SimpleObjectIterator *iter, Bool join) { Bool prevInHorde = m_inHorde; m_inHorde = join; const HordeUpdateModuleData* d = getHordeUpdateModuleData(); switch (d->m_action) { case HORDEACTION_HORDE: { // give/remove bonus effects if( prevInHorde != m_inHorde ) { AIUpdateInterface *ai = getObject()->getAIUpdateInterface(); if( ai ) ai->evaluateMoraleBonus(); else DEBUG_CRASH(( "HordeUpdate::joinOrLeaveHorde - We (%s) must have an AI to benefit from horde\n", getObject()->getTemplate()->getName().str() )); } // end if } break; } } //------------------------------------------------------------------------------------------------- void HordeUpdate::showHideFlag(Bool show) { const HordeUpdateModuleData* d = getHordeUpdateModuleData(); if (!d->m_flagSubObjNames.empty()) { Drawable* draw = getObject()->getDrawable(); Object *obj = getObject(); if (draw && obj) { for (std::vector::const_iterator it = d->m_flagSubObjNames.begin(); it != d->m_flagSubObjNames.end(); ++it) draw->showSubObject(*it, show); draw->updateSubObjects(); } } } //------------------------------------------------------------------------------------------------- /** this is called whenever a drawable is bound to the object. drawable is NOT guaranteed to be non-null. */ void HordeUpdate::onDrawableBoundToObject() { /////////////showHideFlag(m_hasFlag); showHideFlag(FALSE);//hide the flag } //------------------------------------------------------------------------------------------------- UpdateSleepTime HordeUpdate::update( void ) { //This code handles decals and ONLY decals! Object *obj = getObject(); if ( ! obj ) return UPDATE_SLEEP_FOREVER; const HordeUpdateModuleData *md = getHordeUpdateModuleData(); Bool wasInHorde = m_inHorde; // This is a sticky situation, where refreshing the model state (like from default to damaged, for example) // will rebuild the terraindecal and set its size to the default size.... since Vehicles have a special size, // we want to keep it fresh, here, but not do the horde-ing test every frame... Bool isInfantry = ( obj->isKindOf(KINDOF_INFANTRY) ); if ( isInfantry || (TheGameLogic->getFrame() > m_lastHordeRefreshFrame + md->m_updateRate) ) { m_lastHordeRefreshFrame = TheGameLogic->getFrame(); PartitionFilterHordeMember hmFilter(getObject(), md); PartitionFilter *filters[] = { &hmFilter, NULL }; SimpleObjectIterator *iter = ThePartitionManager->iterateObjectsInRange(getObject(), md->m_minDist, FROM_BOUNDINGSPHERE_3D, filters); MemoryPoolObjectHolder hold(iter); if ((iter->getCount() >= md->m_minCount - 1) )//we really are in the thick part of the horde { m_inHorde = TRUE; m_trueHordeMember = TRUE; } else { m_inHorde = FALSE; m_trueHordeMember = FALSE;/// unless... Real rubOffRadiusSq = sqr(md->m_rubOffRadius); for (Object* other = iter->first(); other; other = iter->next()) { HordeUpdateInterface* hui = getHUI(other); if ( hui != NULL && hui->isTrueHordeMember() ) { Real dist = ThePartitionManager->getDistanceSquared(getObject(), other, FROM_CENTER_2D); if (dist <= rubOffRadiusSq ) { m_inHorde = TRUE; break; } } } } AIUpdateInterface *ai = getObject()->getAIUpdateInterface(); if( ai ) ai->evaluateMoraleBonus(); } // This is a sticky situation, where refreshing the model state (like from default to damaged, for example) // will rebuild the terraindecal and set its size to the default size.... since Vehicles have a special size, // we want to keep it fresh, here, but not do the horde-ing test every frame... // This is a weak solution, in that It causes this update to fight the defualt behavior of the modelstate methods, // But in the interest of not breaking the modelstate changing logic for five hundred other units a week before golden, // this is my solution... If anyone gets this note on the next project... please please please, fix the resetting of the // shadows/terraindecals in W3DModelDraw. THanks, ML Drawable* draw = getObject()->getDrawable(); if ( draw && ! obj->isEffectivelyDead() ) { if( TheGameLogic->getDrawIconUI() ) { if(m_inHorde && !obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) )// this not is a ride-on for overlord { TerrainDecalType nuType;// uninitialized if ( isInfantry ) { if( obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ) == TRUE ) { if ( obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ) == TRUE ) nuType = ( TERRAIN_DECAL_HORDE_WITH_FANATICISM_UPGRADE ); else nuType = ( TERRAIN_DECAL_HORDE_WITH_NATIONALISM_UPGRADE ); } else nuType =(TERRAIN_DECAL_HORDE); if (nuType != draw->getTerrainDecalType()) draw->setTerrainDecal(nuType); } else { Real size = 3.5f * obj->getGeometryInfo().getMajorRadius(); draw->setTerrainDecalSize( size, size ); if( obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_NATIONALISM ) == TRUE ) { if ( obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_FANATICISM ) == TRUE ) nuType = ( TERRAIN_DECAL_HORDE_WITH_FANATICISM_UPGRADE ); else nuType = ( TERRAIN_DECAL_HORDE_WITH_NATIONALISM_UPGRADE ); } else nuType = (TERRAIN_DECAL_HORDE_VEHICLE); if (nuType != draw->getTerrainDecalType()) draw->setTerrainDecal(nuType); } } } else { //Scripts have disabled icons so don't draw these! draw->setTerrainDecal( TERRAIN_DECAL_NONE ); } if ( ! wasInHorde && m_inHorde ) draw->setTerrainDecalFadeTarget(1.0f, 0.03f); else if ( wasInHorde && ! m_inHorde ) draw->setTerrainDecalFadeTarget(0.0f, -0.03f); } return isInfantry ? UPDATE_SLEEP(md->m_updateRate) : UPDATE_SLEEP_NONE; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void HordeUpdate::crc( Xfer *xfer ) { // extend base class UpdateModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void HordeUpdate::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class UpdateModule::xfer( xfer ); // in horder xfer->xferBool( &m_inHorde ); // has flag xfer->xferBool( &m_hasFlag ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void HordeUpdate::loadPostProcess( void ) { // extend base class UpdateModule::loadPostProcess(); } // end loadPostProcess