/* ** 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: RailedTransportDockUpdate.cpp //////////////////////////////////////////////////////////// // Author: Colin Day, August 2002 // Desc: Railed Transport Dock Update /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameClient/Drawable.h" #include "GameClient/InGameUI.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/ContainModule.h" #include "GameLogic/Module/OpenContain.h" #include "GameLogic/Module/RailedTransportDockUpdate.h" // TYPES ////////////////////////////////////////////////////////////////////////////////////////// enum { UNLOAD_ALL = -1 }; // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ RailedTransportDockUpdateModuleData::RailedTransportDockUpdateModuleData( void ) { m_pullInsideDurationInFrames = 0; m_pushOutsideDurationInFrames = 0; m_toleranceDistance = 50.0f; } // end RailedTransportDockUpdateModuleData // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ /*static*/ void RailedTransportDockUpdateModuleData::buildFieldParse( MultiIniFieldParse &p ) { DockUpdateModuleData::buildFieldParse( p ); static const FieldParse dataFieldParse[] = { { "PullInsideDuration", INI::parseDurationUnsignedInt, NULL, offsetof( RailedTransportDockUpdateModuleData, m_pullInsideDurationInFrames ) }, { "PushOutsideDuration",INI::parseDurationUnsignedInt, NULL, offsetof( RailedTransportDockUpdateModuleData, m_pushOutsideDurationInFrames ) }, { "ToleranceDistance", INI::parseReal, NULL, offsetof( RailedTransportDockUpdateModuleData, m_toleranceDistance ) }, { 0, 0, 0, 0 } }; p.add( dataFieldParse ); } // end buildFieldParse /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ RailedTransportDockUpdate::RailedTransportDockUpdate( Thing *thing, const ModuleData *moduleData ) : DockUpdate( thing, moduleData ) { m_dockingObjectID = INVALID_ID; m_pullInsideDistancePerFrame = 0.0f; m_unloadingObjectID = INVALID_ID; m_pushOutsideDistancePerFrame = 0.0f; m_unloadCount = UNLOAD_ALL; } // end RailedTransportDockUpdate // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ RailedTransportDockUpdate::~RailedTransportDockUpdate( void ) { } // end ~RailedTransportDockUpdate // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ UpdateSleepTime RailedTransportDockUpdate::update( void ) { // extend functionality UpdateSleepTime result; result = DockUpdate::update(); // do any pull in docking we need to doPullInDocking(); // do any push out docking doPushOutDocking(); return UPDATE_SLEEP_NONE; } // end update // ------------------------------------------------------------------------------------------------ /** The dock action callback, return FALSE when done docking */ // ------------------------------------------------------------------------------------------------ Bool RailedTransportDockUpdate::action( Object *docker, Object *drone ) { Object *us = getObject(); // sanity if( docker == NULL ) return FALSE; // set this object as docking with us if not already done so if( m_dockingObjectID != docker->getID() ) { // // given the amount of time we want it to take to "pull" the object inside, figure out // how much distance it should traverse towards our center every frame // const Coord3D *dockerPos = docker->getPosition(); const Coord3D *dockPos = us->getPosition(); Coord3D v; v.x = dockPos->x - dockerPos->x; v.y = dockPos->y - dockerPos->y; v.z = dockPos->z - dockerPos->z; // how far do we have to go Real mag = v.length(); const RailedTransportDockUpdateModuleData *modData = getRailedTransportDockUpdateModuleData(); //Are we close enough to even be able to get sucked in? if( mag <= modData->m_toleranceDistance ) { m_dockingObjectID = docker->getID(); // don't let the user interact with this object anymore TheGameLogic->deselectObject(docker, PLAYERMASK_ALL, TRUE); docker->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) ); // hold the object so physics doesn't mess with it anymore docker->setDisabled( DISABLED_HELD ); // now that we know how far we must go, now much distance should we travel every frame m_pullInsideDistancePerFrame = mag / modData->m_pullInsideDurationInFrames; // orient docker so its facing toward the transport Coord2D angleVector; angleVector.x = dockPos->x - dockerPos->x; angleVector.y = dockPos->y - dockerPos->y; docker->setOrientation( angleVector.toAngle() ); } } // end if return TRUE; } // end action // ------------------------------------------------------------------------------------------------ /** Is clear to enter the railed transport */ // ------------------------------------------------------------------------------------------------ Bool RailedTransportDockUpdate::isClearToEnter( Object const *docker ) const { const Object *us = getObject(); // first do base class restrictions Bool clear = DockUpdate::isClearToEnter( docker ); if( clear == FALSE ) return FALSE; // we have additional requirements, we are a transporting dock so we can't be full ContainModuleInterface *contain = us->getContain(); if( contain && contain->isValidContainerFor( docker, TRUE ) == FALSE ) return FALSE; return TRUE; } // end isClearToEnter // ------------------------------------------------------------------------------------------------ /** Is anything currently loading or unloading */ // ------------------------------------------------------------------------------------------------ Bool RailedTransportDockUpdate::isLoadingOrUnloading( void ) { if( m_unloadingObjectID != INVALID_ID || m_dockingObjectID != INVALID_ID ) return TRUE; return FALSE; } // end isLoadingOrUnloading // ------------------------------------------------------------------------------------------------ /** Start the unload process */ // ------------------------------------------------------------------------------------------------ void RailedTransportDockUpdate::unloadAll( void ) { // sanity, if we're already unloading, ignore this command and just allow us to finish if( m_unloadingObjectID != INVALID_ID ) return; // start our first object unloading and continue through them all m_unloadCount = UNLOAD_ALL; unloadNext(); } // end manualUnload // ------------------------------------------------------------------------------------------------ /** Unload a single individual only */ // ------------------------------------------------------------------------------------------------ void RailedTransportDockUpdate::unloadSingleObject( Object *obj ) { // start the unload process of a single object m_unloadCount = 1; unloadNext(); } // end unloadSingleObject /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // ------------------------------------------------------------------------------------------------ /** If we have an object recorded as currently docking with us, pull that object inside * and when it is inside, contain it */ // ------------------------------------------------------------------------------------------------ void RailedTransportDockUpdate::doPullInDocking( void ) { // // if we're pulling an object inside of us, do that pull now. we need this so that the // railed transport can "pull" objects inside it because typically those objects can only drive // on land and have a hard time driving "inside" the railed transport ... so we fake it! // if( m_dockingObjectID != INVALID_ID ) { Object *us = getObject(); Object *docker = TheGameLogic->findObjectByID( m_dockingObjectID ); // check for docker gone if( docker == NULL ) m_dockingObjectID = INVALID_ID; // pull it if( docker ) { const Coord3D *dockerPos = docker->getPosition(); const Coord3D *dockPos = us->getPosition(); // get the vector from the docker to the dock pos Coord3D v; v.x = dockPos->x - dockerPos->x; v.y = dockPos->y - dockerPos->y; v.z = dockPos->z - dockerPos->z; v.normalize(); // apply "movement" to the vector v.x *= m_pullInsideDistancePerFrame; v.y *= m_pullInsideDistancePerFrame; // apply current position of the docker to the vector v.x += dockerPos->x; v.y += dockerPos->y; v.z = dockerPos->z; // keep Z height the same and just scoot along the ground // set the new position docker->setPosition( &v ); // // set the model condition for the object as "moving" even though it really // isn't in the traditional sense, but we don't want them to scoot slide into // the transport and look wierd // docker->setModelConditionState( MODELCONDITION_MOVING ); // if we're at the destination then stop and put is inside the dock object Real distSq = ThePartitionManager->getDistanceSquared( docker, us, FROM_CENTER_2D ); Real closeEnoughDistance = 6.0f; if( distSq <= (closeEnoughDistance * closeEnoughDistance) ) { // the object is now no longer "moving" docker->clearModelConditionState( MODELCONDITION_MOVING ); // stop the dock action cancelDock( docker ); // stop the docker from doing anything by going idle AIUpdateInterface *dockerAI = docker->getAIUpdateInterface(); if( dockerAI ) dockerAI->aiIdle( CMD_FROM_AI ); // put object inside us ContainModuleInterface *contain = us->getContain(); if( contain ) { contain->addToContain( docker ); } // no object is docking now m_dockingObjectID = INVALID_ID; } // end if } // end if } // end if } // end doPullInDocking // ------------------------------------------------------------------------------------------------ /** If we have an object recorded as being pushed out of us then do that here */ // ------------------------------------------------------------------------------------------------ void RailedTransportDockUpdate::doPushOutDocking( void ) { if( m_unloadingObjectID ) { Object *unloader = TheGameLogic->findObjectByID( m_unloadingObjectID ); // if unloader is not found (like they got destroyed) unload the next object inside if( unloader == NULL ) { unloadNext(); return; } // end if // pull it if( unloader ) { const Coord3D *unloaderPos = unloader->getPosition(); // get the destination point as the DOCKEND Coord3D destPos; getExitPosition( unloader, &destPos ); destPos.z = TheTerrainLogic->getGroundHeight( destPos.x, destPos.y ); // get the vector from the unloader to the destination point Coord3D v; v.x = destPos.x - unloaderPos->x; v.y = destPos.y - unloaderPos->y; v.z = destPos.z - unloaderPos->z; v.normalize(); // apply "movement" to that vector v.x *= m_pushOutsideDistancePerFrame; v.y *= m_pushOutsideDistancePerFrame; // apply current position of the unloader to the vector v.x += unloaderPos->x; v.y += unloaderPos->y; v.z = destPos.z; // keep Z height the same and just scoot along the ground // set the new position unloader->setPosition( &v ); // // set the model condition for the object as "moving" even though it really // isn't in the traditional sense, but we don't want them to scoot slide into // the transport and look wierd // unloader->setModelConditionState( MODELCONDITION_MOVING ); // if we're at the destination then stop and unload the next object if present Real distSq = sqr( destPos.x - v.x ) + sqr( destPos.y - v.y ) + sqr( destPos.z - v.z ); Real closeEnoughDistance = 3.0f; if( distSq <= sqr( closeEnoughDistance ) ) { Object *us = getObject(); // the object is now no longer "moving" unloader->clearModelConditionState( MODELCONDITION_MOVING ); // set the unloaded object as idle AIUpdateInterface *unloaderAI = unloader->getAIUpdateInterface(); if( unloaderAI ) unloaderAI->aiIdle( CMD_FROM_AI ); // clear the held status from this unloading object unloader->clearDisabled( DISABLED_HELD ); // we can now be selected by the player again unloader->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) ); // tell the unloader to move to one of the dock positions and out of the way Drawable *draw = us->getDrawable(); if( unloaderAI && draw ) { Coord3D finalPos; draw->getPristineBonePositions( "DOCKWAITING07", 0, &finalPos, NULL, 1 ); us->convertBonePosToWorldPos( &finalPos, NULL, &finalPos, NULL ); unloaderAI->aiMoveToPosition( &finalPos, CMD_FROM_AI ); } // end if // unload the next object unloadNext(); } // end if } // end if } // end if, m_unloadingID } // end doPushOutDocking // ------------------------------------------------------------------------------------------------ /** Iterate callback for the finding the first contained object */ // ------------------------------------------------------------------------------------------------ static void getFirstContain( Object *obj, void *userData ) { Object **firstContain = (Object **)userData; // if object has been found get out of here if( *firstContain != NULL ) return; // assign this as the first object found *firstContain = obj; } // end getFirstContain // ------------------------------------------------------------------------------------------------ /** Start the next object contained by us as "unloading and coming out" */ // ------------------------------------------------------------------------------------------------ void RailedTransportDockUpdate::unloadNext( void ) { Object *us = getObject(); // by default, setup our unloading process to be done with no objects being considered m_unloadingObjectID = INVALID_ID; // // if our unload count is zero we can't unload any more until we receive a command to // unload another one or everything we've got // if( m_unloadCount == 0 ) return; // better be an open container ContainModuleInterface *contain = us->getContain(); OpenContain *openContain = contain ? contain->asOpenContain() : NULL; DEBUG_ASSERTCRASH( openContain, ("Unloading next from railed transport, but '%s' has no open container\n", us->getTemplate()->getName().str()) ); // get the first contained object Object *unloader = NULL; openContain->iterateContained( getFirstContain, &unloader, FALSE ); if( unloader ) { // remove us from the container openContain->removeFromContain( unloader ); // set position of the loader to our position unloader->setPosition( us->getPosition() ); // orient unloader to the same angle as us so we can drive out the front unloader->setOrientation( us->getOrientation() ); // mark us as HELD so physics or anything else can't mess with our position unloader->setDisabled( DISABLED_HELD ); // // get the dock point that we're going to go to ... that is where we came in // at the DOCKEND point // Coord3D dockPosition; getExitPosition( unloader, &dockPosition ); // get unloader position const Coord3D *unloaderPos = unloader->getPosition(); // how far is it from our current position to the dock position Coord3D v; v.x = dockPosition.x - unloaderPos->x; v.y = dockPosition.y - unloaderPos->y; v.z = dockPosition.z - unloaderPos->z; Real mag = v.length(); // now that we know how far we must go, now much distance should we travel every frame const RailedTransportDockUpdateModuleData *modData = getRailedTransportDockUpdateModuleData(); m_pushOutsideDistancePerFrame = mag / modData->m_pushOutsideDurationInFrames; // set this as our current unloader m_unloadingObjectID = unloader->getID(); // we've now used an unload (if we're keeping count for single exits) if( m_unloadCount != UNLOAD_ALL ) --m_unloadCount; } // end if } // end unloadNext // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void RailedTransportDockUpdate::crc( Xfer *xfer ) { // extend base class DockUpdate::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void RailedTransportDockUpdate::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class DockUpdate::xfer( xfer ); // docking object id xfer->xferObjectID( &m_dockingObjectID ); // pull inside distance per frame xfer->xferReal( &m_pullInsideDistancePerFrame ); // unloading object id xfer->xferObjectID( &m_unloadingObjectID ); // push outside distance per frame xfer->xferReal( &m_pushOutsideDistancePerFrame ); // unload count xfer->xferInt( &m_unloadCount ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void RailedTransportDockUpdate::loadPostProcess( void ) { // extend base class DockUpdate::loadPostProcess(); } // end loadPostProcess