/* ** 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: FiringTracker.cpp ////////////////////////////////////////////////////////////////////// // Author: Graham Smallwood, February 2002 // Desc: Keeps track of shots fired and people targeted for weapons that want a history of such a thing /////////////////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/AudioHandleSpecialValues.h" #include "Common/GameType.h" #include "Common/GameAudio.h" #include "Common/PerfTimer.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameLogic/FiringTracker.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Module/ObjectHelper.h" #include "GameLogic/Object.h" #include "GameLogic/Weapon.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif //------------------------------------------------------------------------------------------------- FiringTracker::FiringTracker(Thing* thing, const ModuleData *modData) : UpdateModule( thing, modData ) { m_consecutiveShots = 0; m_victimID = INVALID_ID; m_frameToStartCooldown = 0; m_frameToForceReload = 0; m_frameToStopLoopingSound = 0; m_audioHandle = AHSV_NoSound; setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); } //------------------------------------------------------------------------------------------------- FiringTracker::~FiringTracker() { // no need to protect this. TheAudio->removeAudioEvent( m_audioHandle ); m_audioHandle = AHSV_NoSound; } //------------------------------------------------------------------------------------------------- Int FiringTracker::getNumConsecutiveShotsAtVictim( const Object *victim ) const { if( victim == NULL ) return 0;// safety, this function is for asking about shots at a victim if( victim->getID() != m_victimID ) return 0;// nope, not shooting at him return m_consecutiveShots;// this is how any times I have shot at this hoser right now } //------------------------------------------------------------------------------------------------- void FiringTracker::shotFired(const Weapon* weaponFired, ObjectID victimID) { UnsignedInt now = TheGameLogic->getFrame(); if( victimID == m_victimID ) { // Shooting at the same guy ++m_consecutiveShots; } else if( now < m_frameToStartCooldown ) { // Switching targets within the coast time is valid, and we will not spin down ++m_consecutiveShots; m_victimID = victimID; } else { // Start the count over for the new guy m_consecutiveShots = 1; m_victimID = victimID; } // Push back the time that we should force a reload with each shot UnsignedInt autoReloadDelay = weaponFired->getAutoReloadWhenIdleFrames(); if( autoReloadDelay > 0 ) m_frameToForceReload = now + autoReloadDelay; UnsignedInt coast = weaponFired->getContinuousFireCoastFrames(); if (coast) m_frameToStartCooldown = weaponFired->getPossibleNextShotFrame() + coast; else m_frameToStartCooldown = 0; Int shotsNeededOne = weaponFired->getContinuousFireOneShotsNeeded(); Int shotsNeededTwo = weaponFired->getContinuousFireTwoShotsNeeded(); if( getObject()->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ) ) { // Can either go up or down from here. if( m_consecutiveShots < shotsNeededOne ) coolDown(); else if( m_consecutiveShots > shotsNeededTwo ) speedUp(); } else if( getObject()->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ) ) { // Only place I can go here from here is all the way down if( m_consecutiveShots < shotsNeededTwo ) { coolDown(); } } else { // Check to go up if( m_consecutiveShots > shotsNeededOne ) speedUp(); } UnsignedInt fireSoundLoopTime = weaponFired->getFireSoundLoopTime(); if (fireSoundLoopTime != 0) { // If the sound has stopped playing, then we need to re-add it. if (m_frameToStopLoopingSound == 0 || !TheAudio->isCurrentlyPlaying(m_audioHandle)) { AudioEventRTS audio = weaponFired->getFireSound(); audio.setObjectID(getObject()->getID()); m_audioHandle = TheAudio->addAudioEvent( &audio ); } m_frameToStopLoopingSound = now + fireSoundLoopTime; } else { AudioEventRTS fireAndForgetSound = weaponFired->getFireSound(); fireAndForgetSound.setObjectID(getObject()->getID()); TheAudio->addAudioEvent(&fireAndForgetSound); m_frameToStopLoopingSound = 0; } setWakeFrame(getObject(), calcTimeToSleep()); } //------------------------------------------------------------------------------------------------- UpdateSleepTime FiringTracker::update() { //DEBUG_ASSERTCRASH(m_frameToStartCooldown != 0 || m_frameToStopLoopingSound != 0, ("hmm, should be asleep")); UnsignedInt now = TheGameLogic->getFrame(); // I have been idle long enough that I should reload, so I do not hang around with a near empty clip forever. if( m_frameToForceReload != 0 && now >= m_frameToForceReload ) { getObject()->reloadAllAmmo(TRUE); m_frameToForceReload = 0; } // If it has been too long since I fired. I have to start over. // (don't call if we don't need to cool down... it's expensive!) if (m_frameToStopLoopingSound != 0) { if (now >= m_frameToStopLoopingSound) { TheAudio->removeAudioEvent( m_audioHandle ); m_audioHandle = AHSV_NoSound; m_frameToStopLoopingSound = 0; } } if( m_frameToStartCooldown != 0 && now > m_frameToStartCooldown ) { m_frameToStartCooldown = now + LOGICFRAMES_PER_SECOND; coolDown();// if this is the coolest call to cooldown, it will set m_frameToStartCooldown to zero return UPDATE_SLEEP(LOGICFRAMES_PER_SECOND); } UpdateSleepTime sleepTime = calcTimeToSleep(); return sleepTime; } //------------------------------------------------------------------------------------------------- UpdateSleepTime FiringTracker::calcTimeToSleep() { // Figure out the longest amount of time we can sleep as unneeded // If all the timers are off, then we aren't needed at all if (m_frameToStopLoopingSound == 0 && m_frameToStartCooldown == 0 && m_frameToForceReload == 0) return UPDATE_SLEEP_FOREVER; // Otherwise, we need to wake up to service the shortest timer UnsignedInt now = TheGameLogic->getFrame(); UnsignedInt sleepTime = UPDATE_SLEEP_FOREVER; if( m_frameToStopLoopingSound != 0 ) { if( m_frameToStopLoopingSound <= now ) sleepTime = UPDATE_SLEEP_NONE; else if( (m_frameToStopLoopingSound - now) < sleepTime ) sleepTime = m_frameToStopLoopingSound - now; } if( m_frameToStartCooldown != 0 ) { if( m_frameToStartCooldown <= now ) sleepTime = UPDATE_SLEEP_NONE; else if( (m_frameToStartCooldown - now) < sleepTime ) sleepTime = m_frameToStartCooldown - now; } if( m_frameToForceReload != 0 ) { if( m_frameToForceReload <= now ) sleepTime = UPDATE_SLEEP_NONE; else if( (m_frameToForceReload - now) < sleepTime ) sleepTime = m_frameToForceReload - now; } return UPDATE_SLEEP(sleepTime); } //------------------------------------------------------------------------------------------------- void FiringTracker::speedUp() { ModelConditionFlags clr, set; Object *self = getObject(); if( self->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ) ) { //self->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ); //self->clearModelConditionState( MODELCONDITION_CONTINUOUS_FIRE_MEAN ); //self->clearModelConditionState( MODELCONDITION_CONTINUOUS_FIRE_SLOW ); } else if(self->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ) ) { const AudioEventRTS *soundToPlayPtr = self->getTemplate()->getPerUnitSound( "VoiceRapidFire" ); AudioEventRTS soundToPlay = *soundToPlayPtr; soundToPlay.setObjectID( self->getID() ); TheAudio->addAudioEvent( &soundToPlay ); // These flags are exclusive, not cumulative self->setWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ); set.set(MODELCONDITION_CONTINUOUS_FIRE_FAST); // These flags are exclusive, not cumulative self->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ); clr.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN); clr.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW); } else { self->setWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ); set.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN); // These flags are exclusive, not cumulative self->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ); clr.set(MODELCONDITION_CONTINUOUS_FIRE_FAST); clr.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW); } self->clearAndSetModelConditionFlags(clr, set); } //------------------------------------------------------------------------------------------------- void FiringTracker::coolDown() { ModelConditionFlags clr, set; if( getObject()->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ) || getObject()->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN )) { // Straight to zero from wherever it is set.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW); getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ); getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ); clr.set(MODELCONDITION_CONTINUOUS_FIRE_FAST); clr.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN); } else // we stop, now { getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ); getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ); // Just chillin, nothing to change clr.set(MODELCONDITION_CONTINUOUS_FIRE_FAST); clr.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN); clr.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW); m_frameToStartCooldown = 0; } getObject()->clearAndSetModelConditionFlags(clr, set); // Start everything over m_consecutiveShots = 0; m_victimID = INVALID_ID; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void FiringTracker::crc( Xfer *xfer ) { // object helper base class UpdateModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void FiringTracker::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // object helper base class UpdateModule::xfer( xfer ); // consecutive shots xfer->xferInt( &m_consecutiveShots ); // victim id xfer->xferObjectID( &m_victimID ); // frame to start cooldown xfer->xferUnsignedInt( &m_frameToStartCooldown ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void FiringTracker::loadPostProcess( void ) { // object helper back class UpdateModule::loadPostProcess(); } // end loadPostProcess