/*
** 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. //
// //
////////////////////////////////////////////////////////////////////////////////
// JetAIUpdate.cpp //////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_LOCOMOTORSET_NAMES
#include "Common/ActionManager.h"
#include "Common/GlobalData.h"
#include "Common/MiscAudio.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "GameClient/Drawable.h"
#include "GameClient/GameClient.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/CountermeasuresBehavior.h"
#include "GameLogic/Module/JetAIUpdate.h"
#include "GameLogic/Module/ParkingPlaceBehavior.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Weapon.h"
const Real BIGNUM = 99999.0f;
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
enum TaxiType
{
FROM_HANGAR,
FROM_PARKING,
TO_PARKING
};
//-------------------------------------------------------------------------------------------------
enum JetAIStateType
{
// note that these must be distinct (numerically) from AIStateType. ick.
JETAISTATETYPE_FIRST = 1000,
TAXI_FROM_HANGAR,
TAKING_OFF_AWAIT_CLEARANCE,
TAXI_TO_TAKEOFF,
PAUSE_BEFORE_TAKEOFF,
TAKING_OFF,
LANDING_AWAIT_CLEARANCE,
LANDING,
TAXI_FROM_LANDING,
ORIENT_FOR_PARKING_PLACE,
RELOAD_AMMO,
RETURNING_FOR_LANDING,
RETURN_TO_DEAD_AIRFIELD,
CIRCLING_DEAD_AIRFIELD,
JETAISTATETYPE_LAST
};
//-------------------------------------------------------------------------------------------------
Bool JetAIUpdate::getFlag( FlagType f ) const
{
return (m_flags & (1<getWeaponInWeaponSlot((WeaponSlotType)i);
if (weapon == NULL || weapon->getReloadType() != RETURN_TO_BASE_TO_RELOAD)
continue;
++specials;
if (weapon->getStatus() == OUT_OF_AMMO)
++out;
}
return specials > 0 && out == specials;
}
//-------------------------------------------------------------------------------------------------
static ParkingPlaceBehaviorInterface* getPP(ObjectID id, Object** airfieldPP = NULL)
{
if (airfieldPP)
*airfieldPP = NULL;
Object* airfield = TheGameLogic->findObjectByID( id );
if (airfield == NULL || airfield->isEffectivelyDead() || !airfield->isKindOf(KINDOF_FS_AIRFIELD) || airfield->testStatus(OBJECT_STATUS_SOLD))
return NULL;
if (airfieldPP)
*airfieldPP = airfield;
ParkingPlaceBehaviorInterface* pp = NULL;
for (BehaviorModule** i = airfield->getBehaviorModules(); *i; ++i)
{
if ((pp = (*i)->getParkingPlaceBehaviorInterface()) != NULL)
break;
}
return pp;
}
//-------------------------------------------------------------------------------------------------
class PartitionFilterHasParkingPlace : public PartitionFilter
{
private:
ObjectID m_id;
public:
PartitionFilterHasParkingPlace(ObjectID id) : m_id(id) { }
protected:
#if defined(_DEBUG) || defined(_INTERNAL)
virtual const char* debugGetName() { return "PartitionFilterHasParkingPlace"; }
#endif
virtual Bool allow(Object *objOther)
{
ParkingPlaceBehaviorInterface* pp = getPP(objOther->getID());
if (pp != NULL && pp->reserveSpace(m_id, 0.0f, NULL))
return true;
return false;
}
};
//-------------------------------------------------------------------------------------------------
static Object* findSuitableAirfield(Object* jet)
{
PartitionFilterAcceptByKindOf filterKind(MAKE_KINDOF_MASK(KINDOF_FS_AIRFIELD), KINDOFMASK_NONE);
PartitionFilterRejectByObjectStatus filterStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNDER_CONSTRUCTION ), OBJECT_STATUS_MASK_NONE );
PartitionFilterRejectByObjectStatus filterStatusTwo( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_SOLD ), OBJECT_STATUS_MASK_NONE ); // Independent to make it an OR
PartitionFilterRelationship filterTeam(jet, PartitionFilterRelationship::ALLOW_ALLIES);
PartitionFilterAlive filterAlive;
PartitionFilterSameMapStatus filterMapStatus(jet);
PartitionFilterHasParkingPlace filterPP(jet->getID());
PartitionFilter *filters[16];
Int numFilters = 0;
filters[numFilters++] = &filterKind;
filters[numFilters++] = &filterStatus;
filters[numFilters++] = &filterStatusTwo;
filters[numFilters++] = &filterTeam;
filters[numFilters++] = &filterAlive;
filters[numFilters++] = &filterPP;
filters[numFilters++] = &filterMapStatus;
filters[numFilters] = NULL;
return ThePartitionManager->getClosestObject( jet, HUGE_DIST, FROM_CENTER_2D, filters );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/*
Success: we have runway clearance
Failure: no runway clearance
*/
class JetAwaitingRunwayState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetAwaitingRunwayState, "JetAwaitingRunwayState")
protected:
// snapshot interface STUBBED.
virtual void crc( Xfer *xfer ){};
virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
virtual void loadPostProcess(){};
private:
const Bool m_landing;
public:
JetAwaitingRunwayState( StateMachine *machine, Bool landing ) : m_landing(landing), State( machine, "JetAwaitingRunwayState") { }
virtual StateReturnType onEnter()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return STATE_FAILURE;
jetAI->friend_setTakeoffInProgress(!m_landing);
jetAI->friend_setLandingInProgress(m_landing);
jetAI->friend_setAllowCircling(true);
return STATE_CONTINUE;
}
virtual StateReturnType update()
{
Object* jet = getMachineOwner();
if (jet->isEffectivelyDead())
return STATE_FAILURE;
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return STATE_FAILURE;
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
if (pp == NULL)
{
// no producer? just skip this step.
return STATE_SUCCESS;
}
// gotta reserve a space in order to reserve a runway
if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), NULL))
{
DEBUG_ASSERTCRASH(m_landing, ("hmm, this should never happen for taking-off things"));
return STATE_FAILURE;
}
if (pp->reserveRunway(jet->getID(), m_landing))
{
return STATE_SUCCESS;
}
else if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) && !m_landing )
{
//If we're trying to take off an aircraft carrier and fail to reserve a
//runway, it's because we need to be at the front of the carrier queue.
//Therefore, we need to move forward whenever possible until we are in
//the front.
Coord3D bestPos;
if( pp->calcBestParkingAssignment( jet->getID(), &bestPos ) )
{
jetAI->friend_setTaxiInProgress(true);
jetAI->friend_setAllowAirLoco(false);
jetAI->chooseLocomotorSet(LOCOMOTORSET_TAXIING);
jetAI->destroyPath();
Path *movePath;
movePath = newInstance(Path);
Coord3D pos = *jet->getPosition();
movePath->prependNode( &pos, LAYER_GROUND );
movePath->markOptimized();
movePath->appendNode( &bestPos, LAYER_GROUND );
TheAI->pathfinder()->setDebugPath(movePath);
jetAI->friend_setPath( movePath );
DEBUG_ASSERTCRASH(jetAI->getCurLocomotor(), ("no loco"));
jetAI->getCurLocomotor()->setUsePreciseZPos(true);
jetAI->getCurLocomotor()->setUltraAccurate(true);
jetAI->getCurLocomotor()->setAllowInvalidPosition(true);
jetAI->ignoreObstacleID(jet->getProducerID());
}
}
// can't get a runway? gotta wait.
jetAI->setLocomotorGoalNone();
return STATE_CONTINUE;
}
virtual void onExit(StateExitType status)
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if (jetAI)
{
jetAI->friend_setTakeoffInProgress(false);
jetAI->friend_setLandingInProgress(false);
jetAI->friend_setAllowCircling(false);
}
}
};
EMPTY_DTOR(JetAwaitingRunwayState)
//-------------------------------------------------------------------------------------------------
/*
Success: a new suitable airfield has appeared
Failure: shouldn't normally happen
*/
class JetOrHeliCirclingDeadAirfieldState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliCirclingDeadAirfieldState, "JetOrHeliCirclingDeadAirfieldState")
protected:
// snapshot interface STUBBED.
// The state will check immediately after a load game, but I think that's ok. jba.
virtual void crc( Xfer *xfer ){};
virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
virtual void loadPostProcess(){};
private:
Int m_checkAirfield;
enum
{
// only recheck for new airfields every second or so
HOW_OFTEN_TO_CHECK = LOGICFRAMES_PER_SECOND
};
public:
JetOrHeliCirclingDeadAirfieldState( StateMachine *machine ) :
State( machine, "JetOrHeliCirclingDeadAirfieldState"),
m_checkAirfield(0) { }
virtual StateReturnType onEnter()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
{
return STATE_FAILURE;
}
// obscure case: if the jet wasn't spawned, but just placed directly on the map,
// it might not have an owning airfield, and it might be trying to return
// simply due to being idle, not out of ammo. so check and don't die in that
// case, but just punt back out to idle.
if (!jetAI->isOutOfSpecialReloadAmmo() && jet->getProducerID() == INVALID_ID)
{
return STATE_FAILURE;
}
// just stay where we are.
jetAI->setLocomotorGoalNone();
m_checkAirfield = HOW_OFTEN_TO_CHECK;
//Play the "low fuel" voice whenever the craft is circling above the airfield.
AudioEventRTS soundToPlay = *jet->getTemplate()->getPerUnitSound( "VoiceLowFuel" );
soundToPlay.setObjectID( jet->getID() );
TheAudio->addAudioEvent( &soundToPlay );
return STATE_CONTINUE;
}
virtual StateReturnType update()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
{
return STATE_FAILURE;
}
// just stay where we are.
jetAI->setLocomotorGoalNone();
Real damageRate = jetAI->friend_getOutOfAmmoDamagePerSecond();
if (damageRate > 0)
{
// convert to damage/sec to damage/frame
damageRate *= SECONDS_PER_LOGICFRAME_REAL;
// since it's a percentage, multiply times the max health
damageRate *= jet->getBodyModule()->getMaxHealth();
DamageInfo damageInfo;
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
damageInfo.in.m_deathType = DEATH_NORMAL;
damageInfo.in.m_sourceID = INVALID_ID;
damageInfo.in.m_amount = damageRate;
jet->attemptDamage( &damageInfo );
}
if (--m_checkAirfield <= 0)
{
m_checkAirfield = HOW_OFTEN_TO_CHECK;
Object* airfield = findSuitableAirfield( jet );
if (airfield)
{
jet->setProducer(airfield);
return STATE_SUCCESS;
}
}
return STATE_CONTINUE;
}
};
EMPTY_DTOR(JetOrHeliCirclingDeadAirfieldState)
//-------------------------------------------------------------------------------------------------
/*
Success: we returned to the dead-airfield location
Failure: shouldn't normally happen
*/
class JetOrHeliReturningToDeadAirfieldState : public AIInternalMoveToState
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliReturningToDeadAirfieldState, "JetOrHeliReturningToDeadAirfieldState")
public:
JetOrHeliReturningToDeadAirfieldState( StateMachine *machine ) : AIInternalMoveToState( machine, "JetOrHeliReturningToDeadAirfieldState") { }
virtual StateReturnType onEnter()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
{
return STATE_FAILURE;
}
setAdjustsDestination(true);
m_goalPosition = *jetAI->friend_getProducerLocation();
return AIInternalMoveToState::onEnter();
}
};
EMPTY_DTOR(JetOrHeliReturningToDeadAirfieldState)
//-------------------------------------------------------------------------------------------------
// This solution uses the
// http://www.faqs.org/faqs/graphics/algorithms-faq/
// Subject 1.03
static Bool intersectInfiniteLine2D
(
Real ax, Real ay, Real ao,
Real cx, Real cy, Real co,
Real& ix, Real& iy
)
{
Real bx = ax + Cos(ao);
Real by = ay + Sin(ao);
Real dx = cx + Cos(co);
Real dy = cy + Sin(co);
Real denom = ((bx - ax) * (dy - cy) - (by - ay) * (dx - cx));
if (denom == 0.0f)
{
// the lines are parallel.
return false;
}
// The lines intersect.
Real r = ((ay - cy) * (dx - cx) - (ax - cx) * (dy - cy) ) / denom;
ix = ax + r * (bx - ax);
iy = ay + r * (by - ay);
return true;
}
//-------------------------------------------------------------------------------------------------
/*
Success: we are on the ground at the runway start
Failure: we are unable to get on the ground
*/
class JetOrHeliTaxiState : public AIMoveOutOfTheWayState
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliTaxiState, "JetOrHeliTaxiState")
private:
TaxiType m_taxiMode;
public:
JetOrHeliTaxiState( StateMachine *machine, TaxiType m ) : m_taxiMode(m), AIMoveOutOfTheWayState( machine ) { }
virtual StateReturnType onEnter()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return STATE_FAILURE;
jetAI->setCanPathThroughUnits(true);
jetAI->friend_setTakeoffInProgress(m_taxiMode != TO_PARKING);
jetAI->friend_setLandingInProgress(m_taxiMode == TO_PARKING);
jetAI->friend_setTaxiInProgress(true);
if( m_taxiMode == TO_PARKING )
{
//Instantly reload flares.
CountermeasuresBehaviorInterface *cbi = jet->getCountermeasuresBehaviorInterface();
if( cbi )
{
cbi->reloadCountermeasures();
}
}
jetAI->friend_setAllowAirLoco(false);
jetAI->chooseLocomotorSet(LOCOMOTORSET_TAXIING);
DEBUG_ASSERTCRASH(jetAI->getCurLocomotor(), ("no loco"));
Object* airfield;
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID(), &airfield);
if (pp == NULL)
return STATE_SUCCESS; // no airfield? just skip this step.
ParkingPlaceBehaviorInterface::PPInfo ppinfo;
if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo))
return STATE_FAILURE; // full?
Coord3D intermedPt;
Bool intermed = false;
Real orient = atan2(ppinfo.runwayPrep.y - ppinfo.parkingSpace.y, ppinfo.runwayPrep.x - ppinfo.parkingSpace.x);
if (fabs(stdAngleDiff(orient, ppinfo.parkingOrientation)) > PI/128)
{
intermedPt.z = (ppinfo.parkingSpace.z + ppinfo.runwayPrep.z) * 0.5f;
intermed = intersectInfiniteLine2D(
ppinfo.parkingSpace.x, ppinfo.parkingSpace.y, ppinfo.parkingOrientation,
ppinfo.runwayPrep.x, ppinfo.runwayPrep.y, ppinfo.parkingOrientation + PI/2,
intermedPt.x, intermedPt.y);
}
jetAI->destroyPath();
Path *movePath;
movePath = newInstance(Path);
Coord3D pos = *jet->getPosition();
movePath->prependNode( &pos, LAYER_GROUND );
movePath->markOptimized();
if (m_taxiMode == TO_PARKING)
{
if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) )
{
//We're on an aircraft carrier.
const std::vector *pTaxiLocations = pp->getTaxiLocations( jet->getID() );
if( pTaxiLocations )
{
std::vector::const_iterator it;
for( it = pTaxiLocations->begin(); it != pTaxiLocations->end(); it++ )
{
movePath->appendNode( it, LAYER_GROUND );
}
}
//We just landed... see if we can get a better space forward so we don't stop and pause
//at our initially assigned spot.
Coord3D pos;
pp->calcBestParkingAssignment( jet->getID(), &pos );
movePath->appendNode( &pos, LAYER_GROUND );
}
else
{
//We're on a normal airfield
movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND );
if (intermed)
movePath->appendNode( &intermedPt, LAYER_GROUND );
movePath->appendNode( &ppinfo.parkingSpace, LAYER_GROUND );
}
}
else if (m_taxiMode == FROM_PARKING)
{
if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) )
{
if( !(ppinfo.runwayStart == ppinfo.runwayPrep) )
{
movePath->appendNode( &ppinfo.runwayStart, LAYER_GROUND );
}
}
else
{
if (intermed)
movePath->appendNode( &intermedPt, LAYER_GROUND );
movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND );
movePath->appendNode( &ppinfo.runwayStart, LAYER_GROUND );
}
}
else if (m_taxiMode == FROM_HANGAR)
{
if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) )
{
//Aircraft carrier
if( jet->testStatus( OBJECT_STATUS_REASSIGN_PARKING ) )
{
//This status means we are being reassigned a parking space. We're not actually moving from the
//hangar. So simply move to the new parking spot which was just switched from under us in
//FlightDeckBehavior::update()
jet->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_REASSIGN_PARKING ) );
movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND );
}
else
{
const std::vector *pCreationLocations = pp->getCreationLocations( jet->getID() );
if( !pCreationLocations )
{
DEBUG_CRASH( ("No creation locations specified for runway for JetAIBehavior -- taxiing from hanger (Kris).") );
return STATE_FAILURE;
}
std::vector::const_iterator it;
Bool firstNode = TRUE;
for( it = pCreationLocations->begin(); it != pCreationLocations->end(); it++ )
{
if( firstNode )
{
//Skip the first node because it's the creation location.
firstNode = FALSE;
continue;
}
movePath->appendNode( it, LAYER_GROUND );
}
movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND );
}
}
else
{
//Airfield
movePath->appendNode( &ppinfo.parkingSpace, LAYER_GROUND );
}
}
m_waitingForPath = FALSE;
TheAI->pathfinder()->setDebugPath(movePath);
setAdjustsDestination(false); // precision is necessary
jetAI->friend_setPath( movePath );
DEBUG_ASSERTCRASH(jetAI->getCurLocomotor(), ("no loco"));
jetAI->getCurLocomotor()->setUsePreciseZPos(true);
jetAI->getCurLocomotor()->setUltraAccurate(true);
jetAI->getCurLocomotor()->setAllowInvalidPosition(true);
jetAI->ignoreObstacleID(jet->getProducerID());
StateReturnType ret = AIMoveOutOfTheWayState::onEnter();
return ret;
}
virtual StateReturnType update()
{
Object* jet = getMachineOwner();
if (jet->isEffectivelyDead())
return STATE_FAILURE;
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return STATE_FAILURE;
if( m_taxiMode == TO_PARKING || m_taxiMode == FROM_HANGAR )
{
//Keep checking to see if there is a better spot as it moves forward. If we find a better spot, then
//append the position to our move.
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
Coord3D bestPos;
Int oldIndex, newIndex;
// Check pp for null, as it is possible for your airfield to get destroyed while taxiing.jba [8/27/2003]
if( pp!=NULL && pp->calcBestParkingAssignment( jet->getID(), &bestPos, &oldIndex, &newIndex ) )
{
Path *path = jetAI->friend_getPath();
if( path )
{
path->appendNode( &bestPos, LAYER_GROUND );
}
}
}
return AIMoveOutOfTheWayState::update();
}
virtual void onExit( StateExitType status )
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if (jetAI)
{
jetAI->getCurLocomotor()->setUsePreciseZPos(false);
jetAI->getCurLocomotor()->setUltraAccurate(false);
jetAI->getCurLocomotor()->setAllowInvalidPosition(false);
jetAI->friend_setTakeoffInProgress(false);
jetAI->friend_setLandingInProgress(false);
jetAI->friend_setTaxiInProgress(false);
jetAI->setCanPathThroughUnits(false);
}
AIMoveOutOfTheWayState::onExit(status);
}
};
EMPTY_DTOR(JetOrHeliTaxiState)
//-------------------------------------------------------------------------------------------------
/*
Success: we are on the ground at the runway start
Failure: we are unable to get on the ground
*/
class JetTakeoffOrLandingState : public AIFollowPathState
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetTakeoffOrLandingState, "JetTakeoffOrLandingState")
private:
Real m_maxLift;
Real m_maxSpeed;
#ifdef CIRCLE_FOR_LANDING
Coord3D m_circleForLandingPos;
#endif
Bool m_landing;
Bool m_landingSoundPlayed;
public:
JetTakeoffOrLandingState( StateMachine *machine, Bool landing ) : m_landing(landing), AIFollowPathState( machine, "JetTakeoffOrLandingState" ) { }
virtual StateReturnType onEnter()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if (!jetAI)
return STATE_FAILURE;
if (jet->isEffectivelyDead())
return STATE_FAILURE;
jetAI->friend_setTakeoffInProgress(!m_landing);
jetAI->friend_setLandingInProgress(m_landing);
jetAI->friend_setAllowAirLoco(true);
jetAI->chooseLocomotorSet(LOCOMOTORSET_NORMAL);
Locomotor* loco = jetAI->getCurLocomotor();
DEBUG_ASSERTCRASH(loco, ("no loco"));
loco->setMaxLift(BIGNUM);
BodyDamageType bdt = jet->getBodyModule()->getDamageState();
m_maxLift = loco->getMaxLift(bdt);
m_maxSpeed = loco->getMaxSpeedForCondition(bdt);
m_landingSoundPlayed = FALSE;
if (m_landing)
{
loco->setMaxSpeed(loco->getMinSpeed());
}
else
{
loco->setMaxLift(0);
}
loco->setUsePreciseZPos(true);
loco->setUltraAccurate(true);
jetAI->ignoreObstacleID(jet->getProducerID());
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
if (pp == NULL)
return STATE_SUCCESS; // no airfield? just skip this step
ParkingPlaceBehaviorInterface::PPInfo ppinfo;
if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo))
{
// it's full.
return STATE_FAILURE;
}
// only check this for landing; we might have already given up the reservation to the guy behind us for takeoff
if (m_landing)
{
if (!pp->reserveRunway(jet->getID(), m_landing))
{
DEBUG_CRASH(("we should never get to this state unless we have a runway available"));
return STATE_FAILURE;
}
}
std::vector path;
if (m_landing)
{
#ifdef CIRCLE_FOR_LANDING
m_circleForLandingPos = ppinfo.runwayApproach;
m_circleForLandingPos.z = (ppinfo.runwayEnd.z + ppinfo.runwayApproach.z)*0.5f;
#else
path.push_back(ppinfo.runwayApproach);
#endif
if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) )
{
//Assigned to an aircraft carrier which has separate landing strips.
path.push_back( ppinfo.runwayLandingStart );
path.push_back( ppinfo.runwayLandingEnd );
}
else
{
//Assigned to an airstrip -- land the same way we took off but in reverse.
path.push_back(ppinfo.runwayEnd);
path.push_back(ppinfo.runwayStart);
}
}
else
{
ppinfo.runwayEnd.z = ppinfo.runwayApproach.z;
path.push_back(ppinfo.runwayEnd);
path.push_back(ppinfo.runwayExit);
}
setAdjustsDestination(false); // precision is necessary
setAdjustFinalDestination(false); // especially at the endpoint!
jetAI->friend_setGoalPath( &path );
StateReturnType ret = AIFollowPathState::onEnter();
return ret;
}
virtual StateReturnType update()
{
Object* jet = getMachineOwner();
if (jet->isEffectivelyDead())
return STATE_FAILURE;
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return STATE_FAILURE;
if (m_landing)
{
#ifdef CIRCLE_FOR_LANDING
if (jet->getPosition()->z > m_circleForLandingPos.z)
{
const Real THRESH = 4.0f;
jetAI->getCurLocomotor()->setAltitudeChangeThresholdForCircling(THRESH);
jetAI->setLocomotorGoalPositionExplicit(m_circleForLandingPos);
return STATE_CONTINUE;
}
else
#endif
{
jetAI->getCurLocomotor()->setMaxLift(BIGNUM);
#ifdef CIRCLE_FOR_LANDING
jetAI->getCurLocomotor()->setAltitudeChangeThresholdForCircling(0);
#endif
}
if( !m_landingSoundPlayed )
{
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
Real zPos = jet->getPosition()->z;
Real zSlop = 0.25f;
PathfindLayerEnum layer = TheTerrainLogic->getHighestLayerForDestination( jet->getPosition() );
Real groundZ = TheTerrainLogic->getLayerHeight( jet->getPosition()->x, jet->getPosition()->y, layer );
if( pp )
{
groundZ += pp->getLandingDeckHeightOffset();
}
if( zPos - zSlop <= groundZ )
{
m_landingSoundPlayed = TRUE;
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_aircraftWheelScreech;
soundToPlay.setPosition( jet->getPosition() );
TheAudio->addAudioEvent( &soundToPlay );
}
}
}
else
{
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
if (pp)
pp->transferRunwayReservationToNextInLineForTakeoff(jet->getID());
//Calculate the distance of the jet from the end of the runway as a ratio from the start.
//As it approaches the end of the runway, the plane will gain more lift, even if it's already
//going quickly. Using speed for lift is bad in the case of the aircraft carrier, because
//we don't want it to take off quickly.
ParkingPlaceBehaviorInterface::PPInfo ppinfo;
pp->calcPPInfo( jet->getID(), &ppinfo );
Coord3D vector = ppinfo.runwayEnd;
vector.sub( jet->getPosition() );
Real dist = vector.length();
Real ratio = 1.0f - (dist / ppinfo.runwayTakeoffDist);
ratio *= ratio; //dampen it....
if (ratio < 0.0f) ratio = 0.0f;
if (ratio > 1.0f) ratio = 1.0f;
jetAI->getCurLocomotor()->setMaxLift(m_maxLift * ratio);
}
StateReturnType ret = AIFollowPathState::update();
return ret;
}
virtual void onExit( StateExitType status )
{
AIFollowPathState::onExit(status);
// just in case.
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return;
jetAI->friend_setTakeoffInProgress(false);
jetAI->friend_setLandingInProgress(false);
jetAI->friend_enableAfterburners(false);
// Paranoia checks - sometimes onExit is called when we are
// shutting down, and not all pieces are valid. CurLocomotor
// is definitely null in some cases. jba.
Locomotor* loco = jetAI->getCurLocomotor();
if (loco)
{
loco->setUsePreciseZPos(false);
loco->setUltraAccurate(false);
// don't restore lift if dead -- this may fight with JetSlowDeathBehavior!
if (!jet->isEffectivelyDead())
loco->setMaxLift(BIGNUM);
#ifdef CIRCLE_FOR_LANDING
loco->setAltitudeChangeThresholdForCircling(0);
#endif
}
jetAI->ignoreObstacleID(INVALID_ID);
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
if (!m_landing)
{
if (pp && !jetAI->friend_keepsParkingSpaceWhenAirborne())
pp->releaseSpace(jet->getID());
}
if (pp)
pp->releaseRunway(jet->getID());
}
};
EMPTY_DTOR(JetTakeoffOrLandingState)
//-------------------------------------------------------------------------------------------------
static Real calcDistSqr(const Coord3D& a, const Coord3D& b)
{
return sqr(a.x-b.x) + sqr(a.y-b.y) + sqr(a.z-b.z);
}
//-------------------------------------------------------------------------------------------------
/*
Success: we are on the ground at the runway start
Failure: we are unable to get on the ground
*/
class HeliTakeoffOrLandingState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(HeliTakeoffOrLandingState, "HeliTakeoffOrLandingState")
protected:
// snapshot interface
virtual void crc( Xfer *xfer )
{
// empty. jba.
}
virtual void xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// set on create. xfer->xferBool(&m_landing);
xfer->xferCoord3D(&m_path[0]);
xfer->xferCoord3D(&m_path[1]);
xfer->xferInt(&m_index);
xfer->xferCoord3D(&m_parkingLoc);
xfer->xferReal(&m_parkingOrientation);
}
virtual void loadPostProcess()
{
// empty. jba.
}
private:
Coord3D m_path[2];
Int m_index;
Coord3D m_parkingLoc;
Real m_parkingOrientation;
Bool m_landing;
public:
HeliTakeoffOrLandingState( StateMachine *machine, Bool landing ) : m_landing(landing),
State( machine, "HeliTakeoffOrLandingState" ), m_index(0)
{
m_parkingLoc.zero();
}
virtual StateReturnType onEnter()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return STATE_FAILURE;
jetAI->friend_setTakeoffInProgress(!m_landing);
jetAI->friend_setLandingInProgress(m_landing);
jetAI->friend_setAllowAirLoco(true);
jetAI->chooseLocomotorSet(LOCOMOTORSET_NORMAL);
Locomotor* loco = jetAI->getCurLocomotor();
DEBUG_ASSERTCRASH(loco, ("no loco"));
loco->setUsePreciseZPos(true);
loco->setUltraAccurate(true);
jetAI->ignoreObstacleID(jet->getProducerID());
Object* airfield;
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID(), &airfield);
if (pp == NULL)
return STATE_SUCCESS; // no airfield? just skip this step
Coord3D landingApproach;
if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD))
{
if (m_landing)
{
m_parkingLoc = jetAI->friend_getLandingPosForHelipadStuff();
m_parkingOrientation = jet->getOrientation();
}
else
{
m_parkingOrientation = jet->getOrientation();
m_parkingLoc = *jet->getPosition();
}
landingApproach = m_parkingLoc;
landingApproach.z += pp->getApproachHeight() + pp->getLandingDeckHeightOffset();
}
else
{
ParkingPlaceBehaviorInterface::PPInfo ppinfo;
if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo))
return STATE_FAILURE;
m_parkingLoc = ppinfo.parkingSpace;
m_parkingOrientation = ppinfo.parkingOrientation;
landingApproach = m_parkingLoc;
landingApproach.z += (ppinfo.runwayApproach.z - ppinfo.runwayEnd.z);
}
if (m_landing)
{
m_path[0] = landingApproach;
m_path[1] = m_parkingLoc;
}
else
{
m_path[0] = m_parkingLoc;
m_path[1] = landingApproach;
m_path[1].z = landingApproach.z;
}
m_index = 0;
return STATE_CONTINUE;
}
virtual StateReturnType update()
{
Object* jet = getMachineOwner();
if (jet->isEffectivelyDead())
return STATE_FAILURE;
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return STATE_FAILURE;
// I have disabled this because it is no longer necessary and is a bit funky lookin' (srj)
#ifdef NOT_IN_USE
// magically position it correctly.
jet->getPhysics()->scrubVelocity2D(0);
Coord3D hoverloc = m_path[m_index];
hoverloc.z = jet->getPosition()->z;
#if 1
Coord3D pos = *jet->getPosition();
Real dx = hoverloc.x - pos.x;
Real dy = hoverloc.y - pos.y;
Real dSqr = dx*dx+dy*dy;
const Real DARN_CLOSE = 0.25f;
if (dSqr < DARN_CLOSE)
{
jet->setPosition(&hoverloc);
}
else
{
Real dist = sqrtf(dSqr);
if (dist<1) dist = 1;
pos.x += PATHFIND_CELL_SIZE_F*dx/(dist*LOGICFRAMES_PER_SECOND);
pos.y += PATHFIND_CELL_SIZE_F*dy/(dist*LOGICFRAMES_PER_SECOND);
jet->setPosition(&pos);
}
#else
jet->setPosition(&hoverloc);
#endif
jet->setOrientation(m_parkingOrientation);
#endif
if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) || !m_landing)
{
TheAI->pathfinder()->adjustDestination(jet, jetAI->getLocomotorSet(), &m_path[m_index]);
TheAI->pathfinder()->updateGoal(jet, &m_path[m_index], LAYER_GROUND);
}
jetAI->setLocomotorGoalPositionExplicit(m_path[m_index]);
const Real THRESH = 3.0f;
const Real THRESH_SQR = THRESH*THRESH;
const Coord3D* a = jet->getPosition();
const Coord3D* b = &m_path[m_index];
Real distSqr = calcDistSqr(*a, *b);
if (distSqr <= THRESH_SQR)
++m_index;
if (m_index >= 2)
return STATE_SUCCESS;
return STATE_CONTINUE;
}
virtual void onExit( StateExitType status )
{
// just in case.
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return;
jetAI->friend_setTakeoffInProgress(false);
jetAI->friend_setLandingInProgress(false);
// Paranoia checks - sometimes onExit is called when we are
// shutting down, and not all pieces are valid. CurLocomotor
// is definitely null in some cases. jba.
Locomotor* loco = jetAI->getCurLocomotor();
if (loco)
{
loco->setUsePreciseZPos(false);
loco->setUltraAccurate(false);
// don't restore lift if dead -- this may fight with JetSlowDeathBehavior!
if (!jet->isEffectivelyDead())
loco->setMaxLift(BIGNUM);
}
jetAI->ignoreObstacleID(INVALID_ID);
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
if (m_landing)
{
jetAI->friend_setAllowAirLoco(false);
jetAI->chooseLocomotorSet(LOCOMOTORSET_TAXIING);
}
else
{
if (pp && !jetAI->friend_keepsParkingSpaceWhenAirborne())
pp->releaseSpace(jet->getID());
}
}
};
EMPTY_DTOR(HeliTakeoffOrLandingState)
//-------------------------------------------------------------------------------------------------
class JetOrHeliParkOrientState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliParkOrientState, "JetOrHeliParkOrientState")
protected:
// snapshot interface STUBBED.
virtual void crc( Xfer *xfer ){};
virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
virtual void loadPostProcess(){};
public:
JetOrHeliParkOrientState( StateMachine *machine ) : State( machine, "JetOrHeliParkOrientState") { }
virtual StateReturnType onEnter()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return STATE_FAILURE;
if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD))
{
return STATE_SUCCESS;
}
jetAI->friend_setTakeoffInProgress(false);
jetAI->friend_setLandingInProgress(true);
jetAI->ignoreObstacleID(jet->getProducerID());
return STATE_CONTINUE;
}
virtual StateReturnType update()
{
Object* jet = getMachineOwner();
if (jet->isEffectivelyDead())
return STATE_FAILURE;
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
{
return STATE_FAILURE;
}
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
if (pp == NULL)
return STATE_FAILURE;
ParkingPlaceBehaviorInterface::PPInfo ppinfo;
if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo))
return STATE_FAILURE;
const Real THRESH = 0.001f;
if (fabs(stdAngleDiff(jet->getOrientation(), ppinfo.parkingOrientation)) <= THRESH)
return STATE_SUCCESS;
// magically position it correctly.
jet->getPhysics()->scrubVelocity2D(0);
Coord3D hoverloc = ppinfo.parkingSpace;
if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) )
{
hoverloc = ppinfo.runwayPrep;
}
hoverloc.z = jet->getPosition()->z;
jet->setPosition(&hoverloc);
jetAI->setLocomotorGoalOrientation(ppinfo.parkingOrientation);
return STATE_CONTINUE;
}
virtual void onExit( StateExitType status )
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return;
jetAI->friend_setTakeoffInProgress(false);
jetAI->friend_setLandingInProgress(false);
jetAI->ignoreObstacleID(INVALID_ID);
}
};
EMPTY_DTOR(JetOrHeliParkOrientState)
//-------------------------------------------------------------------------------------------------
class JetPauseBeforeTakeoffState : public AIFaceState
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetPauseBeforeTakeoffState, "JetPauseBeforeTakeoffState")
protected:
// snapshot interface
virtual void crc( Xfer *xfer )
{
// empty. jba.
}
virtual void xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// set on create. xfer->xferBool(&m_landing);
xfer->xferUnsignedInt(&m_when);
xfer->xferUnsignedInt(&m_whenTransfer);
xfer->xferBool(&m_afterburners);
xfer->xferBool(&m_resetTimer);
xfer->xferObjectID(&m_waitedForTaxiID);
}
virtual void loadPostProcess()
{
// empty. jba.
}
private:
UnsignedInt m_when;
UnsignedInt m_whenTransfer;
ObjectID m_waitedForTaxiID;
Bool m_resetTimer;
Bool m_afterburners;
Bool findWaiter()
{
Object* jet = getMachineOwner();
ParkingPlaceBehaviorInterface* pp = getPP(getMachineOwner()->getProducerID());
if (pp)
{
Int count = pp->getRunwayCount();
for (Int i = 0; i < count; ++i)
{
Object* otherJet = TheGameLogic->findObjectByID( pp->getRunwayReservation( i, RESERVATION_TAKEOFF ) );
if (otherJet == NULL || otherJet == jet)
continue;
AIUpdateInterface* ai = otherJet->getAIUpdateInterface();
if (ai == NULL)
continue;
if (ai->getCurrentStateID() == TAXI_TO_TAKEOFF)
{
if (m_waitedForTaxiID == INVALID_ID)
{
m_waitedForTaxiID = otherJet->getID();
}
return true;
}
}
}
return false;
}
public:
JetPauseBeforeTakeoffState( StateMachine *machine ) :
AIFaceState(machine, false),
m_when(0),
m_whenTransfer(0),
m_waitedForTaxiID(INVALID_ID),
m_resetTimer(false),
m_afterburners(false)
{
// nothing
}
virtual StateReturnType onEnter()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return STATE_FAILURE;
jetAI->friend_setTakeoffInProgress(true);
jetAI->friend_setLandingInProgress(false);
m_when = 0;
m_whenTransfer = 0;
m_waitedForTaxiID = INVALID_ID;
m_resetTimer = false;
m_afterburners = false;
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
if (pp == NULL)
return STATE_SUCCESS; // no airfield? just skip this step.
ParkingPlaceBehaviorInterface::PPInfo ppinfo;
if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo))
return STATE_SUCCESS; // full?
getMachine()->setGoalPosition(&ppinfo.runwayEnd);
return AIFaceState::onEnter();
}
virtual StateReturnType update()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if (jet->isEffectivelyDead())
return STATE_FAILURE;
// always call this.
StateReturnType superStatus = AIFaceState::update();
if (findWaiter())
return STATE_CONTINUE;
UnsignedInt now = TheGameLogic->getFrame();
if (!m_resetTimer)
{
// we had to wait, but now everyone else is ready, so restart our countdown.
m_when = now + jetAI->friend_getTakeoffPause();
if (m_waitedForTaxiID == INVALID_ID)
{
m_waitedForTaxiID = jet->getID(); // just so we don't pick up anyone else
m_whenTransfer = now + 1;
}
else
{
m_whenTransfer = now + 2; // 2 seems odd, but is correct
}
m_resetTimer = true;
}
if (!m_afterburners)
{
jetAI->friend_enableAfterburners(true);
m_afterburners = true;
}
DEBUG_ASSERTCRASH(m_when != 0, ("hmm"));
DEBUG_ASSERTCRASH(m_whenTransfer != 0, ("hmm"));
// once we start the final wait, release the runways for guys behind us, so they can start taxiing
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
if (pp && now >= m_whenTransfer)
{
pp->transferRunwayReservationToNextInLineForTakeoff(jet->getID());
}
if (now >= m_when)
return superStatus;
return STATE_CONTINUE;
}
virtual void onExit(StateExitType status)
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
jetAI->friend_setTakeoffInProgress(false);
jetAI->friend_setLandingInProgress(false);
AIFaceState::onExit(status);
}
};
EMPTY_DTOR(JetPauseBeforeTakeoffState)
//-------------------------------------------------------------------------------------------------
class JetOrHeliReloadAmmoState : public State
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliReloadAmmoState, "JetOrHeliReloadAmmoState")
private:
UnsignedInt m_reloadTime;
UnsignedInt m_reloadDoneFrame;
protected:
// snapshot interface
virtual void crc( Xfer *xfer )
{
// empty. jba.
}
virtual void xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// set on create. xfer->xferBool(&m_landing);
xfer->xferUnsignedInt(&m_reloadTime);
xfer->xferUnsignedInt(&m_reloadDoneFrame);
}
virtual void loadPostProcess()
{
// empty. jba.
}
public:
JetOrHeliReloadAmmoState( StateMachine *machine ) : State( machine, "JetOrHeliReloadAmmoState") { }
virtual StateReturnType onEnter()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
if( !jetAI )
return STATE_FAILURE;
jetAI->friend_setTakeoffInProgress(false);
jetAI->friend_setLandingInProgress(false);
jetAI->friend_setUseSpecialReturnLoco(false);
m_reloadTime = 0;
for (Int i = 0; i < WEAPONSLOT_COUNT; ++i)
{
const Weapon* w = jet->getWeaponInWeaponSlot((WeaponSlotType)i);
if (w == NULL)
continue;
Int remaining = w->getRemainingAmmo();
Int clipSize = w->getClipSize();
Int rt = w->getClipReloadTime(jet);
if (clipSize > 0)
{
// bias by amount empty.
Int needed = clipSize - remaining;
rt = (rt * needed) / clipSize;
}
if (rt > m_reloadTime)
m_reloadTime = rt;
}
if (m_reloadTime < 1)
m_reloadTime = 1;
m_reloadDoneFrame = m_reloadTime + TheGameLogic->getFrame();
return STATE_CONTINUE;
}
virtual StateReturnType update()
{
Object* jet = getMachineOwner();
UnsignedInt now = TheGameLogic->getFrame();
Bool allDone = true;
for (Int i = 0; i < WEAPONSLOT_COUNT; ++i)
{
Weapon* w = jet->getWeaponInWeaponSlot((WeaponSlotType)i);
if (w == NULL)
continue;
if (now >= m_reloadDoneFrame)
w->setClipPercentFull(1.0f, false);
else
w->setClipPercentFull((Real)(m_reloadTime - (m_reloadDoneFrame - now)) / m_reloadTime, false);
if (w->getRemainingAmmo() != w->getClipSize())
allDone = false;
}
if (allDone)
return STATE_SUCCESS;
return STATE_CONTINUE;
}
virtual void onExit(StateExitType status)
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
jetAI->friend_setTakeoffInProgress(false);
jetAI->friend_setLandingInProgress(false);
}
};
EMPTY_DTOR(JetOrHeliReloadAmmoState)
//-------------------------------------------------------------------------------------------------
/*
Success: we are close enough to a friendly airfield to land
Failure: we are unable to get close enough to a friendly airfield to land
*/
class JetOrHeliReturnForLandingState : public AIInternalMoveToState
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliReturnForLandingState, "JetOrHeliReturnForLandingState")
public:
JetOrHeliReturnForLandingState( StateMachine *machine ) : AIInternalMoveToState( machine, "JetOrHeliReturnForLandingState") { }
virtual StateReturnType onEnter()
{
Object* jet = getMachineOwner();
JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface();
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
if (pp == NULL)
{
// nuke the producer id, since it's dead
jet->setProducer(NULL);
Object* airfield = findSuitableAirfield( jet );
pp = airfield ? getPP(airfield->getID()) : NULL;
if (airfield && pp)
{
jet->setProducer(airfield);
}
else
{
return STATE_FAILURE;
}
}
if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD))
{
m_goalPosition = jetAI->friend_getLandingPosForHelipadStuff();
}
else
{
ParkingPlaceBehaviorInterface::PPInfo ppinfo;
if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo))
return STATE_FAILURE;
m_goalPosition = jetAI->friend_needsRunway() ? ppinfo.runwayApproach : ppinfo.parkingSpace;
}
setAdjustsDestination(false); // precision is necessary
return AIInternalMoveToState::onEnter();
}
};
EMPTY_DTOR(JetOrHeliReturnForLandingState)
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
class JetAIStateMachine : public AIStateMachine
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( JetAIStateMachine, "JetAIStateMachine" );
public:
JetAIStateMachine( Object *owner, AsciiString name );
};
//-------------------------------------------------------------------------------------------------
JetAIStateMachine::JetAIStateMachine(Object *owner, AsciiString name) : AIStateMachine(owner, name)
{
defineState( RETURNING_FOR_LANDING, newInstance(JetOrHeliReturnForLandingState)( this ), LANDING_AWAIT_CLEARANCE, RETURN_TO_DEAD_AIRFIELD );
defineState( TAKING_OFF_AWAIT_CLEARANCE, newInstance(JetAwaitingRunwayState)( this, false ), TAXI_TO_TAKEOFF, AI_IDLE );
defineState( TAXI_TO_TAKEOFF, newInstance(JetOrHeliTaxiState)( this, FROM_PARKING ), PAUSE_BEFORE_TAKEOFF, AI_IDLE );
defineState( PAUSE_BEFORE_TAKEOFF, newInstance(JetPauseBeforeTakeoffState)( this ), TAKING_OFF, AI_IDLE );
defineState( TAKING_OFF, newInstance(JetTakeoffOrLandingState)( this, false ), AI_IDLE, AI_IDLE );
defineState( LANDING_AWAIT_CLEARANCE, newInstance(JetAwaitingRunwayState)( this, true ), LANDING, AI_IDLE );
defineState( LANDING, newInstance(JetTakeoffOrLandingState)( this, true ), TAXI_FROM_LANDING, AI_IDLE );
defineState( TAXI_FROM_LANDING, newInstance(JetOrHeliTaxiState)( this, TO_PARKING ), ORIENT_FOR_PARKING_PLACE, AI_IDLE );
defineState( TAXI_FROM_HANGAR, newInstance(JetOrHeliTaxiState)( this, FROM_HANGAR ), ORIENT_FOR_PARKING_PLACE, AI_IDLE );
defineState( ORIENT_FOR_PARKING_PLACE, newInstance(JetOrHeliParkOrientState)( this ), RELOAD_AMMO, AI_IDLE );
defineState( RELOAD_AMMO, newInstance(JetOrHeliReloadAmmoState)( this ), AI_IDLE, AI_IDLE );
defineState( RETURN_TO_DEAD_AIRFIELD, newInstance(JetOrHeliReturningToDeadAirfieldState)( this ), CIRCLING_DEAD_AIRFIELD, RETURN_TO_DEAD_AIRFIELD );
defineState( CIRCLING_DEAD_AIRFIELD, newInstance(JetOrHeliCirclingDeadAirfieldState)( this ), AI_IDLE, AI_IDLE );
}
//-------------------------------------------------------------------------------------------------
JetAIStateMachine::~JetAIStateMachine()
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
class HeliAIStateMachine : public AIStateMachine
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( HeliAIStateMachine, "HeliAIStateMachine" );
public:
HeliAIStateMachine( Object *owner, AsciiString name );
};
//-------------------------------------------------------------------------------------------------
HeliAIStateMachine::HeliAIStateMachine(Object *owner, AsciiString name) : AIStateMachine(owner, name)
{
defineState( RETURNING_FOR_LANDING, newInstance(JetOrHeliReturnForLandingState)( this ), LANDING_AWAIT_CLEARANCE, RETURN_TO_DEAD_AIRFIELD );
defineState( TAKING_OFF_AWAIT_CLEARANCE, newInstance(SuccessState)( this ), TAKING_OFF, AI_IDLE );
defineState( TAKING_OFF, newInstance(HeliTakeoffOrLandingState)( this, false ), AI_IDLE, AI_IDLE );
defineState( LANDING_AWAIT_CLEARANCE, newInstance(SuccessState)( this ), ORIENT_FOR_PARKING_PLACE, AI_IDLE );
defineState( ORIENT_FOR_PARKING_PLACE, newInstance(JetOrHeliParkOrientState)( this ), LANDING, AI_IDLE );
defineState( LANDING, newInstance(HeliTakeoffOrLandingState)( this, true ), RELOAD_AMMO, AI_IDLE );
defineState( RELOAD_AMMO, newInstance(JetOrHeliReloadAmmoState)( this ), AI_IDLE, AI_IDLE );
defineState( RETURN_TO_DEAD_AIRFIELD, newInstance(JetOrHeliReturningToDeadAirfieldState)( this ), CIRCLING_DEAD_AIRFIELD, RETURN_TO_DEAD_AIRFIELD );
defineState( CIRCLING_DEAD_AIRFIELD, newInstance(JetOrHeliCirclingDeadAirfieldState)( this ), AI_IDLE, AI_IDLE );
defineState( TAXI_FROM_HANGAR, newInstance(JetOrHeliTaxiState)( this, FROM_HANGAR ), AI_IDLE, AI_IDLE );
}
//-------------------------------------------------------------------------------------------------
HeliAIStateMachine::~HeliAIStateMachine()
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
JetAIUpdateModuleData::JetAIUpdateModuleData()
{
m_outOfAmmoDamagePerSecond = 0;
m_needsRunway = true;
m_keepsParkingSpaceWhenAirborne = true;
m_takeoffDistForMaxLift = 0.0f;
m_minHeight = 0.0f;
m_parkingOffset = 0.0f;
m_sneakyOffsetWhenAttacking = 0.0f;
m_takeoffPause = 0;
m_attackingLoco = LOCOMOTORSET_NORMAL;
m_returningLoco = LOCOMOTORSET_NORMAL;
m_attackLocoPersistTime = 0;
m_attackersMissPersistTime = 0;
m_lockonTime = 0;
m_lockonCursor.clear();
m_lockonInitialDist = 100;
m_lockonFreq = 0.5;
m_lockonAngleSpin = 720;
m_returnToBaseIdleTime = 0;
}
//-------------------------------------------------------------------------------------------------
/*static*/ void JetAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
{
AIUpdateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "OutOfAmmoDamagePerSecond", INI::parsePercentToReal, NULL, offsetof( JetAIUpdateModuleData, m_outOfAmmoDamagePerSecond ) },
{ "NeedsRunway", INI::parseBool, NULL, offsetof( JetAIUpdateModuleData, m_needsRunway ) },
{ "KeepsParkingSpaceWhenAirborne",INI::parseBool, NULL, offsetof( JetAIUpdateModuleData, m_keepsParkingSpaceWhenAirborne ) },
{ "TakeoffDistForMaxLift", INI::parsePercentToReal, NULL, offsetof( JetAIUpdateModuleData, m_takeoffDistForMaxLift ) },
{ "TakeoffPause", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_takeoffPause ) },
{ "MinHeight", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_minHeight ) },
{ "ParkingOffset", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_parkingOffset ) },
{ "SneakyOffsetWhenAttacking", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_sneakyOffsetWhenAttacking ) },
{ "AttackLocomotorType", INI::parseIndexList, TheLocomotorSetNames, offsetof( JetAIUpdateModuleData, m_attackingLoco ) },
{ "AttackLocomotorPersistTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_attackLocoPersistTime ) },
{ "AttackersMissPersistTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_attackersMissPersistTime ) },
{ "ReturnForAmmoLocomotorType", INI::parseIndexList, TheLocomotorSetNames, offsetof( JetAIUpdateModuleData, m_returningLoco ) },
{ "LockonTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_lockonTime ) },
{ "LockonCursor", INI::parseAsciiString, NULL, offsetof( JetAIUpdateModuleData, m_lockonCursor ) },
{ "LockonInitialDist", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_lockonInitialDist ) },
{ "LockonFreq", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_lockonFreq ) },
{ "LockonAngleSpin", INI::parseAngleReal, NULL, offsetof( JetAIUpdateModuleData, m_lockonAngleSpin ) },
{ "LockonBlinky", INI::parseBool, NULL, offsetof( JetAIUpdateModuleData, m_lockonBlinky ) },
{ "ReturnToBaseIdleTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_returnToBaseIdleTime ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AIStateMachine* JetAIUpdate::makeStateMachine()
{
if (getJetAIUpdateModuleData()->m_needsRunway)
return newInstance(JetAIStateMachine)( getObject(), "JetAIStateMachine");
else
return newInstance(HeliAIStateMachine)( getObject(), "HeliAIStateMachine");
}
//-------------------------------------------------------------------------------------------------
JetAIUpdate::JetAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData )
{
m_flags = 0;
m_afterburnerSound = *(getObject()->getTemplate()->getPerUnitSound("Afterburner"));
m_afterburnerSound.setObjectID(getObject()->getID());
m_attackLocoExpireFrame = 0;
m_attackersMissExpireFrame = 0;
m_untargetableExpireFrame = 0;
m_returnToBaseFrame = 0;
m_lockonDrawable = NULL;
m_landingPosForHelipadStuff.zero();
//Added By Sadullah Nader
//Initializations missing and needed
m_producerLocation.zero();
//
m_enginesOn = TRUE;
}
//-------------------------------------------------------------------------------------------------
JetAIUpdate::~JetAIUpdate()
{
if (m_lockonDrawable)
{
TheGameClient->destroyDrawable(m_lockonDrawable);
m_lockonDrawable = NULL;
}
}
//-------------------------------------------------------------------------------------------------
Bool JetAIUpdate::isIdle() const
{
// we need to do this because we enter an idle state briefly between takeoff/landing in these cases,
// but scripting relies on us never claiming to be "idle"...
if (getFlag(HAS_PENDING_COMMAND))
return false;
return AIUpdateInterface::isIdle();
}
//-------------------------------------------------------------------------------------------------
Bool JetAIUpdate::isReloading() const
{
StateID stateID = getStateMachine()->getCurrentStateID();
if( stateID == RELOAD_AMMO )
{
return TRUE;
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
Bool JetAIUpdate::isTaxiingToParking() const
{
StateID stateID = getStateMachine()->getCurrentStateID();
switch( stateID )
{
case TAXI_FROM_HANGAR:
case TAXI_FROM_LANDING:
case ORIENT_FOR_PARKING_PLACE:
case RELOAD_AMMO:
case TAKING_OFF_AWAIT_CLEARANCE:
case TAXI_TO_TAKEOFF:
case PAUSE_BEFORE_TAKEOFF:
case TAKING_OFF:
return TRUE;
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
void JetAIUpdate::onObjectCreated()
{
AIUpdateInterface::onObjectCreated();
friend_setAllowAirLoco(false);
chooseLocomotorSet(LOCOMOTORSET_TAXIING);
}
//-------------------------------------------------------------------------------------------------
void JetAIUpdate::onDelete()
{
AIUpdateInterface::onDelete();
ParkingPlaceBehaviorInterface* pp = getPP(getObject()->getProducerID());
if (pp)
pp->releaseSpace(getObject()->getID());
}
//-------------------------------------------------------------------------------------------------
void JetAIUpdate::getProducerLocation()
{
if (getFlag(HAS_PRODUCER_LOCATION))
return;
Object* jet = getObject();
Object* airfield = TheGameLogic->findObjectByID( jet->getProducerID() );
if (airfield == NULL)
m_producerLocation = *jet->getPosition();
else
m_producerLocation = *airfield->getPosition();
/*
if we aren't allowed to fly, then we should be parked (or at least taxiing),
which implies we have a parking place reserved. If we don't, it's probably
because we were directly spawned via script (or directly placed on the map).
So, check to see if we have no parking place, and if not, quietly enable flight.
*/
ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID());
if (!pp || !pp->hasReservedSpace(jet->getID()))
{
friend_setAllowAirLoco(true);
chooseLocomotorSet(LOCOMOTORSET_NORMAL);
}
else
{
friend_setAllowAirLoco(false);
chooseLocomotorSet(LOCOMOTORSET_TAXIING);
}
setFlag(HAS_PRODUCER_LOCATION, true);
}
//-------------------------------------------------------------------------------------------------
UpdateSleepTime JetAIUpdate::update()
{
const JetAIUpdateModuleData* d = getJetAIUpdateModuleData();
getProducerLocation();
Object* jet = getObject();
ParkingPlaceBehaviorInterface* pp = getPP(getObject()->getProducerID());
// If idle & out of ammo, return
// have to call our parent's isIdle, because we override it to never return true
// when we have a pending command...
UnsignedInt now = TheGameLogic->getFrame();
// srj sez: not 100% sure on this. calling RELOAD_AMMO "idle" allows us to get healed while reloading,
// but might have other side effects we didn't want. if this does prove to cause a bug, be sure
// that jets (and ESPECIALLY comanches) are still getting healed at airfields.
if (AIUpdateInterface::isIdle() || getStateMachine()->getCurrentStateID() == RELOAD_AMMO)
{
if (pp != NULL)
{
if (!getFlag(ALLOW_AIR_LOCO) &&
!getFlag(HAS_PENDING_COMMAND) &&
jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) &&
jet->getBodyModule()->getHealth() == jet->getBodyModule()->getMaxHealth())
{
// we're completely healed, so take off again
pp->setHealee(jet, false);
friend_setAllowAirLoco(true);
getStateMachine()->clear();
setLastCommandSource( CMD_FROM_AI );
getStateMachine()->setState( TAKING_OFF_AWAIT_CLEARANCE );
}
else
{
pp->setHealee(jet, !getFlag(ALLOW_AIR_LOCO));
}
}
// note that we might still have weapons with ammo, but still be forced to return to reload.
if (isOutOfSpecialReloadAmmo() && getFlag(ALLOW_AIR_LOCO))
{
m_returnToBaseFrame = 0;
// this is really a "just-in-case" to ensure the targeter list doesn't spin out of control (srj)
pruneDeadTargeters();
setFlag(USE_SPECIAL_RETURN_LOCO, true);
setLastCommandSource( CMD_FROM_AI );
getStateMachine()->setState(RETURNING_FOR_LANDING);
}
else if (getFlag(HAS_PENDING_COMMAND)
// srj sez: if we are reloading ammo, wait will we are done before processing the pending command.
&& getStateMachine()->getCurrentStateID() != RELOAD_AMMO)
{
m_returnToBaseFrame = 0;
AICommandParms parms(AICMD_MOVE_TO_POSITION, CMD_FROM_AI); // values don't matter, will be wiped by next line
m_mostRecentCommand.reconstitute(parms);
setFlag(HAS_PENDING_COMMAND, false);
aiDoCommand(&parms);
}
else if (m_returnToBaseFrame != 0 && now >= m_returnToBaseFrame && getFlag(ALLOW_AIR_LOCO))
{
m_returnToBaseFrame = 0;
DEBUG_ASSERTCRASH(isOutOfSpecialReloadAmmo() == false, ("Hmm, this seems unlikely -- isOutOfSpecialReloadAmmo()==false"));
setFlag(USE_SPECIAL_RETURN_LOCO, false);
setLastCommandSource( CMD_FROM_AI );
getStateMachine()->setState(RETURNING_FOR_LANDING);
}
else if (m_returnToBaseFrame == 0 && d->m_returnToBaseIdleTime > 0 && getFlag(ALLOW_AIR_LOCO))
{
m_returnToBaseFrame = now + d->m_returnToBaseIdleTime;
}
}
else
{
if (pp != NULL)
{
pp->setHealee(getObject(), false);
}
m_returnToBaseFrame = 0;
if (getFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD) &&
isOutOfSpecialReloadAmmo() && getFlag(ALLOW_AIR_LOCO))
{
setFlag(USE_SPECIAL_RETURN_LOCO, true);
setFlag(HAS_PENDING_COMMAND, true);
setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, false);
setLastCommandSource( CMD_FROM_AI );
getStateMachine()->setState(RETURNING_FOR_LANDING);
}
}
Real minHeight = friend_getMinHeight();
if( pp )
{
minHeight += pp->getLandingDeckHeightOffset();
}
Drawable* draw = jet->getDrawable();
if (draw != NULL)
{
StateID id = getStateMachine()->getCurrentStateID();
Bool needToCheckMinHeight = (id >= JETAISTATETYPE_FIRST && id <= JETAISTATETYPE_LAST) ||
!jet->isAboveTerrain() ||
!getFlag(ALLOW_AIR_LOCO);
if( needToCheckMinHeight || jet->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) )
{
Real ht = jet->isAboveTerrain() ? jet->getHeightAboveTerrain() : 0;
if (ht < minHeight)
{
Matrix3D tmp(1);
tmp.Set_Z_Translation(minHeight - ht);
draw->setInstanceMatrix(&tmp);
}
else
{
draw->setInstanceMatrix(NULL);
}
}
else
{
draw->setInstanceMatrix(NULL);
}
}
PhysicsBehavior* physics = jet->getPhysics();
if (physics->getVelocityMagnitude() > 0 && getFlag(ALLOW_AIR_LOCO))
jet->setModelConditionState(MODELCONDITION_JETEXHAUST);
else
jet->clearModelConditionState(MODELCONDITION_JETEXHAUST);
if (jet->testStatus(OBJECT_STATUS_IS_ATTACKING))
{
m_attackLocoExpireFrame = now + d->m_attackLocoPersistTime;
m_attackersMissExpireFrame = now + d->m_attackersMissPersistTime;
}
else
{
if (m_attackLocoExpireFrame != 0 && now >= m_attackLocoExpireFrame)
{
m_attackLocoExpireFrame = 0;
}
if (m_attackersMissExpireFrame != 0 && now >= m_attackersMissExpireFrame)
{
m_attackersMissExpireFrame = 0;
}
}
if (m_untargetableExpireFrame != 0 && now >= m_untargetableExpireFrame)
{
m_untargetableExpireFrame = 0;
}
positionLockon();
if (m_attackLocoExpireFrame != 0)
{
chooseLocomotorSet(d->m_attackingLoco);
}
else if (getFlag(USE_SPECIAL_RETURN_LOCO))
{
chooseLocomotorSet(d->m_returningLoco);
}
if( !jet->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) )
{
Drawable *draw = jet->getDrawable();
if( draw )
{
if( getFlag(TAKEOFF_IN_PROGRESS)
|| getFlag(LANDING_IN_PROGRESS)
|| getObject()->isSignificantlyAboveTerrain()
|| isMoving()
|| isWaitingForPath() )
{
if( !m_enginesOn )
{
//We just started moving, therefore turn on the engines!
draw->enableAmbientSound( TRUE );
m_enginesOn = TRUE;
}
}
else if( m_enginesOn )
{
//We're no longer moving, so turn off the engines!
draw->enableAmbientSound( FALSE );
m_enginesOn = FALSE;
}
}
}
/*UpdateSleepTime ret =*/ AIUpdateInterface::update();
//return (mine < ret) ? mine : ret;
/// @todo srj -- someday, make sleepy. for now, must not sleep.
return UPDATE_SLEEP_NONE;
}
//-------------------------------------------------------------------------------------------------
Bool JetAIUpdate::chooseLocomotorSet(LocomotorSetType wst)
{
const JetAIUpdateModuleData* d = getJetAIUpdateModuleData();
if (!getFlag(ALLOW_AIR_LOCO))
{
wst = LOCOMOTORSET_TAXIING;
}
else if (m_attackLocoExpireFrame != 0)
{
wst = d->m_attackingLoco;
}
else if (getFlag(USE_SPECIAL_RETURN_LOCO))
{
wst = d->m_returningLoco;
}
return AIUpdateInterface::chooseLocomotorSet(wst);
}
//-------------------------------------------------------------------------------------------------
void JetAIUpdate::setLocomotorGoalNone()
{
if ((getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS))
&& getFlag(ALLOW_AIR_LOCO) && !getFlag(ALLOW_CIRCLING))
{
Object* jet = getObject();
Coord3D desiredPos = *jet->getPosition();
const Coord3D* dir = jet->getUnitDirectionVector2D();
desiredPos.x += dir->x * 1000.0f;
desiredPos.y += dir->y * 1000.0f;
setLocomotorGoalPositionExplicit(desiredPos);
}
else
{
AIUpdateInterface::setLocomotorGoalNone();
}
}
//----------------------------------------------------------------------------------------
Bool JetAIUpdate::getSneakyTargetingOffset(Coord3D* offset) const
{
if (m_attackersMissExpireFrame != 0 && TheGameLogic->getFrame() < m_attackersMissExpireFrame)
{
if (offset)
{
const JetAIUpdateModuleData* d = getJetAIUpdateModuleData();
const Object* jet = getObject();
const Coord3D* dir = jet->getUnitDirectionVector2D();
offset->x = dir->x * d->m_sneakyOffsetWhenAttacking;
offset->y = dir->y * d->m_sneakyOffsetWhenAttacking;
offset->z = 0.0f;
}
return true;
}
else
{
return false;
}
}
//----------------------------------------------------------------------------------------
void JetAIUpdate::pruneDeadTargeters()
{
if (!m_targetedBy.empty())
{
for (std::list::iterator it = m_targetedBy.begin(); it != m_targetedBy.end(); /* empty */ )
{
if (TheGameLogic->findObjectByID(*it) == NULL)
{
it = m_targetedBy.erase(it);
}
else
{
++it;
}
}
}
}
//----------------------------------------------------------------------------------------
void JetAIUpdate::positionLockon()
{
if (!m_lockonDrawable)
return;
if (m_untargetableExpireFrame == 0)
{
TheGameClient->destroyDrawable(m_lockonDrawable);
m_lockonDrawable = NULL;
return;
}
const JetAIUpdateModuleData* d = getJetAIUpdateModuleData();
UnsignedInt now = TheGameLogic->getFrame();
UnsignedInt remaining = m_untargetableExpireFrame - now;
UnsignedInt elapsed = d->m_lockonTime - remaining;
Coord3D pos = *getObject()->getPosition();
Real frac = (Real)remaining / (Real)d->m_lockonTime;
Real finalDist = getObject()->getGeometryInfo().getBoundingCircleRadius();
Real dist = finalDist + (d->m_lockonInitialDist - finalDist) * frac;
Real angle = d->m_lockonAngleSpin * frac;
pos.x += Cos(angle) * dist;
pos.y += Sin(angle) * dist;
// pos.z is untouched
m_lockonDrawable->setPosition(&pos);
Real dx = getObject()->getPosition()->x - pos.x;
Real dy = getObject()->getPosition()->y - pos.y;
if (dx || dy)
m_lockonDrawable->setOrientation(atan2(dy, dx));
// the Gaussian sum, to avoid keeping a running total:
//
// 1+2+3+...n = n*(n+1)/2
//
Real elapsedTimeSumPrev = 0.5f * (elapsed-1) * (elapsed);
Real elapsedTimeSumCurr = elapsedTimeSumPrev + elapsed;
Real factor = d->m_lockonFreq / d->m_lockonTime;
Bool lastPhase = ((Int)(factor * elapsedTimeSumPrev) & 1) != 0;
Bool thisPhase = ((Int)(factor * elapsedTimeSumCurr) & 1) != 0;
if (lastPhase && (!thisPhase))
{
AudioEventRTS lockonSound = TheAudio->getMiscAudio()->m_lockonTickSound;
lockonSound.setObjectID(getObject()->getID());
TheAudio->addAudioEvent(&lockonSound);
if (d->m_lockonBlinky)
m_lockonDrawable->setDrawableHidden(false);
}
else
{
if (d->m_lockonBlinky)
m_lockonDrawable->setDrawableHidden(true);
}
}
//----------------------------------------------------------------------------------------
void JetAIUpdate::buildLockonDrawableIfNecessary()
{
if (m_untargetableExpireFrame == 0)
return;
const JetAIUpdateModuleData* d = getJetAIUpdateModuleData();
if (d->m_lockonCursor.isNotEmpty() && m_lockonDrawable == NULL)
{
const ThingTemplate* tt = TheThingFactory->findTemplate(d->m_lockonCursor);
if (tt)
{
m_lockonDrawable = TheThingFactory->newDrawable(tt);
}
}
positionLockon();
}
//----------------------------------------------------------------------------------------
void JetAIUpdate::addTargeter(ObjectID id, Bool add)
{
const JetAIUpdateModuleData* d = getJetAIUpdateModuleData();
UnsignedInt lockonTime = d->m_lockonTime;
if (lockonTime != 0)
{
std::list::iterator it = std::find(m_targetedBy.begin(), m_targetedBy.end(), id);
if (add)
{
if (it == m_targetedBy.end())
{
m_targetedBy.push_back(id);
if (m_untargetableExpireFrame == 0 && m_targetedBy.size() == 1)
{
m_untargetableExpireFrame = TheGameLogic->getFrame() + lockonTime;
buildLockonDrawableIfNecessary();
}
}
}
else
{
if (it != m_targetedBy.end())
{
m_targetedBy.erase(it);
if (m_targetedBy.empty())
{
m_untargetableExpireFrame = 0;
}
}
}
}
}
//----------------------------------------------------------------------------------------
Bool JetAIUpdate::isTemporarilyPreventingAimSuccess() const
{
return m_untargetableExpireFrame != 0 && (TheGameLogic->getFrame() < m_untargetableExpireFrame);
}
//----------------------------------------------------------------------------------------
Bool JetAIUpdate::isAllowedToMoveAwayFromUnit() const
{
// parked (or landing) units don't get to do this.
if (!getFlag(ALLOW_AIR_LOCO) || getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS))
return false;
return AIUpdateInterface::isAllowedToMoveAwayFromUnit();
}
//-------------------------------------------------------------------------------------------------
Bool JetAIUpdate::isDoingGroundMovement(void) const
{
// srj per jba: Air units should never be doing ground movement, even when taxiing...
// (exception: see getTreatAsAircraftForLocoDistToGoal)
return false;
}
//-------------------------------------------------------------------------------------------------
Bool JetAIUpdate::getTreatAsAircraftForLocoDistToGoal() const
{
// exception to isDoingGroundMovement: should never treat as aircraft for dist-to-goal when taxiing.
if (getFlag(TAXI_IN_PROGRESS))
{
return false;
}
else
{
return AIUpdateInterface::getTreatAsAircraftForLocoDistToGoal();
}
}
//----------------------------------------------------------------------------------------
/**
* Follow the path defined by the given array of points
*/
void JetAIUpdate::privateFollowPath( const std::vector* path, Object *ignoreObject, CommandSourceType cmdSource, Bool exitProduction )
{
if (exitProduction)
{
getStateMachine()->clear();
if( ignoreObject )
ignoreObstacle( ignoreObject );
setLastCommandSource( cmdSource );
if (getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD))
getStateMachine()->setState( TAKING_OFF_AWAIT_CLEARANCE );
else
getStateMachine()->setState( TAXI_FROM_HANGAR );
}
else
{
AIUpdateInterface::privateFollowPath(path, ignoreObject, cmdSource, exitProduction);
}
}
//----------------------------------------------------------------------------------------
void JetAIUpdate::privateFollowPathAppend( const Coord3D *pos, CommandSourceType cmdSource )
{
// nothing yet... might need to override. not sure. (srj)
AIUpdateInterface::privateFollowPathAppend(pos, cmdSource);
}
//----------------------------------------------------------------------------------------
void JetAIUpdate::doLandingCommand(Object *airfield, CommandSourceType cmdSource)
{
if (getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD))
{
m_landingPosForHelipadStuff = *airfield->getPosition();
Coord3D tmp;
FindPositionOptions options;
options.maxRadius = airfield->getGeometryInfo().getBoundingCircleRadius() * 10.0f;
if (ThePartitionManager->findPositionAround(&m_landingPosForHelipadStuff, &options, &tmp))
m_landingPosForHelipadStuff = tmp;
}
for (BehaviorModule** i = airfield->getBehaviorModules(); *i; ++i)
{
ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface();
if (pp == NULL)
continue;
if (getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) ||
pp->reserveSpace(getObject()->getID(), friend_getParkingOffset(), NULL))
{
// if we had a space at another airfield, release it
ParkingPlaceBehaviorInterface* oldPP = getPP(getObject()->getProducerID());
if (oldPP != NULL && oldPP != pp)
{
oldPP->releaseSpace(getObject()->getID());
}
getObject()->setProducer(airfield);
DEBUG_ASSERTCRASH(isOutOfSpecialReloadAmmo() == false, ("Hmm, this seems unlikely -- isOutOfSpecialReloadAmmo()==false"));
setFlag(USE_SPECIAL_RETURN_LOCO, false);
setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, false);
setLastCommandSource( cmdSource );
getStateMachine()->setState(RETURNING_FOR_LANDING);
return;
}
}
}
//----------------------------------------------------------------------------------------
void JetAIUpdate::notifyVictimIsDead()
{
if (getJetAIUpdateModuleData()->m_needsRunway)
m_returnToBaseFrame = TheGameLogic->getFrame();
}
//----------------------------------------------------------------------------------------
/**
* Enter the given object
*/
void JetAIUpdate::privateEnter( Object *objectToEnter, CommandSourceType cmdSource )
{
// we are already landing. just ignore it.
if (getFlag(LANDING_IN_PROGRESS))
return;
if( !TheActionManager->canEnterObject( getObject(), objectToEnter, cmdSource, DONT_CHECK_CAPACITY ) )
return;
doLandingCommand(objectToEnter, cmdSource);
}
//----------------------------------------------------------------------------------------
/**
* Get repaired at the repair depot
*/
void JetAIUpdate::privateGetRepaired( Object *repairDepot, CommandSourceType cmdSource )
{
// we are already landing. just ignore it.
if (getFlag(LANDING_IN_PROGRESS))
return;
// sanity, if we can't get repaired from here get out of here
if( TheActionManager->canGetRepairedAt( getObject(), repairDepot, cmdSource ) == FALSE )
return;
// dock with the repair depot
doLandingCommand( repairDepot, cmdSource );
}
//-------------------------------------------------------------------------------------------------
Bool JetAIUpdate::isParkedAt(const Object* obj) const
{
if (!getFlag(ALLOW_AIR_LOCO) &&
!getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) &&
obj != NULL)
{
Object* airfield;
ParkingPlaceBehaviorInterface* pp = getPP(getObject()->getProducerID(), &airfield);
if (pp != NULL && airfield != NULL && airfield == obj)
{
return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------
void JetAIUpdate::aiDoCommand(const AICommandParms* parms)
{
// call this from aiDoCommand as well as update, because this can
// be called before update ever is... if the unit is placed on a map,
// and a script tells it to do something with a condition of TRUE!
getProducerLocation();
if (!isAllowedToRespondToAiCommands(parms))
return;
// note that we always store this, even if nothing will be "pending".
m_mostRecentCommand.store(*parms);
if (getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS))
{
// have to wait for takeoff or landing to complete, just store the sucker
setFlag(HAS_PENDING_COMMAND, true);
return;
}
else if (parms->m_cmd == AICMD_IDLE && getStateMachine()->getCurrentStateID() == RELOAD_AMMO)
{
// uber-special-case... if we are told to idle, but are reloading ammo, ignore it for now,
// since we're already doing "nothing" and responding to this will cease our reload...
// don't just return, tho, in case we were (say) reloading during a guard stint.
setFlag(HAS_PENDING_COMMAND, true);
return;
}
else if( parms->m_cmd == AICMD_IDLE && getObject()->isAirborneTarget() && !getObject()->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) )
{
getStateMachine()->clear();
setLastCommandSource( CMD_FROM_AI );
getStateMachine()->setState( RETURNING_FOR_LANDING );
return;
}
else if (!getFlag(ALLOW_AIR_LOCO))
{
switch (parms->m_cmd)
{
case AICMD_IDLE:
case AICMD_BUSY:
case AICMD_FOLLOW_EXITPRODUCTION_PATH:
// don't need (or want) to take off for these
break;
case AICMD_ENTER:
case AICMD_GET_REPAIRED:
// if we're already parked at the airfield in question, just ignore.
if (isParkedAt(parms->m_obj))
return;
// else fall thru to the default case!
default:
{
// nuke any existing pending cmd
m_mostRecentCommand.store(*parms);
setFlag(HAS_PENDING_COMMAND, true);
getStateMachine()->clear();
setLastCommandSource( CMD_FROM_AI );
getStateMachine()->setState( TAKING_OFF_AWAIT_CLEARANCE );
return;
}
}
}
switch (parms->m_cmd)
{
case AICMD_GUARD_POSITION:
case AICMD_GUARD_OBJECT:
case AICMD_GUARD_AREA:
case AICMD_HUNT:
case AICMD_GUARD_RETALIATE:
setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, true);
break;
default:
setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, false);
break;
}
setFlag(HAS_PENDING_COMMAND, false);
AIUpdateInterface::aiDoCommand(parms);
}
//-------------------------------------------------------------------------------------------------
void JetAIUpdate::friend_setAllowAirLoco(Bool allowAirLoco)
{
setFlag(ALLOW_AIR_LOCO, allowAirLoco);
}
//-------------------------------------------------------------------------------------------------
void JetAIUpdate::friend_enableAfterburners(Bool v)
{
Object* jet = getObject();
if (v)
{
jet->setModelConditionState(MODELCONDITION_JETAFTERBURNER);
if (!m_afterburnerSound.isCurrentlyPlaying())
{
m_afterburnerSound.setObjectID(jet->getID());
m_afterburnerSound.setPlayingHandle(TheAudio->addAudioEvent(&m_afterburnerSound));
}
}
else
{
jet->clearModelConditionState(MODELCONDITION_JETAFTERBURNER);
if (m_afterburnerSound.isCurrentlyPlaying())
{
TheAudio->removeAudioEvent(m_afterburnerSound.getPlayingHandle());
}
}
}
//-------------------------------------------------------------------------------------------------
void JetAIUpdate::friend_addWaypointToGoalPath( const Coord3D &bestPos )
{
privateFollowPathAppend( &bestPos, CMD_FROM_AI );
}
//-------------------------------------------------------------------------------------------------
AICommandType JetAIUpdate::friend_getPendingCommandType() const
{
if( getFlag( HAS_PENDING_COMMAND ) )
{
return m_mostRecentCommand.getCommandType();
}
return AICMD_NO_COMMAND;
}
//-------------------------------------------------------------------------------------------------
void JetAIUpdate::friend_purgePendingCommand()
{
setFlag(HAS_PENDING_COMMAND, false);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void JetAIUpdate::crc( Xfer *xfer )
{
// extend base class
AIUpdateInterface::crc(xfer);
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void JetAIUpdate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
AIUpdateInterface::xfer(xfer);
xfer->xferCoord3D(&m_producerLocation);
m_mostRecentCommand.doXfer(xfer);
xfer->xferUnsignedInt(&m_attackLocoExpireFrame);
xfer->xferUnsignedInt(&m_attackersMissExpireFrame);
xfer->xferUnsignedInt(&m_returnToBaseFrame);
xfer->xferSTLObjectIDList(&m_targetedBy);
xfer->xferUnsignedInt(&m_untargetableExpireFrame);
// Set on create.
//AudioEventRTS m_afterburnerSound; ///< Sound when afterburners on
AsciiString drawName;
if (m_lockonDrawable) {
drawName = m_lockonDrawable->getTemplate()->getName();
}
xfer->xferAsciiString(&drawName);
if (drawName.isNotEmpty() && m_lockonDrawable==NULL)
{
const ThingTemplate* tt = TheThingFactory->findTemplate(drawName);
if (tt)
{
m_lockonDrawable = TheThingFactory->newDrawable(tt);
}
}
xfer->xferInt(&m_flags);
if( version >= 2 )
{
xfer->xferBool( &m_enginesOn );
}
else
{
//We don't have to be accurate -- this is a patch.
if( getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS) || getObject()->isSignificantlyAboveTerrain() || getObject()->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) )
{
m_enginesOn = TRUE;
}
else
{
m_enginesOn = FALSE;
}
}
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void JetAIUpdate::loadPostProcess( void )
{
//When drawables are created, so are their ambient sounds. After loading, only turn off the
//ambient sound if the engine is off.
if( !m_enginesOn )
{
Drawable *draw = getObject()->getDrawable();
if( draw )
{
draw->stopAmbientSound();
}
}
// extend base class
AIUpdateInterface::loadPostProcess();
} // end loadPostProcess