/* ** 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: PointDefenseLaserUpdate.cpp ////////////////////////////////////////////////////////////////////////// // Author: Kris Morness, August 2002 // Desc: Update module to handle independent targeting of point defense laser. /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "Common\BitFlagsIO.h" #include "Common\RandomValue.h" #include "Common\ThingTemplate.h" #include "Common\Xfer.h" #include "GameClient\Drawable.h" #include "GameLogic\GameLogic.h" #include "GameLogic\PartitionManager.h" #include "GameLogic\Object.h" #include "GameLogic\ObjectIter.h" #include "GameLogic\Module\PointDefenseLaserUpdate.h" #include "GameLogic\Module\PhysicsUpdate.h" #include "GameLogic\Weapon.h" //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- PointDefenseLaserUpdateModuleData::PointDefenseLaserUpdateModuleData() { m_weaponTemplate = NULL; m_scanFrames = 0; m_scanRange = 0.0f; m_velocityFactor = 0.0f; } //------------------------------------------------------------------------------------------------- /*static*/ void PointDefenseLaserUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { ModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { { "WeaponTemplate", INI::parseWeaponTemplate, NULL, offsetof( PointDefenseLaserUpdateModuleData, m_weaponTemplate ) }, { "PrimaryTargetTypes", KindOfMaskType::parseFromINI, NULL, offsetof( PointDefenseLaserUpdateModuleData, m_primaryTargetKindOf ) }, { "SecondaryTargetTypes", KindOfMaskType::parseFromINI, NULL, offsetof( PointDefenseLaserUpdateModuleData, m_secondaryTargetKindOf ) }, { "ScanRate", INI::parseDurationUnsignedInt, NULL, offsetof( PointDefenseLaserUpdateModuleData, m_scanFrames ) }, { "ScanRange", INI::parseReal, NULL, offsetof( PointDefenseLaserUpdateModuleData, m_scanRange ) }, { "PredictTargetVelocityFactor", INI::parseReal, NULL, offsetof( PointDefenseLaserUpdateModuleData, m_velocityFactor ) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } //------------------------------------------------------------------------------------------------- PointDefenseLaserUpdate::PointDefenseLaserUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) { m_bestTargetID = INVALID_ID; m_nextScanFrames = 0; m_nextShotAvailableInFrames = 0; m_inRange = false; setWakeFrame(getObject(), UPDATE_SLEEP_NONE);// No starting sleep, but we want to sleep later. } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- PointDefenseLaserUpdate::~PointDefenseLaserUpdate( void ) { } //------------------------------------------------------------------------------------------------- void PointDefenseLaserUpdate::onObjectCreated() { const PointDefenseLaserUpdateModuleData *data = getPointDefenseLaserUpdateModuleData(); //Make sure we have a weapon template if( !data->m_weaponTemplate ) { DEBUG_CRASH( ("PointDefenseLaserUpdate for %s doesn't have a valid weapon template", getObject()->getTemplate()->getName().str() ) ); return; } //Make sure our firing range is smaller than the scan range. WeaponBonus bonus; bonus.clear(); Real attackRange = data->m_weaponTemplate->getAttackRange( bonus ); if( data->m_scanRange <= attackRange ) { DEBUG_CRASH( ("PointDefenseLaserUpdate for %s requires the scan range (%.1f) being larger than the firing range (%.1f)", getObject()->getTemplate()->getName().str(), data->m_scanRange, attackRange ) ); } } //------------------------------------------------------------------------------------------------- /** The update callback. */ //------------------------------------------------------------------------------------------------- UpdateSleepTime PointDefenseLaserUpdate::update() { /// @todo srj use SLEEPY_UPDATE here //*** HERE'S THE UPDATE PHILOSOPHY *** //The point defense laser typically has short range, high rate of fire, and shoots at incoming projectiles //that move fast. This amounts to a potentially very expensive system. Instead of frantically scanning for //targets, we will scan less frequently (data->m_scanFrames) in a larger radius (data->m_scanRange). When //this occurs, we'll store the "best" target, and track only that target until the next update or if it is //killed. Object *me = getObject(); if( me->isEffectivelyDead() ) return UPDATE_SLEEP_FOREVER;//No more laser fo you. const PointDefenseLaserUpdateModuleData *data = getPointDefenseLaserUpdateModuleData(); //Optimized firing at acquired target if( m_nextScanFrames > 0 ) { m_nextScanFrames--; fireWhenReady(); //Only happens if something is tracked. return UPDATE_SLEEP_NONE; } m_nextScanFrames = data->m_scanFrames; //Periodic scanning (expensive) if( scanClosestTarget() ) { //1 frame can make a big difference so fire ASAP! fireWhenReady(); } return UPDATE_SLEEP_NONE; } //------------------------------------------------------------------------------------------------- void PointDefenseLaserUpdate::fireWhenReady() { const PointDefenseLaserUpdateModuleData *data = getPointDefenseLaserUpdateModuleData(); //Track our target Object *target = TheGameLogic->findObjectByID( m_bestTargetID ); if( target ) { WeaponBonus bonus; bonus.clear(); Real fireRange = data->m_weaponTemplate->getAttackRange( bonus ); Object *me = getObject(); Real fDist = sqrt( ThePartitionManager->getDistanceSquared( me, target, FROM_CENTER_2D ) ); if( fDist < fireRange ) { //We are currently in range! m_inRange = true; } else { if( m_inRange ) { //We were in range last frame, but the target has moved out of firing range, so //re-evaluate by forcing a new scan. m_nextScanFrames = GameLogicRandomValue( 0, 3 ); m_bestTargetID = INVALID_ID; if( !m_nextScanFrames ) { scanClosestTarget(); m_nextScanFrames = data->m_scanFrames; target = NULL; //Set target to NULL so we don't shoot at it (might be out of range) } } else { //Not in range m_inRange = false; } } } if( m_nextShotAvailableInFrames > 0 ) { //We can't fire this frame. m_nextShotAvailableInFrames--; return; } WeaponTemplate *wt = data->m_weaponTemplate; if( wt ) { WeaponBonus bonus; //Fire control! if( target && m_inRange ) { if( !target->isEffectivelyDead() ) { Weapon* w = TheWeaponStore->allocateNewWeapon( wt, TERTIARY_WEAPON ); w->loadAmmoNow( getObject() ); w->fireWeapon( getObject(), target ); w->deleteInstance(); // And now that we have shot, set our internal reload timer. m_nextShotAvailableInFrames = wt->getDelayBetweenShots( bonus ); } if( target->isEffectivelyDead() ) { m_nextScanFrames = GameLogicRandomValue( 0, 3 ); m_bestTargetID = INVALID_ID; if( !m_nextScanFrames ) { scanClosestTarget(); m_nextScanFrames = data->m_scanFrames; } } } } } //------------------------------------------------------------------------------------------------- Object* PointDefenseLaserUpdate::scanClosestTarget() { const PointDefenseLaserUpdateModuleData *data = getPointDefenseLaserUpdateModuleData(); Object *me = getObject(); Object *bestTargetOutOfRange[2] = { NULL, NULL }; Object *bestTargetInRange[2] = { NULL, NULL }; Real closestDist[2]; Real closestOutsideRange[2]; Int index; WeaponBonus bonus; bonus.clear(); Real fireRange = data->m_weaponTemplate->getAttackRange( bonus ); ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( me->getPosition(), data->m_scanRange, FROM_CENTER_2D ); MemoryPoolObjectHolder hold(iter); for( Object *other = iter->first(); other; other = iter->next() ) { if( other->isAnyKindOf( data->m_primaryTargetKindOf ) ) { //Primary target type index = 0; } else if( other->isAnyKindOf( data->m_secondaryTargetKindOf ) ) { //Secondary target type (use only if we can't find any primary targets) index = 1; } else { //Not a valid target. continue; } // Since we don't have an actual weapon or a weapon set, we lose all of the automatic checks. // "Borrow" the check for being an AA only laser to stop from shooting planes on airports. if( !other->isAirborneTarget() && !(data->m_weaponTemplate->getAntiMask() & WEAPON_ANTI_GROUND) ) continue; // order matters: we want to know if I consider it to be an enemy, not vice versa if( getObject()->getRelationship( other ) != ENEMIES ) { //Don't kill our friends! continue; } if( other->testStatus( OBJECT_STATUS_STEALTHED ) && !other->testStatus( OBJECT_STATUS_DETECTED ) && !other->testStatus( OBJECT_STATUS_DISGUISED ) ) { //We can't see it. continue; } Real fDist = sqrt( ThePartitionManager->getDistanceSquared( me, other, FROM_CENTER_2D ) ); if( fDist <= fireRange ) { //Inside fire range (which one is closest?) if( !bestTargetInRange[index] || fDist < closestDist[index] ) { closestDist[index] = fDist; bestTargetInRange[index] = other; } } else if( !bestTargetInRange[index] ) { //Outside fire range. //Determine where the target will be based on current velocity using (m_velocityFactor * frames) if( data->m_velocityFactor != 0.0f && !other->isKindOf( KINDOF_IMMOBILE ) ) { Coord3D pos; PhysicsBehavior *physics = other->getPhysics(); if( physics ) { pos.set( physics->getVelocity() ); pos.scale( data->m_velocityFactor ); pos.add( other->getPosition() ); //Recalculate the distance. fDist = sqrt( ThePartitionManager->getDistanceSquared( me, other, FROM_CENTER_2D ) ); } } //Now calculate the best outside range target. if( !bestTargetOutOfRange[index] || fDist < closestOutsideRange[index] ) { closestOutsideRange[index] = fDist; bestTargetOutOfRange[index] = other; } } } // end for, other if( bestTargetInRange[ 0 ] ) { //This is the best primary target in range. m_bestTargetID = bestTargetInRange[ 0 ]->getID(); m_inRange = true; return bestTargetInRange[ 0 ]; } if( bestTargetInRange[ 1 ] ) { //This is the best secondary target in range. m_bestTargetID = bestTargetInRange[ 1 ]->getID(); m_inRange = true; return bestTargetInRange[ 1 ]; } if( bestTargetOutOfRange[ 0 ] ) { //This is the best primary target out of range. m_bestTargetID = bestTargetOutOfRange[ 0 ]->getID(); m_inRange = false; return bestTargetOutOfRange[ 0 ]; } if( bestTargetOutOfRange[ 1 ] ) { //This is the best secondary target out of range. m_bestTargetID = bestTargetOutOfRange[ 1 ]->getID(); m_inRange = false; return bestTargetOutOfRange[ 1 ]; } //Utter failure -- nothing on the scope. m_bestTargetID = INVALID_ID; m_inRange = false; return NULL; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void PointDefenseLaserUpdate::crc( Xfer *xfer ) { // extend base class UpdateModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void PointDefenseLaserUpdate::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class UpdateModule::xfer( xfer ); // best target ID xfer->xferObjectID( &m_bestTargetID ); // in range xfer->xferBool( &m_inRange ); // next scan frames xfer->xferInt( &m_nextScanFrames ); // next shot available in frames xfer->xferInt( &m_nextShotAvailableInFrames ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void PointDefenseLaserUpdate::loadPostProcess( void ) { // extend base class UpdateModule::loadPostProcess(); } // end loadPostProcess