/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: FlightDeckBehavior.cpp ///////////////////////////////////////////////////////////////////
// Author: Kris Morness, May 2003
// Desc: Handles aircraft movement and parking behavior for aircraft carriers.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/CRCDebug.h"
#include "Common/Player.h"
#include "Common/Team.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/ParticleSys.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/TerrainLogic.h"
#include "GameLogic/Weapon.h"
#include "GameLogic/Module/FlightDeckBehavior.h"
#include "GameLogic/Module/JetAIUpdate.h"
#include "GameLogic/Module/ProductionUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
FlightDeckBehaviorModuleData::FlightDeckBehaviorModuleData()
{
//m_framesForFullHeal = 0;
m_healAmount = 0;
m_numRows = 0;
m_numCols = 0;
m_approachHeight = 0.0f;
m_landingDeckHeightOffset = 0.0f;
m_dockAnimationFrames = 0;
m_catapultFireFrames = 0;
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehaviorModuleData::parseRunwayStrip( INI* ini, void *instance, void *store, const void* /*userData*/ )
{
AsciiString *runwayNames = (AsciiString*)store;
const char *token = ini->getNextTokenOrNull();
if( token )
{
runwayNames[ RUNWAY_START_BONE ].format( token );
token = ini->getNextTokenOrNull();
if( token )
{
runwayNames[ RUNWAY_END_BONE ].format( token );
}
}
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
AIUpdateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "NumRunways", INI::parseInt, NULL, offsetof( FlightDeckBehaviorModuleData, m_numCols ) },
{ "NumSpacesPerRunway", INI::parseInt, NULL, offsetof( FlightDeckBehaviorModuleData, m_numRows ) },
{ "Runway1Spaces", INI::parseAsciiStringVector, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 0 ].m_spacesBoneNames ) },
{ "Runway1Takeoff", parseRunwayStrip, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 0 ].m_takeoffBoneNames ) },
{ "Runway1Landing", parseRunwayStrip, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 0 ].m_landingBoneNames ) },
{ "Runway1Taxi", INI::parseAsciiStringVector, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 0 ].m_taxiBoneNames ) },
{ "Runway1Creation", INI::parseAsciiStringVector, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 0 ].m_creationBoneNames ) },
{ "Runway1CatapultSystem", INI::parseParticleSystemTemplate, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 0 ].m_catapultParticleSystem ) },
{ "Runway2Spaces", INI::parseAsciiStringVector, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 1 ].m_spacesBoneNames ) },
{ "Runway2Takeoff", parseRunwayStrip, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 1 ].m_takeoffBoneNames ) },
{ "Runway2Landing", parseRunwayStrip, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 1 ].m_landingBoneNames ) },
{ "Runway2Taxi", INI::parseAsciiStringVector, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 1 ].m_taxiBoneNames ) },
{ "Runway2Creation", INI::parseAsciiStringVector, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 1 ].m_creationBoneNames ) },
{ "Runway2CatapultSystem", INI::parseParticleSystemTemplate, NULL, offsetof( FlightDeckBehaviorModuleData, m_runwayInfo[ 1 ].m_catapultParticleSystem ) },
{ "ApproachHeight", INI::parseReal, NULL, offsetof( FlightDeckBehaviorModuleData, m_approachHeight ) },
{ "LandingDeckHeightOffset",INI::parseReal, NULL, offsetof( FlightDeckBehaviorModuleData, m_landingDeckHeightOffset ) },
{ "HealAmountPerSecond", INI::parseReal, NULL, offsetof( FlightDeckBehaviorModuleData, m_healAmount ) },
{ "ParkingCleanupPeriod", INI::parseDurationUnsignedInt, NULL, offsetof( FlightDeckBehaviorModuleData, m_cleanupFrames ) },
{ "HumanFollowPeriod", INI::parseDurationUnsignedInt, NULL, offsetof( FlightDeckBehaviorModuleData, m_humanFollowFrames ) },
{ "PayloadTemplate", INI::parseAsciiString, NULL, offsetof( FlightDeckBehaviorModuleData, m_thingTemplateName ) },
{ "ReplacementDelay", INI::parseDurationUnsignedInt, NULL, offsetof( FlightDeckBehaviorModuleData, m_replacementFrames ) },
{ "DockAnimationDelay", INI::parseDurationUnsignedInt, NULL, offsetof( FlightDeckBehaviorModuleData, m_dockAnimationFrames ) },
{ "LaunchWaveDelay", INI::parseDurationUnsignedInt, NULL, offsetof( FlightDeckBehaviorModuleData, m_launchWaveFrames ) },
{ "LaunchRampDelay", INI::parseDurationUnsignedInt, NULL, offsetof( FlightDeckBehaviorModuleData, m_launchRampFrames ) },
{ "LowerRampDelay", INI::parseDurationUnsignedInt, NULL, offsetof( FlightDeckBehaviorModuleData, m_lowerRampFrames ) },
{ "CatapultFireDelay", INI::parseDurationUnsignedInt, NULL, offsetof( FlightDeckBehaviorModuleData, m_catapultFireFrames ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FlightDeckBehavior::FlightDeckBehavior( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData )
{
m_gotInfo = false;
m_nextHealFrame = FOREVER;
setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
m_nextCleanupFrame = 0;
m_startedProductionFrame = FOREVER;
m_nextAllowedProductionFrame = 0;
m_designatedTarget = INVALID_ID;
m_designatedCommand = AICMD_NO_COMMAND;
m_designatedPosition.zero();
for( int i = 0; i < MAX_RUNWAYS; i++ )
{
m_nextLaunchWaveFrame[ i ] = 0;
m_rampUpFrame[ i ] = 0;
m_catapultSystemFrame[ i ] = 0;
m_lowerRampFrame[ i ] = 0;
m_rampUp[ i ] = FALSE;
}
m_thingTemplate = NULL;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FlightDeckBehavior::~FlightDeckBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::buildInfo(Bool createUnits)
{
if (m_gotInfo)
return;
const FlightDeckBehaviorModuleData* data = getFlightDeckBehaviorModuleData();
m_thingTemplate = TheThingFactory->findTemplate( data->m_thingTemplateName );
if (getObject()->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ||
getObject()->testStatus(OBJECT_STATUS_SOLD))
return;
//ProductionUpdateInterface* pu = getObject()->getProductionUpdateInterface();
m_spaces.reserve( data->m_numRows * data->m_numCols );
//Initialize the spaces that planes will eventually be assigned to for parking purposes
FlightDeckInfo flightDeckInfo;
//We want to sort the spaces so that we have runway 1 space 1, runway 2 space 1, R1S2, R2S2, R1S3...
for( Int row = 0; row < data->m_numRows; row++ )
{
for( Int col = 0; col < data->m_numCols; col++ )
{
std::vector spaces = data->m_runwayInfo[ col ].m_spacesBoneNames;
std::vector::const_iterator it;
Int counter = 0;
for( it = spaces.begin(); it != spaces.end(), counter < row; it++, counter++ )
{
//just iterate to the spaces.
}
AsciiString tmp;
Matrix3D mtx;
//Convert the module data bone names into coordinates that we can use
getObject()->getSingleLogicalBonePosition( it->str(), &flightDeckInfo.m_prep, &mtx );
flightDeckInfo.m_orientation = mtx.Get_Z_Rotation();
//Init basic runway stuff
flightDeckInfo.m_runway = col;
flightDeckInfo.m_objectInSpace = INVALID_ID;
//Create the payload
Object *jet = NULL;
if( m_thingTemplate && createUnits )
{
jet = TheThingFactory->newObject( m_thingTemplate, getObject()->getControllingPlayer()->getDefaultTeam() );
if( jet )
{
jet->setProducer( getObject() );
jet->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) );
//Init positioning.
jet->setPosition( &flightDeckInfo.m_prep );
jet->setOrientation( flightDeckInfo.m_orientation );
//Assign jet to space
flightDeckInfo.m_objectInSpace = jet->getID();
//validateAssignments();
}
}
m_spaces.push_back( flightDeckInfo );
}
}
//Now initialize the runway take-off and landing information.
RunwayInfo info;
m_runways.reserve(data->m_numCols);
for( Int col = 0; col < data->m_numCols; ++col )
{
AsciiString tmp;
getObject()->getSingleLogicalBonePosition( data->m_runwayInfo[ col ].m_takeoffBoneNames[ RUNWAY_START_BONE ].str(), &info.m_start, NULL);
getObject()->getSingleLogicalBonePosition( data->m_runwayInfo[ col ].m_takeoffBoneNames[ RUNWAY_END_BONE ].str(), &info.m_end, NULL);
getObject()->getSingleLogicalBonePosition( data->m_runwayInfo[ col ].m_landingBoneNames[ RUNWAY_START_BONE ].str(), &info.m_landingStart, NULL);
getObject()->getSingleLogicalBonePosition( data->m_runwayInfo[ col ].m_landingBoneNames[ RUNWAY_END_BONE ].str(), &info.m_landingEnd, NULL);
info.m_inUseByForTakeoff = INVALID_ID;
info.m_inUseByForLanding = INVALID_ID;
//Get the taxi bones and store them as well (possible to have none!)
std::vector locations = data->m_runwayInfo[ col ].m_taxiBoneNames;
std::vector::const_iterator it;
info.m_taxi.clear();
for( it = locations.begin(); it != locations.end(); it++ )
{
Coord3D taxiPos;
Matrix3D mtx;
//Get the position of the taxi bone.
getObject()->getSingleLogicalBonePosition( it->str(), &taxiPos, &mtx );
//Add it to the taxi vector
info.m_taxi.push_back( taxiPos );
}
//Get the creation bones and store them as well
locations = data->m_runwayInfo[ col ].m_creationBoneNames;
info.m_creation.clear();
Bool firstTime = TRUE;
for( it = locations.begin(); it != locations.end(); it++ )
{
Coord3D pos;
Matrix3D mtx;
//Get the position of the creation bone.
getObject()->getSingleLogicalBonePosition( it->str(), &pos, &mtx );
if( firstTime )
{
firstTime = FALSE;
info.m_startOrient = mtx.Get_Z_Rotation();
info.m_startTransform = mtx;
}
//Add it to the taxi vector
info.m_creation.push_back( pos );
}
m_runways.push_back(info);
}
m_gotInfo = true;
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::purgeDead()
{
buildInfo();
for (std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); ++it)
{
if (it->m_objectInSpace != INVALID_ID)
{
Object* obj = TheGameLogic->findObjectByID(it->m_objectInSpace);
if (obj == NULL || obj->isEffectivelyDead())
{
it->m_objectInSpace = INVALID_ID;
}
}
}
{
for (std::vector::iterator it = m_runways.begin(); it != m_runways.end(); ++it)
{
if (it->m_inUseByForTakeoff != INVALID_ID)
{
Object* obj = TheGameLogic->findObjectByID(it->m_inUseByForTakeoff);
if (obj == NULL || obj->isEffectivelyDead())
{
it->m_inUseByForTakeoff = INVALID_ID;
}
}
if (it->m_inUseByForLanding != INVALID_ID)
{
Object* obj = TheGameLogic->findObjectByID(it->m_inUseByForLanding);
if (obj == NULL || obj->isEffectivelyDead())
{
it->m_inUseByForLanding = INVALID_ID;
}
}
}
}
{
Bool anythingPurged = false;
for (std::list::iterator it = m_healing.begin(); it != m_healing.end(); /*++it*/)
{
if (it->m_gettingHealedID != INVALID_ID)
{
Object* objToHeal = TheGameLogic->findObjectByID(it->m_gettingHealedID);
if (objToHeal == NULL || objToHeal->isEffectivelyDead())
{
it = m_healing.erase(it);
anythingPurged = true;
}
else
{
++it;
}
}
}
if (anythingPurged)
resetWakeFrame();
}
}
//-------------------------------------------------------------------------------------------------
// note: called from client, so MUST NOT modify self in any way, or desyncs will occur
Bool FlightDeckBehavior::hasReservedSpace(ObjectID id) const
{
if (!m_gotInfo)
return false;
if (id == INVALID_ID) // shouldn't call this way, but Weapon mistakenly does sometimes, so check for it
return false;
for (std::vector::const_iterator it = m_spaces.begin(); it != m_spaces.end(); ++it)
{
if (it->m_objectInSpace == id)
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------
Int FlightDeckBehavior::getSpaceIndex( ObjectID id ) const
{
if( id == INVALID_ID )
{
return -1;
}
Int index = 0;
for( std::vector::const_iterator it = m_spaces.begin(); it != m_spaces.end(); it++, index++ )
{
if (it->m_objectInSpace == id)
{
return index;
}
}
return -1;
}
//-------------------------------------------------------------------------------------------------
FlightDeckBehavior::FlightDeckInfo* FlightDeckBehavior::findPPI(ObjectID id)
{
DEBUG_ASSERTCRASH(id != INVALID_ID, ("call findEmptyPPI instead"));
if (!m_gotInfo || id == INVALID_ID)
return NULL;
for (std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); ++it)
{
if (it->m_objectInSpace == id)
return it;
}
return NULL;
}
//-------------------------------------------------------------------------------------------------
FlightDeckBehavior::FlightDeckInfo* FlightDeckBehavior::findEmptyPPI()
{
if (!m_gotInfo)
return NULL;
for (std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); ++it)
{
if( it->m_objectInSpace == INVALID_ID )
return it;
}
return NULL;
}
//-------------------------------------------------------------------------------------------------
// note: called from client, so MUST NOT modify self in any way, or desyncs will occur
Bool FlightDeckBehavior::shouldReserveDoorWhenQueued(const ThingTemplate* thing) const
{
return true;
}
//-------------------------------------------------------------------------------------------------
// note: called from client, so MUST NOT modify self in any way, or desyncs will occur
Bool FlightDeckBehavior::hasAvailableSpaceFor(const ThingTemplate* thing) const
{
if (!m_gotInfo) // degenerate case, shouldn't happen, but just in case...
return false;
for (std::vector::const_iterator it = m_spaces.begin(); it != m_spaces.end(); ++it)
{
ObjectID id = it->m_objectInSpace;
// since this is const, and we can't purge the dead safely, just peek and see if we have a dead thing.
if (id != INVALID_ID)
{
Object* obj = TheGameLogic->findObjectByID(id);
if (obj == NULL || obj->isEffectivelyDead())
{
id = INVALID_ID;
}
}
if( id == INVALID_ID )
{
return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------------------
Bool FlightDeckBehavior::reserveSpace(ObjectID id, Real parkingOffset, ParkingPlaceBehaviorInterface::PPInfo* info)
{
buildInfo();
purgeDead();
const FlightDeckBehaviorModuleData* d = getFlightDeckBehaviorModuleData();
FlightDeckInfo* ppi = findPPI(id);
if (ppi == NULL)
{
ppi = findEmptyPPI();
if (ppi == NULL)
{
DEBUG_CRASH(("No parking places!"));
return false; // nothing available
}
}
ppi->m_objectInSpace = id;
//validateAssignments();
if( d->m_landingDeckHeightOffset )
{
Object *obj = TheGameLogic->findObjectByID( id );
if( obj )
{
obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) );
}
}
if( info )
{
calcPPInfo( id, info );
if (parkingOffset != 0.0f)
{
info->parkingSpace.x += parkingOffset * Cos(ppi->m_orientation);
info->parkingSpace.y += parkingOffset * Sin(ppi->m_orientation);
}
}
return true;
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::validateAssignments()
{
Int index = 0, index2 = 0;
for( std::vector::const_iterator it = m_spaces.begin(); it != m_spaces.end(); it++, index++ )
{
ObjectID id = it->m_objectInSpace;
if( id != INVALID_ID )
{
for( std::vector::const_iterator it2 = it; it2 != m_spaces.end(); it2++, index2++ )
{
if( it == it2 )
{
continue;
}
ObjectID id2 = it2->m_objectInSpace;
if( id == id2 )
{
DEBUG_CRASH( ("Aircraft %d assigned to multiple spaces %d and %d", id, index, index2 ) );
}
}
}
}
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::calcPPInfo( ObjectID id, PPInfo *info )
{
FlightDeckInfo *ppi = findPPI( id );
if( !ppi )
{
//Utter failure.
return;
}
const RunwayInfo& rr = m_runways[ ppi->m_runway ];
if( info )
{
const FlightDeckBehaviorModuleData* d = getFlightDeckBehaviorModuleData();
const Real APPROACH_DIST = 0.75f;
info->parkingSpace = ppi->m_prep;
info->runwayPrep = ppi->m_prep;
info->parkingOrientation = ppi->m_orientation;
info->runwayStart = rr.m_start;
info->runwayEnd = rr.m_end;
info->runwayExit = rr.m_end;
info->runwayExit.x += (rr.m_end.x - rr.m_start.x) * APPROACH_DIST;
info->runwayExit.y += (rr.m_end.y - rr.m_start.y) * APPROACH_DIST;
info->runwayExit.z = rr.m_end.z + d->m_approachHeight + d->m_landingDeckHeightOffset;
info->runwayLandingStart = rr.m_landingStart;
info->runwayLandingEnd = rr.m_landingEnd;
info->runwayApproach = rr.m_landingStart;
info->runwayApproach.x += (rr.m_landingStart.x - rr.m_landingEnd.x) * APPROACH_DIST;
info->runwayApproach.y += (rr.m_landingStart.y - rr.m_landingEnd.y) * APPROACH_DIST;
info->runwayApproach.z = rr.m_landingStart.z + d->m_approachHeight + d->m_landingDeckHeightOffset;
//Cache the runway's takeoff distance used by JetAIUpdate for calculating lift.
Coord3D vector = info->runwayStart;
vector.sub( &info->runwayEnd );
info->runwayTakeoffDist = vector.length();
for (std::vector::iterator it = m_runways.begin(); it != m_runways.end(); ++it)
{
if (it->m_inUseByForTakeoff == id )
{
info->runwayStart = info->runwayPrep;
}
}
}
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::releaseSpace(ObjectID id)
{
buildInfo();
purgeDead();
for (std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); ++it)
{
if (it->m_objectInSpace == id)
{
it->m_objectInSpace = INVALID_ID;
}
}
Object *obj = TheGameLogic->findObjectByID( id );
if( obj )
{
obj->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) );
}
}
//-------------------------------------------------------------------------------------------------
ObjectID FlightDeckBehavior::getRunwayReservation( Int runway, RunwayReservationType type )
{
buildInfo();
purgeDead();
switch( type )
{
case RESERVATION_TAKEOFF:
return m_runways[runway].m_inUseByForTakeoff;
case RESERVATION_LANDING:
return m_runways[runway].m_inUseByForLanding;
default:
return INVALID_ID;
}
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::transferRunwayReservationToNextInLineForTakeoff(ObjectID id)
{
//Aircraft carrier controls this functionality with an iron fist.
}
//-------------------------------------------------------------------------------------------------
Bool FlightDeckBehavior::reserveRunway(ObjectID id, Bool forLanding)
{
buildInfo();
purgeDead();
Int runway = -1;
if( !forLanding )
{
//Only look at the front spaces for takeoff. You can't take off from the back!
const FlightDeckBehaviorModuleData *data = getFlightDeckBehaviorModuleData();
for( Int i = 0; i < data->m_numCols; i++ )
{
if( m_spaces[ i ].m_objectInSpace == id )
{
runway = m_spaces[ i ].m_runway;
break;
}
}
}
else
{
//Look at all spaces for landing.
for (std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); ++it)
{
if (it->m_objectInSpace == id)
{
runway = it->m_runway;
break;
}
}
}
if (runway == -1)
{
if( forLanding )
{
DEBUG_CRASH(("only planes with reserved spaces can reserve runways"));
}
return false;
}
RunwayInfo& info = m_runways[runway];
if( info.m_inUseByForTakeoff == id && !forLanding || info.m_inUseByForLanding == id && forLanding )
{
return true;
}
else if( info.m_inUseByForTakeoff == INVALID_ID && !forLanding || info.m_inUseByForLanding == INVALID_ID && forLanding )
{
if( forLanding )
{
info.m_inUseByForLanding = id;
}
else
{
info.m_inUseByForTakeoff = id;
}
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::releaseRunway(ObjectID id)
{
buildInfo();
purgeDead();
for (std::vector::iterator it = m_runways.begin(); it != m_runways.end(); ++it)
{
if( it->m_inUseByForTakeoff == id )
{
it->m_inUseByForTakeoff = INVALID_ID;
}
if( it->m_inUseByForLanding == id )
{
it->m_inUseByForLanding = INVALID_ID;
}
}
}
//-------------------------------------------------------------------------------------------------
const std::vector* FlightDeckBehavior::getTaxiLocations( ObjectID id ) const
{
//Find the runway the object is assigned to.
Int runway = -1;
for( std::vector::const_iterator it = m_spaces.begin(); it != m_spaces.end(); it++ )
{
if( it->m_objectInSpace == id )
{
runway = it->m_runway;
break;
}
}
if( runway == -1 )
{
DEBUG_CRASH(("only planes with reserved spaces can reserve runways"));
return NULL;
}
//Now get the runway we're assigned to and return it's taxi vector
return &(m_runways[ runway ].m_taxi);
}
//-------------------------------------------------------------------------------------------------
const std::vector* FlightDeckBehavior::getCreationLocations( ObjectID id ) const
{
//Find the runway the object is assigned to.
Int runway = -1;
for( std::vector::const_iterator it = m_spaces.begin(); it != m_spaces.end(); it++ )
{
if( it->m_objectInSpace == id )
{
runway = it->m_runway;
break;
}
}
if( runway == -1 )
{
DEBUG_CRASH(("only planes with reserved spaces can reserve runways"));
return NULL;
}
//Now get the runway we're assigned to and return it's creation vector
return &(m_runways[ runway ].m_creation);
}
//-------------------------------------------------------------------------------------------------
Bool FlightDeckBehavior::isAbleToGiveUpParkingSpace( Object *jet )
{
//If we're airborne or non-existant, someone else can have my spot if they need it.
if( !jet || jet->isAirborneTarget() )
{
return TRUE;
}
JetAIUpdate *ai = (JetAIUpdate*)jet->getAI();
if( ai )
{
//If I'm not idle, I'm certainly not parked. But I could be on route to park in my space!
if( !ai->isIdle() && !ai->isTaxiingToParking() )
{
//We're definitely not airborne, so if our pending command is to land at the carrier, nuke the pending
//command.
AICommandType command = ai->friend_getPendingCommandType();
if( command == AICMD_ENTER || m_designatedCommand == AICMD_IDLE )
{
ai->friend_purgePendingCommand();
return FALSE;
}
//But make sure we're not waiting to takeoff!
std::vector< RunwayInfo >::const_iterator it;
for( it = m_runways.begin(); it != m_runways.end(); it++ )
{
if( it->m_inUseByForTakeoff == jet->getID() )
{
return FALSE;
}
}
return TRUE;
}
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
Bool FlightDeckBehavior::isInPositionToTakeoff( const Object &jet ) const
{
const AIUpdateInterface *ai = jet.getAI();
const FlightDeckBehaviorModuleData *data = getFlightDeckBehaviorModuleData();
if( ai )
{
for( int i = 0; i < data->m_numCols; i++ )
{
if( m_spaces[ i ].m_objectInSpace == jet.getID() )
{
//This code fixes a problem where there is a one frame lag between the
//time a jet gets assigned to the front spot, and the time it's AI is able to
//order it to taxi into the position. When this happens, the ramp triggers its
//animation, and the jet drives through it. So to counter that, simply check
//the distance between the jet and the space.
Real distanceSqr = ThePartitionManager->getDistanceSquared( &jet, &m_spaces[ i ].m_prep, FROM_CENTER_2D );
if( distanceSqr < 10.0f )
{
return TRUE;
}
return FALSE;
}
}
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
Bool FlightDeckBehavior::isAbleToMoveForward( const Object &jet ) const
{
JetAIUpdate *jetAI = (JetAIUpdate*)jet.getAI();
if( jetAI && !jet.isAirborneTarget() )
{
//We're not airborne.
if( jetAI->isIdle() )
{
//We're idle (not moving)
return TRUE;
}
//We might be rearming... if so, allow it.
if( jetAI->isReloading() )
{
return TRUE;
}
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
Bool FlightDeckBehavior::calcBestParkingAssignment( ObjectID id, Coord3D *pos, Int *oldIndex, Int *newIndex )
{
//Find the runway the object is assigned to.
Int runway = -1;
Int myIndex = 0;
for( std::vector::iterator myIt = m_spaces.begin(); myIt != m_spaces.end(); myIt++, myIndex++ )
{
if( myIt->m_objectInSpace == id )
{
runway = myIt->m_runway;
if( pos )
{
pos->set( &myIt->m_prep );
}
break;
}
}
if( oldIndex )
{
*oldIndex = myIndex;
}
//Now iterate the runway again in search of an empty spot in front of it. If we reach the same
//plane again, then we're done because there is no better spot!
//Search for the front-most available space that doesn't have any planes blocking it. So start from
//the back and keep looking at empty spaces until we find one with a plane blocking.
Bool checkForPlaneInWay = FALSE;
std::vector::iterator bestIt = NULL;
Object *bestJet = NULL;
Int bestIndex = 0, index = 0;
for( std::vector::iterator thatIt = m_spaces.begin(); thatIt != m_spaces.end(); thatIt++, index++ )
{
Object *nonIdleJet = TheGameLogic->findObjectByID( thatIt->m_objectInSpace );
if( myIt == thatIt )
{
//Done, don't look at my spot, nor spots behind me.
if( bestIt )
{
myIt->m_objectInSpace = bestJet ? bestJet->getID() : INVALID_ID;
bestIt->m_objectInSpace = id;
//validateAssignments();
/*if( bestJet )
{
JetAIUpdate *jetAI = (JetAIUpdate*)bestJet->getAI();
reserveSpace( bestJet->getID(), jetAI->friend_getParkingOffset(), NULL );
}
if( nonIdleJet )
{
JetAIUpdate *jetAI = (JetAIUpdate*)nonIdleJet->getAI();
reserveSpace( nonIdleJet->getID(), jetAI->friend_getParkingOffset(), NULL );
}*/
if( newIndex )
{
*newIndex = bestIndex;
}
//Promoted forward.
return TRUE;
}
//Did not get promoted forward.
return FALSE;
}
if( thatIt->m_runway == runway )
{
if( !nonIdleJet || isAbleToGiveUpParkingSpace( nonIdleJet ) )
{
if( !checkForPlaneInWay )
{
//We can take this spot! But first find the flight deck info entry for it.Now handle assignment swap.
bestIt = thatIt;
bestJet = nonIdleJet;
bestIndex = index;
checkForPlaneInWay = TRUE;
if( pos )
{
pos->set( &thatIt->m_prep );
}
}
}
else if( checkForPlaneInWay )
{
//Ugh, there's a plane parked between us and the best spot!
checkForPlaneInWay = FALSE;
if( pos )
{
pos->set( &myIt->m_prep ); //reset the original position.
bestIt = NULL;
}
}
}
}
//Did not get promoted forward
return FALSE;
}
//-------------------------------------------------------------------------------------------------
// don't really need to autoheal every frame....
const Int HEAL_RATE_FRAMES = LOGICFRAMES_PER_SECOND / 5;
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::resetWakeFrame()
{
if (m_healing.empty())
{
m_nextHealFrame = FOREVER;
}
else
{
m_nextHealFrame = TheGameLogic->getFrame() + HEAL_RATE_FRAMES;
}
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::setHealee(Object* healee, Bool add)
{
if (add)
{
for (std::list::const_iterator it = m_healing.begin(); it != m_healing.end(); ++it)
{
if (it->m_gettingHealedID == healee->getID())
return;
}
HealingInfo info;
info.m_gettingHealedID = healee->getID();
info.m_healStartFrame = TheGameLogic->getFrame();
m_healing.push_back(info);
resetWakeFrame();
}
else
{
for (std::list::iterator it = m_healing.begin(); it != m_healing.end(); /*++it*/)
{
if (it->m_gettingHealedID == healee->getID())
{
it = m_healing.erase(it);
resetWakeFrame();
}
else
{
++it;
}
}
}
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::defectAllParkedUnits(Team* newTeam, UnsignedInt detectionTime)
{
buildInfo();
purgeDead();
for (std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); ++it)
{
if (it->m_objectInSpace != INVALID_ID)
{
Object* obj = TheGameLogic->findObjectByID(it->m_objectInSpace);
if (obj == NULL || obj->isEffectivelyDead())
continue;
// srj sez: evil. fix better someday.
static NameKeyType jetKey = TheNameKeyGenerator->nameToKey("JetAIUpdate");
JetAIUpdate* ju = (JetAIUpdate *)obj->findUpdateModule(jetKey);
Bool takeoffOrLanding = ju ? ju->friend_isTakeoffOrLandingInProgress() : false;
if (obj->isAboveTerrain() && !takeoffOrLanding)
{
// if the new team is a different controlling player, this guys loses his space.
if (newTeam->getControllingPlayer() != obj->getControllingPlayer())
{
releaseSpace(obj->getID());
if (obj->getProducerID() == getObject()->getID())
obj->setProducer(NULL);
}
}
else
{
obj->defect(newTeam, detectionTime);
}
}
}
purgeDead();
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::killAllParkedUnits()
{
buildInfo();
purgeDead();
for (std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); ++it)
{
if (it->m_objectInSpace != INVALID_ID)
{
Object* obj = TheGameLogic->findObjectByID(it->m_objectInSpace);
if (obj == NULL || obj->isEffectivelyDead())
continue;
// srj sez: evil. fix better someday.
static NameKeyType jetKey = TheNameKeyGenerator->nameToKey("JetAIUpdate");
JetAIUpdate* ju = (JetAIUpdate *)obj->findUpdateModule(jetKey);
Bool takeoffOrLanding = ju ? ju->friend_isTakeoffOrLandingInProgress() : false;
if (obj->isAboveTerrain() && !takeoffOrLanding)
continue;
obj->kill();
}
}
purgeDead();
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::onDie( const DamageInfo *damageInfo )
{
killAllParkedUnits();
}
//-------------------------------------------------------------------------------------------------
UpdateSleepTime FlightDeckBehavior::update()
{
// alas, we need to keep the buildInfo and dead-purged stuff pretty much up to date, for
// the client to be able to peek at. at this late date, the most expedient way is to ensure
// our update is run every frame, and do this manually. the extra cost should be trivial, since
// there are generally at most only a few airfields at any given time.
buildInfo();
purgeDead();
const FlightDeckBehaviorModuleData* data = getFlightDeckBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
if( now >= m_nextHealFrame )
{
m_nextHealFrame = now + HEAL_RATE_FRAMES;
for (std::list::iterator it = m_healing.begin(); it != m_healing.end(); /*++it*/)
{
if (it->m_gettingHealedID != INVALID_ID)
{
Object* objToHeal = TheGameLogic->findObjectByID(it->m_gettingHealedID);
if (objToHeal == NULL || objToHeal->isEffectivelyDead())
{
it = m_healing.erase(it);
}
else
{
DamageInfo healInfo;
healInfo.in.m_damageType = DAMAGE_HEALING;
healInfo.in.m_deathType = DEATH_NONE;
healInfo.in.m_sourceID = getObject()->getID();
healInfo.in.m_amount = HEAL_RATE_FRAMES * data->m_healAmount * SECONDS_PER_LOGICFRAME_REAL;
BodyModuleInterface *body = objToHeal->getBodyModule();
body->attemptHealing( &healInfo );
++it;
}
}
}
}
//Periodically, we want to look at all the aircraft assignments and keep them near the front
//of the carrier at all times. We do this with the following steps:
// 1) Detect non-idle plane assigned to frontmost spaces (implying spot is empty)
// 2) Promote next idle plane behind it to move up and take it's spot. The original plane will
// get assigned to it's old spot (thus bubblesorted eventually to the rear).
if( now >= m_nextCleanupFrame )
{
m_nextCleanupFrame = now + data->m_cleanupFrames;
std::vector::iterator tempIt;
Int spaceID = 0;
Bool complete[ MAX_RUNWAYS ] = { FALSE, FALSE };
for( std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); it++, spaceID++ )
{
Object *nonIdleJet = TheGameLogic->findObjectByID( it->m_objectInSpace );
if( !nonIdleJet || isAbleToGiveUpParkingSpace( nonIdleJet ) )
{
//Either we don't have a jet, or the jet is busy (meaning it's not parked there). When a jet
//isn't in his spot, we will look for jets behind him to move up to take up his spot.
Int runwayCount = data->m_numCols;
Int tempID = spaceID;
for( tempIt = it; tempIt != m_spaces.end(); tempIt++, tempID++ )
{
if( runwayCount > 0 )
{
//Because the spaces are sorted Runway 1 Space 1, R2S1, R1S2, R2S2 etc, we simply iterate
//every other space. If there were 3 or 4 runways, then we would jump the number of runways
//before each check so we don't get aircraft from a different runway getting moved up!
runwayCount--;
continue;
}
if( complete[ it->m_runway ] )
{
continue;
}
//Now we have the correct runway. Check if that spot has an idle plane in it!
Object *parkedJet = TheGameLogic->findObjectByID( tempIt->m_objectInSpace );
if( parkedJet && isAbleToMoveForward( *parkedJet ) )
{
//We have found the best candidate to replace our empty space. Now handle assignment swap.
it->m_objectInSpace = parkedJet->getID();
tempIt->m_objectInSpace = nonIdleJet ? nonIdleJet->getID() : INVALID_ID;
//validateAssignments();
//Give the parkedJet a move order to taxi over to the new spot.
//However, we need to set a status bit to tell the AI the difference between taxiing from
//a hangar and reassigning a parking space.
parkedJet->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_REASSIGN_PARKING ) );
//Doesn't matter what we push in now, the JetOrHeliTaxiState::onEnter() will nuke it
//and calculate the taxi point to move through.
std::vector exitPath;
exitPath.push_back( it->m_prep );
parkedJet->getAI()->aiFollowExitProductionPath( &exitPath, getObject(), CMD_FROM_AI );
//Determine if the first plane in each row has completed a move. We want to lag the
//additional planes instead of moving them all simultaneously. So this is the exit
//condition if one plane per runway has been bumped.
complete[ it->m_runway ] = TRUE;
m_nextCleanupFrame = now + data->m_humanFollowFrames;
}
//Break through and advance to the next spot!
break;
}
}
}
}
//Handle automatic production of lost aircraft.
//Reset timer if we just finished building an aircraft.
if( m_nextAllowedProductionFrame <= now )
{
m_startedProductionFrame = FOREVER;
}
for( std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); it++ )
{
//Unassigned space?... so we can build a replacement.
if( it->m_objectInSpace == INVALID_ID )
{
//But are we already building one?
ProductionUpdateInterface *pu = getObject()->getProductionUpdateInterface();
if( pu == NULL )
{
DEBUG_CRASH( ("MSG_QUEUE_UNIT_CREATE: Producer '%s' doesn't have a unit production interface\n", getObject()->getTemplate()->getName().str()) );
break;
} // end if
DEBUG_ASSERTCRASH( m_thingTemplate != NULL, ("flightdeck has a null thingtemplate... no jets for you!\n") );
if( !pu->getProductionCount() && now >= m_nextAllowedProductionFrame && m_thingTemplate != NULL )
{
//Queue the build
pu->queueCreateUnit( m_thingTemplate, pu->requestUniqueUnitID() );
m_startedProductionFrame = now;
m_nextAllowedProductionFrame = now + data->m_replacementFrames + data->m_dockAnimationFrames;
}
break;
}
}
//If the carrier has at least one aircraft, then allow it to attack.
Bool hasAircraft = FALSE;
for( it = m_spaces.begin(); it != m_spaces.end(); it++ )
{
if( it->m_objectInSpace != INVALID_ID )
{
hasAircraft = TRUE;
break;
}
}
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_ATTACK ), !hasAircraft );
Drawable *draw = getObject()->getDrawable();
//Check for timer expiry -- are we allowed to launch the next wave yet?
for( int i = 0; i < data->m_numCols; i++ )
{
Object *jet = TheGameLogic->findObjectByID( m_spaces[ i ].m_objectInSpace );
if( jet && !isAbleToGiveUpParkingSpace( jet ) && isInPositionToTakeoff( *jet ) && hasTakeoffOrders() )
{
if( m_nextLaunchWaveFrame[ i ] <= now )
{
//Handle making the ramp going up and holding the launch until it's completely up.
if( !m_rampUp[ i ] )
{
m_rampUp[ i ] = TRUE;
m_rampUpFrame[ i ] = now + data->m_launchRampFrames;
m_lowerRampFrame[ i ] = FOREVER;
//****MAX_RUNWAYS***** if defined beyond 3, then this code will need to be rewritten or more modelcondition flags will
// need to be added.
ModelConditionFlagType opening = (ModelConditionFlagType)(MODELCONDITION_DOOR_2_OPENING + i * NUM_MODELCONDITION_DOOR_STATES);
ModelConditionFlagType closing = (ModelConditionFlagType)(MODELCONDITION_DOOR_2_CLOSING + i * NUM_MODELCONDITION_DOOR_STATES);
draw->clearAndSetModelConditionState( closing, opening );
}
//Handle launching the wave of fighters.
if( m_rampUp[ i ] && m_rampUpFrame[ i ] <= now )
{
AIUpdateInterface *jetAI = jet->getAI();
if( jetAI )
{
propagateOrderToSpecificPlane( jet );
m_nextLaunchWaveFrame[ i ] = now + data->m_launchWaveFrames;
m_catapultSystemFrame[ i ] = now + data->m_catapultFireFrames;
m_lowerRampFrame[ i ] = now + data->m_lowerRampFrames;
}
}
}
}
//Handle firing of the catapult steam effect upon launching planes.
if( m_catapultSystemFrame[ i ] <= now && data->m_runwayInfo[ i ].m_catapultParticleSystem )
{
ParticleSystem *ps = TheParticleSystemManager->createParticleSystem( data->m_runwayInfo[ i ].m_catapultParticleSystem );
m_catapultSystemFrame[ i ] = FOREVER;
if( ps )
{
ps->setLocalTransform( &m_runways[ i ].m_startTransform );
ps->setPosition( &m_runways[ i ].m_start );
}
}
//Handle lowering the ramp after the fighter has been launched.
if( m_rampUp[ i ] && m_lowerRampFrame[ i ] <= now )
{
m_rampUp[ i ] = FALSE;
//****MAX_RUNWAYS***** if defined beyond 3, then this code will need to be rewritten or more modelcondition flags will
// need to be added.
ModelConditionFlagType opening = (ModelConditionFlagType)(MODELCONDITION_DOOR_2_OPENING + i * NUM_MODELCONDITION_DOOR_STATES);
ModelConditionFlagType closing = (ModelConditionFlagType)(MODELCONDITION_DOOR_2_CLOSING + i * NUM_MODELCONDITION_DOOR_STATES);
draw->clearAndSetModelConditionState( opening, closing );
}
}
return UPDATE_SLEEP_NONE;
}
//-------------------------------------------------------------------------------------------------
ExitDoorType FlightDeckBehavior::reserveDoorForExit( const ThingTemplate* objType, Object *specificObject )
{
//Uses the same door for all production.
return DOOR_1;
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::exitObjectViaDoor( Object *newObj, ExitDoorType exitDoor ) ///< Here is the thing I want you to exit
{
FlightDeckInfo* ppi = NULL;
if (exitDoor != DOOR_NONE_NEEDED)
{
for (std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); ++it)
{
if( it->m_objectInSpace == INVALID_ID )
{
ppi = &(*it);
break;
}
}
if (!ppi)
{
DEBUG_CRASH(("could not find the space. what?"));
return;
}
ppi->m_objectInSpace = newObj->getID();
//validateAssignments();
}
/// @todo srj -- this is evil. fix.
static NameKeyType jetKey = TheNameKeyGenerator->nameToKey( "JetAIUpdate" );
JetAIUpdate* ju = (JetAIUpdate *)newObj->findUpdateModule( jetKey );
Real parkingOffset = ju ? ju->friend_getParkingOffset() : 0.0f;
PPInfo ppinfo;
Matrix3D mtx;
DUMPMATRIX3D(getObject()->getTransformMatrix());
DUMPCOORD3D(getObject()->getPosition());
CRCDEBUG_LOG(("Produced at hangar (door = %d)\n", exitDoor));
DEBUG_ASSERTCRASH(exitDoor != DOOR_NONE_NEEDED, ("Hmm, unlikely"));
if (!reserveSpace(newObj->getID(), parkingOffset, &ppinfo)) //&loc, &orient, NULL, NULL, NULL, NULL, &hangarInternal, &hangOrient))
{
DEBUG_CRASH(("no spaces available, how did we get here?"));
ppinfo.parkingSpace = *getObject()->getPosition();
ppinfo.parkingOrientation = getObject()->getOrientation();
}
const std::vector *pCreationLocations = getCreationLocations( newObj->getID() );
if( !pCreationLocations )
{
DEBUG_CRASH( ("No creation locations specified for runway for FlightDeckBehavior (Kris).") );
return;
}
newObj->setPosition( pCreationLocations->begin() );
newObj->setOrientation( m_runways[ ppi->m_runway ].m_startOrient );
TheAI->pathfinder()->addObjectToPathfindMap( newObj );
AIUpdateInterface *ai = newObj->getAIUpdateInterface();
if( ai )
{
std::vector exitPath;
//Doesn't matter what we push in now, the JetOrHeliTaxiState::onEnter() will nuke it
//and calculate the taxi points to move through.
exitPath.push_back( ppi->m_prep );
ai->aiFollowExitProductionPath( &exitPath, getObject(), CMD_FROM_AI );
}
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::unreserveDoorForExit( ExitDoorType exitDoor )
{
//Aircraft carrier doesn't use the door reservation system.
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::aiDoCommand(const AICommandParms* parms)
{
//Inspect the command and reset everything when necessary.
if( parms->m_cmdSource != CMD_FROM_AI )
{
//Now the only time we care about anything is if we were ordered to attack something or attack move.
switch( parms->m_cmd )
{
case AICMD_GUARD_POSITION:
m_designatedTarget = INVALID_ID;
m_designatedPosition.set( &parms->m_pos );
m_designatedCommand = parms->m_cmd;
propagateOrdersToPlanes();
break;
case AICMD_ATTACK_POSITION:
m_designatedTarget = INVALID_ID;
m_designatedPosition.set( &parms->m_pos );
m_designatedCommand = parms->m_cmd;
propagateOrdersToPlanes();
break;
case AICMD_FORCE_ATTACK_OBJECT:
case AICMD_ATTACK_OBJECT:
m_designatedTarget = parms->m_obj ? parms->m_obj->getID() : INVALID_ID;
m_designatedPosition.zero();
m_designatedCommand = parms->m_cmd;
propagateOrdersToPlanes();
break;
case AICMD_ATTACKMOVE_TO_POSITION:
m_designatedTarget = INVALID_ID;
m_designatedPosition.set( &parms->m_pos );
m_designatedCommand = parms->m_cmd;
propagateOrdersToPlanes();
break;
case AICMD_IDLE:
m_designatedTarget = INVALID_ID;
m_designatedPosition.zero();
m_designatedCommand = parms->m_cmd;
propagateOrdersToPlanes();
break;
default:
m_designatedCommand = AICMD_NO_COMMAND;
break;
}
}
//Do NOT extend the base class AI. The carrier is a dumb stump that only propagates orders
//to it's aircraft.
//AIUpdateInterface::aiDoCommand( parms );
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::propagateOrdersToPlanes()
{
//We just ordered the carrier to stop, so order all the planes that are out to return!
for( std::vector::iterator it = m_spaces.begin(); it != m_spaces.end(); it++ )
{
if( it->m_objectInSpace != INVALID_ID )
{
Object *jet = TheGameLogic->findObjectByID( it->m_objectInSpace );
if( jet && isAbleToGiveUpParkingSpace( jet ) )
{
propagateOrderToSpecificPlane( jet );
}
}
}
}
//-------------------------------------------------------------------------------------------------
Bool FlightDeckBehavior::hasTakeoffOrders()
{
Object *target = TheGameLogic->findObjectByID( m_designatedTarget );
switch( m_designatedCommand )
{
case AICMD_GUARD_POSITION:
return TRUE;
case AICMD_ATTACK_POSITION:
return TRUE;
case AICMD_ATTACKMOVE_TO_POSITION:
return TRUE;
case AICMD_FORCE_ATTACK_OBJECT:
case AICMD_ATTACK_OBJECT:
if( target )
{
return TRUE;
}
m_designatedCommand = AICMD_NO_COMMAND;
m_designatedTarget = INVALID_ID;
return FALSE;
case AICMD_IDLE:
return FALSE;
default:
return FALSE;
}
}
//-------------------------------------------------------------------------------------------------
void FlightDeckBehavior::propagateOrderToSpecificPlane( Object *jet )
{
if( jet )
{
AIUpdateInterface *ai = jet->getAI();
if( ai )
{
Object *target = TheGameLogic->findObjectByID( m_designatedTarget );
switch( m_designatedCommand )
{
case AICMD_GUARD_POSITION:
ai->aiGuardPosition( &m_designatedPosition, GUARDMODE_NORMAL, CMD_FROM_AI );
break;
case AICMD_ATTACK_POSITION:
ai->aiAttackPosition( &m_designatedPosition, NO_MAX_SHOTS_LIMIT, CMD_FROM_AI );
break;
case AICMD_FORCE_ATTACK_OBJECT:
case AICMD_ATTACK_OBJECT:
ai->aiForceAttackObject( target, NO_MAX_SHOTS_LIMIT, CMD_FROM_PLAYER );
break;
case AICMD_ATTACKMOVE_TO_POSITION:
ai->aiAttackMoveToPosition( &m_designatedPosition, NO_MAX_SHOTS_LIMIT, CMD_FROM_AI );
break;
case AICMD_IDLE:
ai->aiEnter( getObject(), CMD_FROM_AI );
break;
}
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FlightDeckBehavior::crc( Xfer *xfer )
{
// extend base class
AIUpdateInterface::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void FlightDeckBehavior::xfer( Xfer *xfer )
{
Int i;
// version
const XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
AIUpdateInterface::xfer( xfer );
if( xfer->getXferMode() == XFER_LOAD )
{
// first, build our info, so it won't be overwritten later.
buildInfo(FALSE); // False, because the planes are going to save themselves. We don't re-create them
}
// spaces info count and data
UnsignedByte spacesCount = m_spaces.size();
xfer->xferUnsignedByte( &spacesCount );
if( xfer->getXferMode() == XFER_SAVE )
{
// save all elements
std::vector< FlightDeckInfo >::iterator it;
for( it = m_spaces.begin(); it != m_spaces.end(); ++it )
{
// object in this space
xfer->xferObjectID( &((*it).m_objectInSpace) ); // This is the one thing not regenerated by buildInfo
} // end for, it
} // end if, save
else if( xfer->getXferMode() == XFER_LOAD )
{
ObjectID objectID;
// read all elements
std::vector< FlightDeckInfo >::iterator it;
it = m_spaces.begin();
for( i = 0; i < spacesCount; ++i )
{
// read object id
xfer->xferObjectID( &objectID );
// store in vector if the vector does indeed still have room for this entry
if( it != m_spaces.end() )
{
(*it).m_objectInSpace = objectID;
//validateAssignments();
++it;
} // end if
} // end for, i
} // end else, load
// runways count and info
UnsignedByte runwaysCount = m_runways.size();
xfer->xferUnsignedByte( &runwaysCount );
if( xfer->getXferMode() == XFER_SAVE )
{
// save all elements
std::vector< RunwayInfo >::iterator it;
for( it = m_runways.begin(); it != m_runways.end(); ++it )
{
// save object ID
xfer->xferObjectID( &((*it).m_inUseByForTakeoff ) );
xfer->xferObjectID( &((*it).m_inUseByForLanding ) );
} // end for, it
} // end if, save
else if( xfer->getXferMode() == XFER_LOAD )
{
// read all elements
std::vector< RunwayInfo >::iterator it;
it = m_runways.begin();
for( i = 0; i < runwaysCount; ++i )
{
ObjectID inUseByForTakeoff, inUseByForLanding;
// Old? Bool wasInLine;
// read object ID
xfer->xferObjectID( &inUseByForTakeoff );
xfer->xferObjectID( &inUseByForLanding );
// Old? xfer->xferBool( &wasInLine );
// store in vector if the vector does indeed still have room for this entry
if( it != m_runways.end() )
{
(*it).m_inUseByForTakeoff = inUseByForTakeoff;
(*it).m_inUseByForLanding = inUseByForLanding;
++it;
} // end if
} // end for, i
} // end else, load
// healees
UnsignedByte healCount = m_healing.size();
xfer->xferUnsignedByte( &healCount );
if( xfer->getXferMode() == XFER_SAVE )
{
// save all elements
std::list< HealingInfo >::iterator it;
for( it = m_healing.begin(); it != m_healing.end(); ++it )
{
// save object ID
xfer->xferObjectID( &((*it).m_gettingHealedID) );
xfer->xferUnsignedInt( &((*it).m_healStartFrame) );
} // end for, it
} // end if, save
else if( xfer->getXferMode() == XFER_LOAD )
{
// read all elements
m_healing.clear();
for( i = 0; i < healCount; ++i )
{
HealingInfo info;
// read object ID
xfer->xferObjectID( &info.m_gettingHealedID );
xfer->xferUnsignedInt( &info.m_healStartFrame );
m_healing.push_back(info);
} // end for, i
} // end else, load
xfer->xferUnsignedInt( &m_nextHealFrame );
xfer->xferUnsignedInt( &m_nextCleanupFrame );
xfer->xferUnsignedInt( &m_startedProductionFrame );
xfer->xferUnsignedInt( &m_nextAllowedProductionFrame );
xfer->xferObjectID( &m_designatedTarget );
Int commandType;
xfer->xferInt( &commandType );
m_designatedCommand = (AICommandType)commandType;
xfer->xferCoord3D( &m_designatedPosition );
UnsignedInt maxRunways = MAX_RUNWAYS;
xfer->xferUnsignedInt( &maxRunways ); //If we're loading, we'll overwrite it. If we're saving, we save it!
for( i = 0; i < MAX_RUNWAYS; i++ )
{
if( maxRunways <= MAX_RUNWAYS )
{
//Save and load everything.
xfer->xferUnsignedInt( &m_nextLaunchWaveFrame[ i ] );
xfer->xferUnsignedInt( &m_rampUpFrame[ i ] );
xfer->xferUnsignedInt( &m_catapultSystemFrame[ i ] );
xfer->xferUnsignedInt( &m_lowerRampFrame[ i ] );
xfer->xferBool( &m_rampUp[ MAX_RUNWAYS ] );
}
else
{
//We must be loading... so load filler but don't assign it.
UnsignedInt dummyInt;
Bool dummyBool;
xfer->xferUnsignedInt( &dummyInt );
xfer->xferUnsignedInt( &dummyInt );
xfer->xferUnsignedInt( &dummyInt );
xfer->xferUnsignedInt( &dummyInt );
xfer->xferBool( &dummyBool );
}
}
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void FlightDeckBehavior::loadPostProcess( void )
{
const FlightDeckBehaviorModuleData* data = getFlightDeckBehaviorModuleData();
m_thingTemplate = TheThingFactory->findTemplate( data->m_thingTemplateName );
// extend base class
AIUpdateInterface::loadPostProcess();
// no, this is bad.. it is NOT SAFE to call setWakeFrame from the xfer system. crap. (srj)
// make sure we are awake... old save games let us sleep
//setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
} // end loadPostProcess