/* ** 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. // // // //////////////////////////////////////////////////////////////////////////////// // TurretAI.cpp // Turret behavior implementation // Author: Steven Johnson, April 2002 #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #define DEFINE_WEAPONSLOTTYPE_NAMES #include "Common/GameAudio.h" #include "Common/PerfTimer.h" #include "Common/RandomValue.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/TerrainLogic.h" #include "GameLogic/TurretAI.h" #include "GameLogic/Weapon.h" #include "GameLogic/WeaponSet.h" const UnsignedInt WAIT_INDEFINITELY = 0xffffffff; #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ static StateReturnType frameToSleepTime( UnsignedInt frame1, UnsignedInt frame2 = FOREVER, UnsignedInt frame3 = FOREVER, UnsignedInt frame4 = FOREVER ) { if (frame1 > frame2) frame1 = frame2; if (frame1 > frame3) frame1 = frame3; if (frame1 > frame4) frame1 = frame4; UnsignedInt now = TheGameLogic->getFrame(); if (frame1 > now) { return STATE_SLEEP(frame1 - now); } else { // ignore times that are in the past, since this can frequently happen return STATE_CONTINUE; } } //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- /** * Create a TurretAI state machine. Define all of the states the machine * can possibly be in, and set the initial (default) state. */ TurretStateMachine::TurretStateMachine( TurretAI* tai, Object *obj, AsciiString name ) : m_turretAI(tai), StateMachine( obj, name ) { static const StateConditionInfo fireConditions[] = { StateConditionInfo(outOfWeaponRangeObject, TURRETAI_AIM, NULL), StateConditionInfo(NULL, NULL, NULL) // keep last }; // order matters: first state is the default state. defineState( TURRETAI_IDLE, newInstance(TurretAIIdleState)( this ), TURRETAI_IDLE, TURRETAI_IDLESCAN ); defineState( TURRETAI_IDLESCAN, newInstance(TurretAIIdleScanState)( this ), TURRETAI_HOLD, TURRETAI_HOLD ); defineState( TURRETAI_AIM, newInstance(TurretAIAimTurretState)( this ), TURRETAI_FIRE, TURRETAI_HOLD ); defineState( TURRETAI_FIRE, newInstance(AIAttackFireWeaponState)( this, tai ), TURRETAI_AIM, TURRETAI_AIM, fireConditions ); defineState( TURRETAI_RECENTER, newInstance(TurretAIRecenterTurretState)( this ), TURRETAI_IDLE, TURRETAI_IDLE ); defineState( TURRETAI_HOLD, newInstance(TurretAIHoldTurretState)( this ), TURRETAI_RECENTER, TURRETAI_RECENTER ); } //---------------------------------------------------------------------------------------------------------- TurretStateMachine::~TurretStateMachine() { } //---------------------------------------------------------------------------------------------------------- void TurretStateMachine::clear() { StateMachine::clear(); TurretAI* turret = getTurretAI(); if (turret) turret->friend_notifyStateMachineChanged(); } //---------------------------------------------------------------------------------------------------------- StateReturnType TurretStateMachine::resetToDefaultState() { StateReturnType tmp = StateMachine::resetToDefaultState(); TurretAI* turret = getTurretAI(); if (turret) turret->friend_notifyStateMachineChanged(); return tmp; } //---------------------------------------------------------------------------------------------------------- StateReturnType TurretStateMachine::setState(StateID newStateID) { StateID oldID = getCurrentStateID(); StateReturnType tmp = StateMachine::setState(newStateID); TurretAI* turret = getTurretAI(); if (turret && oldID != newStateID) turret->friend_notifyStateMachineChanged(); return tmp; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void TurretStateMachine::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer Method */ // ------------------------------------------------------------------------------------------------ void TurretStateMachine::xfer( Xfer *xfer ) { XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void TurretStateMachine::loadPostProcess( void ) { } // end loadPostProcess //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- TurretAIData::TurretAIData() { m_turnRate = DEFAULT_TURN_RATE; m_pitchRate = DEFAULT_PITCH_RATE; m_naturalTurretAngle = 0.0f; m_naturalTurretPitch = 0.0f; for( Int slotIndex = 0; slotIndex < WEAPONSLOT_COUNT; ++slotIndex ) { m_turretFireAngleSweep[slotIndex] = 0.0f; m_turretSweepSpeedModifier[slotIndex] = 1.0f; } m_firePitch = 0.0f; m_minPitch = 0.0f; m_groundUnitPitch = 0; m_turretWeaponSlots = 0; #ifdef INTER_TURRET_DELAY m_interTurretDelay = 0; #endif m_minIdleScanAngle = 0.0f; m_maxIdleScanAngle = 0.0f; m_groundUnitPitch = 0.0f; m_minIdleScanInterval = 9999999; m_maxIdleScanInterval = 9999999; m_recenterTime = 2*LOGICFRAMES_PER_SECOND; m_initiallyDisabled = false; m_firesWhileTurning = FALSE; m_isAllowsPitch = false; } //------------------------------------------------------------------------------------------------- static void parseTWS(INI* ini, void * /*instance*/, void * store, const void* /*userData*/) { UnsignedInt* tws = (UnsignedInt*)store; const char* token = ini->getNextToken(); while (token != NULL) { WeaponSlotType wslot = (WeaponSlotType)INI::scanIndexList(token, TheWeaponSlotTypeNames); *tws |= (1 << wslot); token = ini->getNextTokenOrNull(); } } //------------------------------------------------------------------------------------------------- void TurretAIData::parseTurretSweep(INI* ini, void *instance, void * /*store*/, const void* userData) { TurretAIData* self = (TurretAIData*)instance; WeaponSlotType wslot = (WeaponSlotType)INI::scanIndexList(ini->getNextToken(), TheWeaponSlotTypeNames); INI::parseAngleReal( ini, instance, &self->m_turretFireAngleSweep[wslot], NULL ); } //------------------------------------------------------------------------------------------------- void TurretAIData::parseTurretSweepSpeed(INI* ini, void *instance, void * /*store*/, const void* userData) { TurretAIData* self = (TurretAIData*)instance; WeaponSlotType wslot = (WeaponSlotType)INI::scanIndexList(ini->getNextToken(), TheWeaponSlotTypeNames); INI::parseReal( ini, instance, &self->m_turretSweepSpeedModifier[wslot], NULL ); } //---------------------------------------------------------------------------------------------------------- void TurretAIData::buildFieldParse(MultiIniFieldParse& p) { static const FieldParse dataFieldParse[] = { { "TurretTurnRate", INI::parseAngularVelocityReal, NULL, offsetof( TurretAIData, m_turnRate ) }, { "TurretPitchRate", INI::parseAngularVelocityReal, NULL, offsetof( TurretAIData, m_pitchRate ) }, { "NaturalTurretAngle", INI::parseAngleReal, NULL, offsetof( TurretAIData, m_naturalTurretAngle ) }, { "NaturalTurretPitch", INI::parseAngleReal, NULL, offsetof( TurretAIData, m_naturalTurretPitch ) }, { "FirePitch", INI::parseAngleReal, NULL, offsetof( TurretAIData, m_firePitch ) }, { "MinPhysicalPitch", INI::parseAngleReal, NULL, offsetof( TurretAIData, m_minPitch ) }, { "GroundUnitPitch", INI::parseAngleReal, NULL, offsetof( TurretAIData, m_groundUnitPitch ) }, { "TurretFireAngleSweep", TurretAIData::parseTurretSweep, NULL, NULL }, { "TurretSweepSpeedModifier",TurretAIData::parseTurretSweepSpeed, NULL, NULL }, { "ControlledWeaponSlots", parseTWS, NULL, offsetof( TurretAIData, m_turretWeaponSlots ) }, { "AllowsPitch", INI::parseBool, NULL, offsetof( TurretAIData, m_isAllowsPitch ) }, #ifdef INTER_TURRET_DELAY { "InterTurretDelay", INI::parseDurationUnsignedInt, NULL, offsetof( TurretAIData, m_interTurretDelay ) }, #endif { "MinIdleScanAngle", INI::parseAngleReal, NULL, offsetof( TurretAIData, m_minIdleScanAngle ) }, { "MaxIdleScanAngle", INI::parseAngleReal, NULL, offsetof( TurretAIData, m_maxIdleScanAngle ) }, { "MinIdleScanInterval", INI::parseDurationUnsignedInt, NULL, offsetof( TurretAIData, m_minIdleScanInterval ) }, { "MaxIdleScanInterval", INI::parseDurationUnsignedInt, NULL, offsetof( TurretAIData, m_maxIdleScanInterval ) }, { "RecenterTime", INI::parseDurationUnsignedInt, NULL, offsetof( TurretAIData, m_recenterTime ) }, { "InitiallyDisabled", INI::parseBool, NULL, offsetof( TurretAIData, m_initiallyDisabled ) }, { "FiresWhileTurning", INI::parseBool, NULL, offsetof( TurretAIData, m_firesWhileTurning ) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- TurretAI::TurretAI(Object* owner, const TurretAIData* data, WhichTurretType tur) : m_owner(owner), m_whichTurret(tur), m_data(data), m_turretStateMachine(NULL), m_playRotSound(false), m_playPitchSound(false), m_positiveSweep(true), m_enableSweepUntil(0), m_sleepUntil(0), m_didFire(false), m_target(TARGET_NONE), m_targetWasSetByIdleMood(false), m_enabled(!data->m_initiallyDisabled), m_firesWhileTurning(data->m_firesWhileTurning), m_isForceAttacking(false), //Added By Sadullah Nader //Initialization(s) inserted m_victimInitialTeam(NULL) // { //Added By Sadullah Nader //Initialization(s) inserted m_continuousFireExpirationFrame = -1; // if (!m_data) { DEBUG_CRASH(("TurretAI MUST have ModuleData")); throw INI_INVALID_DATA; } if (m_data->m_turretWeaponSlots == 0) { DEBUG_CRASH(("TurretAI MUST specify controlled weapon slots!")); throw INI_INVALID_DATA; } m_angle = getNaturalTurretAngle(); m_pitch = getNaturalTurretPitch(); #ifdef _DEBUG char smbuf[256]; sprintf(smbuf, "TurretStateMachine for tur %08lx slot %d",this,tur); const char* smname = smbuf; #else const char* smname = "TurretStateMachine"; #endif m_turretStateMachine = newInstance(TurretStateMachine)(this, m_owner, smname); m_turretStateMachine->initDefaultState(); m_turretRotOrPitchSound = *(m_owner->getTemplate()->getPerUnitSound("TurretMoveLoop")); } //---------------------------------------------------------------------------------------------------------- TurretAI::~TurretAI() { stopRotOrPitchSound(); if (m_turretStateMachine) m_turretStateMachine->deleteInstance(); } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void TurretAI::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer Method */ // ------------------------------------------------------------------------------------------------ void TurretAI::xfer( Xfer *xfer ) { // version const XferVersion currentVersion = 2; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); /* These 4 are loaded on creation, and don't change. jba. const TurretAIData* m_data; WhichTurretType m_whichTurret; Object* m_owner; AudioEventRTS m_turretRotOrPitchSound; ///< Sound of turret rotation */ xfer->xferSnapshot(m_turretStateMachine); xfer->xferReal(&m_angle); xfer->xferReal(&m_pitch); xfer->xferUnsignedInt(&m_enableSweepUntil); xfer->xferUser(&m_target, sizeof(m_target)); xfer->xferUnsignedInt(&m_continuousFireExpirationFrame); Bool tmpBool; #define UNPACK_AND_XFER(val) {tmpBool = val; xfer->xferBool(&tmpBool); val = tmpBool;} UNPACK_AND_XFER(m_playRotSound); UNPACK_AND_XFER(m_playPitchSound); UNPACK_AND_XFER(m_positiveSweep); UNPACK_AND_XFER(m_didFire); UNPACK_AND_XFER(m_enabled); UNPACK_AND_XFER(m_firesWhileTurning); UNPACK_AND_XFER(m_targetWasSetByIdleMood); #undef UNPACK_AND_XFER if (version >= 2) xfer->xferUnsignedInt(&m_sleepUntil); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void TurretAI::loadPostProcess( void ) { Object *victim = m_turretStateMachine->getGoalObject(); if (victim) { m_victimInitialTeam = victim->getTeam(); } } // end loadPostProcess //---------------------------------------------------------------------------------------------------------- Bool TurretAI::friend_turnTowardsAngle(Real desiredAngle, Real rateModifier, Real relThresh) { desiredAngle = normalizeAngle(desiredAngle); // rotate turret back to zero angle Real origAngle = getTurretAngle(); Real actualAngle = origAngle; Real turnRate = getTurnRate() * rateModifier; Real angleDiff = normalizeAngle(desiredAngle - actualAngle); // Are we close enough to the desired angle to just snap there? if (fabs(angleDiff) < turnRate) { // we are centered actualAngle = desiredAngle; getOwner()->clearModelConditionState(MODELCONDITION_TURRET_ROTATE); } else { if (angleDiff > 0) actualAngle += turnRate; else actualAngle -= turnRate; getOwner()->setModelConditionState(MODELCONDITION_TURRET_ROTATE); m_playRotSound = true; } m_angle = normalizeAngle(actualAngle); if( m_angle != origAngle ) getOwner()->reactToTurretChange( m_whichTurret, origAngle, m_pitch ); Bool aligned = fabs(m_angle - desiredAngle) <= relThresh; return aligned; } //---------------------------------------------------------------------------------------------------------- Bool TurretAI::friend_turnTowardsPitch(Real desiredPitch, Real rateModifier) { if (!isAllowsPitch()) return true; desiredPitch = normalizeAngle(desiredPitch); // rotate turret back to zero angle Real actualPitch = getTurretPitch(); Real pitchRate = getPitchRate() * rateModifier; Real pitchDiff = normalizeAngle(desiredPitch - actualPitch); if (fabs(pitchDiff) < pitchRate) { // we are centered actualPitch = desiredPitch; } else { if (pitchDiff > 0) actualPitch += pitchRate; else actualPitch -= pitchRate; m_playPitchSound = true; } m_pitch = normalizeAngle(actualPitch); return (m_pitch == desiredPitch); } //---------------------------------------------------------------------------------------------------------- Bool TurretAI::isWeaponSlotOkToFire(WeaponSlotType wslot) const { return isWeaponSlotOnTurret(wslot); } //---------------------------------------------------------------------------------------------------------- Real TurretAI::getTurretFireAngleSweepForWeaponSlot( WeaponSlotType slot ) const { return m_data->m_turretFireAngleSweep[slot]; } //---------------------------------------------------------------------------------------------------------- Real TurretAI::getTurretSweepSpeedModifierForWeaponSlot( WeaponSlotType slot ) const { return m_data->m_turretSweepSpeedModifier[slot]; } //---------------------------------------------------------------------------------------------------------- void TurretAI::notifyFired() { m_didFire = true; } //---------------------------------------------------------------------------------------------------------- void TurretAI::notifyNewVictimChosen(Object* victim) { setTurretTargetObject(victim, FALSE); } //---------------------------------------------------------------------------------------------------------- Bool TurretAI::isTryingToAimAtTarget(const Object* victim) const { StateID sid = m_turretStateMachine->getCurrentStateID(); Object* obj; Coord3D pos; return (sid == TURRETAI_AIM && friend_getTurretTarget(obj, pos) == TARGET_OBJECT && obj == victim); } //---------------------------------------------------------------------------------------------------------- Bool TurretAI::isOwnersCurWeaponOnTurret() const { WeaponSlotType wslot; Weapon* w = m_owner->getCurrentWeapon(&wslot); return w != NULL && isWeaponSlotOnTurret(wslot); } //---------------------------------------------------------------------------------------------------------- Bool TurretAI::isWeaponSlotOnTurret(WeaponSlotType wslot) const { return (m_data->m_turretWeaponSlots & (1 << wslot)) != 0; } //---------------------------------------------------------------------------------------------------------- TurretTargetType TurretAI::friend_getTurretTarget(Object*& obj, Coord3D& pos) const { obj = NULL; pos.zero(); if (m_target == TARGET_OBJECT) { obj = m_turretStateMachine->getGoalObject(); // clear it explicitly if null -- could be deceased but holding the // old (bogus) objectid internally if (obj == NULL || obj->isEffectivelyDead()) { m_turretStateMachine->setGoalObject(NULL); m_target = TARGET_NONE; m_targetWasSetByIdleMood = false; } } else if (m_target == TARGET_POSITION) { obj = NULL; pos = *m_turretStateMachine->getGoalPosition(); } return m_target; } //---------------------------------------------------------------------------------------------------------- void TurretAI::removeSelfAsTargeter() { // be paranoid, in case we are called from dtors, etc. if (m_target == TARGET_OBJECT && m_turretStateMachine != NULL) { Object* self = m_owner; Object* target = m_turretStateMachine->getGoalObject(); if (self != NULL && target != NULL) { AIUpdateInterface* targetAI = target->getAI(); if (targetAI) { targetAI->addTargeter(self->getID(), false); } } } } //---------------------------------------------------------------------------------------------------------- void TurretAI::setTurretTargetObject( Object *victim, Bool forceAttacking ) { if (!victim || victim->isEffectivelyDead() || !isOwnersCurWeaponOnTurret()) { victim = NULL; } if (victim == NULL) { // if nuking the victim, remove self as targeter before doing anything else. // (note that we never ADD self as targeter here; that is done in the aim state) removeSelfAsTargeter(); } m_turretStateMachine->setGoalObject( victim ); // could be null! this is OK! m_target = victim ? TARGET_OBJECT : TARGET_NONE; m_targetWasSetByIdleMood = false; m_isForceAttacking = forceAttacking; StateID sid = m_turretStateMachine->getCurrentStateID(); if (victim != NULL) { // if we're already in the aim state, don't call setState, since // it would go thru the exit/enter stuff, which we don't really want // to do... if (sid != TURRETAI_AIM && sid != TURRETAI_FIRE) m_turretStateMachine->setState( TURRETAI_AIM ); m_victimInitialTeam = victim->getTeam(); } else { // only change states if we are aiming. if (sid == TURRETAI_AIM || sid == TURRETAI_FIRE) m_turretStateMachine->setState(TURRETAI_HOLD); m_victimInitialTeam = NULL; } } //---------------------------------------------------------------------------------------------------------- void TurretAI::setTurretTargetPosition( const Coord3D* pos ) { if (!pos || !isOwnersCurWeaponOnTurret()) { pos = NULL; } // remove self as targeter before doing anything else. // (note that we never ADD self as targeter here; that is done in the aim state) removeSelfAsTargeter(); m_turretStateMachine->setGoalObject( NULL ); if (pos) m_turretStateMachine->setGoalPosition( pos ); m_target = pos ? TARGET_POSITION : TARGET_NONE; m_targetWasSetByIdleMood = false; StateID sid = m_turretStateMachine->getCurrentStateID(); if (pos != NULL) { // if we're already in the aim state, don't call setState, since // it would go thru the exit/enter stuff, which we don't really want // to do... if (sid != TURRETAI_AIM && sid != TURRETAI_FIRE) m_turretStateMachine->setState( TURRETAI_AIM ); m_victimInitialTeam = NULL; } else { // only change states if we are aiming. if (sid == TURRETAI_AIM || sid == TURRETAI_FIRE) m_turretStateMachine->setState(TURRETAI_HOLD); m_victimInitialTeam = NULL; } } //---------------------------------------------------------------------------------------------------------- void TurretAI::recenterTurret() { m_turretStateMachine->setState( TURRETAI_RECENTER ); } //---------------------------------------------------------------------------------------------------------- Bool TurretAI::isTurretInNaturalPosition() const { if( getNaturalTurretAngle() == getTurretAngle() && getNaturalTurretPitch() == getTurretPitch() ) { return true; } return false; } //---------------------------------------------------------------------------------------------------------- void TurretAI::friend_notifyStateMachineChanged() { m_sleepUntil = TheGameLogic->getFrame(); } //---------------------------------------------------------------------------------------------------------- /* Note that TurretAI isn't a behavior at all, so doesn't really "sleep"; however, it does return this information so that its AIUpdate owner can decide how long it (the AIUpdate) should sleep. So it behooves you to maximize "sleep" potential here! (srj) */ DECLARE_PERF_TIMER(TurretAI) UpdateSleepTime TurretAI::updateTurretAI() { USE_PERF_TIMER(TurretAI) #if defined(_DEBUG) || defined(_INTERNAL) DEBUG_ASSERTCRASH(!m_enabled || m_turretStateMachine->peekSleepTill() == 0 || m_turretStateMachine->peekSleepTill() >= m_sleepUntil, ("Turret Machine is less sleepy than turret")); #endif UnsignedInt now = TheGameLogic->getFrame(); if (m_sleepUntil != 0 && now < m_sleepUntil) { return UPDATE_SLEEP(m_sleepUntil - now); } //DEBUG_LOG(("updateTurretAI frame %d: %08lx\n",TheGameLogic->getFrame(),getOwner())); UpdateSleepTime subMachineSleep = UPDATE_SLEEP_FOREVER; // assume the best! // either we don't care about continuous fire stuff, or we care, but time has elapsed if ((!m_firesWhileTurning) || (m_continuousFireExpirationFrame <= now)) { m_playRotSound = false; m_playPitchSound = false; } if (m_enabled || m_turretStateMachine->getCurrentStateID() == TURRETAI_RECENTER) { m_didFire = false; // run the behavior state machine BEFORE doing sound check StateReturnType stRet = m_turretStateMachine->updateStateMachine(); if (m_didFire) { // if we fired, enable sweeping for a few frames. const ENABLE_SWEEP_FRAME_COUNT = 3; m_enableSweepUntil = now + ENABLE_SWEEP_FRAME_COUNT; m_continuousFireExpirationFrame = now + ENABLE_SWEEP_FRAME_COUNT;// so the recent firing will not interrupt the moving sound } if (m_playRotSound || m_playPitchSound) startRotOrPitchSound(); else stopRotOrPitchSound(); if (IS_STATE_SLEEP(stRet)) { Int frames = GET_STATE_SLEEP_FRAMES(stRet); if (frames < subMachineSleep) subMachineSleep = UPDATE_SLEEP(frames); } else { // it's STATE_CONTINUE, STATE_SUCCESS, or STATE_FAILURE, // any of which will probably require next frame subMachineSleep = UPDATE_SLEEP_NONE; } } // if enabled or recentering m_sleepUntil = now + subMachineSleep; #if defined(_DEBUG) || defined(_INTERNAL) DEBUG_ASSERTCRASH(!m_enabled || m_turretStateMachine->peekSleepTill() == 0 || m_turretStateMachine->peekSleepTill() >= m_sleepUntil, ("Turret Machine is less sleepy than turret")); #endif return subMachineSleep; } //------------------------------------------------------------------------------------------------- void TurretAI::setTurretEnabled( Bool enabled ) { if (enabled && !m_enabled) { // be sure we wake up! m_sleepUntil = TheGameLogic->getFrame(); } m_enabled = enabled; } //------------------------------------------------------------------------------------------------- /** * Start turret rotation sound */ void TurretAI::startRotOrPitchSound() { if (!m_turretRotOrPitchSound.isCurrentlyPlaying()) { m_turretRotOrPitchSound.setObjectID(m_owner->getID()); m_turretRotOrPitchSound.setPlayingHandle(TheAudio->addAudioEvent(&m_turretRotOrPitchSound)); } } //------------------------------------------------------------------------------------------------- /** * Stop turret rotation sound */ void TurretAI::stopRotOrPitchSound() { if (m_turretRotOrPitchSound.isCurrentlyPlaying()) { TheAudio->removeAudioEvent(m_turretRotOrPitchSound.getPlayingHandle()); } } #ifdef INTER_TURRET_DELAY //---------------------------------------------------------------------------------------------------------- void TurretAI::getOtherTurretWeaponInfo(Int& numSelf, Int& numSelfReloading, Int& numSelfReady, Int& numOther, Int& numOtherReloading, Int& numOtherReady) const { numSelf = 0; numSelfReady = 0; numSelfReloading = 0; numOther = 0; numOtherReady = 0; numOtherReloading = 0; for( Int i = 0; i < WEAPONSLOT_COUNT; i++ ) { // ignore empty slots. const Weapon* w = getOwner()->getWeaponInWeaponSlot((WeaponSlotType)i); if (w == NULL) continue; // ignore the weapons on this turret. if (isWeaponSlotOnTurret((WeaponSlotType)i)) { ++numSelf; if (w->getStatus() == RELOADING_CLIP) ++numSelfReloading; else if (w->getStatus() == READY_TO_FIRE) ++numSelfReady; } else { ++numOther; if (w->getStatus() == RELOADING_CLIP) ++numOtherReloading; else if (w->getStatus() == READY_TO_FIRE) ++numOtherReady; } } } #endif //---------------------------------------------------------------------------------------------------------- Bool TurretAI::friend_isAnyWeaponInRangeOf(const Object* o) const { // see if we've moved out of range, and if so, nuke the target. for (Int i = 0; i < WEAPONSLOT_COUNT; i++ ) { // ignore empty slots. const Weapon* w = getOwner()->getWeaponInWeaponSlot((WeaponSlotType)i); if (w == NULL || !isWeaponSlotOnTurret((WeaponSlotType)i)) continue; if (w->isWithinAttackRange(getOwner(), o) // srj sez: not sure if we want to do this or not. // jba sez: no, don't do this. isWithinAttackRange checks terrain los now, and // some weapons (like tomahawk) can fire beyond los, so this check is problematical here. // && w->isClearFiringLineOfSightTerrain(getOwner(), o) ) { return true; } } return false; } //---------------------------------------------------------------------------------------------------------- Bool TurretAI::friend_isSweepEnabled() const { if (m_enableSweepUntil != 0 && m_enableSweepUntil > TheGameLogic->getFrame()) return true; return false; } //---------------------------------------------------------------------------------------------------------- UnsignedInt TurretAI::friend_getNextIdleMoodTargetFrame() const { const Object* obj = getOwner(); const AIUpdateInterface *ai = obj->getAIUpdateInterface(); // ai can be null during object construction. return ai ? ai->getNextMoodCheckTime() : TheGameLogic->getFrame(); } //---------------------------------------------------------------------------------------------------------- void TurretAI::friend_checkForIdleMoodTarget() { Object* obj = getOwner(); AIUpdateInterface *ai = obj->getAIUpdateInterface(); // This state is used internally some places, so we don't necessarily want to be looking for targets // Places that use AI_IDLE internally should set this to false in the constructor. jkmcd UnsignedInt moodAdjust = ai->getMoodMatrixActionAdjustment(MM_Action_Idle); if (moodAdjust & MAA_Affect_Range_IgnoreAll) return; // If we're supposed to attack based on mood, etc, then we will do so. Object* enemy = ai->getNextMoodTarget( true, true ); if (enemy) { setTurretTargetObject(enemy, FALSE); obj->chooseBestWeaponForTarget(enemy, PREFER_MOST_DAMAGE, CMD_FROM_AI); // set this magic flag to indicate that this was an independent attack-of-opportunity, // and we should check to see if out-of-range, and if so, nuke it m_targetWasSetByIdleMood = true; } } #ifdef INTER_TURRET_DELAY //---------------------------------------------------------------------------------------------------------- UnsignedInt TurretAI::friend_getInterTurretDelay() { if (m_data->m_interTurretDelay == 0) return 0; Int numSelf; Int numSelfReloading; Int numSelfReady; Int numOther; Int numOtherReloading; Int numOtherReady; getOtherTurretWeaponInfo(numSelf, numSelfReloading, numSelfReady, numOther, numOtherReloading, numOtherReady); AIUpdateInterface* ai = getOwner()->getAIUpdateInterface(); WhichTurretType t = ai->friend_getTurretSync(); if (t == TURRET_INVALID || t == m_whichTurret) { if (numSelf == numSelfReady) { ai->friend_setTurretSync(m_whichTurret); // if we just got the flag, use our delay, otherwise go immediately if (t == TURRET_INVALID) return m_data->m_interTurretDelay; else return 0; } else if (numSelf == numSelfReloading) { // we're done... relinquish the flag ai->friend_setTurretSync(TURRET_INVALID); return WAIT_INDEFINITELY; } else { // we're not ready, but not reloading either, so retain the flag and wait return WAIT_INDEFINITELY; } } else { // someone else has it, so just wait. return WAIT_INDEFINITELY; } } #endif //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- StateReturnType TurretAIAimTurretState::onEnter() { // don't set the "turret-rotate" bit here; wait and see if we really turn or not. #ifdef INTER_TURRET_DELAY m_extraDelay = 0; #endif return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- /** * Rotate our turret to point at the machine's goal */ StateReturnType TurretAIAimTurretState::update() { //DEBUG_LOG(("TurretAIAimTurretState frame %d: %08lx\n",TheGameLogic->getFrame(),getTurretAI()->getOwner())); TurretAI* turret = getTurretAI(); Object* obj = turret->getOwner(); AIUpdateInterface* ai = obj->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } Object* enemy; AIUpdateInterface* enemyAI=NULL; Coord3D enemyPosition; Bool preventing = false; TurretTargetType targetType = turret->friend_getTurretTarget(enemy, enemyPosition); Object *enemyForDistanceCheckOnly = enemy; // Note: Do not use this anywhere except for the range check. Bool nothingInRange = false; switch (targetType) { case TARGET_NONE: { return STATE_FAILURE; } case TARGET_OBJECT: { Bool isPrimaryEnemy = (enemy && enemy == ai->getGoalObject()); // if the enemy is gone, or we're out of range, or it changed teams, the attack is over Bool ableToAttackTarget = obj->isAbleToAttack(); if (ableToAttackTarget) { // srj sez: since we have already acquired this target, we should use // the CONTINUED attack tests, not the new ones. CanAttackResult result = obj->getAbleToAttackSpecificObject( turret->isForceAttacking() ? ATTACK_CONTINUED_TARGET_FORCED : ATTACK_CONTINUED_TARGET, enemy, ai->getLastCommandSource() ); ableToAttackTarget = result == ATTACKRESULT_POSSIBLE || result == ATTACKRESULT_POSSIBLE_AFTER_MOVING; } nothingInRange = !turret->friend_isAnyWeaponInRangeOf(enemy); if (enemy == NULL || !ableToAttackTarget || (!isPrimaryEnemy && nothingInRange) || enemy->getTeam() != turret->friend_getVictimInitialTeam() ) { if (turret->friend_getTargetWasSetByIdleMood()) { turret->setTurretTargetObject(NULL, FALSE); } return STATE_FAILURE; } // aim turret towards enemy (turret angle is relative to its parent object) if (enemy->isKindOf(KINDOF_BRIDGE)) { // Special case - bridges have two attackable points at either end. TBridgeAttackInfo info; TheTerrainLogic->getBridgeAttackPoints(enemy, &info); Real distSqr = ThePartitionManager->getDistanceSquared( obj, &info.attackPoint1, FROM_BOUNDINGSPHERE_3D ); if (distSqr > ThePartitionManager->getDistanceSquared( obj, &info.attackPoint2, FROM_BOUNDINGSPHERE_3D ) ) { enemyPosition = info.attackPoint2; } else { enemyPosition = info.attackPoint1; } } else { enemyPosition = *enemy->getPosition(); } enemyAI = enemy ? enemy->getAI() : NULL; // add ourself as a targeter BEFORE calling isTemporarilyPreventingAimSuccess(). // we do this every time thru, just in case we get into a squabble with our ai // over whether or not we are a targeter... (srj) if (enemyAI) enemyAI->addTargeter(obj->getID(), true); preventing = enemyAI && enemyAI->isTemporarilyPreventingAimSuccess(); // don't use 'enemy' after this point, just the position. to help // enforce this, we'll null it out. enemy = NULL; break; } case TARGET_POSITION: { // nothing, just break. break; } } WeaponSlotType slot; Weapon *curWeapon = obj->getCurrentWeapon( &slot ); if (!curWeapon) { DEBUG_CRASH(("TurretAIAimTurretState::update - curWeapon is NULL.\n")); return STATE_FAILURE; } Real turnSpeedModifier = 1.0f;// Just like how recentering turns you half speed, sweeping can change your turn speed Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &enemyPosition ); Real aimAngle = relAngle; Real sweep = turret->getTurretFireAngleSweepForWeaponSlot( slot ); if (sweep > 0.0f && turret->friend_isSweepEnabled()) { if (turret->friend_getPositiveSweep()) aimAngle += sweep; else aimAngle -= sweep; turnSpeedModifier = turret->getTurretSweepSpeedModifierForWeaponSlot( slot ); } const Real REL_THRESH = 0.035f; // about 2 degrees. (getRelativeAngle2D is current only accurate to about 1.25 degrees) Bool turnAlignedToNemesis = turret->friend_turnTowardsAngle(aimAngle, turnSpeedModifier, REL_THRESH); // this section we do even if sweep is "disabled", so that we can start firing // once we get into sweep "range" if (sweep > 0.0f) { if (turnAlignedToNemesis) turret->friend_setPositiveSweep(!turret->friend_getPositiveSweep()); Real angleDiff = normalizeAngle(relAngle - turret->getTurretAngle()); turnAlignedToNemesis = (fabs(angleDiff) < sweep); } Bool pitchAlignedToNemesis = true; // Now do pitch if( turret->isAllowsPitch() ) { Real desiredPitch = 0; // Some turrets want to fire at a set pitch, and some want to fire straight to the target if( turret->getFirePitch() > 0 ) { desiredPitch = turret->getFirePitch(); } else { // Find the pitch to the target, but unlike Turn, we can't go 360 so bind at 0; Coord3D v; ThePartitionManager->getVectorTo(obj, &enemyPosition, FROM_CENTER_3D, v); //GetVectorTo only takes Object as the first, but we want the angle from our Weapon to the // target, not us to the target. Raise our side to get the line to make sense. v.z -= obj->getGeometryInfo().getMaxHeightAbovePosition() / 2; // I kinda hate our logic/client split. //The point to fire from should be intrinsic to the turret, but in reality it is very slow to look it up. Real actualPitch; if( v.length() > 0 ) actualPitch = ASin( v.z / v.length() ); else actualPitch = 0;// Don't point at NAN, just point at 0 if they are right on us desiredPitch = actualPitch; if( desiredPitch < turret->getMinPitch() ) { desiredPitch = turret->getMinPitch(); } if (turret->getGroundUnitPitch() > 0) { Bool adjust = false; if (!enemy) { adjust = true; // adjust for ground targets. } if (enemy && enemy->isKindOf(KINDOF_IMMOBILE)) { adjust = true; } if (enemyAI && enemyAI->isDoingGroundMovement()) { adjust = true; } if (adjust) { Real range = curWeapon->getAttackRange(obj); Real dist = v.length(); if (range<1) range = 1; // paranoia. jba. // As the unit gets closer, reduce the pitch so we don't shoot over him. Real groundPitch = turret->getGroundUnitPitch() * (dist/range); desiredPitch = actualPitch+groundPitch; if (desiredPitch < turret->getMinPitch()) { desiredPitch = turret->getMinPitch(); } } } } pitchAlignedToNemesis = turret->friend_turnTowardsPitch(desiredPitch, 1.0f); } // For now, we require that we're within range before we can successfully exit the AIM state, // and move into the FIRE state. if (turnAlignedToNemesis && pitchAlignedToNemesis && ((enemyForDistanceCheckOnly && curWeapon->isWithinAttackRange(obj, enemyForDistanceCheckOnly)) || (!enemyForDistanceCheckOnly && curWeapon->isWithinAttackRange(obj, &enemyPosition)))) { #ifdef INTER_TURRET_DELAY if (m_extraDelay > 0) { --m_extraDelay; } else { m_extraDelay = turret->friend_getInterTurretDelay(); if (m_extraDelay == WAIT_INDEFINITELY) { m_extraDelay = 0; return STATE_CONTINUE; } } if (m_extraDelay != 0) { return STATE_CONTINUE; } #endif if (preventing || nothingInRange) { return STATE_CONTINUE; } return STATE_SUCCESS; } // stay in aiming state until directed to do something else return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- void TurretAIAimTurretState::onExit( StateExitType status ) { } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- StateReturnType TurretAIRecenterTurretState::onEnter() { return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- /** * Rotate the owner's turret to its home orientation. */ StateReturnType TurretAIRecenterTurretState::update() { //DEBUG_LOG(("TurretAIRecenterTurretState frame %d: %08lx\n",TheGameLogic->getFrame(),getTurretAI()->getOwner())); TurretAI* turret = getTurretAI(); Bool angleAligned = turret->friend_turnTowardsAngle(turret->getNaturalTurretAngle(), 0.5f, 0.0f); Bool pitchAligned = turret->friend_turnTowardsPitch(turret->getNaturalTurretPitch(), 0.5f); if( angleAligned && pitchAligned ) return STATE_SUCCESS; return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- /** * Stop rotation sound */ void TurretAIRecenterTurretState::onExit( StateExitType status ) { } //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void TurretAIIdleState::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer Method */ // ------------------------------------------------------------------------------------------------ void TurretAIIdleState::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); xfer->xferUnsignedInt(&m_nextIdleScan); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void TurretAIIdleState::loadPostProcess( void ) { } // end loadPostProcess //---------------------------------------------------------------------------------------------------------- void TurretAIIdleState::resetIdleScan() { UnsignedInt now = TheGameLogic->getFrame(); UnsignedInt delay = GameLogicRandomValue(getTurretAI()->getMinIdleScanInterval(), getTurretAI()->getMaxIdleScanInterval()); m_nextIdleScan = now + delay; } //---------------------------------------------------------------------------------------------------------- StateReturnType TurretAIIdleState::onEnter() { AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface(); if (ai) { ai->resetNextMoodCheckTime(); if (ai->friend_getTurretSync() == getTurretAI()->friend_getWhichTurret()) ai->friend_setTurretSync(TURRET_INVALID); } // ai doesn't exist if the object was just created this frame. resetIdleScan(); TurretAI* turret = getTurretAI(); return frameToSleepTime(turret->friend_getNextIdleMoodTargetFrame(), m_nextIdleScan); } //---------------------------------------------------------------------------------------------------------- StateReturnType TurretAIIdleState::update() { //DEBUG_LOG(("TurretAIIdleState frame %d: %08lx\n",TheGameLogic->getFrame(),getTurretAI()->getOwner())); UnsignedInt now = TheGameLogic->getFrame(); if (now >= m_nextIdleScan) { // this is redundant, since we're exiting the state, and will reset // it again in onEnter next time (srj) // resetIdleScan(); return STATE_FAILURE; } TurretAI* turret = getTurretAI(); turret->friend_checkForIdleMoodTarget(); return frameToSleepTime(turret->friend_getNextIdleMoodTargetFrame(), m_nextIdleScan); } //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void TurretAIIdleScanState::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer Method */ // ------------------------------------------------------------------------------------------------ void TurretAIIdleScanState::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); xfer->xferReal(&m_desiredAngle); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void TurretAIIdleScanState::loadPostProcess( void ) { } // end loadPostProcess //---------------------------------------------------------------------------------------------------------- StateReturnType TurretAIIdleScanState::onEnter() { Real minA = getTurretAI()->getMinIdleScanAngle(); Real maxA = getTurretAI()->getMaxIdleScanAngle(); if (minA == 0.0f && maxA == 0.0f) return STATE_SUCCESS; m_desiredAngle = minA + GameLogicRandomValueReal(0, maxA - minA); if (GameLogicRandomValue( 0, 1 ) == 0) m_desiredAngle = -m_desiredAngle; return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- /** * Rotate the owner's turret to its scan orientation. */ StateReturnType TurretAIIdleScanState::update() { //DEBUG_LOG(("TurretAIIdleScanState frame %d: %08lx\n",TheGameLogic->getFrame(),getTurretAI()->getOwner())); Bool angleAligned = getTurretAI()->friend_turnTowardsAngle(getTurretAI()->getNaturalTurretAngle() + m_desiredAngle, 0.5f, 0.0f); Bool pitchAligned = getTurretAI()->friend_turnTowardsPitch(getTurretAI()->getNaturalTurretPitch(), 0.5f); if( angleAligned && pitchAligned ) return STATE_SUCCESS; return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- /** * Stop rotation sound */ void TurretAIIdleScanState::onExit( StateExitType status ) { } //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void TurretAIHoldTurretState::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer Method */ // ------------------------------------------------------------------------------------------------ void TurretAIHoldTurretState::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); xfer->xferUnsignedInt(&m_timestamp); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void TurretAIHoldTurretState::loadPostProcess( void ) { } // end loadPostProcess //---------------------------------------------------------------------------------------------------------- StateReturnType TurretAIHoldTurretState::onEnter() { m_timestamp = TheGameLogic->getFrame() + getTurretAI()->getRecenterTime(); TurretAI* turret = getTurretAI(); return frameToSleepTime(turret->friend_getNextIdleMoodTargetFrame(), m_timestamp); } //------------------------------------------------------------------------------------------------- void TurretAIHoldTurretState::onExit( StateExitType status ) { } //---------------------------------------------------------------------------------------------------------- /** * Hold the turret's position for a short time before returning to center. */ StateReturnType TurretAIHoldTurretState::update() { //DEBUG_LOG(("TurretAIHoldTurretState frame %d: %08lx\n",TheGameLogic->getFrame(),getTurretAI()->getOwner())); if (TheGameLogic->getFrame() >= m_timestamp) return STATE_SUCCESS; TurretAI* turret = getTurretAI(); turret->friend_checkForIdleMoodTarget(); return frameToSleepTime(turret->friend_getNextIdleMoodTargetFrame(), m_timestamp); }