/* ** 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. // // // //////////////////////////////////////////////////////////////////////////////// // ChinookAIUpdate.cpp ////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #define DEFINE_VETERANCY_NAMES // for TheVeterancyNames[] #include "Common/ActionManager.h" #include "Common/DrawModule.h" #include "Common/GameState.h" #include "Common/GlobalData.h" #include "Common/RandomValue.h" #include "Common/Team.h" #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "Common/Player.h" #include "Common/PlayerList.h" #include "GameClient/Drawable.h" #include "GameClient/GameClient.h" #include "GameClient/ParticleSys.h" #include "GameLogic/AIPathfind.h" #include "GameLogic/Locomotor.h" #include "GameLogic/Module/ContainModule.h" #include "GameLogic/Module/ChinookAIUpdate.h" #include "GameLogic/Module/PhysicsUpdate.h" #include "GameLogic/PartitionManager.h" const Real BIGNUM = 99999.0f; #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif //------------------------------------------------------------------------------------------------- enum ChinookAIStateType { // note that these must be distinct (numerically) from AIStateType. ick. ChinookAIStateType_FIRST = 1000, TAKING_OFF, LANDING, MOVE_TO_AND_LAND, MOVE_TO_AND_EVAC, LAND_AND_EVAC, EVAC_AND_TAKEOFF, MOVE_TO_AND_EVAC_AND_EXIT, LAND_AND_EVAC_AND_EXIT, EVAC_AND_EXIT, TAKEOFF_AND_EXIT, HEAD_OFF_MAP, MOVE_TO_COMBAT_DROP, DO_COMBAT_DROP, MOVE_TO_AND_EVAC_AND_EXIT_INIT }; //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- static Real calcDistSqr(const Coord3D& a, const Coord3D& b) { return sqr(a.x-b.x) + sqr(a.y-b.y) + sqr(a.z-b.z); } //------------------------------------------------------------------------------------------------- static Object* getPotentialRappeller(Object* obj) { const ContainedItemsList* items = obj->getContain() ? obj->getContain()->getContainedItemsList() : NULL; if (items) { for (ContainedItemsList::const_iterator it = items->begin(); it != items->end(); ++it ) { Object* rider = *it; if (rider->isKindOf(KINDOF_CAN_RAPPEL)) { return rider; } } } return NULL; } //---------------------------------------------------------------------------------------------------------- class ChinookEvacuateState : public State { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ChinookEvacuateState, "ChinookEvacuateState") protected: // snapshot interface STUBBED - no member vars to save. jba. virtual void crc( Xfer *xfer ){}; virtual void xfer( Xfer *xfer ){}; virtual void loadPostProcess(){}; public: ChinookEvacuateState( StateMachine *machine ) : State( machine, "ChinookEvacuateState" ) { } StateReturnType onEnter() { Object* obj = getMachineOwner(); if( obj->getContain() ) { obj->getContain()->removeAllContained(FALSE); } obj->getTeam()->setActive(); // why? I don't know. return STATE_SUCCESS; } virtual StateReturnType update() { return STATE_SUCCESS; } }; EMPTY_DTOR(ChinookEvacuateState) //------------------------------------------------------------------------------------------------- class ChinookHeadOffMapState : public State { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ChinookHeadOffMapState, "ChinookHeadOffMapState") //I'm outta here protected: // snapshot interface STUBBED - no member vars to save. jba. virtual void crc( Xfer *xfer ){}; virtual void xfer( Xfer *xfer ){}; virtual void loadPostProcess(){}; public: ChinookHeadOffMapState( StateMachine *machine ) : State( machine, "ChinookHeadOffMapState" ) {} StateReturnType onEnter() // Give move order out of town { Object *owner = getMachineOwner(); ChinookAIUpdate* ai = (ChinookAIUpdate*)owner->getAIUpdateInterface(); owner->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_RIDER8 ) ); ai->aiMoveToPosition( ai->getOriginalPosition(), CMD_FROM_AI ); ai->getCurLocomotor()->setAllowInvalidPosition(true); return STATE_CONTINUE; } StateReturnType update() { Object *owner = getMachineOwner(); Region3D mapRegion; TheTerrainLogic->getExtentIncludingBorder( &mapRegion ); if( !mapRegion.isInRegionNoZ( owner->getPosition() ) ) { TheGameLogic->destroyObject(owner); return STATE_SUCCESS; } return STATE_CONTINUE; } void onExit() { Object *owner = getMachineOwner(); owner->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_RIDER8 ) ); } }; EMPTY_DTOR(ChinookHeadOffMapState) //------------------------------------------------------------------------------------------------- class ChinookTakeoffOrLandingState : public State { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ChinookTakeoffOrLandingState, "ChinookTakeoffOrLandingState") private: Coord3D m_destLoc; Bool m_landing; protected: // snapshot interface virtual void crc( Xfer *xfer ) { // empty } virtual void xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); xfer->xferCoord3D(&m_destLoc); xfer->xferBool(&m_landing); } virtual void loadPostProcess() { // empty } public: ChinookTakeoffOrLandingState( StateMachine *machine, Bool landing ) : m_landing(landing), State( machine, "ChinookTakeoffOrLandingState" ) { m_destLoc.zero(); } virtual StateReturnType onEnter() { Object* obj = getMachineOwner(); ChinookAIUpdate* ai = (ChinookAIUpdate*)obj->getAIUpdateInterface(); ai->friend_setFlightStatus(m_landing ? CHINOOK_LANDING : CHINOOK_TAKING_OFF); if( m_landing ) { // A chinook given transport duty loses his supplies. while( ai->loseOneBox() ); } // kill any drift... obj->getPhysics()->scrubVelocity2D(0); ai->chooseLocomotorSet(LOCOMOTORSET_NORMAL); Locomotor* loco = ai->getCurLocomotor(); loco->setUsePreciseZPos(true); loco->setUltraAccurate(true); m_destLoc = *obj->getPosition(); const Bool onlyHealthyBridges = true; // ignore dead bridges. PathfindLayerEnum layerAtDest = TheTerrainLogic->getHighestLayerForDestination(&m_destLoc, onlyHealthyBridges); m_destLoc.z = TheTerrainLogic->getLayerHeight(m_destLoc.x, m_destLoc.y, layerAtDest); if (m_landing) { Coord3D tmp; FindPositionOptions options; options.maxRadius = obj->getGeometryInfo().getBoundingCircleRadius() * 100.0f; if (ThePartitionManager->findPositionAround(&m_destLoc, &options, &tmp)) { m_destLoc = tmp; TheAI->pathfinder()->adjustToLandingDestination(obj, &m_destLoc); } // recalc, since it may have changed. note that findPositionAround() will ALWAYS // return a position on the ground proper, so if our initial search start pos was // above a bridge, this will put us below the bridge, which would be unfortunate. // so recheck to be sure. (note that the partitionmgr is 2d-only, so if it sez that // the position on the ground at that xy is clear, it will be clear for both the // ground proper and the bridge itself.) also note: don't call objectInteractsWithBridgeLayer(), // since it assumes that things that aren't close in z shouldn't interact. tmp = m_destLoc; tmp.z = obj->getPosition()->z; layerAtDest = TheTerrainLogic->getHighestLayerForDestination(&tmp, onlyHealthyBridges); m_destLoc.z = TheTerrainLogic->getLayerHeight(m_destLoc.x, m_destLoc.y, layerAtDest); obj->setLayer(layerAtDest); } else { m_destLoc.z += loco->getPreferredHeight(); obj->setLayer(LAYER_GROUND); } return STATE_CONTINUE; } virtual StateReturnType update() { Object* obj = getMachineOwner(); if (obj->isEffectivelyDead()) return STATE_FAILURE; ChinookAIUpdate* ai = (ChinookAIUpdate*)obj->getAIUpdateInterface(); ai->setLocomotorGoalPositionExplicit(m_destLoc); const Real THRESH = 3.0f; const Real THRESH_SQR = THRESH*THRESH; if (calcDistSqr(*obj->getPosition(), m_destLoc) <= THRESH_SQR) return STATE_SUCCESS; return STATE_CONTINUE; } virtual void onExit( StateExitType status ) { Object* obj = getMachineOwner(); ChinookAIUpdate* ai = (ChinookAIUpdate*)obj->getAIUpdateInterface(); ai->friend_setFlightStatus(m_landing ? CHINOOK_LANDED : CHINOOK_FLYING); // Paranoia checks - sometimes onExit is called when we are // shutting down, and not all pieces are valid. CurLocomotor // is definitely null in some cases. jba. Locomotor* loco = ai->getCurLocomotor(); if (loco) { loco->setUsePreciseZPos(false); loco->setUltraAccurate(false); // don't restore lift if dead -- this may fight with JetSlowDeathBehavior! if (!obj->isEffectivelyDead()) loco->setMaxLift(BIGNUM); } if (m_landing) { ai->chooseLocomotorSet(LOCOMOTORSET_TAXIING); } else { // when takeoff is complete, always go back to layer-ground, rather than // some bridge layer. obj->setLayer(LAYER_GROUND); } } }; EMPTY_DTOR(ChinookTakeoffOrLandingState) //------------------------------------------------------------------------------------------------- class ChinookCombatDropState : public State { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ChinookCombatDropState, "ChinookCombatDropState") private: struct RopeInfo { Drawable* ropeDrawable; DrawableID ropeID; // used only during save-load process Matrix3D dropStartMtx; Real ropeSpeed; Real ropeLen; Real ropeLenMax; UnsignedInt nextDropTime; std::list rappellerIDs; }; std::vector m_ropes; void removeDoneRappellers() { for (std::vector::iterator it = m_ropes.begin(); it != m_ropes.end(); ++it) { for (std::list::iterator oit = it->rappellerIDs.begin(); oit != it->rappellerIDs.end(); ) { Object* rappeller = TheGameLogic->findObjectByID(*oit); if (rappeller == NULL || rappeller->isEffectivelyDead() || !rappeller->isAboveTerrain()) { oit = it->rappellerIDs.erase(oit); } else { ++oit; } } } } static void initRopeParms(Drawable* rope, Real length, Real width, const RGBColor& color, Real wobbleLen, Real wobbleAmp, Real wobbleRate) { RopeDrawInterface* tdi = NULL; for (DrawModule** d = rope->getDrawModules(); *d; ++d) { if ((tdi = (*d)->getRopeDrawInterface()) != NULL) { tdi->initRopeParms(length, width, color, wobbleLen, wobbleAmp, wobbleRate); } } } static void setRopeCurLen(Drawable* rope, Real length) { RopeDrawInterface* tdi = NULL; for (DrawModule** d = rope->getDrawModules(); *d; ++d) { if ((tdi = (*d)->getRopeDrawInterface()) != NULL) { tdi->setRopeCurLen(length); } } } static void setRopeSpeed(Drawable* rope, Real curSpeed, Real maxSpeed, Real accel) { RopeDrawInterface* tdi = NULL; for (DrawModule** d = rope->getDrawModules(); *d; ++d) { if ((tdi = (*d)->getRopeDrawInterface()) != NULL) { tdi->setRopeSpeed(curSpeed, maxSpeed, accel); } } } protected: // snapshot interface virtual void crc( Xfer *xfer ) { // empty } virtual void xfer( Xfer *xfer ) { // version const XferVersion currentVersion = 2; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); Int numRopes = m_ropes.size(); xfer->xferInt(&numRopes); if (version >= 2) { if (xfer->getXferMode() == XFER_LOAD) { if (!m_ropes.empty()) { DEBUG_CRASH(( "ChinookCombatDropState - ropes should be empty\n" )); throw SC_INVALID_DATA; } m_ropes.resize(numRopes); } for (Int i = 0; i < numRopes; ++i) { RopeInfo info; if (xfer->getXferMode() == XFER_SAVE) { info = m_ropes[i]; // always overwrite this, since it's probably stale info.ropeID = info.ropeDrawable ? info.ropeDrawable->getID() : INVALID_DRAWABLE_ID; } xfer->xferDrawableID(&info.ropeID); xfer->xferMatrix3D(&info.dropStartMtx); xfer->xferReal(&info.ropeSpeed); xfer->xferReal(&info.ropeLen); xfer->xferReal(&info.ropeLenMax); xfer->xferUnsignedInt(&info.nextDropTime); xfer->xferSTLObjectIDList(&info.rappellerIDs); if (xfer->getXferMode() == XFER_LOAD) { info.ropeDrawable = NULL; // filled in via loadPostProcess m_ropes[i] = info; } } } } virtual void loadPostProcess() { for (std::vector::iterator it = m_ropes.begin(); it != m_ropes.end(); ++it) { it->ropeDrawable = TheGameClient->findDrawableByID(it->ropeID); // always nuke this, since we're done with it till we save/load again it->ropeID = INVALID_DRAWABLE_ID; } } public: ChinookCombatDropState( StateMachine *machine ): State( machine, "ChinookCombatDropState" ) { } // -------------- virtual StateReturnType onEnter() { Object* obj = getMachineOwner(); Drawable* draw = obj->getDrawable(); if (draw == NULL) return STATE_FAILURE; ChinookAIUpdate* ai = (ChinookAIUpdate*)obj->getAIUpdateInterface(); const ChinookAIUpdateModuleData* d = ai->friend_getData(); obj->setDisabled( DISABLED_HELD ); ai->friend_setFlightStatus(CHINOOK_DOING_COMBAT_DROP); // A chinook given combat drop duty also loses his supplies. while( ai->loseOneBox() ); UnsignedInt now = TheGameLogic->getFrame(); const ThingTemplate* ropeTmpl = TheThingFactory->findTemplate(d->m_ropeName); const Int MAX_BONES = 32; Coord3D ropePos[MAX_BONES]; Matrix3D dropMtx[MAX_BONES]; Int ropeCount = draw->getPristineBonePositions("RopeStart", 1, ropePos, NULL, MAX_BONES); Int dropCount = draw->getPristineBonePositions("RopeEnd", 1, NULL, dropMtx, MAX_BONES); Int numRopes = d->m_numRopes; if (numRopes > ropeCount) numRopes = ropeCount; if (numRopes > dropCount) numRopes = dropCount; if (numRopes <= 0) return STATE_FAILURE; m_ropes.clear(); for (Int i = 0; i < numRopes; ++i) { RopeInfo info; obj->convertBonePosToWorldPos( NULL, &dropMtx[i], NULL, &info.dropStartMtx ); info.ropeDrawable = ropeTmpl ? TheThingFactory->newDrawable(ropeTmpl) : NULL; if (info.ropeDrawable) { obj->convertBonePosToWorldPos( &ropePos[i], NULL, &ropePos[i], NULL ); info.ropeDrawable->setPosition(&ropePos[i]); info.ropeSpeed = 0.0f; info.ropeLen = 1.0f; const Bool onlyHealthyBridges = true; // ignore dead bridges. PathfindLayerEnum layerAtDest = TheTerrainLogic->getHighestLayerForDestination(&ropePos[i], onlyHealthyBridges); info.ropeLenMax = ropePos[i].z - TheTerrainLogic->getLayerHeight(ropePos[i].x, ropePos[i].y, layerAtDest) - d->m_ropeFinalHeight; initRopeParms(info.ropeDrawable, info.ropeLenMax, d->m_ropeWidth, d->m_ropeColor, d->m_ropeWobbleLen, d->m_ropeWobbleAmp, d->m_ropeWobbleRate); } info.nextDropTime = now + GameLogicRandomValue(d->m_perRopeDelayMin, d->m_perRopeDelayMax) - d->m_perRopeDelayMin; info.rappellerIDs.clear(); m_ropes.push_back(info); } return STATE_CONTINUE; } // -------------- virtual StateReturnType update() { Object* obj = getMachineOwner(); ChinookAIUpdate* ai = (ChinookAIUpdate*)obj->getAIUpdateInterface(); const ChinookAIUpdateModuleData* d = ai->friend_getData(); if (obj->isEffectivelyDead()) { return STATE_FAILURE; } // first, eliminate "done" rappellers removeDoneRappellers(); UnsignedInt now = TheGameLogic->getFrame(); // ok, now check each rope: if it's empty, or we're at the next drop time, spawn a new rappeller Int numRopesInUse = 0; for (std::vector::iterator it = m_ropes.begin(); it != m_ropes.end(); ++it) { if (it->ropeLen < it->ropeLenMax) { it->ropeSpeed += fabs(TheGlobalData->m_gravity); if (it->ropeSpeed > d->m_ropeDropSpeed) it->ropeSpeed = d->m_ropeDropSpeed; it->ropeLen += it->ropeSpeed; setRopeCurLen(it->ropeDrawable, it->ropeLen); if (d->m_waitForRopesToDrop) { // can't use this rope till it's dropped all the way ++it->nextDropTime; continue; } } if (now >= it->nextDropTime) { Object* rappeller = getPotentialRappeller(obj); if (rappeller != NULL) { ExitInterface *exitInterface = obj->getObjectExitInterface(); ExitDoorType exitDoor = exitInterface ? exitInterface->reserveDoorForExit(rappeller->getTemplate(), rappeller) : DOOR_NONE_AVAILABLE; if(exitDoor != DOOR_NONE_AVAILABLE) { exitInterface->exitObjectViaDoor(rappeller, exitDoor); } else { DEBUG_CRASH(("rappeller is not free to exit... what?")); } rappeller->setTransformMatrix(&it->dropStartMtx); AIUpdateInterface* rappellerAI = rappeller ? rappeller->getAIUpdateInterface() : NULL; if (rappellerAI) { rappellerAI->setDesiredSpeed(d->m_rappelSpeed); rappellerAI->aiRappelInto(getMachineGoalObject(), *getMachineGoalPosition(), CMD_FROM_AI); } it->rappellerIDs.push_back(rappeller->getID()); it->nextDropTime = now + GameLogicRandomValue(d->m_perRopeDelayMin, d->m_perRopeDelayMax); } } if (!it->rappellerIDs.empty()) { ++numRopesInUse; } } if (numRopesInUse == 0 && getPotentialRappeller(obj) == NULL) { // we're done! return STATE_SUCCESS; } return STATE_CONTINUE; } // -------------- virtual void onExit( StateExitType status ) { Object* obj = getMachineOwner(); ChinookAIUpdate* ai = (ChinookAIUpdate*)obj->getAIUpdateInterface(); const ChinookAIUpdateModuleData* d = ai->friend_getData(); obj->clearDisabled( DISABLED_HELD ); ai->friend_setFlightStatus(CHINOOK_FLYING); if (obj->isEffectivelyDead()) { // oops. drop the rangers. for (std::vector::iterator it = m_ropes.begin(); it != m_ropes.end(); ++it) { for (std::list::iterator oit = it->rappellerIDs.begin(); oit != it->rappellerIDs.end(); ++oit) { Object* rappeller = TheGameLogic->findObjectByID(*oit); AIUpdateInterface* rappellerAI = rappeller ? rappeller->getAIUpdateInterface() : NULL; if (rappellerAI != NULL) { rappellerAI->aiIdle(CMD_FROM_AI); } } } } UnsignedInt now = TheGameLogic->getFrame(); for (Int i = 0; i < m_ropes.size(); ++i) { if (m_ropes[i].ropeDrawable) { const UnsignedInt ROPE_EXPIRATION_TIME = LOGICFRAMES_PER_SECOND * 5; const Real initialSpeed = TheGlobalData->m_gravity * 30; // give it a little kick setRopeSpeed(m_ropes[i].ropeDrawable, initialSpeed, d->m_ropeDropSpeed, TheGlobalData->m_gravity); m_ropes[i].ropeDrawable->setExpirationDate(now + ROPE_EXPIRATION_TIME); m_ropes[i].ropeDrawable = NULL; // we're done with it, so null it so we won't save it } } m_ropes.clear(); } }; EMPTY_DTOR(ChinookCombatDropState) //----------------------------------------------------------------------------------------------------------- /** * Move to the GoalPosition, or GoalObject. */ class ChinookMoveToBldgState : public AIMoveToState { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ChinookMoveToBldgState, "ChinookMoveToBldgState") private: Real m_oldPreferredHeight; Real m_newPreferredHeight; Real m_destZ; protected: // snapshot interface virtual void crc( Xfer *xfer ) { // empty } virtual void xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); xfer->xferReal(&m_oldPreferredHeight); xfer->xferReal(&m_newPreferredHeight); xfer->xferReal(&m_destZ); } virtual void loadPostProcess() { // empty } public: ChinookMoveToBldgState( StateMachine *machine ): AIMoveToState( machine ) { } virtual StateReturnType onEnter() { Object* obj = getMachineOwner(); ChinookAIUpdate* ai = (ChinookAIUpdate*)obj->getAIUpdateInterface(); const ChinookAIUpdateModuleData* d = ai->friend_getData(); Locomotor* loco = ai->getCurLocomotor(); loco->setUltraAccurate(true); m_oldPreferredHeight = loco->getPreferredHeight(); m_newPreferredHeight = m_oldPreferredHeight; const Coord3D* destPos; Object* bldg = getMachineGoalObject(); if (bldg != NULL && !bldg->isEffectivelyDead() && bldg->isKindOf(KINDOF_STRUCTURE)) { destPos = bldg->getPosition(); m_newPreferredHeight = bldg->getGeometryInfo().getMaxHeightAbovePosition() + d->m_minDropHeight; if (m_newPreferredHeight < m_oldPreferredHeight) m_newPreferredHeight = m_oldPreferredHeight; } else { destPos = getMachineGoalPosition(); } loco->setPreferredHeight(m_newPreferredHeight); m_destZ = TheTerrainLogic->getGroundHeight(destPos->x, destPos->y) + m_newPreferredHeight; return AIMoveToState::onEnter(); } virtual StateReturnType update() { Object* obj = getMachineOwner(); // the normal moveto state will bail when 2d pos matches; we need z, too StateReturnType status = AIMoveToState::update(); const Real THRESH = 3.0f; if (status != STATE_CONTINUE && fabs(obj->getPosition()->z - m_destZ) > THRESH) status = STATE_CONTINUE; return status; } virtual void onExit( StateExitType status ) { Object* obj = getMachineOwner(); ChinookAIUpdate* ai = (ChinookAIUpdate*)obj->getAIUpdateInterface(); Locomotor* loco = ai->getCurLocomotor(); loco->setPreferredHeight(m_oldPreferredHeight); loco->setUltraAccurate(false); AIMoveToState::onExit(status); } }; EMPTY_DTOR(ChinookMoveToBldgState) //----------------------------------------------------------------------------------------------------------- /** * Store the original position we are assigned to for exit. */ class ChinookRecordCreationState : public State { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ChinookRecordCreationState, "ChinookRecordCreationState") protected: // snapshot interface virtual void crc( Xfer *xfer ) { // empty } virtual void xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); } virtual void loadPostProcess() { // empty } public: ChinookRecordCreationState( StateMachine *machine ): State( machine, "ChinookRecordCreationState" ) { } virtual StateReturnType onEnter() { Object* obj = getMachineOwner(); ChinookAIUpdate* ai = (ChinookAIUpdate*)obj->getAIUpdateInterface(); if( ai ) { ai->recordOriginalPosition( *obj->getPosition() ); } return STATE_SUCCESS; } virtual StateReturnType update() { return STATE_SUCCESS; } }; EMPTY_DTOR(ChinookRecordCreationState) //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- class ChinookAIStateMachine : public AIStateMachine { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ChinookAIStateMachine, "ChinookAIStateMachine" ); public: ChinookAIStateMachine( Object *owner, AsciiString name ); }; //------------------------------------------------------------------------------------------------- ChinookAIStateMachine::ChinookAIStateMachine(Object *owner, AsciiString name) : AIStateMachine(owner, name) { defineState( TAKING_OFF, newInstance(ChinookTakeoffOrLandingState)( this, false ), AI_IDLE, AI_IDLE ); defineState( LANDING, newInstance(ChinookTakeoffOrLandingState)( this, true ), AI_IDLE, AI_IDLE ); defineState( MOVE_TO_COMBAT_DROP, newInstance(ChinookMoveToBldgState)( this ), DO_COMBAT_DROP, AI_IDLE ); defineState( DO_COMBAT_DROP, newInstance(ChinookCombatDropState)( this ), AI_IDLE, AI_IDLE ); defineState( MOVE_TO_AND_LAND, newInstance(AIMoveToState)( this ), LANDING, AI_IDLE ); defineState( MOVE_TO_AND_EVAC, newInstance(AIMoveToState)( this ), LAND_AND_EVAC, AI_IDLE ); defineState( LAND_AND_EVAC, newInstance(ChinookTakeoffOrLandingState)( this, true ), EVAC_AND_TAKEOFF, AI_IDLE ); defineState( EVAC_AND_TAKEOFF, newInstance(ChinookEvacuateState)( this ), TAKING_OFF, AI_IDLE ); defineState( MOVE_TO_AND_EVAC_AND_EXIT_INIT, newInstance(ChinookRecordCreationState)( this ), MOVE_TO_AND_EVAC_AND_EXIT, AI_IDLE ); defineState( MOVE_TO_AND_EVAC_AND_EXIT, newInstance(AIMoveToState)( this ), LAND_AND_EVAC_AND_EXIT, AI_IDLE ); defineState( LAND_AND_EVAC_AND_EXIT, newInstance(ChinookTakeoffOrLandingState)( this, true ), EVAC_AND_EXIT, AI_IDLE ); defineState( EVAC_AND_EXIT, newInstance(ChinookEvacuateState)( this ), TAKEOFF_AND_EXIT, AI_IDLE ); defineState( TAKEOFF_AND_EXIT, newInstance(ChinookTakeoffOrLandingState)( this, false ), HEAD_OFF_MAP, AI_IDLE ); defineState( HEAD_OFF_MAP, newInstance(ChinookHeadOffMapState)( this ), AI_IDLE, AI_IDLE ); } //------------------------------------------------------------------------------------------------- ChinookAIStateMachine::~ChinookAIStateMachine() { } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- ChinookAIUpdateModuleData::ChinookAIUpdateModuleData() { m_numRopes = 4; m_ropeWidth = 0.5f; m_ropeColor.red = 0.9f; m_ropeColor.green = 0.8f; m_ropeColor.blue = 0.7f; m_perRopeDelayMin = 0x7fffffff; m_perRopeDelayMax = 0x7fffffff; m_ropeName = "GenericRope"; m_waitForRopesToDrop = true; m_minDropHeight = 30.0f; m_ropeFinalHeight = 0.0f; m_ropeDropSpeed = 1e10f; // um, fast. m_rappelSpeed = fabs(TheGlobalData->m_gravity) * LOGICFRAMES_PER_SECOND * 0.5f; m_ropeWobbleLen = 10.0f; m_ropeWobbleAmp = 1.0f; m_ropeWobbleRate = 0.1f; m_rotorWashParticleSystem.clear(); m_upgradedSupplyBoost = 0; } //------------------------------------------------------------------------------------------------- /*static*/ void ChinookAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { SupplyTruckAIUpdateModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { { "RappelSpeed", INI::parseVelocityReal, 0, offsetof(ChinookAIUpdateModuleData, m_rappelSpeed) }, { "RopeDropSpeed", INI::parseVelocityReal, 0, offsetof(ChinookAIUpdateModuleData, m_ropeDropSpeed) }, { "RopeName", INI::parseAsciiString, 0, offsetof(ChinookAIUpdateModuleData, m_ropeName) }, { "RopeFinalHeight", INI::parseReal, 0, offsetof(ChinookAIUpdateModuleData, m_ropeFinalHeight) }, { "RopeWidth", INI::parseReal, 0, offsetof(ChinookAIUpdateModuleData, m_ropeWidth) }, { "RopeWobbleLen", INI::parseReal, 0, offsetof(ChinookAIUpdateModuleData, m_ropeWobbleLen) }, { "RopeWobbleAmplitude", INI::parseReal, 0, offsetof(ChinookAIUpdateModuleData, m_ropeWobbleAmp) }, { "RopeWobbleRate", INI::parseAngularVelocityReal, 0, offsetof(ChinookAIUpdateModuleData, m_ropeWobbleRate) }, { "RopeColor", INI::parseRGBColor, 0, offsetof(ChinookAIUpdateModuleData, m_ropeColor) }, { "NumRopes", INI::parseUnsignedInt, 0, offsetof(ChinookAIUpdateModuleData, m_numRopes) }, { "PerRopeDelayMin", INI::parseDurationUnsignedInt, 0, offsetof(ChinookAIUpdateModuleData, m_perRopeDelayMin) }, { "PerRopeDelayMax", INI::parseDurationUnsignedInt, 0, offsetof(ChinookAIUpdateModuleData, m_perRopeDelayMax) }, { "MinDropHeight", INI::parseReal, 0, offsetof(ChinookAIUpdateModuleData, m_minDropHeight) }, { "WaitForRopesToDrop", INI::parseBool, 0, offsetof(ChinookAIUpdateModuleData, m_waitForRopesToDrop) }, { "RotorWashParticleSystem", INI::parseAsciiString, NULL, offsetof( ChinookAIUpdateModuleData, m_rotorWashParticleSystem ) }, { "UpgradedSupplyBoost", INI::parseInt, NULL, offsetof( ChinookAIUpdateModuleData, m_upgradedSupplyBoost) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- AIStateMachine* ChinookAIUpdate::makeStateMachine() { return newInstance(ChinookAIStateMachine)( getObject(), "ChinookAIStateMachine"); } //------------------------------------------------------------------------------------------------- ChinookAIUpdate::ChinookAIUpdate( Thing *thing, const ModuleData* moduleData ) : SupplyTruckAIUpdate( thing, moduleData ) { m_hasPendingCommand = false; m_flightStatus = CHINOOK_FLYING; // yep, that's right, even if we start "on ground" m_airfieldForHealing = INVALID_ID; m_originalPos.zero(); } //------------------------------------------------------------------------------------------------- ChinookAIUpdate::~ChinookAIUpdate() { } //------------------------------------------------------------------------------------------------- static ParkingPlaceBehaviorInterface* getPP(ObjectID id) { Object* airfield = TheGameLogic->findObjectByID( id ); if (airfield == NULL || airfield->isEffectivelyDead() || !airfield->isKindOf(KINDOF_FS_AIRFIELD)) return NULL; ParkingPlaceBehaviorInterface* pp = NULL; for (BehaviorModule** i = airfield->getBehaviorModules(); *i; ++i) { if ((pp = (*i)->getParkingPlaceBehaviorInterface()) != NULL) break; } return pp; } //------------------------------------------------------------------------------------------------- void ChinookAIUpdate::setAirfieldForHealing(ObjectID id) { // make sure we de-register with current one, if any if (m_airfieldForHealing != INVALID_ID && m_airfieldForHealing != id) { ParkingPlaceBehaviorInterface* pp = getPP(m_airfieldForHealing); if (pp != NULL) { pp->setHealee(getObject(), false); } } m_airfieldForHealing = id; } //------------------------------------------------------------------------------------------------- Bool ChinookAIUpdate::isIdle() const { // we need to do this because we enter an idle state briefly between takeoff/landing in these cases, // but scripting relies on us never claiming to be "idle"... if (m_hasPendingCommand) return false; Bool result = SupplyTruckAIUpdate::isIdle(); if (result && m_flightStatus == CHINOOK_LANDED) { // ditto: if we are waiting to disgorge some folks, we aren't 'idle' ContainModuleInterface* contain = getObject()->getContain(); if (contain && contain->hasObjectsWantingToEnterOrExit()) result = false; } return result; } //------------------------------------------------------------------------------------------------- Bool ChinookAIUpdate::isCurrentlyFerryingSupplies() const { return SupplyTruckAIUpdate::isCurrentlyFerryingSupplies(); } //------------------------------------------------------------------------------------------------- Bool ChinookAIUpdate::isAvailableForSupplying() const { if (!SupplyTruckAIUpdate::isAvailableForSupplying()) return false; ContainModuleInterface* contain = getObject()->getContain(); if( !contain || contain->hasObjectsWantingToEnterOrExit() || contain->getContainCount() || contain->isSpecialOverlordStyleContainer()) return false; return true; } //------------------------------------------------------------------------------------------------- Bool ChinookAIUpdate::isAllowedToAdjustDestination() const { if (m_flightStatus == CHINOOK_LANDED) return false; if( getCurLocomotor()->isInvalidPositionAllowed() ) { return FALSE; } return SupplyTruckAIUpdate::isAllowedToAdjustDestination(); } //------------------------------------------------------------------------------------------------- ObjectID ChinookAIUpdate::getBuildingToNotPathAround() const { if (getAIStateType() == MOVE_TO_COMBAT_DROP || getAIStateType() == DO_COMBAT_DROP) { const Object* goalObj = getStateMachine()->getGoalObject(); if (goalObj) return goalObj->getID(); } return INVALID_ID; } //------------------------------------------------------------------------------------------------- AIFreeToExitType ChinookAIUpdate::getAiFreeToExit(const Object* exiter) const { if (m_flightStatus == CHINOOK_LANDED || (m_flightStatus == CHINOOK_DOING_COMBAT_DROP && exiter->isKindOf(KINDOF_CAN_RAPPEL))) return FREE_TO_EXIT; return WAIT_TO_EXIT; } //------------------------------------------------------------------------------------------------- Bool ChinookAIUpdate::chooseLocomotorSet(LocomotorSetType wst) { if (m_flightStatus == CHINOOK_LANDED) wst = LOCOMOTORSET_TAXIING; return SupplyTruckAIUpdate::chooseLocomotorSet(wst); } //------------------------------------------------------------------------------------------------- UpdateSleepTime ChinookAIUpdate::update() { ParkingPlaceBehaviorInterface* pp = getPP(m_airfieldForHealing); if (pp != NULL) { if (m_flightStatus == CHINOOK_LANDED && !m_hasPendingCommand && getObject()->getBodyModule()->getHealth() == getObject()->getBodyModule()->getMaxHealth()) { // we're completely healed, so take off again pp->setHealee(getObject(), false); setMyState(TAKING_OFF, NULL, NULL, CMD_FROM_AI); } else { pp->setHealee(getObject(), m_flightStatus == CHINOOK_LANDED); } } else { setAirfieldForHealing(INVALID_ID); } // have to call our parent's isIdle, because we override it to never return true // when we have a pending command... ContainModuleInterface* contain = getObject()->getContain(); if( contain ) { if (SupplyTruckAIUpdate::isIdle()) { Bool waitingToEnterOrExit = contain->hasObjectsWantingToEnterOrExit(); if (m_hasPendingCommand) { AICommandParms parms(AICMD_MOVE_TO_POSITION, CMD_FROM_AI); // values don't matter, will be wiped by next line m_pendingCommand.reconstitute(parms); m_hasPendingCommand = false; aiDoCommand(&parms); } else if (waitingToEnterOrExit && m_flightStatus != CHINOOK_LANDED) { setMyState(LANDING, NULL, NULL, CMD_FROM_AI); } else if (!waitingToEnterOrExit && m_flightStatus == CHINOOK_LANDED && m_airfieldForHealing == INVALID_ID) { setMyState(TAKING_OFF, NULL, NULL, CMD_FROM_AI); } } if ( TheGameLogic->getFrame()%10 == 1 ) { Object *victim = getCurrentVictim(); if ( victim ) { // privateAttackObject( victim, 9999, CMD_FROM_AI ); //If we are attacking something, lets make sure our passengers follow suit if ( contain->isPassengerAllowedToFire() ) { const ContainedItemsList *passengerList = contain->getContainedItemsList(); ContainedItemsList::const_iterator passengerIterator; passengerIterator = passengerList->begin(); while( passengerIterator != passengerList->end() ) { Object *passenger = *passengerIterator; //Advance to the next iterator passengerIterator++; AIUpdateInterface *passengerAI = passenger->getAIUpdateInterface(); if( passengerAI && (passengerAI->getCurrentVictim() == NULL) ) { passengerAI->aiAttackObject( victim, 999, CMD_FROM_AI ); } } } } } } // Just a handy spot to handle that groovy client effect of the rotor wash if ( getObject()->getShroudedStatus( ThePlayerList->getLocalPlayer()->getPlayerIndex()) == OBJECTSHROUD_CLEAR ) { if ( m_flightStatus == CHINOOK_LANDING || m_flightStatus == CHINOOK_TAKING_OFF || m_flightStatus == CHINOOK_LANDED ) { Coord3D pos = *getObject()->getPosition(); Real chopperElevation = pos.z; pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y ) + 3.0f; chopperElevation -= pos.z; if ( GameClientRandomValueReal( 0.0f, chopperElevation ) < 5.0f ) { const ParticleSystemTemplate *tmp = TheParticleSystemManager->findTemplate( getChinookAIUpdateModuleData()->m_rotorWashParticleSystem ); ParticleSystem *system; if( tmp ) { system = TheParticleSystemManager->createParticleSystem( tmp ); if( system ) { system->setPosition( &pos ); } } } } } return SupplyTruckAIUpdate::update(); } //------------------------------------------------------------------------------------------------- void ChinookAIUpdate::setMyState( StateID cmd, Object* target, const Coord3D* pos, CommandSourceType cmdSource ) { getStateMachine()->clear(); getStateMachine()->setGoalObject( target ); setGoalPositionClipped(pos, cmdSource); // yeah, null is ok here. setLastCommandSource( cmdSource ); getStateMachine()->setState( cmd ); } //---------------------------------------------------------------------------------------- /** * Get repaired at the repair depot */ void ChinookAIUpdate::privateGetRepaired( Object *repairDepot, CommandSourceType cmdSource ) { // we are already landing. just ignore it. if (m_flightStatus == CHINOOK_LANDING || m_flightStatus == CHINOOK_LANDED) return; // sanity, if we can't get repaired from here get out of here if( TheActionManager->canGetRepairedAt( getObject(), repairDepot, cmdSource ) == FALSE ) return; setAirfieldForHealing(repairDepot->getID()); Coord3D pos = *repairDepot->getPosition(); Coord3D tmp; FindPositionOptions options; options.maxRadius = repairDepot->getGeometryInfo().getBoundingCircleRadius() * 100.0f; if (ThePartitionManager->findPositionAround(&pos, &options, &tmp)) pos = tmp; setMyState(MOVE_TO_AND_LAND, NULL, &pos, cmdSource); } //------------------------------------------------------------------------------------------------- void ChinookAIUpdate::privateCombatDrop( Object* target, const Coord3D& pos, CommandSourceType cmdSource ) { // // when there is a target present, we must verify that we can logically do the action when // we get commands from players (we'll assume AI knows what its doing) // if( target != NULL && cmdSource == CMD_FROM_PLAYER && TheActionManager->canEnterObject( getObject(), target, cmdSource, COMBATDROP_INTO ) == FALSE ) return; Coord3D localPos = pos; if (target == NULL) { // if target is null, we are dropping at a pos, not into a bldg. // in this case, ensure there is no structure at the pos... this can happen // if you combat-drop into a spot in the fog-of-war. Coord3D tmp; FindPositionOptions options; options.maxRadius = getObject()->getGeometryInfo().getBoundingCircleRadius() * 100.0f; if (ThePartitionManager->findPositionAround(&localPos, &options, &tmp)) { localPos = tmp; } } // start the combat drop process setMyState(MOVE_TO_COMBAT_DROP, target, &localPos, cmdSource); } //------------------------------------------------------------------------------------------------- void ChinookAIUpdate::aiDoCommand(const AICommandParms* parms) { // this gets reset every time a command is issued. setAirfieldForHealing(INVALID_ID); if (!isAllowedToRespondToAiCommands(parms)) return; if (m_flightStatus == CHINOOK_TAKING_OFF || m_flightStatus == CHINOOK_LANDING || m_flightStatus == CHINOOK_DOING_COMBAT_DROP) { // have to wait for takeoff or landing (or rappel) to complete, just store the sucker. m_pendingCommand.store(*parms); m_hasPendingCommand = true; return; } Bool passItThru = true; switch (parms->m_cmd) { case AICMD_IDLE: case AICMD_BUSY: case AICMD_FOLLOW_EXITPRODUCTION_PATH: { // don't need (or want) to take off for these... // just pass it thru. } break; case AICMD_MOVE_TO_POSITION_AND_EVACUATE: case AICMD_MOVE_TO_POSITION_AND_EVACUATE_AND_EXIT: { const Real THRESH = 3.0f; const Real THRESH_SQR = THRESH*THRESH; if (calcDistSqr(*getObject()->getPosition(), parms->m_pos) > THRESH_SQR && m_flightStatus == CHINOOK_LANDED) { // gotta take off first! m_pendingCommand.store(*parms); m_hasPendingCommand = true; setMyState(TAKING_OFF, NULL, NULL, CMD_FROM_AI); passItThru = false; } else { // do this INSTEAD of the standard stuff setMyState( (parms->m_cmd == AICMD_MOVE_TO_POSITION_AND_EVACUATE) ? MOVE_TO_AND_EVAC : MOVE_TO_AND_EVAC_AND_EXIT_INIT, NULL, &parms->m_pos, CMD_FROM_AI); passItThru = false; } } break; case AICMD_EXIT: case AICMD_EVACUATE: { if (m_flightStatus != CHINOOK_LANDED) { // gotta land first! m_pendingCommand.store(*parms); m_hasPendingCommand = true; setMyState(LANDING, NULL, NULL, CMD_FROM_AI); passItThru = false; } } break; default: { if (m_flightStatus != CHINOOK_FLYING) { // gotta take off first! m_pendingCommand.store(*parms); m_hasPendingCommand = true; setMyState(TAKING_OFF, NULL, NULL, CMD_FROM_AI); passItThru = false; } } break; } if (passItThru) { m_hasPendingCommand = false; SupplyTruckAIUpdate::aiDoCommand(parms); } } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void ChinookAIUpdate::crc( Xfer *xfer ) { SupplyTruckAIUpdate::crc(xfer); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void ChinookAIUpdate::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 2; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class SupplyTruckAIUpdate::xfer(xfer); xfer->xferBool(&m_hasPendingCommand); if (m_hasPendingCommand) { m_pendingCommand.doXfer(xfer); } xfer->xferUser(&m_flightStatus, sizeof(m_flightStatus)); xfer->xferObjectID(&m_airfieldForHealing); if( version >= 2 ) { xfer->xferCoord3D( &m_originalPos ); } } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void ChinookAIUpdate::loadPostProcess( void ) { SupplyTruckAIUpdate::loadPostProcess(); } // end loadPostProcess //---------------------------------------------------------------------------------------- /** * Enter idle state. */ void ChinookAIUpdate::privateIdle(CommandSourceType cmdSource) { // Just an extra step, here, before extending idle to parent classes. // Living in you own privateIdle-ho. ContainModuleInterface* contain = getObject()->getContain(); if( contain != NULL ) { Object *rider = (Object*)contain->friend_getRider(); if ( rider ) { AIUpdateInterface *riderAI = rider->getAIUpdateInterface(); if( riderAI ) riderAI->aiIdle( cmdSource ); } } SupplyTruckAIUpdate::privateIdle( cmdSource ); } //------------------------------------------------------------------------------------------------- /** * Attack given object */ void ChinookAIUpdate::privateAttackObject( Object *victim, Int maxShotsToFire, CommandSourceType cmdSource ) { if ( ! getObject()->isKindOf( KINDOF_CAN_ATTACK ) ) return; ContainModuleInterface* contain = getObject()->getContain(); if( contain != NULL ) { // As an extension of the normal attack, I may want to tell my passengers to attack // too, but only if this is a direct command. (As opposed to a passive aquire) if( (cmdSource == CMD_FROM_PLAYER || cmdSource == CMD_FROM_SCRIPT) ) { //if ( contain->isPassengerAllowedToFire() )//moved to below { const ContainedItemsList *passengerList = contain->getContainedItemsList(); ContainedItemsList::const_iterator passengerIterator; passengerIterator = passengerList->begin(); while( passengerIterator != passengerList->end() ) { Object *passenger = *passengerIterator; //Advance to the next iterator passengerIterator++; if ( ! contain->isPassengerAllowedToFire( passenger->getID() ) ) continue; if ( ! passenger->isKindOf( KINDOF_INFANTRY )) continue; // If I am an overlord with a gattling upgrade, I do not tell it to fire if it is disabled if ( passenger->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) { if( passenger->isDisabledByType( DISABLED_HACKED ) || passenger->isDisabledByType( DISABLED_EMP ) || passenger->isDisabledByType( DISABLED_SUBDUED ) || passenger->isDisabledByType( DISABLED_PARALYZED) ) continue; } AIUpdateInterface *passengerAI = passenger->getAIUpdateInterface(); if( passengerAI ) { passengerAI->aiAttackObject( victim, maxShotsToFire, cmdSource ); } } } private___TellPortableStructureToAttackWithMe( victim, maxShotsToFire, cmdSource ); } } AIUpdateInterface::privateAttackObject( victim, maxShotsToFire, cmdSource ); } void ChinookAIUpdate::private___TellPortableStructureToAttackWithMe( Object *victim, Int maxShotsToFire, CommandSourceType cmdSource ) { ContainModuleInterface* contain = getObject()->getContain(); if( contain != NULL ) { //--------- THE GATTLING UPGRADE OR THE GUYS IN THE BUNKER_NOT_A_BUNKER------------- Object *rider = (Object*)contain->friend_getRider(); if ( rider && rider->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && !rider->isDisabledByType( DISABLED_HACKED ) && !rider->isDisabledByType( DISABLED_EMP ) && !rider->isDisabledByType( DISABLED_SUBDUED ) && !rider->isDisabledByType( DISABLED_PARALYZED) ) { AIUpdateInterface *riderAI = rider->getAIUpdateInterface(); if( riderAI ) { riderAI->aiAttackObject( victim, maxShotsToFire, cmdSource ); } } } } //------------------------------------------------------------------------------------------------- /** * Attack given object */ void ChinookAIUpdate::privateForceAttackObject( Object *victim, Int maxShotsToFire, CommandSourceType cmdSource ) { if ( ! getObject()->isKindOf( KINDOF_CAN_ATTACK ) ) return; ContainModuleInterface* contain = getObject()->getContain(); if( contain != NULL ) { // As an extension of the normal attack, I may want to tell my passengers to attack // too, but only if this is a direct command. (As opposed to a passive aquire) if( (cmdSource == CMD_FROM_PLAYER || cmdSource == CMD_FROM_SCRIPT) ) { // if ( contain->isPassengerAllowedToFire() ) { const ContainedItemsList *passengerList = contain->getContainedItemsList(); ContainedItemsList::const_iterator passengerIterator; passengerIterator = passengerList->begin(); while( passengerIterator != passengerList->end() ) { Object *passenger = *passengerIterator; //Advance to the next iterator passengerIterator++; if ( ! contain->isPassengerAllowedToFire( passenger->getID() ) ) continue; if ( ! passenger->isKindOf( KINDOF_INFANTRY )) continue; // If I am an overlord with a gattling upgrade, I do not tell it to fire if it is disabled if ( passenger->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) { if( passenger->isDisabledByType( DISABLED_HACKED ) || passenger->isDisabledByType( DISABLED_EMP ) || passenger->isDisabledByType( DISABLED_SUBDUED ) || passenger->isDisabledByType( DISABLED_PARALYZED) ) continue; } AIUpdateInterface *passengerAI = passenger->getAIUpdateInterface(); if( passengerAI ) { passengerAI->aiForceAttackObject( victim, maxShotsToFire, cmdSource ); } } } //--------- THE GATTLING UPGRADE OR THE GUYS IN THE BUNKER_NOT_A_BUNKER------------- Object *rider = (Object*)contain->friend_getRider(); if ( rider && rider->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && !rider->isDisabledByType( DISABLED_HACKED ) && !rider->isDisabledByType( DISABLED_EMP ) && !rider->isDisabledByType( DISABLED_SUBDUED ) && !rider->isDisabledByType( DISABLED_PARALYZED) ) { AIUpdateInterface *riderAI = rider->getAIUpdateInterface(); if( riderAI ) { riderAI->aiForceAttackObject( victim, maxShotsToFire, cmdSource ); } } } } AIUpdateInterface::privateForceAttackObject( victim, maxShotsToFire, cmdSource ); } //------------------------------------------------------------------------------------------------- /** * Attack given position */ void ChinookAIUpdate::privateAttackPosition( const Coord3D *pos, Int maxShotsToFire, CommandSourceType cmdSource ) { if ( ! getObject()->isKindOf( KINDOF_CAN_ATTACK ) ) return; ContainModuleInterface* contain = getObject()->getContain(); if( contain != NULL ) { // As an extension of the normal attack, I may want to tell my passengers to attack // too, but only if this is a direct command. (As opposed to a passive aquire) if( (cmdSource == CMD_FROM_PLAYER || cmdSource == CMD_FROM_SCRIPT) ) { //if ( contain->isPassengerAllowedToFire() ) { const ContainedItemsList *passengerList = contain->getContainedItemsList(); ContainedItemsList::const_iterator passengerIterator; passengerIterator = passengerList->begin(); while( passengerIterator != passengerList->end() ) { Object *passenger = *passengerIterator; //Advance to the next iterator passengerIterator++; if ( ! contain->isPassengerAllowedToFire( passenger->getID() ) ) continue; if ( ! passenger->isKindOf( KINDOF_INFANTRY )) continue; // If I am an overlord with a gattling upgrade, I do not tell it ti fire if it is disabled if ( passenger->isKindOf( KINDOF_PORTABLE_STRUCTURE ) ) { if( passenger->isDisabledByType( DISABLED_HACKED ) || passenger->isDisabledByType( DISABLED_EMP) || passenger->isDisabledByType( DISABLED_SUBDUED) || passenger->isDisabledByType( DISABLED_PARALYZED) ) continue; } AIUpdateInterface *passengerAI = passenger->getAIUpdateInterface(); if( passengerAI ) { passengerAI->aiAttackPosition( pos, maxShotsToFire, cmdSource ); } } } //--------- THE GATTLING UPGRADE OR THE GUYS IN THE BUNKER_NOT_A_BUNKER------------- Object *rider = (Object*)contain->friend_getRider(); if ( rider && rider->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && !rider->isDisabledByType( DISABLED_HACKED ) && !rider->isDisabledByType( DISABLED_EMP ) && !rider->isDisabledByType( DISABLED_SUBDUED ) && !rider->isDisabledByType( DISABLED_PARALYZED) ) { AIUpdateInterface *riderAI = rider->getAIUpdateInterface(); if( riderAI ) { riderAI->aiAttackPosition( pos, maxShotsToFire, cmdSource ); } } } } AIUpdateInterface::privateAttackPosition( pos, maxShotsToFire, cmdSource ); } //------------------------------------------------------------------------------------------------ Int ChinookAIUpdate::getUpgradedSupplyBoost() const { Player *player = getObject()->getControllingPlayer(); static const UpgradeTemplate *supplyLinesTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_AmericaSupplyLines" ); if (player && supplyLinesTemplate && player->hasUpgradeComplete(supplyLinesTemplate)) return getChinookAIUpdateModuleData()->m_upgradedSupplyBoost; else return 0; }