/* ** 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: RiderChangeContain.cpp ////////////////////////////////////////////////////////////////////// // Author: Kris Morness, May 2003 // Desc: Contain module for the combat bike (transport that switches units). /////////////////////////////////////////////////////////////////////////////////////////////////// // USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #define DEFINE_LOCOMOTORSET_NAMES //Gain access to TheLocomotorSetNames[] #include "Common/Player.h" #include "Common/PlayerList.h" #include "Common/ThingTemplate.h" #include "Common/ThingFactory.h" #include "Common/Xfer.h" #include "GameClient/ControlBar.h" #include "GameClient/Drawable.h" #include "GameClient/InGameUI.h" #include "GameLogic/AI.h" #include "GameLogic/AIPathfind.h" #include "GameLogic/ExperienceTracker.h" #include "GameLogic/Object.h" #include "GameLogic/Locomotor.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/Module/PhysicsUpdate.h" #include "GameLogic/Module/StealthUpdate.h" #include "GameLogic/Module/RiderChangeContain.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ RiderChangeContainModuleData::RiderChangeContainModuleData() { m_scuttleFrames = 0; m_scuttleState = MODELCONDITION_TOPPLED; } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RiderChangeContainModuleData::parseRiderInfo( INI* ini, void *instance, void *store, const void* /*userData*/ ) { RiderInfo* rider = (RiderInfo*)store; const char* name = ini->getNextToken(); //Template name rider->m_templateName.format( name ); //Model condition state INI::parseIndexList( ini, instance, &(rider->m_modelConditionFlagType), ModelConditionFlags::getBitNames() ); //Weaponset INI::parseIndexList( ini, instance, &(rider->m_weaponSetFlag), WeaponSetFlags::getBitNames() ); //Object status INI::parseIndexList( ini, instance, &(rider->m_objectStatusType), ObjectStatusMaskType::getBitNames() ); //Command set override name = ini->getNextToken(); rider->m_commandSet.format( name ); //Locomotor set type rider->m_locomotorSetType = (LocomotorSetType)INI::scanIndexList( ini->getNextToken(), TheLocomotorSetNames ); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RiderChangeContainModuleData::buildFieldParse(MultiIniFieldParse& p) { TransportContainModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { { "Rider1", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[0] ) }, { "Rider2", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[1] ) }, { "Rider3", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[2] ) }, { "Rider4", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[3] ) }, { "Rider5", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[4] ) }, { "Rider6", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[5] ) }, { "Rider7", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[6] ) }, { "Rider8", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[7] ) }, { "ScuttleDelay", INI::parseDurationUnsignedInt, NULL, offsetof( RiderChangeContainModuleData, m_scuttleFrames ) }, { "ScuttleStatus", INI::parseIndexList, ModelConditionFlags::getBitNames(), offsetof( RiderChangeContainModuleData, m_scuttleState ) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } // PRIVATE //////////////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Int RiderChangeContain::getContainMax( void ) const { if (getRiderChangeContainModuleData()) return getRiderChangeContainModuleData()->m_slotCapacity; return 0; } // PUBLIC ///////////////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- RiderChangeContain::RiderChangeContain( Thing *thing, const ModuleData *moduleData ) : TransportContain( thing, moduleData ) { m_extraSlotsInUse = 0; m_frameExitNotBusy = 0; m_containing = FALSE; m_scuttledOnFrame = 0; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- RiderChangeContain::~RiderChangeContain( void ) { } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- /** can this container contain this kind of object? and, if checkCapacity is TRUE, does this container have enough space left to hold the given unit? */ Bool RiderChangeContain::isValidContainerFor(const Object* rider, Bool checkCapacity) const { //Don't check capacity because our rider will kick the other rider out! if( TransportContain::isValidContainerFor( rider, FALSE ) ) { if( m_scuttledOnFrame != 0 ) { //Scuttled... too late! return FALSE; } //We can enter this bike... but now we need to extend the base functionality by limiting //which infantry can enter. const RiderChangeContainModuleData *data = getRiderChangeContainModuleData(); for( int i = 0; i < MAX_RIDERS; i++ ) { const ThingTemplate *thing = TheThingFactory->findTemplate( data->m_riders[ i ].m_templateName ); if( thing->isEquivalentTo( rider->getTemplate() ) ) { //We found a valid rider, so return success. return TRUE; } } } return FALSE; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void RiderChangeContain::onContaining( Object *rider, Bool wasSelected ) { Object *obj = getObject(); m_containing = TRUE; //Remove our existing rider if( m_payloadCreated ) { obj->getAI()->aiEvacuateInstantly( TRUE, CMD_FROM_AI ); } //If the rider is currently selected, transfer selection to the container and preserve other units //that may be already selected. Note that containing the rider will automatically cause it to be //deselected, so all we have to do is select the container (if not already selected)! Drawable *containDraw = getObject()->getDrawable(); if( containDraw && wasSelected && !containDraw->isSelected() ) { //Create the selection message GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); teamMsg->appendBooleanArgument( FALSE );// not creating new team so pass false teamMsg->appendObjectIDArgument( getObject()->getID() ); TheInGameUI->selectDrawable( containDraw ); TheInGameUI->setDisplayedMaxWarning( FALSE ); } //Find the rider in the list and set the appropriate model condition const RiderChangeContainModuleData *data = getRiderChangeContainModuleData(); for( int i = 0; i < MAX_RIDERS; i++ ) { const ThingTemplate *thing = TheThingFactory->findTemplate( data->m_riders[ i ].m_templateName ); if( thing->isEquivalentTo( rider->getTemplate() ) ) { //This is our rider, so set the correct model condition. obj->setModelConditionState( data->m_riders[ i ].m_modelConditionFlagType ); //Also set the correct weaponset flag obj->setWeaponSetFlag( data->m_riders[ i ].m_weaponSetFlag ); //Also set the object status obj->setStatus( MAKE_OBJECT_STATUS_MASK( data->m_riders[ i ].m_objectStatusType ) ); //Set the new commandset override obj->setCommandSetStringOverride( data->m_riders[ i ].m_commandSet ); TheControlBar->markUIDirty(); // Refresh the UI in case we are selected //Change the locomotor. AIUpdateInterface* ai = obj->getAI(); if( ai ) { ai->chooseLocomotorSet( data->m_riders[ i ].m_locomotorSetType ); } if( obj->getStatusBits().test( OBJECT_STATUS_STEALTHED ) ) { StealthUpdate* stealth = obj->getStealth(); if( stealth ) { stealth->markAsDetected(); } } //Transfer experience from the rider to the bike. ExperienceTracker *riderTracker = rider->getExperienceTracker(); ExperienceTracker *bikeTracker = obj->getExperienceTracker(); bikeTracker->setVeterancyLevel( riderTracker->getVeterancyLevel(), FALSE ); riderTracker->setExperienceAndLevel( 0, FALSE ); break; } } //Extend base class TransportContain::onContaining( rider, wasSelected ); m_containing = FALSE; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void RiderChangeContain::onRemoving( Object *rider ) { Object *bike = getObject(); //Note if the bike dies, the rider dies too. if( bike->isEffectivelyDead() ) { TheGameLogic->destroyObject( rider ); return; } if( m_payloadCreated ) { //Extend base class TransportContain::onRemoving( rider ); } //Find the rider in the list and clear various data. const RiderChangeContainModuleData *data = getRiderChangeContainModuleData(); for( int i = 0; i < MAX_RIDERS; i++ ) { const ThingTemplate *thing = TheThingFactory->findTemplate( data->m_riders[ i ].m_templateName ); if( thing->isEquivalentTo( rider->getTemplate() ) ) { //This is our rider, so clear the current model condition. bike->clearModelConditionFlags( MAKE_MODELCONDITION_MASK2( data->m_riders[ i ].m_modelConditionFlagType, MODELCONDITION_DOOR_1_CLOSING ) ); //Also clear the current weaponset flag bike->clearWeaponSetFlag( data->m_riders[ i ].m_weaponSetFlag ); //Also clear the object status bike->clearStatus( MAKE_OBJECT_STATUS_MASK( data->m_riders[ i ].m_objectStatusType ) ); if( rider->getControllingPlayer() != NULL ) { //Wow, completely unforseeable game teardown order crash. SetVeterancyLevel results in a call to player //about upgrade masks. So if we have a null player, it is game teardown, so don't worry about transfering exp. //Transfer experience from the bike to the rider. ExperienceTracker *riderTracker = rider->getExperienceTracker(); ExperienceTracker *bikeTracker = bike->getExperienceTracker(); riderTracker->setVeterancyLevel( bikeTracker->getVeterancyLevel(), FALSE ); bikeTracker->setExperienceAndLevel( 0, FALSE ); } break; } } //If we're not replacing the rider, then if the cycle is selected, transfer selection //to the rider getting off (because the bike is gonna blow). if( !m_containing ) { Drawable *containDraw = bike->getDrawable(); Drawable *riderDraw = rider->getDrawable(); if( containDraw && riderDraw ) { //Create the selection message for the rider if it's ours and SELECTED! if( bike->getControllingPlayer() == ThePlayerList->getLocalPlayer() && containDraw->isSelected() ) { GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP ); teamMsg->appendBooleanArgument( FALSE );// not creating new team so pass false teamMsg->appendObjectIDArgument( rider->getID() ); TheInGameUI->selectDrawable( riderDraw ); TheInGameUI->setDisplayedMaxWarning( FALSE ); //Create the de-selection message for the container teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_REMOVE_FROM_SELECTED_GROUP ); teamMsg->appendObjectIDArgument( bike->getID() ); TheInGameUI->deselectDrawable( containDraw ); } //Finally, scuttle the bike so nobody else can use it! m_scuttledOnFrame = TheGameLogic->getFrame(); bike->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) ); bike->setModelConditionState( data->m_scuttleState ); if( !bike->getAI()->isMoving() ) { bike->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_IMMOBILE ) ); } } } } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RiderChangeContain::createPayload() { // extend base class TransportContain::createPayload(); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ UpdateSleepTime RiderChangeContain::update() { if( m_scuttledOnFrame != 0 ) { //Bike in the process of getting scuttled. const RiderChangeContainModuleData *data = getRiderChangeContainModuleData(); UnsignedInt now = TheGameLogic->getFrame(); if( m_scuttledOnFrame + data->m_scuttleFrames <= now ) { //We have scuttled the bike (at least as far as tipping it over via scuttle animation. Now //kill the bike in a way that will cause it to sink into the ground without any real destruction. getObject()->kill( DAMAGE_UNRESISTABLE, DEATH_TOPPLED ); //Sneaky, eh? Toppled heheh. } } // extend base class return TransportContain::update(); //extend } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RiderChangeContain::unreserveDoorForExit( ExitDoorType exitDoor ) { /* nothing */ } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RiderChangeContain::killRidersWhoAreNotFreeToExit() { // extend base class TransportContain::killRidersWhoAreNotFreeToExit(); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Bool RiderChangeContain::isSpecificRiderFreeToExit(Object* specificObject) { // extend base class return TransportContain::isSpecificRiderFreeToExit( specificObject ); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ ExitDoorType RiderChangeContain::reserveDoorForExit( const ThingTemplate* objType, Object *specificObject ) { // extend base class return TransportContain::reserveDoorForExit( objType, specificObject ); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Bool RiderChangeContain::isExitBusy() const ///< Contain style exiters are getting the ability to space out exits, so ask this before reserveDoor as a kind of no-commitment check. { // extend base class return FALSE; //return TransportContain::isExitBusy(); } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RiderChangeContain::onCapture( Player *oldOwner, Player *newOwner ) { // extend base class TransportContain::onCapture( oldOwner, newOwner ); } //------------------------------------------------------------------------------------------------- Bool RiderChangeContain::getContainerPipsToShow(Int& numTotal, Int& numFull) { //Don't show any pips for motorcycles as they always have one rider unless dead! numTotal = 0; numFull = 0; return false; } //------------------------------------------------------------------------------------------------- const Object *RiderChangeContain::friend_getRider() const { if( m_containListSize > 0 ) // Yes, this does assume that infantry never ride double on the bike return m_containList.front(); return NULL; } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void RiderChangeContain::crc( Xfer *xfer ) { // extend base class TransportContain::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version */ // ------------------------------------------------------------------------------------------------ void RiderChangeContain::xfer( Xfer *xfer ) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class TransportContain::xfer( xfer ); // payload created xfer->xferBool( &m_payloadCreated ); // extra slots in use xfer->xferInt( &m_extraSlotsInUse ); // frame exit not busy xfer->xferUnsignedInt( &m_frameExitNotBusy ); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void RiderChangeContain::loadPostProcess( void ) { // extend base class TransportContain::loadPostProcess(); } // end loadPostProcess