/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ //////////////////////////////////////////////////////////////////////////////// // // // (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: RailedTransportAIUpdate.cpp ////////////////////////////////////////////////////////////// // Author: Colin Day, August 2002 // Desc: Railed Transport AI /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/ThingTemplate.h" #include "Common/GameState.h" #include "GameLogic/Locomotor.h" #include "GameLogic/Module/RailedTransportAIUpdate.h" #include "GameLogic/Module/RailedTransportDockUpdate.h" #include "GameLogic/Object.h" // TYPES ////////////////////////////////////////////////////////////////////////////////////////// static const Int INVALID_PATH = -1; // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ RailedTransportAIUpdateModuleData::RailedTransportAIUpdateModuleData( void ) { } // end RailedTransportAIUpdateModuleData // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RailedTransportAIUpdateModuleData::buildFieldParse( MultiIniFieldParse &p ) { AIUpdateModuleData::buildFieldParse( p ); static const FieldParse dataFieldParse[] = { { "PathPrefixName", INI::parseAsciiString, NULL, offsetof( RailedTransportAIUpdateModuleData, m_pathPrefixName ) }, { 0, 0, 0, 0 } }; p.add( dataFieldParse ); } // end buildFieldParse //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- RailedTransportAIUpdate::RailedTransportAIUpdate( Thing *thing, const ModuleData *moduleData ) : AIUpdateInterface( thing, moduleData ) { m_inTransit = FALSE; for( Int i = 0; i < MAX_WAYPOINT_PATHS; ++i ) { m_path[ i ].startWaypointID = 0; m_path[ i ].endWaypointID = 0; } // end for i m_numPaths = 0; m_currentPath = INVALID_PATH; m_waypointDataLoaded = FALSE; } // end RailedTransportAIUpdate //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- RailedTransportAIUpdate::~RailedTransportAIUpdate( void ) { } // end ~RailedTransportAIUpdate // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RailedTransportAIUpdate::loadWaypointData( void ) { const RailedTransportAIUpdateModuleData *modData = getRailedTransportAIUpdateModuleData(); // find all the possible waypoint paths we can use Waypoint *start, *end; AsciiString name; for( Int i = 0; i < MAX_WAYPOINT_PATHS; ++i ) { // find start waypoint name.format( "%sStart%02d", modData->m_pathPrefixName.str(), i + 1 ); start = TheTerrainLogic->getWaypointByName( name ); // find end waypoint name.format( "%sEnd%02d", modData->m_pathPrefixName.str(), i + 1 ); end = TheTerrainLogic->getWaypointByName( name ); // if we have a start and an end, we have a valid path if( start && end ) { m_path[ i ].startWaypointID = start->getID(); m_path[ i ].endWaypointID = end->getID(); m_numPaths++; } // end if } // end for i // waypoint data is loaded m_waypointDataLoaded = TRUE; } // end loadWaypointData // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RailedTransportAIUpdate::pickAndMoveToInitialLocation( void ) { Object *us = getObject(); const Coord3D *ourPos = us->getPosition(); // select the path with the closest ending waypoint to our location Waypoint *waypoint, *closestEndWaypoint = NULL; Int closestPath = INVALID_PATH; Real closestDist = 99999999.9f; for( Int i = 0; i < m_numPaths; ++i ) { // get this waypoint waypoint = TheTerrainLogic->getWaypointByID( m_path[ i ].endWaypointID ); if( waypoint ) { Coord3D v; // vector from us to waypoint v.x = waypoint->getLocation()->x - ourPos->x; v.y = waypoint->getLocation()->y - ourPos->y; v.z = waypoint->getLocation()->z - ourPos->z; // what is the distance Real dist = v.length(); // if this distance is smaller, use this one if( dist < closestDist ) { closestDist = dist; closestPath = i; closestEndWaypoint = waypoint; } // end if } // end if } // end for i // a path must have been found DEBUG_ASSERTCRASH( closestPath != INVALID_PATH, ("No suitable starting waypoint path could be found for '%s'\n", us->getTemplate()->getName().str()) ); // follow the waypoint path to its destination end point aiFollowWaypointPath( closestEndWaypoint, CMD_FROM_AI ); // this is now our current path m_currentPath = closestPath; // we are now "in transit" setInTransit( TRUE ); } // end pickAndMoveToInitialLocation // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ UpdateSleepTime RailedTransportAIUpdate::update( void ) { Object *us = getObject(); // load the waypoint data if not loaded if( m_waypointDataLoaded == FALSE ) loadWaypointData(); // extend base class UpdateSleepTime result; result = AIUpdateInterface::update(); // railed transports move ultra accurate like Locomotor *currentLocomotor = getCurLocomotor(); if( currentLocomotor ) currentLocomotor->setUltraAccurate( TRUE ); // // if we have no current path selected pick one and move to the end waypoint of that // path. this will set us up in an initial position at the end of the closest path // so that stuff can be loaded into us // if( m_currentPath == INVALID_PATH && m_numPaths > 0 ) pickAndMoveToInitialLocation(); // // if we're in transit, see if we're close enough to the destination waypoint to be // considered as "there" and open up the dock // if( m_inTransit ) { // sanity DEBUG_ASSERTCRASH( m_currentPath != INVALID_PATH, ("RailedTransportAIUpdate: Invalid current path '%s'\n", m_currentPath) ); // get our target waypoint Waypoint *waypoint = TheTerrainLogic->getWaypointByID( m_path[ m_currentPath ].endWaypointID ); // sanity DEBUG_ASSERTCRASH( waypoint, ("RailedTransportAIUpdate: Invalid target waypoint\n") ); // how far away are we from the target waypoint const Coord3D *start = us->getPosition(); const Coord3D *end = waypoint->getLocation(); Coord3D v; v.x = end->x - start->x; v.y = end->y - start->y; v.z = end->z - start->z; Real dist = v.length(); if( dist <= 5.0f || isIdle() ) { // we are no longer in transit setInTransit( FALSE ); } // end if } // end if return UPDATE_SLEEP_NONE; } // end update // ------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------- void RailedTransportAIUpdate::aiDoCommand( const AICommandParms *parms ) { // if not allowed to respond to any command get out of here if( isAllowedToRespondToAiCommands(parms) == FALSE ) return; // we ignore all commands from the player except the one to start a transit and to unload if( parms->m_cmdSource == CMD_FROM_PLAYER && parms->m_cmd != AICMD_EXECUTE_RAILED_TRANSPORT && parms->m_cmd != AICMD_EVACUATE ) return; // call the default do command AIUpdateInterface::aiDoCommand( parms ); } // end aiDoCommand /////////////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE //////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RailedTransportAIUpdate::setInTransit( Bool inTransit ) { Object *us = getObject(); DockUpdateInterface *dui = us->getDockUpdateInterface(); // open up the dock if( dui ) dui->setDockOpen( !inTransit ); // no longer in transit m_inTransit = inTransit; } // end setInTransit // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RailedTransportAIUpdate::privateExecuteRailedTransport( CommandSourceType cmdSource ) { Object *us = getObject(); // // find us our railed dock interface, note that we MUST go through the modules here because // if we just call the method getReailedTransportDockUpdateInterface, it will execute // the method for *THIS AI UPDATE MODULE* which of course is not our dock update // RailedTransportDockUpdateInterface *rtdui = NULL; for( BehaviorModule **u = us->getBehaviorModules(); *u; ++u ) if( (rtdui = (*u)->getRailedTransportDockUpdateInterface()) != NULL ) break; // if we've in the process of loading or unloading anything we can't do a transport sequence if( rtdui == NULL || rtdui->isLoadingOrUnloading() ) return; // pick the next path if( ++m_currentPath >= m_numPaths ) m_currentPath = 0; // find the start waypoint for our current path Waypoint *startWaypoint = TheTerrainLogic->getWaypointByID( m_path[ m_currentPath ].startWaypointID ); DEBUG_ASSERTCRASH( startWaypoint, ("RailedTransportAIUpdate: Start waypoint not found\n") ); // follow this waypoint path aiFollowWaypointPath( startWaypoint, CMD_FROM_AI ); // we are now in transit setInTransit( TRUE ); } // end privateExecuteRailedTransport // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RailedTransportAIUpdate::privateEvacuate( Int exposeStealthUnits, CommandSourceType cmdSource ) { Object *us = getObject(); // // find us our railed dock interface, note that we MUST go through the modules here because // if we just call the method getReailedTransportDockUpdateInterface, it will execute // the method for *THIS AI UPDATE MODULE* which of course is not our dock update // RailedTransportDockUpdateInterface *rtdui = NULL; for( BehaviorModule **u = us->getBehaviorModules(); *u; ++u ) if( (rtdui = (*u)->getRailedTransportDockUpdateInterface()) != NULL ) break; // sanity if( rtdui == NULL ) return; // can't unload when in transit if( m_inTransit == TRUE ) return; // cannot evacuate when we're loading or unloading anything if( rtdui->isLoadingOrUnloading() ) return; // start the manual undocking process rtdui->unloadAll(); } // end privateEvacuate // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void RailedTransportAIUpdate::crc( Xfer *xfer ) { // extend base class AIUpdateInterface::crc(xfer); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void RailedTransportAIUpdate::xfer( Xfer *xfer ) { XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class AIUpdateInterface::xfer(xfer); xfer->xferBool(&m_inTransit); xfer->xferInt(&m_numPaths); if (m_numPaths > MAX_WAYPOINT_PATHS) { DEBUG_CRASH(("m_numPaths %d exceeds limit %d.", m_numPaths, MAX_WAYPOINT_PATHS)); throw SC_INVALID_DATA; } Int i; for (i=0; ixferUnsignedInt(&m_path[i].startWaypointID); xfer->xferUnsignedInt(&m_path[i].endWaypointID); } xfer->xferInt(&m_currentPath); xfer->xferBool(&m_waypointDataLoaded); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void RailedTransportAIUpdate::loadPostProcess( void ) { // extend base class AIUpdateInterface::loadPostProcess(); } // end loadPostProcess