/* ** 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. // // // //////////////////////////////////////////////////////////////////////////////// // DeliverPayloadAIUpdate.cpp //////////// // Author: Graham Smallwood, March 2002 // Desc: State machine that controls the approach and deployment of airborne cargo #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #define DEFINE_WEAPONSLOTTYPE_NAMES #include "Common/Player.h" #include "Common/RandomValue.h" #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameClient/Drawable.h" #include "GameClient/FXList.h" #include "GameClient/InGameUI.h" #include "GameLogic/Locomotor.h" #include "GameLogic/Module/SmartBombTargetHomingUpdate.h" #include "GameLogic/Module/GenerateMinefieldBehavior.h" #include "GameLogic/Module/DeliverPayloadAIUpdate.h" #include "GameLogic/Module/ParachuteContain.h" #include "GameLogic/Module/ContainModule.h" #include "GameLogic/Module/PhysicsUpdate.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/Weapon.h" #include "GameLogic/WeaponSet.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif //------------------------------------------------------------------------------------------------- const FieldParse* DeliverPayloadData::getFieldParse() { static const FieldParse dataFieldParse[] = { { "DeliveryDistance", INI::parseReal, NULL, offsetof( DeliverPayloadData, m_distToTarget) }, { "PreOpenDistance", INI::parseReal, NULL, offsetof( DeliverPayloadData, m_preOpenDistance) }, { "MaxAttempts", INI::parseInt, NULL, offsetof( DeliverPayloadData, m_maxAttempts) }, //Drop information { "DropDelay", INI::parseDurationUnsignedInt,NULL, offsetof( DeliverPayloadData, m_dropDelay ) }, { "DropOffset", INI::parseCoord3D, NULL, offsetof( DeliverPayloadData, m_dropOffset ) }, { "DropVariance", INI::parseCoord3D, NULL, offsetof( DeliverPayloadData, m_dropVariance ) }, { "InheritTransportVelocity", INI::parseBool, NULL, offsetof( DeliverPayloadData, m_inheritTransportVelocity ) }, { "ExitPitchRate", INI::parseAngularVelocityReal,NULL, offsetof( DeliverPayloadData, m_exitPitchRate ) }, { "ParachuteDirectly", INI::parseBool, NULL, offsetof( DeliverPayloadData, m_isParachuteDirectly) }, //Visible payload information (payload assumed to be show visibly and it's created only when dropped) { "VisibleItemsDroppedPerInterval", INI::parseInt, NULL, offsetof( DeliverPayloadData, m_visibleItemsDroppedPerInterval ) }, { "VisibleDropBoneBaseName", INI::parseAsciiString, NULL, offsetof( DeliverPayloadData, m_visibleDropBoneName ) }, { "VisibleSubObjectBaseName", INI::parseAsciiString, NULL, offsetof( DeliverPayloadData, m_visibleSubObjectName ) }, { "VisibleNumBones", INI::parseInt, NULL, offsetof( DeliverPayloadData, m_visibleNumBones ) }, { "VisiblePayloadTemplateName", INI::parseAsciiString, NULL, offsetof( DeliverPayloadData, m_visiblePayloadTemplateName ) }, { "VisiblePayloadWeaponTemplate", INI::parseWeaponTemplate, NULL, offsetof( DeliverPayloadData, m_visiblePayloadWeaponTemplate ) }, { "SelfDestructObject", INI::parseBool, NULL, offsetof( DeliverPayloadData, m_selfDestructObject ) }, //Weapon based payload { "FireWeapon", INI::parseBool, NULL, offsetof( DeliverPayloadData, m_fireWeapon ) }, //Specify an additional weaponslot to be fired while strafing { "DiveStartDistance", INI::parseReal, NULL, offsetof( DeliverPayloadData, m_diveStartDistance ) }, { "DiveEndDistance", INI::parseReal, NULL, offsetof( DeliverPayloadData, m_diveEndDistance ) }, { "StrafingWeaponSlot", INI::parseLookupList, TheWeaponSlotTypeNamesLookupList, offsetof( DeliverPayloadData, m_strafingWeaponSlot ) }, { "StrafeWeaponFX", INI::parseFXList, NULL, offsetof( DeliverPayloadData, m_strafeFX ) }, { "StrafeLength", INI::parseReal, NULL, offsetof( DeliverPayloadData, m_strafeLength ) }, { "DeliveryDecal", RadiusDecalTemplate::parseRadiusDecalTemplate, NULL, offsetof( DeliverPayloadData, m_deliveryDecalTemplate ) }, { "DeliveryDecalRadius", INI::parseReal, NULL, offsetof(DeliverPayloadData, m_deliveryDecalRadius) }, { 0, 0, 0, 0 } }; return dataFieldParse; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- AIStateMachine* DeliverPayloadAIUpdate::makeStateMachine() { return newInstance(AIStateMachine)( getObject(), "DeliverPayloadBasicAI"); } //------------------------------------------------------------------------------------------------- DeliverPayloadAIUpdate::DeliverPayloadAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) { m_deliverPayloadStateMachine = NULL; m_targetPos.zero(); m_moveToPos.zero(); m_visibleItemsDelivered = 0; m_deliveryDecal.clear(); m_previousDistanceSqr = 0; m_freeToExit = FALSE; m_acceptingCommands = TRUE; // Added By Sadullah Nader // Initialization missing and needed m_diveState = DIVESTATE_PREDIVE; // End Add } //------------------------------------------------------------------------------------------------- DeliverPayloadAIUpdate::~DeliverPayloadAIUpdate( void ) { m_deliveryDecal.clear(); if (m_deliverPayloadStateMachine) m_deliverPayloadStateMachine->deleteInstance(); } //------------------------------------------------------------------------------------------------- AIFreeToExitType DeliverPayloadAIUpdate::getAiFreeToExit(const Object* exiter) const { if( getObject()->isEffectivelyDead() ) return NOT_FREE_TO_EXIT; return m_freeToExit ? FREE_TO_EXIT : NOT_FREE_TO_EXIT; } //------------------------------------------------------------------------------------------------- void DeliverPayloadAIUpdate::killDeliveryDecal() { m_deliveryDecal.clear(); } //------------------------------------------------------------------------------------------------- Bool DeliverPayloadAIUpdate::isAllowedToRespondToAiCommands(const AICommandParms* parms) const { if (!m_acceptingCommands) { return false; } return AIUpdateInterface::isAllowedToRespondToAiCommands(parms); } //------------------------------------------------------------------------------------------------- UpdateSleepTime DeliverPayloadAIUpdate::update( void ) { m_deliveryDecal.update(); if(!(isAiInDeadState()) && m_deliverPayloadStateMachine) m_deliverPayloadStateMachine->updateStateMachine(); //Handling diving logic (regardless of state) if( m_diveState != DIVESTATE_POSTDIVE ) { if( m_diveState == DIVESTATE_PREDIVE ) { //Check to see if we are close enough to start diving. Real startDiveDistanceSquared = sqr( getData()->m_diveStartDistance ); Real currentDistanceSquared = ThePartitionManager->getDistanceSquared( getObject(), getTargetPos(), FROM_CENTER_2D ); if( currentDistanceSquared <= startDiveDistanceSquared ) { m_diveState = DIVESTATE_DIVING; getObject()->getAIUpdateInterface()->getCurLocomotor()->setUsePreciseZPos( true ); AudioEventRTS soundDive = *(getObject()->getTemplate()->getPerUnitSound("StartDive")); if( soundDive.getEventName().isNotEmpty() ) { soundDive.setPosition( getObject()->getPosition() ); TheAudio->addAudioEvent( &soundDive ); } } } else { //Check to see when we shall end diving Real endDiveDistanceSquared = sqr( getData()->m_diveEndDistance ); Real currentDistanceSquared = ThePartitionManager->getDistanceSquared( getObject(), getTargetPos(), FROM_CENTER_3D ); if( currentDistanceSquared <= endDiveDistanceSquared ) { m_diveState = DIVESTATE_POSTDIVE; getObject()->getAIUpdateInterface()->getCurLocomotor()->setUsePreciseZPos( false ); } if( m_data.m_strafingWeaponSlot != -1 ) { //SHOOT!! const Coord3D *velocity = getObject()->getPhysics()->getVelocity(); if( velocity->z < 5.0f ) { //Calc strafe ratio Real startDiveDistance = getData()->m_diveStartDistance; Real endDiveDistance = sqrt( endDiveDistanceSquared ); Real currentDistance = sqrt( currentDistanceSquared ); Real diveRatio = (startDiveDistance - currentDistance) / (startDiveDistance - endDiveDistance); Coord3D velocity = *getObject()->getPhysics()->getVelocity(); velocity.z = 0.0f; velocity.normalize(); velocity.scale( diveRatio * 100.0f ); Coord3D backwards = velocity; backwards.scale( 0.33f ); Coord3D strafePoint = *getTargetPos(); strafePoint.sub( &backwards ); strafePoint.add( &velocity ); strafePoint.z = TheTerrainLogic->getGroundHeight( strafePoint.x, strafePoint.y ); // lock it just till the weapon is empty or the attack is "done" getObject()->setWeaponLock( m_data.m_strafingWeaponSlot, LOCKED_TEMPORARILY ); getObject()->fireCurrentWeapon( &strafePoint ); FXList::doFXPos( m_data.m_strafeFX, &strafePoint ); } } } } AIUpdateInterface::update(); return UPDATE_SLEEP_NONE; // ignore our parent, and never sleep } //------------------------------------------------------------------------------------------------- void DeliverPayloadAIUpdate::deliverPayload( const Coord3D *moveToPos, const Coord3D *targetPos, const DeliverPayloadData *data ) { //**************************************************** //THIS GETS CALLED VIA OBJECT CREATION LISTS ONLY!!! //**************************************************** if (m_deliverPayloadStateMachine) m_deliverPayloadStateMachine->deleteInstance(); m_deliverPayloadStateMachine = NULL; m_moveToPos = *moveToPos; m_targetPos = *targetPos; m_data = *data; m_deliveryDecal.clear(); m_data.m_deliveryDecalTemplate.createRadiusDecal(*targetPos, m_data.m_deliveryDecalRadius, getObject()->getControllingPlayer(), m_deliveryDecal); if( m_data.m_diveStartDistance <= 0.0f ) { //We can't dive, so setting it to post-dive will prevent //dive logic from being processed. m_diveState = DIVESTATE_POSTDIVE; } else { m_diveState = DIVESTATE_PREDIVE; } m_visibleItemsDelivered = 0; //If we have visible bones, then show each of those subobjects. Drawable *draw = getObject()->getDrawable(); if( draw ) { for( int i = 1; i <= m_data.m_visibleNumBones; i++ ) { if( m_data.m_visibleSubObjectName.isNotEmpty() ) { AsciiString name; name.format( "%s%02d", m_data.m_visibleSubObjectName.str(), i ); draw->showSubObject( name, true ); } } } // must make the state machine AFTER initing the other stuff, since it may inquire of its values... m_deliverPayloadStateMachine = newInstance(DeliverPayloadStateMachine)( getObject() ); m_deliverPayloadStateMachine->initDefaultState(); #ifdef _DEBUG m_deliverPayloadStateMachine->setName("DeliverPayloadSpecificAI"); #endif } //------------------------------------------------------------------------------------------------- void DeliverPayloadAIUpdate::deliverPayloadViaModuleData( const Coord3D *moveToPos ) { const DeliverPayloadAIUpdateModuleData *data = getDeliverPayloadAIUpdateModuleData(); //**************************************************** //THIS GETS CALLED FOR SCRIPTED REINFORCEMENTS ONLY!!! //These don't and can't use object creation lists. //**************************************************** DeliverPayloadData dpData; dpData.m_dropOffset.set( &data->m_dropOffset ); dpData.m_dropVariance.set( &data->m_dropVariance ); dpData.m_distToTarget = data->m_maxDistanceToTarget; dpData.m_maxAttempts = data->m_maxNumberAttempts; dpData.m_dropDelay = data->m_dropDelay; dpData.m_deliveryDecalTemplate = data->m_deliveryDecalTemplate; dpData.m_deliveryDecalRadius = data->m_deliveryDecalRadius; deliverPayload( moveToPos, moveToPos, &dpData ); } //------------------------------------------------------------------------------------------------- const ThingTemplate* DeliverPayloadAIUpdate::getPutInContainerTemplateViaModuleData() const { AsciiString name = getDeliverPayloadAIUpdateModuleData()->m_putInContainerName; return name.isEmpty() ? NULL : TheThingFactory->findTemplate( name ); } //------------------------------------------------------------------------------------------------- Real DeliverPayloadAIUpdate::calcMinTurnRadius(Real* timeToTravelThatDist) const { const Locomotor* loco = getCurLocomotor(); BodyDamageType bdt = getObject()->getBodyModule()->getDamageState(); /// @todo srj -- this should probably use min-speed, not max-speed... fix after E3 Real maxSpeed = loco->getMaxSpeedForCondition(bdt); // in dist/frame Real maxTurnRate = loco->getMaxTurnRate(bdt); // in rads/frame /* our minimum circumference will be like so: Real minTurnCircum = maxSpeed * (2*PI / maxTurnRate); so therefore our minimum turn radius is: Real minTurnRadius = minTurnCircum / 2*PI; so we just eliminate the middleman: */ Real minTurnRadius = (maxTurnRate > 0.0f) ? (maxSpeed / maxTurnRate) : 999999.0f; if (timeToTravelThatDist) *timeToTravelThatDist = minTurnRadius / maxSpeed; return minTurnRadius; } //------------------------------------------------------------------------------------------------- Bool DeliverPayloadAIUpdate::isCloseEnoughToTarget() { // In addition to testing distance, it is also sensitive to being in/outward bound ////The new getPreOpenDistance() allows the deliver state to fire early, but only if inbound, ////so the doors can open and payload can get ready... Real allowedDistanceSqr = sqr( getAllowedDistanceToTarget() ); Real currentDistanceSqr = ThePartitionManager->getDistanceSquared( getObject(), getTargetPos(), FROM_CENTER_2D ); Bool inBound = m_previousDistanceSqr > currentDistanceSqr; m_previousDistanceSqr = currentDistanceSqr;// for the next test if ( inBound ) allowedDistanceSqr = sqr(getAllowedDistanceToTarget() + getPreOpenDistance()); //DEBUG_LOG(("Dist to target is %f (allowed %f)\n",sqrt(currentDistanceSqr),sqrt(allowedDistanceSqr))); if ( allowedDistanceSqr > currentDistanceSqr ) return TRUE; return FALSE; } //------------------------------------------------------------------------------------------------- Bool DeliverPayloadAIUpdate::isOffMap() const { Region3D mapRegion; TheTerrainLogic->getExtentIncludingBorder( &mapRegion ); if (!mapRegion.isInRegionNoZ( getObject()->getPosition() )) return true; return false; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void DeliverPayloadAIUpdate::crc( Xfer *xfer ) { // extend base class AIUpdateInterface::crc(xfer); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version * 5: Whoops version. preOpenDistance just wasn't saved. */ // ------------------------------------------------------------------------------------------------ void DeliverPayloadAIUpdate::xfer( Xfer *xfer ) { // version const XferVersion currentVersion = 5; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class AIUpdateInterface::xfer(xfer); xfer->xferCoord3D(&m_targetPos); xfer->xferCoord3D(&m_moveToPos); xfer->xferInt(&m_visibleItemsDelivered); xfer->xferUser(&m_diveState, sizeof(m_diveState)); DeliverPayloadData data = m_data; xfer->xferAsciiString(&data.m_visibleDropBoneName); xfer->xferAsciiString(&data.m_visibleSubObjectName); xfer->xferAsciiString(&data.m_visiblePayloadTemplateName); xfer->xferReal(&data.m_distToTarget); if( version >= 5 ) xfer->xferReal(&data.m_preOpenDistance); xfer->xferInt(&data.m_maxAttempts); xfer->xferCoord3D(&data.m_dropOffset); xfer->xferCoord3D(&data.m_dropVariance); xfer->xferUnsignedInt(&data.m_dropDelay); xfer->xferBool(&data.m_fireWeapon); xfer->xferBool(&data.m_selfDestructObject); xfer->xferInt(&data.m_visibleNumBones); xfer->xferReal(&data.m_diveStartDistance); xfer->xferReal(&data.m_diveEndDistance); xfer->xferUser(&data.m_strafingWeaponSlot, sizeof(data.m_strafingWeaponSlot)); xfer->xferInt(&data.m_visibleItemsDroppedPerInterval); xfer->xferBool(&data.m_inheritTransportVelocity); xfer->xferBool(&data.m_isParachuteDirectly); xfer->xferReal(&data.m_exitPitchRate); // const FXList *m_strafeFX; xfer->xferReal(&data.m_strafeLength); AsciiString weaponTemplateName; if (data.m_visiblePayloadWeaponTemplate) { weaponTemplateName = data.m_visiblePayloadWeaponTemplate->getName(); } xfer->xferAsciiString(&weaponTemplateName); if( xfer->getXferMode() == XFER_LOAD && weaponTemplateName.isNotEmpty()) { data.m_visiblePayloadWeaponTemplate = TheWeaponStore->findWeaponTemplate(weaponTemplateName); } data.m_deliveryDecalTemplate.xferRadiusDecalTemplate(xfer); xfer->xferReal(&data.m_deliveryDecalRadius); *((DeliverPayloadData*)&m_data) = data; Bool hasStateMachine = m_deliverPayloadStateMachine!=NULL; xfer->xferBool(&hasStateMachine); if (hasStateMachine && m_deliverPayloadStateMachine==NULL) { m_deliverPayloadStateMachine = newInstance(DeliverPayloadStateMachine)( getObject() ); } if (m_deliverPayloadStateMachine && version >= 2) { xfer->xferSnapshot(m_deliverPayloadStateMachine); } m_deliveryDecal.xferRadiusDecal(xfer); if (version >= 2) { xfer->xferBool(&m_freeToExit); } if (version >= 3) { xfer->xferBool(&m_acceptingCommands); } if (version >= 4) { xfer->xferReal(&m_previousDistanceSqr); } } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void DeliverPayloadAIUpdate::loadPostProcess( void ) { // extend base class AIUpdateInterface::loadPostProcess(); } // end loadPostProcess //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- DeliverPayloadStateMachine::DeliverPayloadStateMachine( Object *owner ) : StateMachine( owner, "DeliverPayloadStateMachine" ) { // DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); static const StateConditionInfo considerConditions[] = { StateConditionInfo(DeliverPayloadStateMachine::isOffMap, RECOVER_FROM_OFF_MAP, NULL), StateConditionInfo(NULL, NULL, NULL) // keep last }; // order matters: first state is the default state. defineState( APPROACH, newInstance(ApproachState)( this ), DELIVERING, CONSIDER_NEW_APPROACH ); defineState( DELIVERING, newInstance(DeliveringState)( this ), HEAD_OFF_MAP, CONSIDER_NEW_APPROACH ); defineState( CONSIDER_NEW_APPROACH, newInstance(ConsiderNewApproachState)( this ), APPROACH, HEAD_OFF_MAP, considerConditions ); defineState( RECOVER_FROM_OFF_MAP, newInstance(RecoverFromOffMapState)( this ), APPROACH, APPROACH ); defineState( HEAD_OFF_MAP, newInstance(HeadOffMapState)( this ), CLEAN_UP, CLEAN_UP ); defineState( CLEAN_UP, newInstance(CleanUpState)( this ), INVALID_STATE_ID, INVALID_STATE_ID ); } //------------------------------------------------------------------------------------------------- DeliverPayloadStateMachine::~DeliverPayloadStateMachine() { } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void DeliverPayloadStateMachine::crc( Xfer *xfer ) { StateMachine::crc(xfer); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer Method */ // ------------------------------------------------------------------------------------------------ void DeliverPayloadStateMachine::xfer( Xfer *xfer ) { XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv ); StateMachine::xfer(xfer); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void DeliverPayloadStateMachine::loadPostProcess( void ) { StateMachine::loadPostProcess(); } // end loadPostProcess //------------------------------------------------------------------------------------------------- /*static*/ Bool DeliverPayloadStateMachine::isOffMap( State *thisState, void* userData ) { Object *owner = thisState->getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return TRUE; } return ai->isOffMap(); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- StateReturnType ApproachState::onEnter() // Give the move command { Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } if (ai->isAiInDeadState()) { return STATE_FAILURE; } ai->aiMoveToPosition( ai->getMoveToPos(), CMD_FROM_AI ); return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- StateReturnType ApproachState::update() { Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } if (ai->isAiInDeadState()) { return STATE_FAILURE; } if (ai->isCloseEnoughToTarget()) return STATE_SUCCESS; if (ai->getAIStateType() != AI_MOVE_TO) { if ( ai->getAIStateType() == AI_IDLE ) // Because something outside us has told us to IDLE, so geez, ignore it and try our approach again return STATE_FAILURE; else { DEBUG_CRASH(("hmm, bailed from moveto state early... should this be possible?")); ai->aiMoveToPosition( ai->getMoveToPos(), CMD_FROM_AI ); } } return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void DeliveringState::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer Method */ // ------------------------------------------------------------------------------------------------ void DeliveringState::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); xfer->xferUnsignedInt(&m_dropDelayLeft); xfer->xferBool(&m_didOpen); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void DeliveringState::loadPostProcess( void ) { } // end loadPostProcess //------------------------------------------------------------------------------------------------- StateReturnType DeliveringState::onEnter() // Open the pod bay doors, Hal { Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } /// @todo srj -- for now, this assumes at most one door owner->clearAndSetModelConditionState( MODELCONDITION_DOOR_1_CLOSING, MODELCONDITION_DOOR_1_OPENING ); m_dropDelayLeft = ai->getDoorDelay(); m_didOpen = false; return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- StateReturnType DeliveringState::update() // Kick a dude out every so often { if (m_dropDelayLeft > 0) { --m_dropDelayLeft; return STATE_CONTINUE; } Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } ai->friend_setFreeToExit(true); m_didOpen = true; m_dropDelayLeft = ai->getDropDelay(); if (!ai->isCloseEnoughToTarget()) return STATE_FAILURE; const ContainedItemsList* items = owner->getContain() ? owner->getContain()->getContainedItemsList() : NULL; if( (!items || !items->size()) && ai->getVisibleItemsDelivered() == ai->getData()->m_visibleNumBones ) { //We are out of payload to drop AND our visible payload is empty. It's possible for deliverers to //have one or the other or even both. return STATE_SUCCESS; } //Handle contained payload delivery. if( items ) { Object* item = items->front(); if( ai->isFireWeapon() ) { Coord3D pos = *ai->getTargetPos(); pos.x += ai->getDropOffset().x; pos.y += ai->getDropOffset().y; pos.z += ai->getDropOffset().z; owner->fireCurrentWeapon( &pos ); TheGameLogic->destroyObject( item ); } else { AIUpdateInterface* itemAI = item->getAIUpdateInterface(); if (itemAI) { itemAI->aiExit(NULL, CMD_FROM_AI); } Coord3D pos = *item->getPosition(); if (ai->getDropVariance().x > 0) pos.x += GameLogicRandomValueReal(-ai->getDropVariance().x, ai->getDropVariance().x); if (ai->getDropVariance().y > 0) pos.y += GameLogicRandomValueReal(-ai->getDropVariance().y, ai->getDropVariance().y); if (ai->getDropVariance().z > 0) pos.z += GameLogicRandomValueReal(-ai->getDropVariance().z, ai->getDropVariance().z); pos.x += ai->getDropOffset().x; pos.y += ai->getDropOffset().y; pos.z += ai->getDropOffset().z; item->setPosition(&pos); ContainModuleInterface *contain = item->getContain(); if( ai->getData()->m_isParachuteDirectly && contain ) { contain->setOverrideDestination( ai->getTargetPos() );// go for the direct hit } else if (itemAI) { // This will most likely be ignored, as the item is still in the transport, and therefore Held. itemAI->aiMoveToPosition( ai->getMoveToPos(), CMD_FROM_AI ); } /// @todo srj -- urg. icky. static NameKeyType key_GenerateMinefieldBehavior = NAMEKEY("GenerateMinefieldBehavior"); GenerateMinefieldBehavior* mfb = (GenerateMinefieldBehavior *)item->findUpdateModule(key_GenerateMinefieldBehavior); if (mfb) { mfb->setMinefieldTarget(ai->getMoveToPos()); } static NameKeyType key_SmartBombTargetHomingUpdate = NAMEKEY("SmartBombTargetHomingUpdate"); SmartBombTargetHomingUpdate* smthu = (SmartBombTargetHomingUpdate *)item->findUpdateModule(key_SmartBombTargetHomingUpdate); if (smthu) { smthu->SetTargetPosition( *ai->getMoveToPos() ); } if( ai->getData()->m_inheritTransportVelocity ) { Coord3D velocity = *owner->getPhysics()->getVelocity(); item->getPhysics()->applyForce( &velocity ); } } } //Handle visible payload delivery. if( ai->getVisibleItemsDelivered() < ai->getData()->m_visibleNumBones ) { Int attemptDrops = ai->getData()->m_visibleItemsDroppedPerInterval; Drawable *draw = owner->getDrawable(); Bool updateSubObjects = false; while( attemptDrops && ai->getVisibleItemsDelivered() < ai->getData()->m_visibleNumBones ) { //Drop an item! if( draw ) { //First hide the subobject as it becomes the delivered payload. if( ai->getData()->m_visibleSubObjectName.isNotEmpty() ) { AsciiString name; name.format( "%s%02d", ai->getData()->m_visibleSubObjectName.str(), ai->getVisibleItemsDelivered() + 1 ); draw->showSubObject( name, false ); updateSubObjects = true; } //Second, create the subobject for real if( ai->getData()->m_visiblePayloadTemplateName.isNotEmpty() ) { const ThingTemplate* thing = TheThingFactory->findTemplate( ai->getData()->m_visiblePayloadTemplateName.str() ); Object *payload = TheThingFactory->newObject( thing, owner->getControllingPlayer()->getDefaultTeam() ); if( payload ) { payload->setProducer( owner ); if( ai->getData()->m_visibleDropBoneName.isNotEmpty() ) { Coord3D pos; AsciiString bone; bone.format( "%s%02d", ai->getData()->m_visibleDropBoneName.str(), ai->getVisibleItemsDelivered() + 1 ); if( draw->getPristineBonePositions( ai->getData()->m_visibleDropBoneName.str(), ai->getVisibleItemsDelivered() + 1, &pos, NULL, 1 ) > 0 ) { draw->convertBonePosToWorldPos( &pos, NULL, &pos, NULL ); payload->setPosition( &pos ); } else { payload->setPosition( owner->getPosition() ); } } else { payload->setPosition( owner->getPosition() ); } payload->setOrientation( owner->getOrientation() ); //If we want the payload to inherit the transport's velocity, do so now. if( ai->getData()->m_inheritTransportVelocity ) { Coord3D startingForce = *owner->getPhysics()->getVelocity(); startingForce.scale( payload->getPhysics()->getMass() ); payload->getPhysics()->applyMotiveForce( &startingForce ); Coord3D backPosition = *owner->getPhysics()->getVelocity(); backPosition.scale( -1.0f ); backPosition.add( payload->getPosition() ); payload->setPosition( &backPosition ); } //Are we firing a missile? Bool projectileFired = false; for( BehaviorModule** u = payload->getBehaviorModules(); *u; ++u ) { ProjectileUpdateInterface* pui = (*u)->getProjectileUpdateInterface(); if( pui ) { //Missile! const WeaponTemplate *weaponTemplate = ai->getData()->m_visiblePayloadWeaponTemplate; if( !weaponTemplate ) { DEBUG_ASSERTCRASH( 0, ("%s tried to fire missile %s via DeliverPayload, and is missing required weapon template in ObjectCreationList.ini entry.", owner->getTemplate()->getName().str(), payload->getTemplate()->getName().str() ) ); break; } VeterancyLevel v = owner->getVeterancyLevel(); pui->projectileFireAtObjectOrPosition( NULL, ai->getTargetPos(), weaponTemplate, weaponTemplate->getProjectileExhaust(v) ); projectileFired = true; //damageInfo.in.m_sourceID = pui->projectileGetLauncherID(); break; } } //If not, are we dropping a bomb? if( !projectileFired ) { //Bomb! if( ai->getData()->m_exitPitchRate != 0 ) { //If the payload needs to pitch, do so now. Real pitchRate = payload->getPhysics()->getCenterOfMassOffset() * ai->getData()->m_exitPitchRate; payload->getPhysics()->setPitchRate( pitchRate ); } //If payload can move independently, order the move. A lot of bombs don't have locomotors, //in which case the "start move sound" is played, but nothing else actually happens. if( payload->getAIUpdateInterface() ) { payload->getAIUpdateInterface()->aiMoveToPosition( ai->getMoveToPos(), CMD_FROM_AI ); } } } } } //Update the counters attemptDrops--; ai->setVisibleItemsDelivered( ai->getVisibleItemsDelivered() + 1 ); } if( updateSubObjects ) { draw->updateSubObjects(); } } return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- void DeliveringState::onExit( StateExitType ) // Close the doors { Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if (ai) ai->friend_setFreeToExit(false); DEBUG_ASSERTCRASH(m_didOpen, ("Not enough time for the doors to open to drop anything!")); /// @todo srj -- for now, this assumes at most one door owner->clearAndSetModelConditionState( MODELCONDITION_DOOR_1_OPENING, MODELCONDITION_DOOR_1_CLOSING ); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void ConsiderNewApproachState::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer Method */ // ------------------------------------------------------------------------------------------------ void ConsiderNewApproachState::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); xfer->xferInt(&m_numberEntriesToState); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void ConsiderNewApproachState::loadPostProcess( void ) { } // end loadPostProcess //------------------------------------------------------------------------------------------------- StateReturnType ConsiderNewApproachState::onEnter() // Increment local counter o' futility { Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } ++m_numberEntriesToState; DEBUG_LOG(("Considering approach #%d...\n",m_numberEntriesToState)); if( m_numberEntriesToState > ai->getMaxNumberAttempts() ) { DEBUG_LOG(("Too many approaches! Time to give up.\n")); return STATE_FAILURE; } // ok, play some games here... the pathfinder is pretty dumb about things with big turning radii, // so we have to force the issue here... basically, estimate our necessary turning radius // based on loco values, move off far enough so we can turn, then head back. (if we just say // "head back directly", the code will just keep turning in circles, not realizing that our // turning radius is too large for that ever to work.) Real minTurnRadius = ai->calcMinTurnRadius(NULL); // how far is "far enough"? we must be at least 2*radius dist away from our target. // (we add a little fudge since we may not be able to travel our max speed while // turning.) const Real DIST_FUDGE = 2.2f; Real minReApproachDist = minTurnRadius * DIST_FUDGE; const Coord3D* dir = owner->getUnitDirectionVector2D(); Coord3D reApproachPoint; reApproachPoint.x = owner->getPosition()->x + dir->x * minReApproachDist; reApproachPoint.y = owner->getPosition()->y + dir->y * minReApproachDist; reApproachPoint.z = 0.0f; // yeah, yeah, should be terrainpos, but doesn't really matter here... ai->aiMoveToPosition( &reApproachPoint, CMD_FROM_AI ); // we allow these guys to go to invalid (ie, off-map) positions ai->getCurLocomotor()->setAllowInvalidPosition(true); return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- StateReturnType ConsiderNewApproachState::update() // Success if we should try again, failure if we should give up { Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } // stay in this state 'till we reach our goal. if (ai->getAIStateType() != AI_MOVE_TO) { return STATE_SUCCESS; } return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- void ConsiderNewApproachState::onExit( StateExitType status ) { Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return; } // go back to normal. ai->getCurLocomotor()->setAllowInvalidPosition(true); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void RecoverFromOffMapState::crc( Xfer *xfer ) { } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer Method */ // ------------------------------------------------------------------------------------------------ void RecoverFromOffMapState::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); xfer->xferUnsignedInt(&m_reEntryFrame); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void RecoverFromOffMapState::loadPostProcess( void ) { } // end loadPostProcess //------------------------------------------------------------------------------------------------- StateReturnType RecoverFromOffMapState::onEnter() // Increment local counter o' futility { Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } // have him hold in place, if possible. ai->aiMoveToPosition( owner->getPosition(), CMD_FROM_AI ); // a little cheesy... make a delay based on turn-radius time to simulate offscreen // maneuvering. Real timeToTravelThatDist; /*Real minTurnRadius =*/ ai->calcMinTurnRadius(&timeToTravelThatDist); m_reEntryFrame = TheGameLogic->getFrame() + REAL_TO_INT_CEIL(timeToTravelThatDist); // kill its momentum PhysicsBehavior* physics = owner->getPhysics(); physics->resetDynamicPhysics(); ThePartitionManager->unRegisterObject( owner ); if( owner->getDrawable() ) owner->getDrawable()->setDrawableHidden( true ); return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- StateReturnType RecoverFromOffMapState::update() // Success if we should try again, failure if we should give up { if (TheGameLogic->getFrame() < m_reEntryFrame) return STATE_CONTINUE; Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } ThePartitionManager->registerObject( owner ); if( owner->getDrawable() ) owner->getDrawable()->setDrawableHidden( false ); Coord3D enterCoord = TheTerrainLogic->findClosestEdgePoint( owner->getPosition() ); if (owner->isAboveTerrain()) enterCoord.z = owner->getPosition()->z; owner->setPosition(&enterCoord); Real enterAngle = atan2(ai->getMoveToPos()->y - enterCoord.y, ai->getMoveToPos()->x - enterCoord.x); owner->setOrientation(enterAngle); PhysicsBehavior* physics = owner->getPhysics(); physics->resetDynamicPhysics(); return STATE_SUCCESS; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- StateReturnType HeadOffMapState::onEnter() // Give move order out of town { Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } ai->killDeliveryDecal(); if ( ai->getData()->m_selfDestructObject ) //Lorenzen... this makes delivery unit pop away like a bubble... { TheGameLogic->destroyObject( owner ); return STATE_CONTINUE; } //Coord3D exitCoord = TheTerrainLogic->findClosestEdgePoint( owner->getPosition() ); // just keep moving straight ahead till we exit the map. Coord3D exitCoord = *owner->getPosition(); const Coord3D* dir = owner->getUnitDirectionVector2D(); Region3D terrainExtent; TheTerrainLogic->getExtent( &terrainExtent ); const Real FUDGE = 1.2f; Real HUGE_DIST = FUDGE * sqrt(sqr(terrainExtent.hi.x - terrainExtent.lo.x) + sqr(terrainExtent.hi.y - terrainExtent.lo.y)); exitCoord.x += dir->x * HUGE_DIST; exitCoord.y += dir->y * HUGE_DIST; ai->getCurLocomotor()->setAllowInvalidPosition(true); ai->getCurLocomotor()->setUltraAccurate(true); // set ultra-accurate just so AI won't try to adjust our dest ai->aiMoveToPosition( &exitCoord, CMD_FROM_AI ); // once we get into head-off-map state, we're done... don't respond to anything else. // this addresses the bug where a plane delivering team "foo" hasn't made it off the map // before all of team "foo" lands, whereupon team "foo" is given a script command to // (say) move to a given point... and since the cargo plane might be a member of "foo" // it would respond to said command, which sucks and is confusing. might be better // to have these guys generally ignore most commands, but this is pretty safe, in that // AFAIK nothing with deliverpayload should ever respond to anything, not even IDLE, // once they start heading off the map. (srj) ai->friend_setAcceptingCommands(false); owner->getUnitDirectionVector3D( facingDirectionUponDelivery ); return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- StateReturnType HeadOffMapState::update() { Object *owner = getMachineOwner(); DeliverPayloadAIUpdate *ai = (DeliverPayloadAIUpdate*)owner->getAIUpdateInterface(); if( !ai ) { return STATE_FAILURE; } if (ai->isOffMap()) return STATE_SUCCESS; // greasy as this hack seems... if terrain or objects or whatever have turned me away from the map-edge that I had first headed for,... //I blow up, rather than face eternally spinning on the point of a mineret or derrick or something awful. if ( owner->getPhysics()->getTurning() != 0 ) { Coord3D currentDirection; owner->getUnitDirectionVector3D( currentDirection ); Real dot = facingDirectionUponDelivery.x * currentDirection.x + facingDirectionUponDelivery.y * currentDirection.y + facingDirectionUponDelivery.z * currentDirection.z; if ( dot < 0.3f ) owner->kill(); } // Coord3D pos = *owner->getPosition(); // // if( TheTerrainLogic->getGroundHeight( pos->x, pos,y ) ) return STATE_CONTINUE; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- StateReturnType CleanUpState::onEnter() // Delete my successful butt { if( getMachineOwner()->getContain() ) { DEBUG_ASSERTCRASH(getMachineOwner()->getContain()->getContainCount() == 0, ("did not drop all items!")); } Object *owner = getMachineOwner(); TheGameLogic->destroyObject( owner ); return STATE_CONTINUE; }