Initial commit of Command & Conquer Generals and Command & Conquer Generals Zero Hour source code.

This commit is contained in:
LFeenanEA
2025-02-27 17:34:39 +00:00
parent 2e338c00cb
commit 3d0ee53a05
6072 changed files with 2283311 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: ArmorTemplate.cpp ///////////////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, November 2001
// Desc: ArmorTemplate descriptions
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/INI.h"
#include "Common/ThingFactory.h"
#include "GameLogic/Armor.h"
#include "GameLogic/Damage.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC DATA ////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
ArmorStore* TheArmorStore = NULL; ///< the ArmorTemplate store definition
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
ArmorTemplate::ArmorTemplate()
{
clear();
}
//-------------------------------------------------------------------------------------------------
void ArmorTemplate::clear()
{
for (int i = 0; i < DAMAGE_NUM_TYPES; i++)
{
m_damageCoefficient[i] = 1.0f;
}
}
//-------------------------------------------------------------------------------------------------
Real ArmorTemplate::adjustDamage(DamageType t, Real damage) const
{
if (t == DAMAGE_UNRESISTABLE)
return damage;
if (t == DAMAGE_SUBDUAL_UNRESISTABLE)
return damage;
damage *= m_damageCoefficient[t];
if (damage < 0.0f)
damage = 0.0f;
return damage;
}
//-------------------------------------------------------------------------------------------Static
/*static*/ void ArmorTemplate::parseArmorCoefficients( INI* ini, void *instance, void* /* store */, const void* userData )
{
ArmorTemplate* self = (ArmorTemplate*) instance;
const char* damageName = ini->getNextToken();
Real pct = INI::scanPercentToReal(ini->getNextToken());
if (stricmp(damageName, "Default") == 0)
{
for (Int i = 0; i < DAMAGE_NUM_TYPES; i++)
{
self->m_damageCoefficient[i] = pct;
}
return;
}
DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(damageName);
self->m_damageCoefficient[dt] = pct;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ArmorStore::ArmorStore()
{
m_armorTemplates.clear();
}
//-------------------------------------------------------------------------------------------------
ArmorStore::~ArmorStore()
{
m_armorTemplates.clear();
}
//-------------------------------------------------------------------------------------------------
const ArmorTemplate* ArmorStore::findArmorTemplate(AsciiString name) const
{
NameKeyType namekey = TheNameKeyGenerator->nameToKey(name);
ArmorTemplateMap::const_iterator it = m_armorTemplates.find(namekey);
if (it == m_armorTemplates.end())
{
return NULL;
}
else
{
return &(*it).second;
}
}
//-------------------------------------------------------------------------------------------------
/*static */ void ArmorStore::parseArmorDefinition(INI *ini)
{
static const FieldParse myFieldParse[] =
{
{ "Armor", ArmorTemplate::parseArmorCoefficients, NULL, 0 }
};
const char *c = ini->getNextToken();
NameKeyType key = TheNameKeyGenerator->nameToKey(c);
ArmorTemplate& armorTmpl = TheArmorStore->m_armorTemplates[key];
armorTmpl.clear();
ini->initFromINI(&armorTmpl, myFieldParse);
}
//-------------------------------------------------------------------------------------------------
/*static*/ void INI::parseArmorDefinition(INI *ini)
{
ArmorStore::parseArmorDefinition(ini);
}

View File

@@ -0,0 +1,381 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: AutoHealBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Thing.h"
#include "Common/ThingTemplate.h"
#include "Common/INI.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/Anim2D.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/Module/AutoHealBehavior.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
struct AutoHealPlayerScanHelper
{
KindOfMaskType m_kindOfToTest;
KindOfMaskType m_forbiddenKindOf;
Object *m_theHealer;
ObjectPointerList *m_objectList;
Bool m_skipSelfForHealing;
};
static void checkForAutoHeal( Object *testObj, void *userData )
{
AutoHealPlayerScanHelper *helper = (AutoHealPlayerScanHelper*)userData;
ObjectPointerList *listToAddTo = helper->m_objectList;
if( testObj->isEffectivelyDead() )
return;
if( testObj->getControllingPlayer() != helper->m_theHealer->getControllingPlayer() )
return;
if( testObj->isOffMap() )
return;
if( helper->m_skipSelfForHealing && testObj == helper->m_theHealer )
return;
if( !testObj->isAnyKindOf(helper->m_kindOfToTest) )
return;
if( testObj->isAnyKindOf( helper->m_forbiddenKindOf ) )
return;
if( testObj->getBodyModule()->getHealth() >= testObj->getBodyModule()->getMaxHealth() )
return;
listToAddTo->push_back(testObj);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AutoHealBehavior::AutoHealBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData();
m_radiusParticleSystemID = INVALID_PARTICLE_SYSTEM_ID;
m_soonestHealFrame = 0;
m_stopped = false;
Object *obj = getObject();
{
if( d->m_radiusParticleSystemTmpl )
{
ParticleSystem *particleSystem;
particleSystem = TheParticleSystemManager->createParticleSystem( d->m_radiusParticleSystemTmpl );
if( particleSystem )
{
particleSystem->setPosition( obj->getPosition() );
m_radiusParticleSystemID = particleSystem->getSystemID();
}
}
}
if (d->m_initiallyActive)
{
giveSelfUpgrade();
// start these guys with random phasings so that we don't
// have all of 'em check on the same frame.
UnsignedInt delay = getAutoHealBehaviorModuleData()->m_healingDelay;
setWakeFrame(getObject(), UPDATE_SLEEP(GameLogicRandomValue(1, delay)));
}
else
{
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AutoHealBehavior::~AutoHealBehavior( void )
{
if( m_radiusParticleSystemID != INVALID_PARTICLE_SYSTEM_ID )
TheParticleSystemManager->destroyParticleSystemByID( m_radiusParticleSystemID );
}
//-------------------------------------------------------------------------------------------------
void AutoHealBehavior::stopHealing()
{
m_stopped = true;
m_soonestHealFrame = FOREVER;
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
//-------------------------------------------------------------------------------------------------
void AutoHealBehavior::undoUpgrade()
{
m_soonestHealFrame = 0;
setUpgradeExecuted( FALSE );
}
//-------------------------------------------------------------------------------------------------
/** Damage has been dealt, this is an opportunity to reach to that damage */
//-------------------------------------------------------------------------------------------------
void AutoHealBehavior::onDamage( DamageInfo *damageInfo )
{
if (m_stopped)
return;
const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData();
if (isUpgradeActive() && d->m_radius == 0.0f)
{
// if this is nonzero, getting damaged resets our healing process. so go to
// sleep for this long.
if (d->m_startHealingDelay > 0)
{
setWakeFrame(getObject(), UPDATE_SLEEP(d->m_startHealingDelay));
}
else if( TheGameLogic->getFrame() > m_soonestHealFrame )
{
// We can only force an immediate wake if we are ready to heal. Otherwise we will
// heal on a timer AND at every damage input.
setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
}
}
}
//-------------------------------------------------------------------------------------------------
/** The update callback. */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime AutoHealBehavior::update( void )
{
if (m_stopped)
return UPDATE_SLEEP_FOREVER;
Object *obj = getObject();
const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData();
// do not heal if our status bit is not on.
// do not heal if our status is effectively dead. There ain't no coming back, man!
if (!isUpgradeActive() || obj->isEffectivelyDead())
{
DEBUG_ASSERTCRASH(isUpgradeActive(), ("hmm, this should not be possible"));
return UPDATE_SLEEP_FOREVER;
}
//DEBUG_LOG(("doing auto heal %d\n",TheGameLogic->getFrame()));
if( d->m_affectsWholePlayer )
{
// Even newer system, I can ignore radius and iterate objects on the owning player. Faster than scanning range 10,000,000
ObjectPointerList objectsToHeal;
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer )
{
AutoHealPlayerScanHelper helper;
helper.m_kindOfToTest = d->m_kindOf;
helper.m_forbiddenKindOf = d->m_forbiddenKindOf;
helper.m_objectList = &objectsToHeal;
helper.m_theHealer = getObject();
helper.m_skipSelfForHealing = d->m_skipSelfForHealing;
// Smack all objects with this function, and we will end up with a list of Objects deserving of pulseHealObject
owningPlayer->iterateObjects( checkForAutoHeal, &helper );
for( ObjectPointerListIterator iter = objectsToHeal.begin(); iter != objectsToHeal.end(); ++iter )
{
pulseHealObject(*iter);
}
objectsToHeal.clear();
}
return UPDATE_SLEEP(d->m_healingDelay);
}
else if( d->m_radius == 0.0f )
{
//ORIGINAL SYSTEM -- JUST HEAL SELF!
// do not heal if we are at max health already
BodyModuleInterface *body = obj->getBodyModule();
if( body->getHealth() < body->getMaxHealth() )
{
pulseHealObject( obj );
return UPDATE_SLEEP(d->m_healingDelay);
}
else
{
// go to sleep forever -- we'll wake back up when we are damaged again
return UPDATE_SLEEP_FOREVER;
}
}
else
{
//EXPANDED SYSTEM -- HEAL FRIENDLIES IN RADIUS
// setup scan filters
PartitionFilterRelationship relationship( obj, PartitionFilterRelationship::ALLOW_ALLIES );
PartitionFilterSameMapStatus filterMapStatus(obj);
PartitionFilterAlive filterAlive;
PartitionFilter *filters[] = { &relationship, &filterAlive, &filterMapStatus, NULL };
// scan objects in our region
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( obj->getPosition(), d->m_radius, FROM_CENTER_2D, filters );
MemoryPoolObjectHolder hold( iter );
for( obj = iter->first(); obj; obj = iter->next() )
{
// do not heal if we are at max health already
BodyModuleInterface *body = obj->getBodyModule();
if( body->getHealth() < body->getMaxHealth() )
{
if( obj->isAnyKindOf( d->m_kindOf ) && !obj->isAnyKindOf( d->m_forbiddenKindOf ) )
{
if( !d->m_skipSelfForHealing || obj != getObject() )
{
pulseHealObject( obj );
if( d->m_singleBurst && TheGameLogic->getDrawIconUI() )
{
if( TheAnim2DCollection && TheGlobalData->m_getHealedAnimationName.isEmpty() == FALSE )
{
Anim2DTemplate *animTemplate = TheAnim2DCollection->findTemplate( TheGlobalData->m_getHealedAnimationName );
if ( animTemplate )
{
Coord3D iconPosition;
iconPosition.set(obj->getPosition()->x,
obj->getPosition()->y,
obj->getPosition()->z + obj->getGeometryInfo().getMaxHeightAbovePosition() );
TheInGameUI->addWorldAnimation( animTemplate, &iconPosition, WORLD_ANIM_FADE_ON_EXPIRE,
TheGlobalData->m_getHealedAnimationDisplayTimeInSeconds,
TheGlobalData->m_getHealedAnimationZRisePerSecond);
}
}
}
}
}
}
} // end for obj
return UPDATE_SLEEP( d->m_singleBurst ? UPDATE_SLEEP_FOREVER : d->m_healingDelay );
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void AutoHealBehavior::pulseHealObject( Object *obj )
{
if (m_stopped)
return;
const AutoHealBehaviorModuleData *data = getAutoHealBehaviorModuleData();
if ( data->m_radius == 0.0f )
obj->attemptHealing(data->m_healingAmount, getObject());
else
obj->attemptHealingFromSoleBenefactor( data->m_healingAmount, getObject(), data->m_healingDelay );
if( data->m_unitHealPulseParticleSystemTmpl )
{
ParticleSystem *system = TheParticleSystemManager->createParticleSystem( data->m_unitHealPulseParticleSystemTmpl );
if( system )
{
system->setPosition( obj->getPosition() );
}
}
m_soonestHealFrame = TheGameLogic->getFrame() + data->m_healingDelay;// In case onDamage tries to wake us up early
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AutoHealBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
// extend base class
UpgradeMux::upgradeMuxCRC( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void AutoHealBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// extend base class
UpgradeMux::upgradeMuxXfer( xfer );
// particle system id
xfer->xferUser( &m_radiusParticleSystemID, sizeof( ParticleSystemID ) );
// Timer safety
xfer->xferUnsignedInt( &m_soonestHealFrame );
// stopped
xfer->xferBool( &m_stopped );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AutoHealBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
// extend base class
UpgradeMux::upgradeMuxLoadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,326 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: BattleBusSlowDeathBehavior.cpp /////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, June 2003
// Desc: Death for the Battle Bus. Can do a fake slow death before the real one if triggered intentionally
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Module/BattleBusSlowDeathBehavior.h"
#include "Common/ModelState.h"
#include "GameClient/FXList.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
enum
{
GROUND_CHECK_DELAY = 10, ///< Check for colliding with the ground only after this long, to prevent hitting on the way up
EMPTY_HULK_CHECK_DELAY = 15 ///< Check for empty hulk every this often instead of every frame
};
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BattleBusSlowDeathBehaviorModuleData::BattleBusSlowDeathBehaviorModuleData( void )
{
m_fxStartUndeath = NULL;
m_oclStartUndeath = NULL;
m_fxHitGround = NULL;
m_oclHitGround = NULL;
m_throwForce = 1.0f;
m_percentDamageToPassengers = 0.0f;
m_emptyHulkDestructionDelay = 0;
} // end BattleBusSlowDeathBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void BattleBusSlowDeathBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
SlowDeathBehaviorModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "FXStartUndeath", INI::parseFXList, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_fxStartUndeath ) },
{ "OCLStartUndeath", INI::parseObjectCreationList, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_oclStartUndeath ) },
{ "FXHitGround", INI::parseFXList, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_fxHitGround ) },
{ "OCLHitGround", INI::parseObjectCreationList, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_oclHitGround ) },
{ "ThrowForce", INI::parseReal, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_throwForce ) },
{ "PercentDamageToPassengers", INI::parsePercentToReal, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_percentDamageToPassengers ) },
{ "EmptyHulkDestructionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_emptyHulkDestructionDelay ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BattleBusSlowDeathBehavior::BattleBusSlowDeathBehavior( Thing *thing, const ModuleData *moduleData )
: SlowDeathBehavior( thing, moduleData )
{
m_isRealDeath = FALSE;
m_isInFirstDeath = FALSE;
m_groundCheckFrame = 0;
m_penaltyDeathFrame = 0;
} // end BattleBusSlowDeathBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BattleBusSlowDeathBehavior::~BattleBusSlowDeathBehavior( void )
{
} // end ~BattleBusSlowDeathBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BattleBusSlowDeathBehavior::onDie( const DamageInfo *damageInfo )
{
m_isRealDeath = TRUE;// Set beforehand because onDie could result in picking us to beginSlowDeath
m_isInFirstDeath = FALSE; // and clear this incase we died while in the alternate death
SlowDeathBehavior::onDie(damageInfo);
} // end onDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BattleBusSlowDeathBehavior::beginSlowDeath( const DamageInfo *damageInfo )
{
Object *me = getObject();
const BattleBusSlowDeathBehaviorModuleData * data = getBattleBusSlowDeathBehaviorModuleData();
if( !m_isRealDeath )
{
// We can intercept and do our extra slow death if this is not from a real onDie
m_isInFirstDeath = TRUE;
m_groundCheckFrame = TheGameLogic->getFrame() + GROUND_CHECK_DELAY;
// First do the special effects
FXList::doFXObj(data->m_fxStartUndeath, me );
ObjectCreationList::create(data->m_oclStartUndeath, me, NULL );
if( me->getAI() )
{
// Then stop what we were doing
me->getAI()->aiIdle(CMD_FROM_AI);
}
if( me->getPhysics() )
{
// Then stop physically
me->getPhysics()->clearAcceleration();
me->getPhysics()->scrubVelocity2D(0);
// Then get chucked in the air
Coord3D throwForce;
throwForce.x = 0;
throwForce.y = 0;
throwForce.z = data->m_throwForce;
me->getPhysics()->applyShock(&throwForce);
me->getPhysics()->applyRandomRotation();
}
// And finally hit those inside for some damage
if( me->getContain() )
me->getContain()->processDamageToContained(data->m_percentDamageToPassengers);
setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
}
else
{
// If a real death, we aren't allowed to do anything
SlowDeathBehavior::beginSlowDeath( damageInfo );
}
} // end beginSlowDeath
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime BattleBusSlowDeathBehavior::update( void )
{
Object *me = getObject();
const BattleBusSlowDeathBehaviorModuleData * data = getBattleBusSlowDeathBehaviorModuleData();
if( m_isInFirstDeath )
{
// First death means we are doing our throw up in the air conversion
if( (m_groundCheckFrame< TheGameLogic->getFrame() ) && !me->isAboveTerrain() )
{
// We're done since we hit the ground
m_isInFirstDeath = FALSE;
// Do the special FX
FXList::doFXObj(data->m_fxHitGround, me );
ObjectCreationList::create(data->m_oclHitGround, me, NULL );
me->setModelConditionState(MODELCONDITION_SECOND_LIFE);
// And stop us forever
if( me->getAI() )
{
// Stop doing anything again (could try to move while in air)
me->getAI()->aiIdle(CMD_FROM_AI);
}
if( me->getPhysics() )
{
// And stop physically again
me->getPhysics()->clearAcceleration();
me->getPhysics()->scrubVelocity2D(0);
}
me->setDisabled(DISABLED_HELD);
// We can only sleep if we don't have to watch out for being empty.
if( data->m_emptyHulkDestructionDelay == 0 )
return UPDATE_SLEEP_FOREVER;
else
return UPDATE_SLEEP_NONE; // None, so we check for empty as soon as possible
}
return UPDATE_SLEEP_NONE;// None, since we are waiting to be able to check for ground collision
}
else if( m_isRealDeath )
{
// If a real death, we aren't allowed to do anything
return SlowDeathBehavior::update();
}
else
{
// If neither death is active, we must be awake to check for emptiness
const ContainModuleInterface *contain = me->getContain();
// Safety, no need to be awake if no special case to wait for
if( contain == NULL )
return UPDATE_SLEEP_FOREVER;
if( m_penaltyDeathFrame != 0 )
{
// We have been timed for death, see if we have re-filled first
if( contain->getContainCount() > 0 )
{
m_penaltyDeathFrame = 0;
return UPDATE_SLEEP(EMPTY_HULK_CHECK_DELAY);
}
else if( TheGameLogic->getFrame() > m_penaltyDeathFrame )
{
// No salvation, see if the timer is up to be killed. Penalty prevents effects, Extra 4 prevents this die module from recalling.
// Can't use Suicided death because a Terrorist actually inflicts Suicide deaths on others.
me->kill(DAMAGE_PENALTY, DEATH_EXTRA_4);
return UPDATE_SLEEP_FOREVER;// Forever, since we are dead and some other death module is in charge
}
else
{
return UPDATE_SLEEP(EMPTY_HULK_CHECK_DELAY);
}
}
else
{
// We are not marked for death, so see if we should be
if( contain->getContainCount() == 0 )
{
m_penaltyDeathFrame = TheGameLogic->getFrame() + data->m_emptyHulkDestructionDelay;
}
return UPDATE_SLEEP(EMPTY_HULK_CHECK_DELAY);// Stay awake regardless
}
}
} // end update
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BattleBusSlowDeathBehavior::crc( Xfer *xfer )
{
// extend base class
SlowDeathBehavior::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BattleBusSlowDeathBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
SlowDeathBehavior::xfer( xfer );
xfer->xferBool( &m_isRealDeath );
xfer->xferBool( &m_isInFirstDeath );
xfer->xferUnsignedInt( &m_groundCheckFrame );
xfer->xferUnsignedInt( &m_penaltyDeathFrame );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BattleBusSlowDeathBehavior::loadPostProcess( void )
{
// extend base class
SlowDeathBehavior::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,73 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: BehaviorModule.cpp ///////////////////////////////////////////////////////////////////////
// Author: Colin Day, September 2002
// Desc: Implementaion for anything in the base BehaviorModule
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/BehaviorModule.h"
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BehaviorModule::crc( Xfer *xfer )
{
// call base class
ObjectModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BehaviorModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// call base class
ObjectModule::xfer( xfer );
} // xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BehaviorModule::loadPostProcess( void )
{
// call base class
ObjectModule::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,362 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: BridgeScaffoldBehavior.cpp ///////////////////////////////////////////////////////////////
// Author: Colin Day, September 2002
// Desc: Bridge scaffold
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BridgeScaffoldBehavior.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BridgeScaffoldBehavior::BridgeScaffoldBehavior( Thing *thing, const ModuleData *moduleData )
: UpdateModule( thing, moduleData )
{
m_targetMotion = STM_STILL;
m_createPos.zero();
m_riseToPos.zero();
m_buildPos.zero();
m_targetPos.zero();
m_lateralSpeed = 1.0f;
m_verticalSpeed = 1.0f;
} // end BridgeScaffoldBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BridgeScaffoldBehavior::~BridgeScaffoldBehavior( void )
{
} // end ~BridgeScaffoldBehavior
// ------------------------------------------------------------------------------------------------
/** Set all of the target positions that we're care about as a moving scaffold object */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::setPositions( const Coord3D *createPos,
const Coord3D *riseToPos,
const Coord3D *buildPos )
{
m_createPos = *createPos;
m_riseToPos = *riseToPos;
m_buildPos = *buildPos;
} // end setPositions
// ------------------------------------------------------------------------------------------------
/** Set us moving to the right target position for the requested motion type */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::setMotion( ScaffoldTargetMotion targetMotion )
{
// save the target motion type
m_targetMotion = targetMotion;
// given the target motion, pick a destination target
switch( m_targetMotion )
{
// --------------------------------------------------------------------------------------------
case STM_RISE:
case STM_TEAR_DOWN_ACROSS:
m_targetPos = m_riseToPos;
break;
// --------------------------------------------------------------------------------------------
case STM_BUILD_ACROSS:
m_targetPos = m_buildPos;
break;
// --------------------------------------------------------------------------------------------
case STM_SINK:
m_targetPos = m_createPos;
break;
} // end switch
} // end setMotion
// ------------------------------------------------------------------------------------------------
/** Whatever our current state of motion is, reverse it */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::reverseMotion( void )
{
switch( m_targetMotion )
{
case STM_STILL:
setMotion( STM_TEAR_DOWN_ACROSS );
break;
case STM_RISE:
setMotion( STM_SINK );
break;
case STM_BUILD_ACROSS:
setMotion( STM_TEAR_DOWN_ACROSS );
break;
case STM_TEAR_DOWN_ACROSS:
setMotion( STM_BUILD_ACROSS );
break;
case STM_SINK:
setMotion( STM_RISE );
break;
} // end switch
} // end reverseMotion
// ------------------------------------------------------------------------------------------------
/** The update method */
// ------------------------------------------------------------------------------------------------
UpdateSleepTime BridgeScaffoldBehavior::update( void )
{
// do nothing if we're not in motion
if( m_targetMotion == STM_STILL )
return UPDATE_SLEEP_NONE;
// get our info
Object *us = getObject();
const Coord3D *ourPos = us->getPosition();
// compute direction vector from our position to the target position
Coord3D dirV;
dirV.x = m_targetPos.x - ourPos->x;
dirV.y = m_targetPos.y - ourPos->y;
dirV.z = m_targetPos.z - ourPos->z;
// use normalized direction vector "v" to do the pulling movement
Coord3D v = dirV;
v.normalize();
// depending on our motion type, we move at different speeds
Real topSpeed = 1.0f;
Coord3D *start, *end;
switch( m_targetMotion )
{
case STM_RISE:
topSpeed = m_verticalSpeed;
start = &m_createPos;
end = &m_riseToPos;
break;
case STM_SINK:
topSpeed = m_verticalSpeed;
start = &m_riseToPos;
end = &m_createPos;
break;
case STM_BUILD_ACROSS:
topSpeed = m_lateralSpeed;
start = &m_riseToPos;
end = &m_buildPos;
break;
case STM_TEAR_DOWN_ACROSS:
topSpeed = m_lateralSpeed;
start = &m_buildPos;
end = &m_riseToPos;
break;
} // end switch
// adjust speed so it's slower at the end of motion
Coord3D speedVector;
speedVector.x = end->x - start->x;
speedVector.y = end->y - start->y;
speedVector.z = end->z - start->z;
Real totalDistance = speedVector.length() * 0.25f;
speedVector.x = end->x - ourPos->x;
speedVector.y = end->y - ourPos->y;
speedVector.z = end->z - ourPos->z;
Real ourDistance = speedVector.length();
Real speed = (ourDistance / totalDistance) * topSpeed;
Real minSpeed = topSpeed * 0.08f;
if( speed < minSpeed )
speed = minSpeed;
if( speed > topSpeed )
speed = topSpeed;
//
// make sure that speed can't get so incredibly small that we never finish our
// movement no matter what the speed and distance are
//
if( speed < 0.001f )
speed = 0.001f;
// compute the new position given the speed
Coord3D newPos;
newPos.x = v.x * speed + ourPos->x;
newPos.y = v.y * speed + ourPos->y;
newPos.z = v.z * speed + ourPos->z;
//
// will this new position push us beyond our target destination, we will take the vector
// from the new position to the destination and the vector from our current present position
// tot he destination and dot them togehter ... if the result is < 0 then we have will
// overshoot the distance if we use the new position
//
Coord3D tooFarVector;
tooFarVector.x = m_targetPos.x - newPos.x;
tooFarVector.y = m_targetPos.y - newPos.y;
tooFarVector.z = m_targetPos.z - newPos.z;
if( tooFarVector.x * dirV.x + tooFarVector.y * dirV.y + tooFarVector.z * dirV.z <= 0.0f )
{
// use the destination position
newPos = m_targetPos;
//
// we have reached our target position, switch motion to the next position in
// the chain (which may be stay still and don't move anymore)
//
switch( m_targetMotion )
{
case STM_RISE: setMotion( STM_BUILD_ACROSS ); break;
case STM_BUILD_ACROSS: setMotion( STM_STILL ); break;
case STM_TEAR_DOWN_ACROSS: setMotion( STM_SINK ); break;
case STM_SINK:
{
// we are done with a sinking motion, destroy the scaffold object as our job is done
TheGameLogic->destroyObject( us );
break;
} // end case
} // end switch
} // end if
// set the new position
us->setPosition( &newPos );
// do not sleep
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
/** STATIC MEMBER:
* Helper function to retrieve a bridge scaffold interface from an object if one is present */
// ------------------------------------------------------------------------------------------------
BridgeScaffoldBehaviorInterface *BridgeScaffoldBehavior::getBridgeScaffoldBehaviorInterfaceFromObject( Object *obj )
{
// santiy
if( obj == NULL )
return NULL;
// get the bridge tower behavior interface
BridgeScaffoldBehaviorInterface *bridgeScaffoldInterface = NULL;
BehaviorModule **bmi;
for( bmi = obj->getBehaviorModules(); *bmi; ++bmi )
{
bridgeScaffoldInterface = (*bmi)->getBridgeScaffoldBehaviorInterface();
if( bridgeScaffoldInterface )
return bridgeScaffoldInterface;
} // end for bmi
// interface not found
return NULL;
} // end getBridgeScaffoldBehaviorInterfaceFromObject
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// target motion
xfer->xferUser( &m_targetMotion, sizeof( ScaffoldTargetMotion ) );
// create pos
xfer->xferCoord3D( &m_createPos );
// rise to pos
xfer->xferCoord3D( &m_riseToPos );
// build pos
xfer->xferCoord3D( &m_buildPos );
// lateral speed
xfer->xferReal( &m_lateralSpeed );
// vertical speed
xfer->xferReal( &m_verticalSpeed );
// current target pos
xfer->xferCoord3D( &m_targetPos );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,335 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: BridgeTowerBehavior.cpp //////////////////////////////////////////////////////////////////
// Author: Colin Day, July 2002
// Desc: Behavior module for the towers attached to bridges that can be targeted
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/BridgeBehavior.h"
#include "GameLogic/Module/BridgeTowerBehavior.h"
#include "GameLogic/TerrainLogic.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BridgeTowerBehavior::BridgeTowerBehavior( Thing *thing, const ModuleData *moduleData )
: BehaviorModule( thing, moduleData )
{
m_bridgeID = INVALID_ID;
} // end BridgeTowerBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BridgeTowerBehavior::~BridgeTowerBehavior( void )
{
} // end ~BridgeTowerBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::setBridge( Object *bridge )
{
if( bridge == NULL )
m_bridgeID = INVALID_ID;
else
m_bridgeID = bridge->getID();
} // end setBridge
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ObjectID BridgeTowerBehavior::getBridgeID( void )
{
return m_bridgeID;
} // end getBridge
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::setTowerType( BridgeTowerType type )
{
m_type = type;
} // end setTowerType
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::onDamage( DamageInfo *damageInfo )
{
Object *bridge = TheGameLogic->findObjectByID( getBridgeID() );
// sanity
if( bridge == NULL )
return;
//
// get our body info so we now how much damage percent is being done to us ... we need this
// so that we can propagate the same damage percentage amont the towers and the bridge
//
BodyModuleInterface *body = getObject()->getBodyModule();
Real damagePercentage = damageInfo->in.m_amount / body->getMaxHealth();
// get the bridge behavior module for our bridge
BehaviorModule **bmi;
BridgeBehaviorInterface *bridgeInterface = NULL;
for( bmi = bridge->getBehaviorModules(); *bmi; ++bmi )
{
bridgeInterface = (*bmi)->getBridgeBehaviorInterface();
if( bridgeInterface )
break;
} // end for bmi
DEBUG_ASSERTCRASH( bridgeInterface != NULL, ("BridgeTowerBehavior::onDamage - no 'BridgeBehaviorInterface' found\n") );
if( bridgeInterface )
{
//
// damage each of the other towers if the source of this damage isn't from the bridge
// or other towers
//
Object *source = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
if( source == NULL ||
(source->isKindOf( KINDOF_BRIDGE ) == FALSE &&
source->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE) )
{
for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i )
{
Object *tower;
tower = TheGameLogic->findObjectByID( bridgeInterface->getTowerID( (BridgeTowerType)i ) );
if( tower && tower != getObject() )
{
BodyModuleInterface *towerBody = tower->getBodyModule();
DamageInfo towerDamage;
towerDamage.in.m_amount = damagePercentage * towerBody->getMaxHealth();
towerDamage.in.m_sourceID = getObject()->getID(); // we're now the source
towerDamage.in.m_damageType = damageInfo->in.m_damageType;
towerDamage.in.m_deathType = damageInfo->in.m_deathType;
tower->attemptDamage( &towerDamage );
} // end if
} // end for i
//
// damage bridge object, but make sure it's done through the bridge interface
// so that it doesn't automatically propagate that damage to the towers
//
BodyModuleInterface *bridgeBody = bridge->getBodyModule();
DamageInfo bridgeDamage;
bridgeDamage.in.m_amount = damagePercentage * bridgeBody->getMaxHealth();
bridgeDamage.in.m_sourceID = getObject()->getID(); // we're now the source
bridgeDamage.in.m_damageType = damageInfo->in.m_damageType;
bridgeDamage.in.m_deathType = damageInfo->in.m_deathType;
bridge->attemptDamage( &bridgeDamage );
} // end if
} // end if
} // end onDamage
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::onHealing( DamageInfo *damageInfo )
{
Object *bridge = TheGameLogic->findObjectByID( getBridgeID() );
// sanity
if( bridge == NULL )
return;
//
// get our body info so we now how much healing percent is being done to us ... we need this
// so that we can propagate the same healing percentage amont the towers and the bridge
//
BodyModuleInterface *body = getObject()->getBodyModule();
Real healingPercentage = damageInfo->in.m_amount / body->getMaxHealth();
// get the bridge behavior module for our bridge
BehaviorModule **bmi;
BridgeBehaviorInterface *bridgeInterface = NULL;
for( bmi = bridge->getBehaviorModules(); *bmi; ++bmi )
{
bridgeInterface = (*bmi)->getBridgeBehaviorInterface();
if( bridgeInterface )
break;
} // end for bmi
DEBUG_ASSERTCRASH( bridgeInterface != NULL, ("BridgeTowerBehavior::onHealing - no 'BridgeBehaviorInterface' found\n") );
if( bridgeInterface )
{
//
// heal each of the other towers if the source of this healing isn't from the bridge
// or other towers
//
Object *source = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
if( source == NULL ||
(source->isKindOf( KINDOF_BRIDGE ) == FALSE &&
source->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE) )
{
for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i )
{
Object *tower;
tower = TheGameLogic->findObjectByID( bridgeInterface->getTowerID( (BridgeTowerType)i ) );
if( tower && tower != getObject() )
{
BodyModuleInterface *towerBody = tower->getBodyModule();
tower->attemptHealing(healingPercentage * towerBody->getMaxHealth(), getObject());
} // end if
} // end for i
//
// heal bridge object, but make sure it's done through the bridge interface
// so that it doesn't automatically propagate that healing to the towers.
//
BodyModuleInterface *bridgeBody = bridge->getBodyModule();
bridge->attemptHealing(healingPercentage * bridgeBody->getMaxHealth(), getObject());
} // end if
} // end if
} // end onHealing
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::onBodyDamageStateChange( const DamageInfo* damageInfo,
BodyDamageType oldState,
BodyDamageType newState )
{
} // end onBodyDamageStateChange
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::onDie( const DamageInfo *damageInfo )
{
// kill the bridge object, this will kill all the towers
Object *bridge = TheGameLogic->findObjectByID( getBridgeID() );
if( bridge )
bridge->kill();
} // end onDie
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
/** Given an object, return a bridge tower interface if that object has one */
// ------------------------------------------------------------------------------------------------
BridgeTowerBehaviorInterface *BridgeTowerBehavior::getBridgeTowerBehaviorInterfaceFromObject( Object *obj )
{
// sanity
if( obj == NULL || obj->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE )
return NULL;
BehaviorModule **bmi;
BridgeTowerBehaviorInterface *bridgeTowerInterface = NULL;
for( bmi = obj->getBehaviorModules(); *bmi; ++bmi )
{
bridgeTowerInterface = (*bmi)->getBridgeTowerBehaviorInterface();
if( bridgeTowerInterface )
return bridgeTowerInterface;
} // end for bmi
// interface not found
return NULL;
} // getBridgeTowerBehaviorInterfaceFromObject
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
BehaviorModule::xfer( xfer );
// xfer bridge object ID
xfer->xferObjectID( &m_bridgeID );
// xfer tower type
xfer->xferUser( &m_type, sizeof( BridgeTowerType ) );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::loadPostProcess( void )
{
// extend base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,297 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: BunkerBusterBehavior.cpp ////////////////////////////////////////////////////////////////
// Author: Mark Lorenzen, June 2003
// Desc: Behavior module for Bunker Buster... it kills garrisoned objects
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Upgrade.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/BunkerBusterBehavior.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Weapon.h"
#include "GameClient/TerrainVisual.h"//Seismic simulations!
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
static DomeStyleSeismicFilter bunkerBusterHeavingEarthSeismicFilter;
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BunkerBusterBehaviorModuleData::BunkerBusterBehaviorModuleData( void )
{
m_upgradeRequired = NULL;
m_detonationFX = NULL;
m_crashThroughBunkerFX = NULL;
m_crashThroughBunkerFXFrequency = 4;
m_seismicEffectRadius = 140.0f;
m_seismicEffectMagnitude = 6.0f;
m_shockwaveWeaponTemplate = NULL;
m_occupantDamageWeaponTemplate = NULL;
} // end BunkerBusterBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void BunkerBusterBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "UpgradeRequired", INI::parseAsciiString, NULL, offsetof( BunkerBusterBehaviorModuleData, m_upgradeRequired ) },
{ "DetonationFX", INI::parseFXList, NULL, offsetof( BunkerBusterBehaviorModuleData, m_detonationFX ) },
{ "CrashThroughBunkerFX", INI::parseFXList, NULL, offsetof( BunkerBusterBehaviorModuleData, m_crashThroughBunkerFX ) },
{ "CrashThroughBunkerFXFrequency", INI::parseDurationUnsignedInt, NULL, offsetof( BunkerBusterBehaviorModuleData, m_crashThroughBunkerFXFrequency ) },
{ "SeismicEffectRadius", INI::parseReal, NULL, offsetof( BunkerBusterBehaviorModuleData, m_seismicEffectRadius ) },
{ "SeismicEffectMagnitude", INI::parseReal, NULL, offsetof( BunkerBusterBehaviorModuleData, m_seismicEffectMagnitude ) },
{ "ShockwaveWeaponTemplate", INI::parseWeaponTemplate, NULL, offsetof( BunkerBusterBehaviorModuleData, m_shockwaveWeaponTemplate ) },
{ "OccupantDamageWeaponTemplate", INI::parseWeaponTemplate, NULL, offsetof( BunkerBusterBehaviorModuleData, m_occupantDamageWeaponTemplate ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BunkerBusterBehavior::BunkerBusterBehavior( Thing *thing, const ModuleData *modData )
: UpdateModule( thing, modData )
{
// THIS HAS AN UPDATE... BECAUSE I FORSEE THE NEED FOR ONE, BUT RIGHT NOW IT DOES NOTHING
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
m_victimID = INVALID_ID;
m_upgradeRequired = NULL;
} // end BunkerBusterBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BunkerBusterBehavior::~BunkerBusterBehavior( void )
{
} // end ~BunkerBusterBehavior
void BunkerBusterBehavior::onObjectCreated( void )
{
const BunkerBusterBehaviorModuleData *modData = getBunkerBusterBehaviorModuleData();
// convert module upgrade name to a pointer
m_upgradeRequired = TheUpgradeCenter->findUpgrade( modData->m_upgradeRequired );
} // end onObjectCreated
// ------------------------------------------------------------------------------------------------
/** The update callback */
// ------------------------------------------------------------------------------------------------
UpdateSleepTime BunkerBusterBehavior::update( void )
{
const BunkerBusterBehaviorModuleData *modData = getBunkerBusterBehaviorModuleData();
AIUpdateInterface *ai = getObject()->getAI();
if ( ai )// is this a SMART bomb?
{
if ( m_victimID == INVALID_ID )
{
Object *victim = ai->getCurrentVictim();
if ( victim )
m_victimID = victim->getID();
DEBUG_ASSERTCRASH( victim, ("BunkerBusterBehavior::update... AIUpdateInterface reports no victim." ) );
}
DEBUG_ASSERTCRASH( ai, ("BunkerBusterBehavior::update could not find an AIUpdateInterface." ) );
if ( TheGameLogic->getFrame()%modData->m_crashThroughBunkerFXFrequency == 1 )// not too much
{
const FXList *crashFX = modData->m_crashThroughBunkerFX;
if ( getObject()->testStatus( OBJECT_STATUS_MISSILE_KILLING_SELF ) && crashFX )
FXList::doFXObj( crashFX, getObject() );// CrashFX done on the missile/bomb
}
}
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
/** The death callback */
// ------------------------------------------------------------------------------------------------
void BunkerBusterBehavior::onDie( const DamageInfo *damageInfo )
{
// do what we came here to do!
bustTheBunker();
}
// ------------------------------------------------------------------------------------------------
/** The bunker-busting effect callback */
// ------------------------------------------------------------------------------------------------
void BunkerBusterBehavior::bustTheBunker( void )
{
const BunkerBusterBehaviorModuleData *modData = getBunkerBusterBehaviorModuleData();
if ( m_upgradeRequired != NULL )
{
Bool weaponUpgraded = getObject()->getControllingPlayer()->hasUpgradeComplete( m_upgradeRequired );
if ( ! weaponUpgraded )
return;
}
// here is where we kill everyone inside any targeted garrisoned buildings
// AIUpdateInterface *ai = getObject()->getAI();
Object *target = TheGameLogic->findObjectByID( m_victimID );
Object *objectForFX = getObject();
if ( target ) // Was the pilot aiming at an object?
{
objectForFX = target;
ContainModuleInterface *contain = target->getContain();
if ( contain && contain->isBustable() ) // Was that object something that bunkerbusters bust?
{
if ( modData->m_occupantDamageWeaponTemplate )
{
DamageInfo damageInfo;
damageInfo.in.m_damageType = modData->m_occupantDamageWeaponTemplate->getDamageType();
damageInfo.in.m_deathType = modData->m_occupantDamageWeaponTemplate->getDeathType();
damageInfo.in.m_sourceID = getObject()->getID();
damageInfo.in.m_sourcePlayerMask = getObject()->getControllingPlayer()->getPlayerMask();
damageInfo.in.m_amount = 100.0f;
contain->harmAndForceExitAllContained( &damageInfo ); // Ouch!
}
else
contain->killAllContained();
}
}
const FXList *detonationFX = modData->m_detonationFX;
if ( detonationFX )
FXList::doFXObj( detonationFX, objectForFX );//DetonationFX done on the building
#ifdef DO_SEISMIC_SIMULATIONS
// Okay, the right proper way to do this is to add SeismicSim support to FXList...
// But until that day, I'm just gonna do it here, sorry, M Lorenzen 6/26/03
SeismicSimulationNode sim(
objectForFX->getPosition(),
modData->m_seismicEffectRadius,
modData->m_seismicEffectMagnitude,
&bunkerBusterHeavingEarthSeismicFilter );
TheTerrainVisual->addSeismicSimulation( sim );
#endif
if ( modData->m_shockwaveWeaponTemplate )
TheWeaponStore->createAndFireTempWeapon(modData->m_shockwaveWeaponTemplate, objectForFX, objectForFX->getPosition());
} // end onDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BunkerBusterBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BunkerBusterBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BunkerBusterBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,418 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: CountermeasuresBehavior.cpp //////////////////////////////////////////////////////////////
// Author: Kris Morness, April 2003
// Desc: Handles countermeasure firing when under missile threat, and responsible
// for diverting missiles to the flares.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Thing.h"
#include "Common/ThingTemplate.h"
#include "Common/INI.h"
#include "Common/Player.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/Anim2D.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/Module/CountermeasuresBehavior.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
struct CountermeasuresPlayerScanHelper
{
KindOfMaskType m_kindOfToTest;
Object *m_theHealer;
ObjectPointerList *m_objectList;
};
static void checkForCountermeasures( Object *testObj, void *userData )
{
CountermeasuresPlayerScanHelper *helper = (CountermeasuresPlayerScanHelper*)userData;
ObjectPointerList *listToAddTo = helper->m_objectList;
if( testObj->isEffectivelyDead() )
return;
if( testObj->getControllingPlayer() != helper->m_theHealer->getControllingPlayer() )
return;
if( testObj->isOffMap() )
return;
if( !testObj->isAnyKindOf(helper->m_kindOfToTest) )
return;
if( testObj->getBodyModule()->getHealth() >= testObj->getBodyModule()->getMaxHealth() )
return;
listToAddTo->push_back(testObj);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CountermeasuresBehavior::CountermeasuresBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
m_availableCountermeasures = data->m_numberOfVolleys * data->m_volleySize;
m_reactionFrame = 0;
m_activeCountermeasures = 0;
m_divertedMissiles = 0;
m_incomingMissiles = 0;
m_nextVolleyFrame = 0;
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CountermeasuresBehavior::~CountermeasuresBehavior( void )
{
}
// ------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::reportMissileForCountermeasures( Object *missile )
{
if( !missile )
{
return;
}
//Record the number of missiles that have been fired at us
m_incomingMissiles++;
if( m_availableCountermeasures + m_activeCountermeasures > 0 )
{
//We have countermeasures we can use. Determine now whether or not the incoming missile will
//be diverted.
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
if( GameLogicRandomValueReal( 0.0f, 1.0f ) < data->m_evasionRate )
{
//This missile will be diverted!
ProjectileUpdateInterface* pui = NULL;
for( BehaviorModule** u = missile->getBehaviorModules(); *u; ++u )
{
if( (pui = (*u)->getProjectileUpdateInterface()) != NULL )
{
//Make sure the missile diverts after a delay. The delay needs to be larger than
//the countermeasure reaction time or else the missile won't have a countermeasure to divert to!
DEBUG_ASSERTCRASH( data->m_countermeasureReactionFrames < data->m_missileDecoyFrames,
("MissileDecoyDelay needs to be less than CountermeasureReactionTime in order to function properly.") );
pui->setFramesTillCountermeasureDiversionOccurs( data->m_missileDecoyFrames );
m_divertedMissiles++;
if( m_activeCountermeasures == 0 && m_reactionFrame == 0 )
{
//We need to launch our first volley of countermeasures, but we can't do it now. If we
//do, it'll look too artificial. Instead, we need to set up a timer to fake a reaction
//delay.
m_reactionFrame = TheGameLogic->getFrame() + data->m_countermeasureReactionFrames;
}
break;
}
}
}
}
}
//-------------------------------------------------------------------------------------------------
ObjectID CountermeasuresBehavior::calculateCountermeasureToDivertTo( const Object& victim )
{
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
//Flares are pushed to the front of the list, but we only want to acquire the "newest" of the flares, therefore
//stop iterating after we've reached size of a single volley.
Int iteratorMax = MAX( data->m_volleySize, 1 );
Real closestDist = 1e15f;
Object *closestFlare = NULL;
//Start at the end of the list and go towards the beginning.
CountermeasuresVec::iterator it = m_counterMeasures.end();
//end is actually the end so advance the iterator.
if( it )
{
--it;
while( iteratorMax-- )
{
Object *obj = TheGameLogic->findObjectByID( *it );
if( obj )
{
Real dist = ThePartitionManager->getDistanceSquared( obj, getObject(), FROM_CENTER_2D );
if( dist < closestDist )
{
closestDist = dist;
closestFlare = obj;
}
}
else
{
--it;
}
}
}
if( closestFlare )
{
return closestFlare->getID();
}
return INVALID_ID;
}
//-------------------------------------------------------------------------------------------------
Bool CountermeasuresBehavior::isActive() const
{
return isUpgradeActive();
}
//-------------------------------------------------------------------------------------------------
/** The update callback. */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime CountermeasuresBehavior::update( void )
{
UnsignedInt now = TheGameLogic->getFrame();
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
Object *obj = getObject();
if( obj->isEffectivelyDead() )
{
return UPDATE_SLEEP_FOREVER;
}
if( !isUpgradeActive() )
{
return UPDATE_SLEEP_FOREVER;
}
//Validate all existing flares, and clean them up as needed.
for (CountermeasuresVec::iterator it = m_counterMeasures.begin(); it != m_counterMeasures.end(); /*nothing*/ )
{
Object *obj = TheGameLogic->findObjectByID( *it );
if( !obj )
{
it = m_counterMeasures.erase( it );
m_activeCountermeasures--;
}
else
{
++it;
}
}
if( obj->isAirborneTarget() )
{
//Handle flare volley launching (initial reaction, and continuation firing).
if( m_availableCountermeasures )
{
//Deal with the initial volley, but wait until we are permitted to react.
if( m_reactionFrame )
{
if( m_reactionFrame == now )
{
//We have been shot at and now that the reaction timer has expired, fire a full volley of
//countermeasures.
launchVolley();
m_nextVolleyFrame = now + data->m_framesBetweenVolleys;
m_reactionFrame = 0;
}
}
//Handle subsequent volley launching.
if( m_nextVolleyFrame == now )
{
launchVolley();
m_nextVolleyFrame = now + data->m_framesBetweenVolleys;
}
}
}
//Handle auto-reloading (data->m_reloadFrames of zero means it's not possible to auto-reload).
//Aircraft that don't auto-reload require landing at an airfield for resupply.
if( !m_availableCountermeasures && data->m_reloadFrames )
{
if( m_reloadFrame != 0 )
{
if( m_reloadFrame <= now )
{
//We've successfully reloaded automatically.
reloadCountermeasures();
}
}
else
{
//We just started reloading, so set the frame it'll be ready.
m_reloadFrame = now + data->m_reloadFrames;
}
}
return UPDATE_SLEEP( UPDATE_SLEEP_NONE );
}
//-------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::reloadCountermeasures()
{
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
m_availableCountermeasures = data->m_numberOfVolleys * data->m_volleySize;
m_reloadFrame = 0;
}
//-------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::launchVolley()
{
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
Object *obj = getObject();
Real volleySize = (Real)data->m_volleySize;
for( int i = 0; i < data->m_volleySize; i++ )
{
//Each flare in a volley will calculate a different vector to fly out. We have a +/- angle to
//spread out equally. With only one flare, it'll come straight out the back. Two flares will
//launch at the extreme positive and negative angle. Three flares will launch at extreme angles
//plus straight back. Four or more will divy it up equally.
Real currentVolley = (Real)i;
Real ratio = 0.0f;
if( volleySize != 1.0f )
{
//ratio between -1.0 and +1.0f
ratio = currentVolley / (volleySize - 1.0f) * 2.0f - 1.0f;
}
//Now calculate the angle. Simply multiply it by the ratio!
Real angle = ratio * data->m_volleyArcAngle;
Coord3D vel;
PhysicsBehavior *physics = obj->getPhysics();
//Calculate the angle to fire the flare by taking the facing angle and rotating it
//and then scaling it by it's velocity (if it's moving).
obj->getUnitDirectionVector3D( vel );
Vector2 flareVector;
flareVector.X = vel.x;
flareVector.Y = vel.y;
flareVector.Normalize();
flareVector.Rotate( angle );
//Give it back to the Coord3D
vel.x = flareVector.X;
vel.y = flareVector.Y;
vel.z = 0.0f;
Real velocity = physics->getVelocityMagnitude();
if( velocity < 1.0f )
{
velocity = -10.0f;
}
vel.scale( velocity * data->m_volleyVelocityFactor );
const ThingTemplate *thing = TheThingFactory->findTemplate( data->m_flareTemplateName );
if( thing )
{
Object *flare = TheThingFactory->newObject( thing, obj->getControllingPlayer()->getDefaultTeam() );
flare->setPosition( obj->getPosition() );
flare->setOrientation( obj->getOrientation() );
physics->transferVelocityTo( flare->getPhysics() );
flare->getPhysics()->applyMotiveForce( &vel );
m_activeCountermeasures++;
m_availableCountermeasures--;
m_counterMeasures.push_back( flare->getID() );
}
}
}
//------------------------------------------------------------------------------------------------
/** CRC */
//------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
// extend base class
UpgradeMux::upgradeMuxCRC( xfer );
} // end crc
//------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
//------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// extend base class
UpgradeMux::upgradeMuxXfer( xfer );
if( currentVersion >= 2 )
{
xfer->xferSTLObjectIDVector( &m_counterMeasures );
xfer->xferUnsignedInt( &m_availableCountermeasures );
xfer->xferUnsignedInt( &m_activeCountermeasures );
xfer->xferUnsignedInt( &m_divertedMissiles );
xfer->xferUnsignedInt( &m_incomingMissiles );
xfer->xferUnsignedInt( &m_reactionFrame );
xfer->xferUnsignedInt( &m_nextVolleyFrame );
}
} // end xfer
//------------------------------------------------------------------------------------------------
/** Load post process */
//------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
// extend base class
UpgradeMux::upgradeMuxLoadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,799 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: DumbProjectileBehavior.cpp
// Author: Steven Johnson, July 2002
// Desc:
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/BezierSegment.h"
#include "Common/GameCommon.h"
#include "Common/GameState.h"
#include "Common/Player.h"
#include "Common/ThingTemplate.h"
#include "Common/RandomValue.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/FXList.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/DumbProjectileBehavior.h"
#include "GameLogic/Module/MissileAIUpdate.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Weapon.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const Int DEFAULT_MAX_LIFESPAN = 10 * LOGICFRAMES_PER_SECOND;
//-----------------------------------------------------------------------------
DumbProjectileBehaviorModuleData::DumbProjectileBehaviorModuleData() :
m_maxLifespan(DEFAULT_MAX_LIFESPAN),
m_detonateCallsKill(FALSE),
m_orientToFlightPath(TRUE),
m_tumbleRandomly(FALSE),
m_firstHeight(0.0f),
m_secondHeight(0.0f),
m_firstPercentIndent(0.0f),
m_secondPercentIndent(0.0f),
m_garrisonHitKillCount(0),
m_garrisonHitKillFX(NULL),
m_flightPathAdjustDistPerFrame(0.0f)
{
}
//-----------------------------------------------------------------------------
void DumbProjectileBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
UpdateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "MaxLifespan", INI::parseDurationUnsignedInt, NULL, offsetof( DumbProjectileBehaviorModuleData, m_maxLifespan ) },
{ "TumbleRandomly", INI::parseBool, NULL, offsetof( DumbProjectileBehaviorModuleData, m_tumbleRandomly ) },
{ "DetonateCallsKill", INI::parseBool, NULL, offsetof( DumbProjectileBehaviorModuleData, m_detonateCallsKill ) },
{ "OrientToFlightPath", INI::parseBool, NULL, offsetof( DumbProjectileBehaviorModuleData, m_orientToFlightPath ) },
{ "FirstHeight", INI::parseReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_firstHeight ) },
{ "SecondHeight", INI::parseReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_secondHeight ) },
{ "FirstPercentIndent", INI::parsePercentToReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_firstPercentIndent ) },
{ "SecondPercentIndent", INI::parsePercentToReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_secondPercentIndent ) },
{ "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillKindof ) },
{ "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillKindofNot ) },
{ "GarrisonHitKillCount", INI::parseUnsignedInt, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillCount ) },
{ "GarrisonHitKillFX", INI::parseFXList, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillFX ) },
{ "FlightPathAdjustDistPerSecond", INI::parseVelocityReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_flightPathAdjustDistPerFrame ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DumbProjectileBehavior::DumbProjectileBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
m_launcherID = INVALID_ID;
m_victimID = INVALID_ID;
m_detonationWeaponTmpl = NULL;
m_lifespanFrame = 0;
m_flightPath.clear();
m_flightPathSegments = 0;
m_flightPathSpeed = 0;
m_flightPathStart.zero();
m_flightPathEnd.zero();
m_currentFlightPathStep = 0;
m_extraBonusFlags = 0;
m_hasDetonated = FALSE;
}
//-------------------------------------------------------------------------------------------------
DumbProjectileBehavior::~DumbProjectileBehavior()
{
}
//-------------------------------------------------------------------------------------------------
inline Bool within(Real min, Real val, Real max)
{
return min <= val && val <= max;
}
//-------------------------------------------------------------------------------------------------
inline Bool notWithin(Real min, Real val, Real max)
{
return val < min || val > max;
}
#ifdef NOT_IN_USE
#define NO_ONLY_RETURN_CLIPPED_PITCHES
//-------------------------------------------------------------------------------------------------
static Bool calcTrajectory(
const Coord3D& start, // in: where the projectile starts
const Coord3D& end, // in: where the projectile wants to end up
Real velocity, // in: the initial speed of the projectile
Real minPitch, // in: min pitch (-PI/2)
Real maxPitch, // in: max pitch (-PI/2)
Bool preferShortPitch, // in: prefer the shorter or longer path?
Real& angle, // out: the angle to aim
Real& pitch // out: the pitch to aim for
)
{
Bool exactTarget = false;
angle = 0.0f;
pitch = 0.0f;
if (velocity <= 0.0f)
{
DEBUG_CRASH(("cant get there from here (1)"));
return false;
}
Real dx = end.x - start.x;
Real dy = end.y - start.y;
Real dz = end.z - start.z;
// calculating the angle is trivial.
angle = atan2(dy, dx);
// calculating the pitch requires a bit more effort.
Real horizDistSqr = sqr(dx) + sqr(dy);
Real horizDist = sqrt(horizDistSqr);
// calc the two possible pitches that will cover the given horizontal range.
// (this is actually only true if dz==0, but is a good first guess)
Real gravity = fabs(TheGlobalData->m_gravity);
Real gravityTwoDZ = gravity * 2.0f * dz;
// let's start by aiming directly for it. we know this isn't right (unless gravity
// is zero, which it's not) but is a good starting point...
Real theta = atan2(dz, horizDist);
// if the angle isn't pretty shallow, we can get a better initial guess by using
// the code below...
const Real SHALLOW_ANGLE = 0.5f * PI / 180.0f;
if (fabs(theta) > SHALLOW_ANGLE)
{
Real t = horizDist / velocity;
Real vz = (dz/t + 0.5f*gravity*t);
Real sineOfAngle = clamp(-1.0f, vz / velocity, 1.0f);
theta = ASin(sineOfAngle)*0.5f;
}
/*
this is, in theory, the "right" formula for dz==0, but I
get better results with the stuff above:
Real sineOfAngle = (gravity * horizDist) / sqr(velocity);
if (sineOfAngle > 1.0f)
{
return false;
}
Real theta = ASin(sineOfAngle)*0.5f;
*/
Real pitches[2];
Real cosPitches[2];
Real sinPitches[2];
Real theta_min = max(minPitch, -PI/2);
Real theta_max = min(maxPitch, PI/2);
const Real MIN_ANGLE_DIFF = (PI/(180.0f*16.0f)); // 1/16th of a degree. yes, we need that accuracy.
//Int numLoops = 0;
while (theta_max > theta_min + MIN_ANGLE_DIFF)
{
//++numLoops;
pitches[0] = theta; // shallower angle
pitches[1] = (theta >= 0.0) ? (PI/2 - theta) : (-PI/2 - theta); // steeper angle
DEBUG_ASSERTCRASH(pitches[0]<=PI/2&&pitches[0]>=-PI/2,("bad pitches[0] %f\n",rad2deg(pitches[0])));
DEBUG_ASSERTCRASH(pitches[1]<=PI/2&&pitches[1]>=-PI/2,("bad pitches[1] %f\n",rad2deg(pitches[1])));
// calc the horiz-speed & time for each.
// note that time can only be negative for 90<angle<270, and since we
// ruled those out above, we're gold.
sinPitches[0] = Sin(pitches[0]);
sinPitches[1] = Sin(pitches[1]);
cosPitches[0] = Cos(pitches[0]);
cosPitches[1] = Cos(pitches[1]);
Real t0 = (horizDist / (velocity * cosPitches[0]));
Real t1 = (horizDist / (velocity * cosPitches[1]));
t0 = MAX(0,t0);
t1 = MAX(0,t1);
DEBUG_ASSERTCRASH(t0>=0&&t1>=0,("neg time"));
Int preferred = ((t0 < t1) == (preferShortPitch)) ? 0 : 1;
// ok, NOW... since dz is virtually NEVER zero, do a little approximation
// to get a better guess. (solving the equations directly are hard and
// it's simpler to approximate)
Bool tooClose = false;
Real vx, vz, root;
vz = velocity*sinPitches[preferred];
root = sqr(vz) - gravityTwoDZ;
if (root < 0.0f)
{
// oops, no solution for our preferred pitch. try the other one.
if (preferred == 0)
tooClose = true; // if this fails for the shallow case, it's 'cuz the result is too close
preferred = 1 - preferred;
vz = velocity*sinPitches[preferred];
root = sqr(vz) - gravityTwoDZ;
if (root < 0.0f)
{
DEBUG_CRASH(("cant get there from here (2)"));
return false;
}
}
#ifdef ONLY_RETURN_CLIPPED_PITCHES
// if we get this far, we have some plausible solution...
// it actually may be off, but let's go ahead and set the result
// (along with the "exact" flag) so that the caller will want to
// try to use the result. (we'll probably iterate and overwrite
// this result with a more precise one.)
if (within(minPitch, pitches[preferred], maxPitch))
{
pitch = pitches[preferred];
exactTarget = true;
}
#else
// if we get this far, we have some plausible solution...
// it actually may be off, but let's go ahead and set the result
// (along with the "exact" flag) so that the caller will want to
// try to use the result. (we'll probably iterate and overwrite
// this result with a more precise one.) NOTE however that we
// don't validate it's within the proper range... caller must do that!
pitch = pitches[preferred];
exactTarget = true;
#endif
vx = velocity*cosPitches[preferred];
Real actualRange = (vx*(vz + sqrt(root)))/gravity;
const Real CLOSE_ENOUGH_RANGE = 5.0f;
if (tooClose || (actualRange < horizDist - CLOSE_ENOUGH_RANGE))
{
theta_min = theta;
theta = (theta + theta_max) * 0.5f;
}
else if (actualRange > horizDist + CLOSE_ENOUGH_RANGE)
{
theta_max = theta;
theta = (theta + theta_min) * 0.5f;
}
else
{
break;
}
}
//DEBUG_LOG(("took %d loops to find a match\n",numLoops));
if (exactTarget)
return true;
return false;
}
#endif NOT_IN_USE
//-------------------------------------------------------------------------------------------------
// Prepares the missile for launch via proper weapon-system channels.
//-------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::projectileLaunchAtObjectOrPosition(
const Object* victim,
const Coord3D* victimPos,
const Object* launcher,
WeaponSlotType wslot,
Int specificBarrelToUse,
const WeaponTemplate* detWeap,
const ParticleSystemTemplate* exhaustSysOverride
)
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
DEBUG_ASSERTCRASH(specificBarrelToUse>=0, ("specificBarrelToUse must now be explicit"));
m_launcherID = launcher ? launcher->getID() : INVALID_ID;
m_extraBonusFlags = launcher ? launcher->getWeaponBonusCondition() : 0;
m_victimID = victim ? victim->getID() : INVALID_ID;
m_detonationWeaponTmpl = detWeap;
m_lifespanFrame = TheGameLogic->getFrame() + d->m_maxLifespan;
Object* projectile = getObject();
Weapon::positionProjectileForLaunch(projectile, launcher, wslot, specificBarrelToUse);
projectileFireAtObjectOrPosition( victim, victimPos, detWeap, exhaustSysOverride );
}
//-------------------------------------------------------------------------------------------------
// The actual firing of the missile once setup. Uses a Bezier curve with points parameterized in ini
//-------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::projectileFireAtObjectOrPosition( const Object *victim, const Coord3D *victimPos, const WeaponTemplate *detWeap, const ParticleSystemTemplate* exhaustSysOverride )
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
Object* projectile = getObject();
Real weaponSpeed = detWeap ? detWeap->getWeaponSpeed() : 0.0f;
Real minWeaponSpeed = detWeap ? detWeap->getMinWeaponSpeed() : 0.0f;
// if an object, aim at the center, not the ground part
Coord3D victimPosToUse;
if (victim)
victim->getGeometryInfo().getCenterPosition(*victim->getPosition(), victimPosToUse);
else
victimPosToUse = *victimPos;
if( detWeap && detWeap->isScaleWeaponSpeed() )
{
// Some weapons want to scale their start speed to the range
Real minRange = detWeap->getMinimumAttackRange();
Real maxRange = detWeap->getUnmodifiedAttackRange();
Real range = sqrt(ThePartitionManager->getDistanceSquared( projectile, &victimPosToUse, FROM_CENTER_2D ) );
Real rangeRatio = (range - minRange) / (maxRange - minRange);
m_flightPathSpeed = (rangeRatio * (weaponSpeed - minWeaponSpeed)) + minWeaponSpeed;
}
else
{
m_flightPathSpeed = weaponSpeed;
}
PhysicsBehavior* physics = projectile->getPhysics();
if ( d->m_tumbleRandomly && physics)
{
physics->setPitchRate( GameLogicRandomValueReal( -1.0f/PI, 1.0f/PI ) );
physics->setYawRate( GameLogicRandomValueReal( -1.0f/PI, 1.0f/PI ) );
physics->setRollRate( GameLogicRandomValueReal( -1.0f/PI, 1.0f/PI ) );
}
m_flightPathStart = *getObject()->getPosition();
m_flightPathEnd = victimPosToUse;
if (!calcFlightPath(true))
{
//Can only fail if wildly incorrect points
TheGameLogic->destroyObject( projectile );
return;
}
m_currentFlightPathStep = 0;// We are at the first point, because the launching put us there
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool DumbProjectileBehavior::calcFlightPath(Bool recalcNumSegments)
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
Coord3D controlPoints[4];
//First point is us, last point is them
controlPoints[0] = m_flightPathStart;
controlPoints[3] = m_flightPathEnd;
Real highestInterveningTerrain;
Bool onMap = ThePartitionManager->estimateTerrainExtremesAlongLine( controlPoints[0], controlPoints[3], NULL, &highestInterveningTerrain, NULL, NULL );
if( !onMap )
{
return false;
}
// X and Y for inner points are along the line between us, so normalize and scale a vector between us, but
// only use the x and y of the result
Vector3 targetVector;// 0 origin vector between me and him
targetVector.X = controlPoints[3].x - controlPoints[0].x;
targetVector.Y = controlPoints[3].y - controlPoints[0].y;
targetVector.Z = controlPoints[3].z - controlPoints[0].z;
Real targetDistance = targetVector.Length();
targetVector.Normalize();
Vector3 firstPointAlongLine = targetVector * (targetDistance * d->m_firstPercentIndent );
Vector3 secondPointAlongLine = targetVector * (targetDistance * d->m_secondPercentIndent );
controlPoints[1].x = firstPointAlongLine.X + controlPoints[0].x;// add world start to offset along the origin based vector
controlPoints[1].y = firstPointAlongLine.Y + controlPoints[0].y;
controlPoints[2].x = secondPointAlongLine.X + controlPoints[0].x;
controlPoints[2].y = secondPointAlongLine.Y + controlPoints[0].y;
// Z's are determined using the highest intervening height so they won't hit hills, low end bounded by current Zs
highestInterveningTerrain = max( highestInterveningTerrain, controlPoints[0].z );
highestInterveningTerrain = max( highestInterveningTerrain, controlPoints[3].z );
controlPoints[1].z = highestInterveningTerrain + d->m_firstHeight;
controlPoints[2].z = highestInterveningTerrain + d->m_secondHeight;
// With four control points, we have a curve. We will decide how many frames we want to take to get to the target,
// and fill our vector with those curve points.
BezierSegment flightCurve( controlPoints );
if (recalcNumSegments)
{
Real flightDistance = flightCurve.getApproximateLength();
m_flightPathSegments = ceil( flightDistance / m_flightPathSpeed );
}
flightCurve.getSegmentPoints( m_flightPathSegments, &m_flightPath );
DEBUG_ASSERTCRASH(m_flightPathSegments == m_flightPath.size(), ("m_flightPathSegments mismatch"));
#if defined(_DEBUG) || defined(_INTERNAL)
if( TheGlobalData->m_debugProjectilePath )
displayFlightPath();
#endif
return true;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool DumbProjectileBehavior::projectileHandleCollision( Object *other )
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
if (other != NULL)
{
Object *projectileLauncher = TheGameLogic->findObjectByID( projectileGetLauncherID() );
// if it's not the specific thing we were targeting, see if we should incidentally collide...
if (!m_detonationWeaponTmpl->shouldProjectileCollideWith(projectileLauncher, getObject(), other, m_victimID))
{
//DEBUG_LOG(("ignoring projectile collision with %s at frame %d\n",other->getTemplate()->getName().str(),TheGameLogic->getFrame()));
return true;
}
if (d->m_garrisonHitKillCount > 0)
{
ContainModuleInterface* contain = other->getContain();
if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() )
{
Int numKilled = 0;
// garrisonable buildings subvert the normal process here.
const ContainedItemsList* items = contain->getContainedItemsList();
if (items)
{
for (ContainedItemsList::const_iterator it = items->begin(); *it != NULL && numKilled < d->m_garrisonHitKillCount; )
{
Object* thingToKill = *it++;
if (!thingToKill->isEffectivelyDead() && thingToKill->isKindOfMulti(d->m_garrisonHitKillKindof, d->m_garrisonHitKillKindofNot))
{
//DEBUG_LOG(("Killed a garrisoned unit (%08lx %s) via Flash-Bang!\n",thingToKill,thingToKill->getTemplate()->getName().str()));
if (projectileLauncher)
projectileLauncher->scoreTheKill( thingToKill );
thingToKill->kill();
++numKilled;
}
} // next contained item
} // if items
if (numKilled > 0)
{
// note, fx is played at center of building, not at grenade's location
FXList::doFXObj(d->m_garrisonHitKillFX, other, NULL);
getObject()->getControllingPlayer()->getAcademyStats()->recordClearedGarrisonedBuilding();
// don't do the normal explosion; just destroy ourselves & return
TheGameLogic->destroyObject(getObject());
return true;
}
} // if a garrisonable thing
}
}
// collided with something... blow'd up!
detonate();
// mark ourself as "no collisions" (since we might still exist in slow death mode)
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
return true;
}
//-------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::detonate()
{
if ( m_hasDetonated )
return;
Object* obj = getObject();
if (m_detonationWeaponTmpl)
{
TheWeaponStore->handleProjectileDetonation(m_detonationWeaponTmpl, obj, obj->getPosition(), m_extraBonusFlags);
if ( getDumbProjectileBehaviorModuleData()->m_detonateCallsKill )
{
// don't call kill(); do it manually, so we can specify DEATH_DETONATED
DamageInfo damageInfo;
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
damageInfo.in.m_deathType = DEATH_DETONATED;
damageInfo.in.m_sourceID = INVALID_ID;
damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth();
obj->attemptDamage( &damageInfo );
}
else
{
TheGameLogic->destroyObject( obj );
}
}
else
{
// don't call kill(); do it manually, so we can specify DEATH_DETONATED
DamageInfo damageInfo;
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
damageInfo.in.m_deathType = DEATH_DETONATED;
damageInfo.in.m_sourceID = INVALID_ID;
damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth();
obj->attemptDamage( &damageInfo );
}
if (obj->getDrawable())
obj->getDrawable()->setDrawableHidden(true);
m_hasDetonated = TRUE;
}
//-------------------------------------------------------------------------------------------------
/**
* Simulate one frame of a missile's behavior
*/
UpdateSleepTime DumbProjectileBehavior::update()
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
if (m_lifespanFrame != 0 && TheGameLogic->getFrame() >= m_lifespanFrame)
{
// lifetime demands detonation
detonate();
return UPDATE_SLEEP_NONE;
}
if( m_currentFlightPathStep >= m_flightPath.size() )
{
// No more steps to use. Would go out of bounds on vector, so have to do something.
// We could allow physics to take over and make us fall, but the point of this whole task
// is to guarentee where the shell explodes. This way, it _will_ explode at the target point.
detonate();
return UPDATE_SLEEP_NONE;
}
if (m_victimID != INVALID_ID && d->m_flightPathAdjustDistPerFrame > 0.0f)
{
Object* victim = TheGameLogic->findObjectByID(m_victimID);
if (victim)
{
Coord3D newVictimPos;
victim->getGeometryInfo().getCenterPosition(*victim->getPosition(), newVictimPos);
Coord3D delta;
delta.x = newVictimPos.x - m_flightPathEnd.x;
delta.y = newVictimPos.y - m_flightPathEnd.y;
delta.z = newVictimPos.z - m_flightPathEnd.z;
Real distVictimMovedSqr = sqr(delta.x) + sqr(delta.y) + sqr(delta.z);
if (distVictimMovedSqr > 0.1f)
{
Real distVictimMoved = sqrtf(distVictimMovedSqr);
if (distVictimMoved > d->m_flightPathAdjustDistPerFrame)
distVictimMoved = d->m_flightPathAdjustDistPerFrame;
delta.normalize();
m_flightPathEnd.x += distVictimMoved * delta.x;
m_flightPathEnd.y += distVictimMoved * delta.y;
m_flightPathEnd.z += distVictimMoved * delta.z;
if (!calcFlightPath(false))
{
DEBUG_CRASH(("Hmm, recalc of flight path returned false... should this happen?"));
detonate();
return UPDATE_SLEEP_NONE;
}
}
}
}
//Otherwise, continue to force the flight path
Coord3D flightStep = m_flightPath[m_currentFlightPathStep];
if (d->m_orientToFlightPath && (!d->m_tumbleRandomly) )
{
if ( m_currentFlightPathStep > 0)
{
// this seems reasonable; however, if this object has a PhysicsBehavior on it, this calc will be wrong,
// since Physics is applying gravity, which we duly ignore, but the prevPos won't be what we expect.
// get it from the flight path instead. (srj)
//Coord3D prevPos = *getObject()->getPosition();
Coord3D prevPos = m_flightPath[m_currentFlightPathStep - 1];
Vector3 curDir(flightStep.x - prevPos.x, flightStep.y - prevPos.y, flightStep.z - prevPos.z);
curDir.Normalize(); // buildTransformMatrix wants it this way
Matrix3D orientMtx;
orientMtx.buildTransformMatrix(Vector3(flightStep.x, flightStep.y, flightStep.z), curDir);
getObject()->setTransformMatrix(&orientMtx);
}
else // oops! how do we orient the projectile on the zeroeth frame? This didn't matter until we started using the
//long, blurry projectile graphics which look badly oriented on step 0 of the flight path
// so lets orient it the same as if it were on frame 1!
{
Coord3D prevPos = m_flightPath[0];
Coord3D curPos = m_flightPath[1];
Vector3 curDir(curPos.x - prevPos.x, curPos.y - prevPos.y, curPos.z - prevPos.z);
curDir.Normalize(); // buildTransformMatrix wants it this way
Matrix3D orientMtx;
orientMtx.buildTransformMatrix(Vector3(flightStep.x, flightStep.y, flightStep.z), curDir);
getObject()->setTransformMatrix(&orientMtx);
}
}
else
{
getObject()->setPosition(&flightStep);
}
// note that we want to use getHighestLayerForDestination() here, so that anything even slightly
// below the bridge translates into GROUND. (getLayerForDestination just does a "closest" check)
PathfindLayerEnum oldLayer = getObject()->getLayer();
PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(getObject()->getPosition());
getObject()->setLayer(newLayer);
if (oldLayer != LAYER_GROUND && newLayer == LAYER_GROUND)
{
// see if we' still in the bridge's xy area
Coord3D tmp = *getObject()->getPosition();
tmp.z = 9999.0f;
PathfindLayerEnum testLayer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
if (testLayer == oldLayer)
{
// ensure we are slightly above the bridge, to account for fudge & sloppy art
const Real FUDGE = 2.0f;
tmp.z = TheTerrainLogic->getLayerHeight(tmp.x, tmp.y, testLayer) + FUDGE;
getObject()->setPosition(&tmp);
// blow'd up!
detonate();
return UPDATE_SLEEP_NONE;
}
}
++m_currentFlightPathStep;
return UPDATE_SLEEP_NONE;//This no longer flys with physics, so it needs to not sleep
}
// ------------------------------------------------------------------------------------------------
/** displayFlightPath for debugging */
// ------------------------------------------------------------------------------------------------
#if defined(_DEBUG) || defined(_INTERNAL)
void DumbProjectileBehavior::displayFlightPath()
{
extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color);
for( Int pointIndex = 0; pointIndex < m_flightPath.size(); ++pointIndex )
{
addIcon(&m_flightPath[pointIndex], TheGlobalData->m_debugProjectileTileWidth,
TheGlobalData->m_debugProjectileTileDuration,
TheGlobalData->m_debugProjectileTileColor);
}
}
#endif
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// launcher
xfer->xferObjectID( &m_launcherID );
// victim ID
xfer->xferObjectID( &m_victimID );
xfer->xferInt( &m_flightPathSegments );
xfer->xferReal( &m_flightPathSpeed );
xfer->xferCoord3D( &m_flightPathStart );
xfer->xferCoord3D( &m_flightPathEnd );
// weapon template
AsciiString weaponTemplateName = AsciiString::TheEmptyString;
if( m_detonationWeaponTmpl )
weaponTemplateName = m_detonationWeaponTmpl->getName();
xfer->xferAsciiString( &weaponTemplateName );
if( xfer->getXferMode() == XFER_LOAD )
{
if( weaponTemplateName == AsciiString::TheEmptyString )
m_detonationWeaponTmpl = NULL;
else
{
// find template
m_detonationWeaponTmpl = TheWeaponStore->findWeaponTemplate( weaponTemplateName );
// sanity
if( m_detonationWeaponTmpl == NULL )
{
DEBUG_CRASH(( "DumbProjectileBehavior::xfer - Unknown weapon template '%s'\n",
weaponTemplateName.str() ));
throw SC_INVALID_DATA;
} // end if
} // end else
} // end if
// lifespan frame
xfer->xferUnsignedInt( &m_lifespanFrame );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,365 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: FireWeaponWhenDamagedBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_SLOWDEATHPHASE_NAMES
#include "Common/Thing.h"
#include "Common/ThingTemplate.h"
#include "Common/INI.h"
#include "Common/RandomValue.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/FireWeaponWhenDamagedBehavior.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
const Int MAX_IDX = 32;
const Real BEGIN_MIDPOINT_RATIO = 0.35f;
const Real END_MIDPOINT_RATIO = 0.65f;
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponWhenDamagedBehavior::FireWeaponWhenDamagedBehavior( Thing *thing, const ModuleData* moduleData ) :
UpdateModule( thing, moduleData ),
m_reactionWeaponPristine( NULL ),
m_reactionWeaponDamaged( NULL ),
m_reactionWeaponReallyDamaged( NULL ),
m_reactionWeaponRubble( NULL ),
m_continuousWeaponPristine( NULL ),
m_continuousWeaponDamaged( NULL ),
m_continuousWeaponReallyDamaged( NULL ),
m_continuousWeaponRubble( NULL )
{
const FireWeaponWhenDamagedBehaviorModuleData *d = getFireWeaponWhenDamagedBehaviorModuleData();
const Object* obj = getObject();
if ( d->m_reactionWeaponPristine )
{
m_reactionWeaponPristine = TheWeaponStore->allocateNewWeapon(
d->m_reactionWeaponPristine, PRIMARY_WEAPON);
m_reactionWeaponPristine->reloadAmmo( obj );
}
if ( d->m_reactionWeaponDamaged )
{
m_reactionWeaponDamaged = TheWeaponStore->allocateNewWeapon(
d->m_reactionWeaponDamaged, PRIMARY_WEAPON);
m_reactionWeaponDamaged->reloadAmmo( obj );
}
if ( d->m_reactionWeaponReallyDamaged )
{
m_reactionWeaponReallyDamaged = TheWeaponStore->allocateNewWeapon(
d->m_reactionWeaponReallyDamaged, PRIMARY_WEAPON);
m_reactionWeaponReallyDamaged->reloadAmmo( obj );
}
if ( d->m_reactionWeaponRubble )
{
m_reactionWeaponRubble = TheWeaponStore->allocateNewWeapon(
d->m_reactionWeaponRubble, PRIMARY_WEAPON);
m_reactionWeaponRubble->reloadAmmo( obj );
}
if ( d->m_continuousWeaponPristine )
{
m_continuousWeaponPristine = TheWeaponStore->allocateNewWeapon(
d->m_continuousWeaponPristine, PRIMARY_WEAPON);
m_continuousWeaponPristine->reloadAmmo( obj );
}
if ( d->m_continuousWeaponDamaged )
{
m_continuousWeaponDamaged = TheWeaponStore->allocateNewWeapon(
d->m_continuousWeaponDamaged, PRIMARY_WEAPON);
m_continuousWeaponDamaged->reloadAmmo( obj );
}
if ( d->m_continuousWeaponReallyDamaged )
{
m_continuousWeaponReallyDamaged = TheWeaponStore->allocateNewWeapon(
d->m_continuousWeaponReallyDamaged, PRIMARY_WEAPON);
m_continuousWeaponReallyDamaged->reloadAmmo( obj );
}
if ( d->m_continuousWeaponRubble )
{
m_continuousWeaponRubble = TheWeaponStore->allocateNewWeapon(
d->m_continuousWeaponRubble, PRIMARY_WEAPON);
m_continuousWeaponRubble->reloadAmmo( obj );
}
if (d->m_initiallyActive)
{
giveSelfUpgrade();
}
if (isUpgradeActive() &&
(d->m_continuousWeaponPristine != NULL ||
d->m_continuousWeaponDamaged != NULL ||
d->m_continuousWeaponReallyDamaged != NULL ||
d->m_continuousWeaponRubble != NULL))
{
setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
}
else
{
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponWhenDamagedBehavior::~FireWeaponWhenDamagedBehavior( void )
{
if (m_reactionWeaponPristine)
m_reactionWeaponPristine->deleteInstance();
if (m_reactionWeaponDamaged)
m_reactionWeaponDamaged->deleteInstance();
if (m_reactionWeaponReallyDamaged)
m_reactionWeaponReallyDamaged->deleteInstance();
if (m_reactionWeaponRubble)
m_reactionWeaponRubble->deleteInstance();
if (m_continuousWeaponPristine)
m_continuousWeaponPristine->deleteInstance();
if (m_continuousWeaponDamaged)
m_continuousWeaponDamaged->deleteInstance();
if (m_continuousWeaponReallyDamaged)
m_continuousWeaponReallyDamaged->deleteInstance();
if (m_continuousWeaponRubble)
m_continuousWeaponRubble->deleteInstance();
}
//-------------------------------------------------------------------------------------------------
/** Damage has been dealt, this is an opportunity to reach to that damage */
//-------------------------------------------------------------------------------------------------
void FireWeaponWhenDamagedBehavior::onDamage( DamageInfo *damageInfo )
{
if (!isUpgradeActive())
return;
const FireWeaponWhenDamagedBehaviorModuleData* d = getFireWeaponWhenDamagedBehaviorModuleData();
// right type?
if (!getDamageTypeFlag(d->m_damageTypes, damageInfo->in.m_damageType))
return;
// right amount? (use actual [post-armor] damage dealt)
if (damageInfo->out.m_actualDamageDealt < d->m_damageAmount)
return;
const Object *obj = getObject();
BodyDamageType bdt = obj->getBodyModule()->getDamageState();
if ( bdt == BODY_RUBBLE )
{
if( m_reactionWeaponRubble && m_reactionWeaponRubble->getStatus() == READY_TO_FIRE )
{
m_reactionWeaponRubble->forceFireWeapon( obj, obj->getPosition() );
}
}
else if ( bdt == BODY_REALLYDAMAGED )
{
if( m_reactionWeaponReallyDamaged && m_reactionWeaponReallyDamaged->getStatus() == READY_TO_FIRE )
{
m_reactionWeaponReallyDamaged->forceFireWeapon( obj, obj->getPosition() );
}
}
else if ( bdt == BODY_DAMAGED )
{
if( m_reactionWeaponDamaged && m_reactionWeaponDamaged->getStatus() == READY_TO_FIRE )
{
m_reactionWeaponDamaged->forceFireWeapon( obj, obj->getPosition() );
}
}
else // not damaged yet
{
if( m_reactionWeaponPristine && m_reactionWeaponPristine->getStatus() == READY_TO_FIRE )
{
m_reactionWeaponPristine->forceFireWeapon( obj, obj->getPosition() );
}
}
}
//-------------------------------------------------------------------------------------------------
/** if object fires weapon constantly, figure out which one and do it */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime FireWeaponWhenDamagedBehavior::update( void )
{
if (!isUpgradeActive())
{
DEBUG_ASSERTCRASH(isUpgradeActive(), ("hmm, this should not be possible"));
return UPDATE_SLEEP_FOREVER;
}
const Object *obj = getObject();
BodyDamageType bdt = obj->getBodyModule()->getDamageState();
if ( bdt == BODY_RUBBLE )
{
if( m_continuousWeaponRubble && m_continuousWeaponRubble->getStatus() == READY_TO_FIRE )
{
m_continuousWeaponRubble->forceFireWeapon( obj, obj->getPosition() );
}
}
else if ( bdt == BODY_REALLYDAMAGED )
{
if( m_continuousWeaponReallyDamaged && m_continuousWeaponReallyDamaged->getStatus() == READY_TO_FIRE )
{
m_continuousWeaponReallyDamaged->forceFireWeapon( obj, obj->getPosition() );
}
}
else if ( bdt == BODY_DAMAGED )
{
if( m_continuousWeaponDamaged && m_continuousWeaponDamaged->getStatus() == READY_TO_FIRE )
{
m_continuousWeaponDamaged->forceFireWeapon( obj, obj->getPosition() );
}
}
else // not damaged yet
{
if( m_continuousWeaponPristine && m_continuousWeaponPristine->getStatus() == READY_TO_FIRE )
{
m_continuousWeaponPristine->forceFireWeapon( obj, obj->getPosition() );
}
}
return UPDATE_SLEEP_NONE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDamagedBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
// extend upgrade mux
UpgradeMux::upgradeMuxCRC( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDamagedBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// extend upgrade mux
UpgradeMux::upgradeMuxXfer( xfer );
Bool weaponPresent;
// reaction pristine
weaponPresent = m_reactionWeaponPristine ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_reactionWeaponPristine );
// reaction damaged
weaponPresent = m_reactionWeaponDamaged ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_reactionWeaponDamaged );
// reaction really damaged
weaponPresent = m_reactionWeaponReallyDamaged ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_reactionWeaponReallyDamaged );
// reaction rubble
weaponPresent = m_reactionWeaponRubble ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_reactionWeaponRubble );
// continuous pristine
weaponPresent = m_continuousWeaponPristine ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_continuousWeaponPristine );
// continuous damaged
weaponPresent = m_continuousWeaponDamaged ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_continuousWeaponDamaged );
// continuous really damaged
weaponPresent = m_continuousWeaponReallyDamaged ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_continuousWeaponReallyDamaged );
// continuous rubble
weaponPresent = m_continuousWeaponRubble ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_continuousWeaponRubble );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDamagedBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
// extend upgrade mux
UpgradeMux::upgradeMuxLoadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,168 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: FireWeaponWhenDeadBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_SLOWDEATHPHASE_NAMES
#include "Common/Thing.h"
#include "Common/ThingTemplate.h"
#include "Common/INI.h"
#include "Common/RandomValue.h"
#include "Common/Xfer.h"
#include "Common/Player.h"
#include "GameClient/Drawable.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/FireWeaponWhenDeadBehavior.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
const Int MAX_IDX = 32;
const Real BEGIN_MIDPOINT_RATIO = 0.35f;
const Real END_MIDPOINT_RATIO = 0.65f;
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponWhenDeadBehavior::FireWeaponWhenDeadBehavior( Thing *thing, const ModuleData* moduleData ) :
BehaviorModule( thing, moduleData )
{
if (getFireWeaponWhenDeadBehaviorModuleData()->m_initiallyActive)
{
giveSelfUpgrade();
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponWhenDeadBehavior::~FireWeaponWhenDeadBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void FireWeaponWhenDeadBehavior::onDie( const DamageInfo *damageInfo )
{
const FireWeaponWhenDeadBehaviorModuleData* d = getFireWeaponWhenDeadBehaviorModuleData();
Object *obj = getObject();
if (!isUpgradeActive())
return;
// right type?
if (!d->m_dieMuxData.isDieApplicable(getObject(), damageInfo))
return;
// This will never apply until built. Otherwise canceling construction sets it off, and killing
// a one hitpoint one percent building will too.
if( obj->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
return;
UpgradeMaskType activation, conflicting;
getUpgradeActivationMasks( activation, conflicting );
if( obj->getObjectCompletedUpgradeMask().testForAny( conflicting ) )
{
return;
}
if( obj->getControllingPlayer() && obj->getControllingPlayer()->getCompletedUpgradeMask().testForAny( conflicting ) )
{
return;
}
if (d->m_deathWeapon)
{
// fire the default weapon
TheWeaponStore->createAndFireTempWeapon(d->m_deathWeapon, obj, obj->getPosition());
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDeadBehavior::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
// extend upgrade mux
UpgradeMux::upgradeMuxCRC( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDeadBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
BehaviorModule::xfer( xfer );
// extend upgrade mux
UpgradeMux::upgradeMuxXfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDeadBehavior::loadPostProcess( void )
{
// extend base class
BehaviorModule::loadPostProcess();
// extend upgrade mux
UpgradeMux::upgradeMuxLoadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,589 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: GenerateMinefieldBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_SLOWDEATHPHASE_NAMES
#include "Common/GlobalData.h"
#include "Common/Thing.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/INI.h"
#include "Common/Player.h"
#include "Common/RandomValue.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/GenerateMinefieldBehavior.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectIter.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
GenerateMinefieldBehaviorModuleData::GenerateMinefieldBehaviorModuleData()
{
m_mineName.clear();
m_mineNameUpgraded.clear();
m_mineUpgradeTrigger.clear();
m_genFX = NULL;
m_distanceAroundObject = TheGlobalData->m_standardMinefieldDistance;
m_minesPerSquareFoot = TheGlobalData->m_standardMinefieldDensity;
m_onDeath = false;
m_borderOnly = true;
m_alwaysCircular = false;
m_upgradable = false;
m_smartBorder = false;
m_smartBorderSkipInterior = true;
m_randomJitter = 0.0f;
m_skipIfThisMuchUnderStructure = 0.33f;
}
//-------------------------------------------------------------------------------------------------
/*static*/ void GenerateMinefieldBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
static const FieldParse dataFieldParse[] =
{
{ "MineName", INI::parseAsciiString, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_mineName ) },
{ "UpgradedMineName", INI::parseAsciiString, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_mineNameUpgraded ) },
{ "UpgradedTriggeredBy", INI::parseAsciiString, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_mineUpgradeTrigger ) },
{ "GenerationFX", INI::parseFXList, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_genFX ) },
{ "DistanceAroundObject", INI::parseReal, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_distanceAroundObject ) },
{ "MinesPerSquareFoot", INI::parseReal, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_minesPerSquareFoot ) },
{ "GenerateOnlyOnDeath", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_onDeath) },
{ "BorderOnly", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_borderOnly) },
{ "SmartBorder", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_smartBorder) },
{ "SmartBorderSkipInterior", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_smartBorderSkipInterior) },
{ "AlwaysCircular", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_alwaysCircular) },
{ "Upgradable", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_upgradable) },
{ "RandomJitter", INI::parsePercentToReal, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_randomJitter) },
{ "SkipIfThisMuchUnderStructure", INI::parsePercentToReal, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_skipIfThisMuchUnderStructure) },
{ 0, 0, 0, 0 }
};
BehaviorModuleData::buildFieldParse(p);
p.add(dataFieldParse);
p.add(UpgradeMuxData::getFieldParse(), offsetof( GenerateMinefieldBehaviorModuleData, m_upgradeMuxData ));
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GenerateMinefieldBehavior::GenerateMinefieldBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
m_target.zero();
m_generated = false;
m_hasTarget = false;
m_upgraded = false;
m_mineList.clear();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GenerateMinefieldBehavior::~GenerateMinefieldBehavior( void )
{
m_mineList.clear();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::upgradeImplementation()
{
placeMines();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::onDie( const DamageInfo *damageInfo )
{
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
if (d->m_onDeath)
{
placeMines();
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::setMinefieldTarget(const Coord3D* pos)
{
if (pos)
{
m_hasTarget = true;
m_target = *pos;
}
else
{
m_hasTarget = false;
m_target.zero();
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
const Coord3D* GenerateMinefieldBehavior::getMinefieldTarget() const
{
return m_hasTarget ? &m_target : getObject()->getPosition();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
static Bool isAnythingTooClose2D(const std::vector<Object*>& v, const Coord3D& pos, Real minDistSqr)
{
for (std::vector<Object*>::const_iterator it = v.begin(); it != v.end(); ++it)
{
const Coord3D* p = (*it)->getPosition();
Real distSqr = sqr(p->x - pos.x) + sqr(p->y - pos.y);
if (distSqr < minDistSqr)
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
static void offsetBySmallRandomAmount(Coord3D& pt, Real maxAmt)
{
pt.x += GameLogicRandomValueReal(-maxAmt, maxAmt);
pt.y += GameLogicRandomValueReal(-maxAmt, maxAmt);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Object* GenerateMinefieldBehavior::placeMineAt(const Coord3D& pt, const ThingTemplate* mineTemplate, Team* team, const Object* producer)
{
Coord3D tmp = pt;
tmp.z = 99999.0f;
PathfindLayerEnum layer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
if (layer == LAYER_GROUND && TheTerrainLogic->isUnderwater(pt.x, pt.y))
return NULL;
if (layer == LAYER_GROUND && TheTerrainLogic->isCliffCell(pt.x, pt.y))
return NULL;
Real orient = GameLogicRandomValueReal(-PI, PI);
// if the mine will be "mostly" under a structure, don't place it.
// for now, "mostly" means "central third of radius would overlap"
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
GeometryInfo geom = mineTemplate->getTemplateGeometryInfo();
Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
geom.expandFootprint(mineRadius * -(1.0f - d->m_skipIfThisMuchUnderStructure));
ObjectIterator *iter = ThePartitionManager->iteratePotentialCollisions( &pt, geom, orient );
MemoryPoolObjectHolder hold(iter);
for (Object* them = iter->first(); them; them = iter->next())
{
if (them->isKindOf(KINDOF_STRUCTURE))
return NULL;
}
Object* mine = TheThingFactory->newObject(mineTemplate, team);
mine->setPosition(&pt);
mine->setOrientation(orient);
mine->setProducer(producer);
for (BehaviorModule** bmi = mine->getBehaviorModules(); *bmi; ++bmi)
{
LandMineInterface* lmi = (*bmi)->getLandMineInterface();
if (lmi)
{
lmi->setScootParms(*producer->getPosition(), pt);
break;
}
}
// Keep track of the mines
if (mine && d->m_upgradable)
{
m_mineList.push_back(mine->getID());
}
return mine;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::placeMinesAlongLine(const Coord3D& posStart, const Coord3D& posEnd, const ThingTemplate* mineTemplate, Bool skipOneAtStart)
{
const Object* obj = getObject();
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
Team* team = obj->getControllingPlayer()->getDefaultTeam();
Real dx = posEnd.x - posStart.x;
Real dy = posEnd.y - posStart.y;
Real len = sqrt(sqr(dx) + sqr(dy));
Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
Real mineDiameter = mineRadius * 2.0f;
Real mineJitter = mineRadius*d->m_randomJitter;
Int numMines = REAL_TO_INT_CEIL(len / mineDiameter);
if (numMines < 1)
numMines = 1;
Real inc = len/numMines;
for (Real place = skipOneAtStart ? inc : 0; place <= len; place += inc)
{
Coord3D pt;
pt.x = posStart.x + place * dx / len;
pt.y = posStart.y + place * dy / len;
pt.z = TheTerrainLogic->getGroundHeight( pt.x, pt.y );
offsetBySmallRandomAmount(pt, mineJitter);
placeMineAt(pt, mineTemplate, team, obj);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
static void makeCorner(const Coord3D& pos, Real majorRadius, Real minorRadius, const Matrix3D& mtx, Coord3D& corner)
{
Vector3 tmp;
tmp.X = majorRadius;
tmp.Y = minorRadius;
tmp.Z = 0;
Matrix3D::Transform_Vector(mtx, tmp, &tmp);
corner.x = tmp.X;
corner.y = tmp.Y;
corner.z = tmp.Z;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::placeMinesAroundRect(const Coord3D& pos, Real majorRadius, Real minorRadius, const ThingTemplate* mineTemplate)
{
const Object* obj = getObject();
const Matrix3D* mtx = obj->getTransformMatrix();
Coord3D pt[4];
makeCorner(pos, majorRadius, minorRadius, *mtx, pt[0]);
makeCorner(pos, -majorRadius, minorRadius, *mtx, pt[1]);
makeCorner(pos, -majorRadius, -minorRadius, *mtx, pt[2]);
makeCorner(pos, majorRadius, -minorRadius, *mtx, pt[3]);
placeMinesAlongLine(pt[0], pt[1], mineTemplate, true);
placeMinesAlongLine(pt[1], pt[2], mineTemplate, true);
placeMinesAlongLine(pt[2], pt[3], mineTemplate, true);
placeMinesAlongLine(pt[3], pt[0], mineTemplate, true);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::placeMinesAroundCircle(const Coord3D& pos, Real radius, const ThingTemplate* mineTemplate)
{
const Object* obj = getObject();
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
Team* team = obj->getControllingPlayer()->getDefaultTeam();
Real circum = 2.0f * PI * radius;
Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
Real mineDiameter = mineRadius * 2.0f;
Real mineJitter = mineRadius*d->m_randomJitter;
Int numMines = REAL_TO_INT_CEIL(circum / mineDiameter);
if (numMines < 1)
numMines = 1;
Real angleInc = (2*PI)/numMines;
Real angleLim = (2*PI) - angleInc*0.5f;
for (Real angle = 0; angle < angleLim; angle += angleInc)
{
Coord3D pt;
pt.x = pos.x + radius * Cos(angle);
pt.y = pos.y + radius * Sin(angle);
pt.z = TheTerrainLogic->getGroundHeight( pt.x, pt.y );
offsetBySmallRandomAmount(pt, mineJitter);
placeMineAt(pt, mineTemplate, team, obj);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::placeMinesInFootprint(const GeometryInfo& geom, const ThingTemplate* mineTemplate)
{
const Object* obj = getObject();
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
Team* team = obj->getControllingPlayer()->getDefaultTeam();
Real area = geom.getFootprintArea();
Int numMines = REAL_TO_INT_CEIL(d->m_minesPerSquareFoot * area);
if (numMines < 1)
numMines = 1;
const Coord3D* target = getMinefieldTarget();
std::vector<Object*> minesCreatedSoFar;
Real minDistSqr = sqr(mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius() * 2.0f);
for (int i = 0; i < numMines; ++i)
{
Coord3D pt;
Int maxRetry = 100;
do
{
geom.makeRandomOffsetWithinFootprint(pt);
pt.x += target->x;
pt.y += target->y;
pt.z += target->z;
--maxRetry;
} while (isAnythingTooClose2D(minesCreatedSoFar, pt, minDistSqr) && maxRetry > 0);
DEBUG_ASSERTCRASH(maxRetry>0,("ran out of retries %f",minDistSqr));
if (getObject()->getGeometryInfo().isPointInFootprint(*target, pt))
continue;
Object* mine = placeMineAt(pt, mineTemplate, team, obj); // can return null.
if (mine)
minesCreatedSoFar.push_back(mine);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::placeMines()
{
if (m_generated)
return;
m_generated = true;
const Object* obj = getObject();
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
const ThingTemplate* mineTemplate = 0;
if (m_upgraded)
mineTemplate = TheThingFactory->findTemplate(d->m_mineNameUpgraded);
else
mineTemplate = TheThingFactory->findTemplate(d->m_mineName);
if (!mineTemplate)
{
DEBUG_CRASH(("mine %s not found\n",d->m_mineName.str()));
return;
}
const Coord3D* target = getMinefieldTarget();
if (d->m_smartBorder)
{
GeometryInfo geom = obj->getGeometryInfo();
if (!d->m_smartBorderSkipInterior)
{
geom = mineTemplate->getTemplateGeometryInfo();
placeMineAt(*target, mineTemplate, obj->getControllingPlayer()->getDefaultTeam(), obj);
}
if (d->m_alwaysCircular)
geom.set(GEOMETRY_CYLINDER, false, 1, geom.getBoundingCircleRadius(), geom.getBoundingCircleRadius());
Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
Real mineDiameter = mineRadius * 2.0f;
geom.expandFootprint(mineRadius);
do
{
if (geom.getGeomType() == GEOMETRY_BOX && !d->m_alwaysCircular)
{
placeMinesAroundRect(*target, geom.getMajorRadius(), geom.getMinorRadius(), mineTemplate);
}
else
{
placeMinesAroundCircle(*target, geom.getMajorRadius(), mineTemplate);
}
geom.expandFootprint(mineDiameter);
} while (geom.getBoundingCircleRadius() < d->m_distanceAroundObject);
}
else if (d->m_borderOnly)
{
GeometryInfo geom = obj->getGeometryInfo();
geom.expandFootprint(d->m_distanceAroundObject);
if (geom.getGeomType() == GEOMETRY_BOX && !d->m_alwaysCircular)
{
placeMinesAroundRect(*target, geom.getMajorRadius(), geom.getMinorRadius(), mineTemplate);
}
else
{
placeMinesAroundCircle(*target, geom.getMajorRadius(), mineTemplate);
}
}
else
{
GeometryInfo geom = obj->getGeometryInfo();
geom.expandFootprint(d->m_distanceAroundObject);
if (d->m_alwaysCircular)
geom.set(GEOMETRY_CYLINDER, false, 1, geom.getBoundingCircleRadius(), geom.getBoundingCircleRadius());
placeMinesInFootprint(geom, mineTemplate);
}
FXList::doFXObj(d->m_genFX, obj);
}
// ------------------------------------------------------------------------------------------------
UpdateSleepTime GenerateMinefieldBehavior::update()
{
// Test to see if we need to replace the current mines with upgraded ones
if (!m_upgraded && getGenerateMinefieldBehaviorModuleData()->m_upgradable)
{
if (m_generated)
{
// Upgraded minefield to next level for China Player
const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_ChinaEMPMines" );
if (upgradeTemplate)
{
UpgradeMaskType upgradeMask = upgradeTemplate->getUpgradeMask();
UpgradeMaskType objMask = getObject()->getObjectCompletedUpgradeMask();
if (objMask.testForAny(upgradeMask))
{
m_upgraded = TRUE;
// Remove all old mine objects if present
for (std::list<ObjectID>::iterator it = m_mineList.begin(); it != m_mineList.end(); ++it)
{
ObjectID objID = *it;
Object *obj = TheGameLogic->findObjectByID(objID);
if (obj)
{
TheGameLogic->destroyObject(obj);
}
}
m_mineList.clear();
// Place new mines down (Replace old ones that we removed
m_generated = false;
placeMines();
}
}
}
return UPDATE_SLEEP_NONE;
}
return UPDATE_SLEEP_FOREVER;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
// extend base class
UpgradeMux::upgradeMuxCRC( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// base class
BehaviorModule::xfer( xfer );
// mux "base class"
UpgradeMux::upgradeMuxXfer( xfer );
// generated
xfer->xferBool( &m_generated );
xfer->xferBool( &m_hasTarget );
xfer->xferBool( &m_upgraded );
xfer->xferCoord3D( &m_target );
// spaces info count and objectID data
UnsignedByte spacesCount = m_mineList.size();
xfer->xferUnsignedByte( &spacesCount );
if( xfer->getXferMode() == XFER_SAVE )
{
// save all elements
std::list<ObjectID>::iterator it;
for( it = m_mineList.begin(); it != m_mineList.end(); ++it )
{
// object in this space
xfer->xferObjectID( &(*it) );
} // end for, it
} // end if, save
else if( xfer->getXferMode() == XFER_LOAD )
{
ObjectID objectID;
m_mineList.clear();
// read all elements
std::list<ObjectID>::iterator it;
it = m_mineList.begin();
for(int i = 0; i < spacesCount; ++i )
{
// read object id
xfer->xferObjectID( &objectID );
m_mineList.push_back(objectID);
} // end for, i
} // end else, load
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::loadPostProcess( void )
{
// extend base class
BehaviorModule::loadPostProcess();
// extend base class
UpgradeMux::upgradeMuxLoadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,255 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: GrantStealthBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author: Lorenzen
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Thing.h"
#include "Common/ThingTemplate.h"
#include "Common/INI.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/Anim2D.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/GrantStealthBehavior.h"
#include "GameLogic/Module/StealthUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
struct GrantStealthPlayerScanHelper
{
KindOfMaskType m_kindOfToTest;
Object *m_theGrantor;
ObjectPointerList *m_objectList;
};
static void checkForGrantStealth( Object *testObj, void *userData )
{
GrantStealthPlayerScanHelper *helper = (GrantStealthPlayerScanHelper*)userData;
ObjectPointerList *listToAddTo = helper->m_objectList;
if( testObj->isEffectivelyDead() )
return;
if( testObj->getControllingPlayer() != helper->m_theGrantor->getControllingPlayer() )
return;
if( testObj->isOffMap() )
return;
if( !testObj->isAnyKindOf(helper->m_kindOfToTest) )
return;
listToAddTo->push_back(testObj);
if( testObj->getContain() )
{
// have to tag visible riders too, or they will float around and look silly.
Object *rider = (Object*)testObj->getContain()->friend_getRider();
if( rider )
{
listToAddTo->push_back(rider);
}
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GrantStealthBehavior::GrantStealthBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData();
m_radiusParticleSystemID = INVALID_PARTICLE_SYSTEM_ID;
m_currentScanRadius = d->m_startRadius;
Object *obj = getObject();
{
if( d->m_radiusParticleSystemTmpl )
{
ParticleSystem *particleSystem;
particleSystem = TheParticleSystemManager->createParticleSystem( d->m_radiusParticleSystemTmpl );
if( particleSystem )
{
particleSystem->setPosition( obj->getPosition() );
m_radiusParticleSystemID = particleSystem->getSystemID();
}
}
}
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GrantStealthBehavior::~GrantStealthBehavior( void )
{
if( m_radiusParticleSystemID != INVALID_PARTICLE_SYSTEM_ID )
TheParticleSystemManager->destroyParticleSystemByID( m_radiusParticleSystemID );
}
//-------------------------------------------------------------------------------------------------
/** The update callback. */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime GrantStealthBehavior::update( void )
{
Object *self = getObject();
if ( self->isEffectivelyDead())
return UPDATE_SLEEP_FOREVER;
const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData();
// setup scan filters
PartitionFilterRelationship relationship( self, PartitionFilterRelationship::ALLOW_ALLIES );
PartitionFilterSameMapStatus filterMapStatus( self );
PartitionFilterAlive filterAlive;
PartitionFilter *filters[] = { &relationship, &filterAlive, &filterMapStatus, NULL };
m_currentScanRadius += d->m_radiusGrowRate;
Bool thisIsFinalScan = FALSE;
if ( m_currentScanRadius >= d->m_finalRadius )
{
m_currentScanRadius = d->m_finalRadius;
thisIsFinalScan = TRUE;
}
// scan objects in our region
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( self->getPosition(), m_currentScanRadius, FROM_CENTER_2D, filters );
MemoryPoolObjectHolder hold( iter );
// GRANT STEALTH TO FRIENDLIES IN RADIUS
for( Object *obj = iter->first(); obj; obj = iter->next() )
grantStealthToObject( obj );
if ( thisIsFinalScan )
{
TheGameLogic->destroyObject( self );
return UPDATE_SLEEP_FOREVER;
}
return UPDATE_SLEEP_NONE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GrantStealthBehavior::grantStealthToObject( Object *obj )
{
if ( obj == getObject() )
return;
const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData();
if ( ! obj->isAnyKindOf( d->m_kindOf ) )
return;
StealthUpdate* stealth = obj->getStealth();
if( stealth )
{
stealth->receiveGrant();
Drawable *draw = obj->getDrawable();
if ( draw )
{
draw->flashAsSelected();
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void GrantStealthBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void GrantStealthBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// particle system id
xfer->xferUser( &m_radiusParticleSystemID, sizeof( ParticleSystemID ) );
// Timer safety
xfer->xferReal( &m_currentScanRadius );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void GrantStealthBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,215 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: InstantDeathBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_SLOWDEATHPHASE_NAMES
#include "Common/Thing.h"
#include "Common/ThingTemplate.h"
#include "Common/INI.h"
#include "Common/RandomValue.h"
#include "Common/GameLOD.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/InstantDeathBehavior.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
//-------------------------------------------------------------------------------------------------
InstantDeathBehaviorModuleData::InstantDeathBehaviorModuleData()
{
// redundant.
//m_fx.clear();
//m_ocls.clear();
//m_weapons.clear();
}
//-------------------------------------------------------------------------------------------------
static void parseFX( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
InstantDeathBehaviorModuleData* self = (InstantDeathBehaviorModuleData*)instance;
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const FXList *fxl = TheFXListStore->findFXList((token)); // could be null! this is OK!
self->m_fx.push_back(fxl);
}
}
//-------------------------------------------------------------------------------------------------
static void parseOCL( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
InstantDeathBehaviorModuleData* self = (InstantDeathBehaviorModuleData*)instance;
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const ObjectCreationList *ocl = TheObjectCreationListStore->findObjectCreationList(token); // could be null! this is OK!
self->m_ocls.push_back(ocl);
}
}
//-------------------------------------------------------------------------------------------------
static void parseWeapon( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
InstantDeathBehaviorModuleData* self = (InstantDeathBehaviorModuleData*)instance;
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const WeaponTemplate *wt = TheWeaponStore->findWeaponTemplate(token); // could be null! this is OK!
self->m_weapons.push_back(wt);
}
}
//-------------------------------------------------------------------------------------------------
/*static*/ void InstantDeathBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
DieModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "FX", parseFX, NULL, 0 },
{ "OCL", parseOCL, NULL, 0 },
{ "Weapon", parseWeapon, NULL, 0 },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InstantDeathBehavior::InstantDeathBehavior( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InstantDeathBehavior::~InstantDeathBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
void InstantDeathBehavior::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
AIUpdateInterface* ai = getObject()->getAIUpdateInterface();
if (ai)
{
// has another AI already handled us. (hopefully another InstantDeathBehavior)
if (ai->isAiInDeadState())
return;
ai->markAsDead();
}
const InstantDeathBehaviorModuleData* d = getInstantDeathBehaviorModuleData();
Int idx, listSize;
listSize = d->m_fx.size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const FXListVec& v = d->m_fx;
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const FXList* fxl = v[idx];
FXList::doFXObj(fxl, getObject(), NULL);
}
listSize = d->m_ocls.size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const OCLVec& v = d->m_ocls;
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const ObjectCreationList* ocl = v[idx];
ObjectCreationList::create(ocl, getObject(), NULL);
}
listSize = d->m_weapons.size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const WeaponTemplateVec& v = d->m_weapons;
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const WeaponTemplate* wt = v[idx];
if (wt)
{
TheWeaponStore->createAndFireTempWeapon(wt, getObject(), getObject()->getPosition());
}
}
TheGameLogic->destroyObject(getObject());
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void InstantDeathBehavior::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void InstantDeathBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void InstantDeathBehavior::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,393 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: JetSlowDeathBehavior.cpp /////////////////////////////////////////////////////////////////
// Author: Colin Day
// Desc: Death sequence for jets
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GlobalData.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/JetSlowDeathBehavior.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
JetSlowDeathBehaviorModuleData::JetSlowDeathBehaviorModuleData( void )
{
m_fxOnGroundDeath = NULL;
m_oclOnGroundDeath = NULL;
m_fxInitialDeath = NULL;
m_oclInitialDeath = NULL;
m_delaySecondaryFromInitialDeath = 0;
m_fxSecondary = NULL;
m_oclSecondary = NULL;
m_fxHitGround = NULL;
m_oclHitGround = NULL;
m_delayFinalBlowUpFromHitGround = 0;
m_fxFinalBlowUp = NULL;
m_oclFinalBlowUp = NULL;
m_rollRate = 0.0f;
m_rollRateDelta = 1.0f;
m_pitchRate = 0.0f;
m_fallHowFast = 0.0f;
} // end JetSlowDeathBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void JetSlowDeathBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
SlowDeathBehaviorModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "FXOnGroundDeath", INI::parseFXList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fxOnGroundDeath ) },
{ "OCLOnGroundDeath", INI::parseObjectCreationList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_oclOnGroundDeath ) },
{ "FXInitialDeath", INI::parseFXList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fxInitialDeath ) },
{ "OCLInitialDeath", INI::parseObjectCreationList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_oclInitialDeath ) },
{ "DelaySecondaryFromInitialDeath", INI::parseDurationUnsignedInt, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_delaySecondaryFromInitialDeath ) },
{ "FXSecondary", INI::parseFXList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fxSecondary ) },
{ "OCLSecondary", INI::parseObjectCreationList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_oclSecondary ) },
{ "FXHitGround", INI::parseFXList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fxHitGround ) },
{ "OCLHitGround", INI::parseObjectCreationList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_oclHitGround ) },
{ "DelayFinalBlowUpFromHitGround", INI::parseDurationUnsignedInt, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_delayFinalBlowUpFromHitGround ) },
{ "FXFinalBlowUp", INI::parseFXList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fxFinalBlowUp ) },
{ "OCLFinalBlowUp", INI::parseObjectCreationList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_oclFinalBlowUp ) },
{ "DeathLoopSound", INI::parseAudioEventRTS, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_deathLoopSound ) },
// @todo srj -- RollRate and RollRateDelta and PitchRate should use parseAngularVelocityReal
{ "RollRate", INI::parseReal, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_rollRate ) },
{ "RollRateDelta", INI::parsePercentToReal, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_rollRateDelta ) },
{ "PitchRate", INI::parseReal, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_pitchRate ) },
{ "FallHowFast", INI::parsePercentToReal, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fallHowFast ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
JetSlowDeathBehavior::JetSlowDeathBehavior( Thing *thing, const ModuleData *moduleData )
: SlowDeathBehavior( thing, moduleData )
{
m_timerDeathFrame = 0;
m_timerOnGroundFrame = 0;
m_rollRate = 0.0f;
} // end JetSlowDeathBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
JetSlowDeathBehavior::~JetSlowDeathBehavior( void )
{
} // end ~JetSlowDeathBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void JetSlowDeathBehavior::onDie( const DamageInfo *damageInfo )
{
Object *us = getObject();
// if the jet is on the ground we do just our ground fx death
if( us->isSignificantlyAboveTerrain() == FALSE || us->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) )
{
const JetSlowDeathBehaviorModuleData *modData = getJetSlowDeathBehaviorModuleData();
// execute fx
FXList::doFXObj( modData->m_fxOnGroundDeath, us );
// execute ocl
ObjectCreationList::create( modData->m_oclOnGroundDeath, us, NULL );
// destroy object
TheGameLogic->destroyObject( us );
} // end if
else
{
// extend base class for slow death and begin the slow death behavior
SlowDeathBehavior::onDie( damageInfo );
} // end else
getObject()->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) );
} // end onDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void JetSlowDeathBehavior::beginSlowDeath( const DamageInfo *damageInfo )
{
// extend functionality
SlowDeathBehavior::beginSlowDeath( damageInfo );
// get our info
Object *us = getObject();
const JetSlowDeathBehaviorModuleData *modData = getJetSlowDeathBehaviorModuleData();
// record the frame we died on
m_timerDeathFrame = TheGameLogic->getFrame();
// do some effects
FXList::doFXObj( modData->m_fxInitialDeath, us );
ObjectCreationList::create( modData->m_oclInitialDeath, us, NULL );
// start audio loop playing
m_deathLoopSound = modData->m_deathLoopSound;
if( m_deathLoopSound.getEventName().isEmpty() == FALSE )
{
m_deathLoopSound.setObjectID( us->getID() );
m_deathLoopSound.setPlayingHandle( TheAudio->addAudioEvent( &m_deathLoopSound ) );
} // end if
// initialize our roll rate to that defined as the initial value in the module data
m_rollRate = modData->m_rollRate;
// set the locomotor so that the plane starts falling
Locomotor *locomotor = us->getAIUpdateInterface()->getCurLocomotor();
locomotor->setMaxLift( -TheGlobalData->m_gravity * (1.0f - modData->m_fallHowFast) );
// do not allow the jet to turn anymore
locomotor->setMaxTurnRate( 0.0f );
} // end beginSlowDeath
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime JetSlowDeathBehavior::update( void )
{
// extend functionality of base class
SlowDeathBehavior::update();
// if the death is not activated, do nothing else
if( isSlowDeathActivated() == FALSE )
return UPDATE_SLEEP_NONE;
// get object info
Object *us = getObject();
const JetSlowDeathBehaviorModuleData *modData = getJetSlowDeathBehaviorModuleData();
// roll us around in the air
PhysicsBehavior *physics = us->getPhysics();
DEBUG_ASSERTCRASH( physics, ("JetSlowDeathBehavior::beginSlowDeath - '%s' has no physics\n",
us->getTemplate()->getName().str()) );
if( physics )
physics->setRollRate( m_rollRate );
// adjust the roll rate over time
m_rollRate *= modData->m_rollRateDelta;
// do effects for death while in the air
if( m_timerOnGroundFrame == 0 )
{
PathfindLayerEnum layer = TheTerrainLogic->getLayerForDestination(us->getPosition());
us->setLayer(layer);
Real height;
if (layer == LAYER_GROUND)
{
// (this is more efficient than getGroundHeight because the info is cached)
height = us->getHeightAboveTerrain();
}
else
{
Real layerHeight = TheTerrainLogic->getLayerHeight( us->getPosition()->x, us->getPosition()->y, layer );
height = us->getPosition()->z - layerHeight;
// slop a little bit for bridges, since we tend to end up fractionally
// above 'em, and it's easier to just slop it here
if (height >= 0.0f && height <= 1.0f)
height = 0.0f;
}
Bool hitATree = FALSE;
// Here we want to make sure we crash if we collide with a tree on the way down
PhysicsBehavior *phys = us->getPhysics();
if ( m_timerOnGroundFrame == 0 && phys )
{
ObjectID treeID = phys->getLastCollidee();
Object *tree = TheGameLogic->findObjectByID( treeID );
if ( tree )
{
if (tree->isKindOf( KINDOF_SHRUBBERY ) )
hitATree = TRUE;
}
}
// when we've hit the ground, we're totally done
if( height <= 0.0f || hitATree )
{
// stop the death looping sound at the right time
TheAudio->removeAudioEvent( m_deathLoopSound.getPlayingHandle() );
// do some effects
FXList::doFXObj( modData->m_fxHitGround, us );
ObjectCreationList::create( modData->m_oclHitGround, us, NULL );
// we are now on the ground
m_timerOnGroundFrame = TheGameLogic->getFrame();
// start us rolling on another axis too
if( physics )
physics->setPitchRate( modData->m_pitchRate );
} // end if
// timers for the secondary effect
if( m_timerDeathFrame != 0 &&
TheGameLogic->getFrame() - m_timerDeathFrame >= modData->m_delaySecondaryFromInitialDeath )
{
// do some effects
FXList::doFXObj( modData->m_fxSecondary, us );
ObjectCreationList::create( modData->m_oclSecondary, us, NULL );
// clear the death frame timer since we've already executed the event now
m_timerDeathFrame = 0;
} //end if
} // end if
else
{
// we are on the ground, pay attention to the final explosion timers
if( TheGameLogic->getFrame() - m_timerOnGroundFrame >= modData->m_delayFinalBlowUpFromHitGround )
{
// do some effects
FXList::doFXObj( modData->m_fxFinalBlowUp, us );
ObjectCreationList::create( modData->m_oclFinalBlowUp, us, NULL );
// we're all done now
TheGameLogic->destroyObject( us );
} // end if
} // end else
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void JetSlowDeathBehavior::crc( Xfer *xfer )
{
// extend base class
SlowDeathBehavior::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void JetSlowDeathBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
SlowDeathBehavior::xfer( xfer );
// timer death frame
xfer->xferUnsignedInt( &m_timerDeathFrame );
// on ground frame
xfer->xferUnsignedInt( &m_timerOnGroundFrame );
// roll rate
xfer->xferReal( &m_rollRate );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void JetSlowDeathBehavior::loadPostProcess( void )
{
// extend base class
SlowDeathBehavior::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,707 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: MinefieldBehavior.cpp //////////////////////////////////////////////////////////////////
// Author: Steven Johnson, June 2002
// Desc: Minefield behavior
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_RELATIONSHIP_NAMES
#include "Common/GameState.h"
#include "Common/RandomValue.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Module/MinefieldBehavior.h"
#include "GameLogic/Module/AutoHealBehavior.h"
#include "GameLogic/Weapon.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// detonation never puts our health below this, since we probably auto-regen
const Real MIN_HEALTH = 0.1f;
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
MinefieldBehaviorModuleData::MinefieldBehaviorModuleData()
{
m_detonationWeapon = NULL;
m_detonatedBy = (1 << ENEMIES) | (1 << NEUTRAL);
m_stopsRegenAfterCreatorDies = true;
m_regenerates = false;
m_workersDetonate = false;
m_creatorDeathCheckRate = LOGICFRAMES_PER_SECOND;
m_scootFromStartingPointTime = 0;
m_repeatDetonateMoveThresh = 1.0f;
m_numVirtualMines = 1;
m_healthPercentToDrainPerSecond = 0.0f;
m_ocl = 0;
}
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void MinefieldBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "DetonationWeapon", INI::parseWeaponTemplate, NULL, offsetof( MinefieldBehaviorModuleData, m_detonationWeapon ) },
{ "DetonatedBy", INI::parseBitString32, TheRelationshipNames, offsetof( MinefieldBehaviorModuleData, m_detonatedBy ) },
{ "StopsRegenAfterCreatorDies", INI::parseBool, NULL, offsetof( MinefieldBehaviorModuleData, m_stopsRegenAfterCreatorDies ) },
{ "Regenerates", INI::parseBool, NULL, offsetof( MinefieldBehaviorModuleData, m_regenerates ) },
{ "WorkersDetonate", INI::parseBool, NULL, offsetof( MinefieldBehaviorModuleData, m_workersDetonate ) },
{ "CreatorDeathCheckRate", INI::parseDurationUnsignedInt, NULL, offsetof( MinefieldBehaviorModuleData, m_creatorDeathCheckRate ) },
{ "ScootFromStartingPointTime", INI::parseDurationUnsignedInt, NULL, offsetof( MinefieldBehaviorModuleData, m_scootFromStartingPointTime ) },
{ "NumVirtualMines", INI::parseUnsignedInt, NULL, offsetof( MinefieldBehaviorModuleData, m_numVirtualMines ) },
{ "RepeatDetonateMoveThresh", INI::parseReal, NULL, offsetof( MinefieldBehaviorModuleData, m_repeatDetonateMoveThresh ) },
{ "DegenPercentPerSecondAfterCreatorDies", INI::parsePercentToReal, NULL, offsetof( MinefieldBehaviorModuleData, m_healthPercentToDrainPerSecond ) },
{ "CreationList", INI::parseObjectCreationList, NULL, offsetof( MinefieldBehaviorModuleData, m_ocl ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MinefieldBehavior::MinefieldBehavior( Thing *thing, const ModuleData* moduleData )
: UpdateModule( thing, moduleData )
{
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
m_nextDeathCheckFrame = 0;
m_scootFramesLeft = 0;
m_scootVel.zero();
m_scootAccel.zero();
m_detonators.clear();
m_ignoreDamage = false;
m_regenerates = d->m_regenerates;
m_draining = false;
m_virtualMinesRemaining = d->m_numVirtualMines;
for (Int i = 0; i < MAX_IMMUNITY; ++i)
{
m_immunes[i].id = INVALID_ID;
m_immunes[i].collideTime = 0;
}
// start off awake, and we will calcSleepTime from here on
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
// mines aren't auto-acquirable
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_ATTACK_FROM_AI ) );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MinefieldBehavior::~MinefieldBehavior()
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime MinefieldBehavior::calcSleepTime()
{
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
// if we're draining we have to update every frame
if (m_draining)
return UPDATE_SLEEP_NONE;
// if we're scooting we need to update every frame
if( m_scootFramesLeft > 0 )
return UPDATE_SLEEP_NONE;
// if there is anybody in our immulity monitoring we need to update every frame
for( Int i = 0; i < MAX_IMMUNITY; ++i )
if( m_immunes[ i ].id != INVALID_ID )
return UPDATE_SLEEP_NONE;
UnsignedInt sleepTime = FOREVER;
UnsignedInt now = TheGameLogic->getFrame();
//
// sleep until the next death check frame we already have figured outif we care
// about it (that is, when our creator dies)
//
if (m_regenerates && d->m_stopsRegenAfterCreatorDies)
sleepTime = min( sleepTime, m_nextDeathCheckFrame - now );
// if we don't want to sleep forever, prevent 0 frame sleeps
if( sleepTime == 0 )
sleepTime = 1;
// sleep forever
return UPDATE_SLEEP( sleepTime );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime MinefieldBehavior::update()
{
Object* obj = getObject();
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
if (m_scootFramesLeft > 0)
{
Coord3D pt = *obj->getPosition();
m_scootVel.x += m_scootAccel.x;
m_scootVel.y += m_scootAccel.y;
m_scootVel.z += m_scootAccel.z;
pt.x += m_scootVel.x;
pt.y += m_scootVel.y;
pt.z += m_scootVel.z;
// srj sez: scooting mines always go on the highest layer.
Coord3D tmp = pt;
tmp.z = 99999.0f;
PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
obj->setLayer(newLayer);
Real ground = TheTerrainLogic->getLayerHeight( pt.x, pt.y, newLayer );
if (newLayer != LAYER_GROUND)
{
// ensure we are slightly above the bridge, to account for fudge & sloppy art
const Real FUDGE = 1.0f;
ground += FUDGE;
}
if (pt.z < ground || m_scootFramesLeft <= 1)
pt.z = ground;
obj->setPosition(&pt);
--m_scootFramesLeft;
}
// check for expired immunities.
for (Int i = 0; i < MAX_IMMUNITY; ++i)
{
if (m_immunes[i].id == INVALID_ID)
continue;
if (TheGameLogic->findObjectByID(m_immunes[i].id) == NULL ||
now > m_immunes[i].collideTime + 2)
{
//DEBUG_LOG(("expiring an immunity %d\n",m_immunes[i].id));
m_immunes[i].id = INVALID_ID; // he's dead, jim.
m_immunes[i].collideTime = 0;
}
}
if (now >= m_nextDeathCheckFrame)
{
// check to see if there is an enemy building on me... since enemy buildings can be build on top of me
// check to see if the building that made me is gone, and whether therefore, I should go
if (m_regenerates && d->m_stopsRegenAfterCreatorDies)
{
m_nextDeathCheckFrame = now + d->m_creatorDeathCheckRate;
ObjectID producerID = getObject()->getProducerID();
if (producerID != INVALID_ID)
{
Object* producer = TheGameLogic->findObjectByID(producerID);
if (producer == NULL || producer->isEffectivelyDead())
{
m_regenerates = false;
m_draining = true;
static const NameKeyType key_AutoHealBehavior = NAMEKEY("AutoHealBehavior");
AutoHealBehavior* ahb = (AutoHealBehavior*)obj->findUpdateModule( key_AutoHealBehavior );
if (ahb)
ahb->stopHealing();
}
}
}
}
if (m_draining)
{
DamageInfo damageInfo;
damageInfo.in.m_amount = (obj->getBodyModule()->getMaxHealth() * d->m_healthPercentToDrainPerSecond) / LOGICFRAMES_PER_SECOND;
damageInfo.in.m_sourceID = obj->getID();
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
damageInfo.in.m_deathType = DEATH_NORMAL;
obj->attemptDamage( &damageInfo );
}
return calcSleepTime();
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::detonateOnce(const Coord3D& position)
{
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
if (d->m_detonationWeapon)
{
Object* obj = getObject();
TheWeaponStore->createAndFireTempWeapon(d->m_detonationWeapon, obj, &position);
}
if (m_virtualMinesRemaining > 0)
--m_virtualMinesRemaining;
if (!m_regenerates && m_virtualMinesRemaining == 0)
{
TheGameLogic->destroyObject(getObject());
}
else
{
Real percent = (Real)m_virtualMinesRemaining / (Real)d->m_numVirtualMines;
BodyModuleInterface* body = getObject()->getBodyModule();
Real health = body->getHealth();
Real desired = percent * body->getMaxHealth();
if (desired < MIN_HEALTH)
desired = MIN_HEALTH;
Real amount = health - desired;
if (amount > 0.0f)
{
m_ignoreDamage = true;
//body->internalChangeHealth(desired - health);
//can't use this, AutoHeal won't work unless we go thru normal damage stuff
DamageInfo extraDamageInfo;
extraDamageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
extraDamageInfo.in.m_deathType = DEATH_NONE;
extraDamageInfo.in.m_sourceID = getObject()->getID();
extraDamageInfo.in.m_amount = amount;
getObject()->attemptDamage(&extraDamageInfo);
m_ignoreDamage = false;
}
}
if (m_virtualMinesRemaining == 0)
{
getObject()->setModelConditionState(MODELCONDITION_RUBBLE);
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
}
else
{
getObject()->clearModelConditionState(MODELCONDITION_RUBBLE);
getObject()->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
}
if (d->m_ocl)
{
ObjectCreationList::create(d->m_ocl, getObject(), getObject());
}
}
//-----------------------------------------------------------------------------
static Real calcDistSquared(const Coord3D& a, const Coord3D& b)
{
return sqr(a.x - b.x) + sqr(a.y - b.y) + sqr(a.z - b.z);
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
{
if (other == NULL || other->isEffectivelyDead())
return;
if (m_virtualMinesRemaining == 0)
return;
Object* obj = getObject();
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
// is this guy in our immune list?
// NOTE NOTE NOTE, must always do this check FIRST so that 'collideTime' is updated...
for (Int i = 0; i < MAX_IMMUNITY; ++i)
{
if (m_immunes[i].id == other->getID())
{
//DEBUG_LOG(("ignoring due to immunity %d\n",m_immunes[i].id));
m_immunes[i].collideTime = now;
return;
}
}
if (!d->m_workersDetonate)
{
// infantry+dozer=worker.
if (other->isKindOf(KINDOF_INFANTRY) && other->isKindOf(KINDOF_DOZER))
return;
}
Int requiredMask = 0;
Relationship r = obj->getRelationship(other);
if (r == ALLIES) requiredMask = (1 << ALLIES);
else if (r == ENEMIES) requiredMask = (1 << ENEMIES);
else if (r == NEUTRAL) requiredMask = (1 << NEUTRAL);
if ((d->m_detonatedBy & requiredMask) == 0)
return;
// are we active?
if (m_scootFramesLeft > 0)
return;
// things that are in the process of clearing mines are immune to mine detonation,
// even if we aren't the specific mine they are trying to clear. (however, they must
// have a real mine they area trying to clear... it's possible they could be trying to
// clear a position where there is no mine, in which case we grant them no immunity, muwahahaha)
AIUpdateInterface* otherAI = other->getAI();
if (otherAI && otherAI->isClearingMines() && otherAI->getGoalObject() != NULL)
{
// mine-clearers are granted immunity to us for as long as they continuously
// collide, even if no longer clearing mines. (this prevents the problem
// of a guy who touches two close-together mines while clearing, then puts up his
// detector and is blown to smithereens by the other one.)
for (Int i = 0; i < MAX_IMMUNITY; ++i)
{
if (m_immunes[i].id == INVALID_ID || m_immunes[i].id == other->getID())
{
//DEBUG_LOG(("add/update immunity %d\n",m_immunes[i].id));
m_immunes[i].id = other->getID();
m_immunes[i].collideTime = now;
// wake up
setWakeFrame( obj, calcSleepTime() );
break;
}
}
return;
}
// if we detonated another one nearby, we have to move a little bit to detonate another one.
Bool found = false;
for (std::vector<DetonatorInfo>::iterator it = m_detonators.begin(); it != m_detonators.end(); ++it)
{
if (other->getID() == it->id)
{
found = TRUE;
Real distSqr = calcDistSquared(*other->getPosition(), it->where);
if (distSqr <= sqr(d->m_repeatDetonateMoveThresh))
{
// too close. punt for now.
return;
}
else
{
// far enough. update the loc, then break out and blow up.
it->where = *other->getPosition();
break;
}
}
}
if (!found)
{
// add him to the list.
DetonatorInfo detInfo;
detInfo.id = other->getID();
detInfo.where = *other->getPosition();
m_detonators.push_back(detInfo);
}
Coord3D detPt = *other->getPosition();
obj->getGeometryInfo().clipPointToFootprint(*obj->getPosition(), detPt);
detonateOnce(detPt);
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::onDamage( DamageInfo *damageInfo )
{
if (m_ignoreDamage)
return;
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
// detonate as many times as neccessary for our virtual mine count to match our health
BodyModuleInterface* body = getObject()->getBodyModule();
for (;;)
{
Real virtualMinesExpectedF = ((Real)d->m_numVirtualMines * body->getHealth() / body->getMaxHealth());
Int virtualMinesExpected =
damageInfo->in.m_damageType == DAMAGE_HEALING ?
REAL_TO_INT_FLOOR(virtualMinesExpectedF) :
REAL_TO_INT_CEIL(virtualMinesExpectedF);
if (virtualMinesExpected > d->m_numVirtualMines)
virtualMinesExpected = d->m_numVirtualMines;
if (m_virtualMinesRemaining < virtualMinesExpected)
{
m_virtualMinesRemaining = virtualMinesExpected;
}
else if (m_virtualMinesRemaining > virtualMinesExpected)
{
if (m_draining &&
damageInfo->in.m_sourceID == getObject()->getID() &&
damageInfo->in.m_damageType == DAMAGE_UNRESISTABLE)
{
// don't detonate.... just ditch a mine
--m_virtualMinesRemaining;
}
else
{
detonateOnce(*getObject()->getPosition());
}
}
else
{
break;
}
}
if (m_virtualMinesRemaining == 0)
{
// oops, if someone did weapon damage they may have nuked our health to zero,
// which would be bad if we regen. prevent this. (srj)
if (m_regenerates && body->getHealth() < MIN_HEALTH)
{
body->internalChangeHealth(MIN_HEALTH - body->getHealth());
}
getObject()->setModelConditionState(MODELCONDITION_RUBBLE);
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
}
else
{
getObject()->clearModelConditionState(MODELCONDITION_RUBBLE);
getObject()->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
}
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::onHealing( DamageInfo *damageInfo )
{
onDamage(damageInfo);
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::onDie( const DamageInfo *damageInfo )
{
TheGameLogic->destroyObject(getObject());
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::disarm()
{
if (!m_regenerates)
{
TheGameLogic->destroyObject(getObject());
return;
}
// detonation never puts our health below this, since we probably auto-regen
const Real MIN_HEALTH = 0.1f;
BodyModuleInterface* body = getObject()->getBodyModule();
Real desired = MIN_HEALTH;
Real amount = body->getHealth() - desired;
m_ignoreDamage = true;
//body->internalChangeHealth(desired - health);
//can't use this, AutoHeal won't work unless we go thru normal damage stuff
DamageInfo extraDamageInfo;
extraDamageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
extraDamageInfo.in.m_deathType = DEATH_NONE;
extraDamageInfo.in.m_sourceID = getObject()->getID();
extraDamageInfo.in.m_amount = amount;
getObject()->attemptDamage(&extraDamageInfo);
m_ignoreDamage = false;
m_virtualMinesRemaining = 0;
getObject()->setModelConditionState(MODELCONDITION_RUBBLE);
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::setScootParms(const Coord3D& start, const Coord3D& end)
{
Object* obj = getObject();
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
UnsignedInt scootFromStartingPointTime = d->m_scootFromStartingPointTime;
Coord3D endOnGround = end;
endOnGround.z = TheTerrainLogic->getGroundHeight( endOnGround.x, endOnGround.y );
if (start.z > endOnGround.z)
{
// figure out how long it will take to fall, and replace scoot time with that
UnsignedInt fallingTime = REAL_TO_INT_CEIL(sqrtf(2.0f * (start.z - endOnGround.z) / fabs(TheGlobalData->m_gravity)));
// we can scoot after we land, but don't want to stop scooting before we land
if (scootFromStartingPointTime < fallingTime)
scootFromStartingPointTime = fallingTime;
}
if (scootFromStartingPointTime == 0)
{
obj->setPosition(&endOnGround);
m_scootFramesLeft = 0;
}
else
{
// x = x0 + vt + 0.5at^2
// thus 2(dx - vt)/t^2 = a
Real dx = endOnGround.x - start.x;
Real dy = endOnGround.y - start.y;
Real dz = endOnGround.z - start.z;
Real dist = sqrt(sqr(dx) + sqr(dy));
if (dist <= 0.1f && fabs(dz) <= 0.1f)
{
obj->setPosition(&endOnGround);
m_scootFramesLeft = 0;
}
else
{
Real t = (Real)scootFromStartingPointTime;
Real scootFromStartingPointSpeed = dist / t;
Real accelMag = fabs(2.0f * (dist - scootFromStartingPointSpeed*t)/sqr(t));
Real dxNorm = (dist <= 0.1f) ? 0.0f : (dx / dist);
Real dyNorm = (dist <= 0.1f) ? 0.0f : (dy / dist);
m_scootVel.x = dxNorm * scootFromStartingPointSpeed;
m_scootVel.y = dyNorm * scootFromStartingPointSpeed;
m_scootAccel.x = -dxNorm * accelMag;
m_scootAccel.y = -dyNorm * accelMag;
m_scootAccel.z = TheGlobalData->m_gravity;
obj->setPosition(&start);
m_scootFramesLeft = scootFromStartingPointTime;
// we need to wake ourselves up because we could be lying here sleeping forever
setWakeFrame( obj, calcSleepTime() );
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void MinefieldBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void MinefieldBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// mines remaining
/// @todo srj -- ensure health, appearance, etc are correct for save/reload! post-MP!
xfer->xferUnsignedInt( &m_virtualMinesRemaining );
// next death check frame
xfer->xferUnsignedInt( &m_nextDeathCheckFrame );
// scoot frames left
xfer->xferUnsignedInt( &m_scootFramesLeft );
// scoot velocity
xfer->xferCoord3D( &m_scootVel );
// scoot acceleration
xfer->xferCoord3D( &m_scootAccel );
xfer->xferBool( &m_ignoreDamage );
xfer->xferBool( &m_regenerates );
xfer->xferBool( &m_draining );
// immunities
UnsignedByte maxImmunity = MAX_IMMUNITY;
xfer->xferUnsignedByte( &maxImmunity );
if( maxImmunity != MAX_IMMUNITY )
{
DEBUG_CRASH(( "MinefieldBehavior::xfer - MAX_IMMUNITY has changed size, you must version this code and then you can remove this error message\n" ));
throw SC_INVALID_DATA;
} // end if
for( UnsignedByte i = 0; i < maxImmunity; ++i )
{
// object id
xfer->xferObjectID( &m_immunes[ i ].id );
// collide time
xfer->xferUnsignedInt( &m_immunes[ i ].collideTime );
} // end for, i
if( xfer->getXferMode() == XFER_LOAD )
m_detonators.clear();
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void MinefieldBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,204 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: NeutronBlastBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author: Daniel Teh
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameLogic/Module/NeutronBlastBehavior.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameClient/Drawable.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
NeutronBlastBehavior::NeutronBlastBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
setWakeFrame( getObject(), UPDATE_SLEEP_FOREVER );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
NeutronBlastBehavior::~NeutronBlastBehavior()
{
// GAME STUFF DOES NOT GO IN THE DESTRUCTOR
// (Crash if end game with Neutron shell in air)
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void NeutronBlastBehavior::onDie( const DamageInfo *damageInfo )
{
// On death, perform the Neutron Blast!!
Object *self = getObject();
if (!self)
return;
const NeutronBlastBehaviorModuleData *data = getNeutronBlastBehaviorModuleData();
Real blastRadius = data->m_blastRadius;
Bool hitAir = data->m_isAffectAirborne;
// setup scan filters
PartitionFilterSameMapStatus filterMapStatus( self );
PartitionFilterAlive filterAlive;
PartitionFilter *filters[] = { &filterAlive, &filterMapStatus, NULL };
// scan objects in our region
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( self->getPosition(), blastRadius, FROM_CENTER_2D, filters );
MemoryPoolObjectHolder hold( iter );
// Apply neutron blast to object
for( Object *obj = iter->first(); obj; obj = iter->next() )
{
if( hitAir || ( !obj->isKindOf(KINDOF_AIRCRAFT) && !obj->isAirborneTarget() ) )
{
neutronBlastToObject( obj );
}
}
}
//-------------------------------------------------------------------------------------------------
/** The update callback. */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime NeutronBlastBehavior::update( void )
{
return UPDATE_SLEEP_FOREVER;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void NeutronBlastBehavior::neutronBlastToObject( Object *obj )
{
// early exit check
if ( !obj || obj == getObject() )
return;
// Check for allies and quick exit if we are not suppose to hurt our own.
const NeutronBlastBehaviorModuleData *data = getNeutronBlastBehaviorModuleData();
if (!data->m_affectAllies && getObject()->getRelationship( obj ) == ALLIES)
{
return;
}
// Kill if object is infantry
if (obj->isKindOf(KINDOF_INFANTRY))
{
obj->kill();
}
// Kill all contained if it is a container
ContainModuleInterface *contain = obj->getContain();
if( contain )
{
contain->killAllContained();
}
// Kill pilots of vehicles
if( obj->isKindOf( KINDOF_VEHICLE ) && !obj->isKindOf( KINDOF_DRONE ) )
{
// If the vehicle is a combat bike, kill the whole thing
if ( obj->isKindOf( KINDOF_CLIFF_JUMPER ) )
{
obj->kill();
}
// Just kill the pilot of the vehicle
else
{
// Make it unmanned, so units can easily check the ability to "take control of it"
obj->setDisabled( DISABLED_UNMANNED );
if ( obj->getAI() )
obj->getAI()->aiIdle( CMD_FROM_AI );
TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE);
// Clear any terrain decals here
Drawable* draw = obj->getDrawable();
if (draw)
draw->setTerrainDecal(TERRAIN_DECAL_NONE);
// Convert it to the neutral team so it renders gray giving visual representation that it is unmanned.
obj->setTeam( ThePlayerList->getNeutralPlayer()->getDefaultTeam() );
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void NeutronBlastBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void NeutronBlastBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void NeutronBlastBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,328 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: OverchargeBehavior.cpp ///////////////////////////////////////////////////////////////////
// Author: Colin Day, June 2002
// Desc: Objects with this behavior module will get the ability to produce more power
// for a short amount of time, during this "overcharge" state object health is
// slowly reduced
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/OverchargeBehavior.h"
#include "GameLogic/Module/PowerPlantUpdate.h"
#include "GameClient/InGameUI.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
OverchargeBehaviorModuleData::OverchargeBehaviorModuleData( void )
{
m_healthPercentToDrainPerSecond = 0.0f;
m_notAllowedWhenHealthBelowPercent = 0.0f;
} // end OverchargeBehaviorModuleData
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void OverchargeBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "HealthPercentToDrainPerSecond", INI::parsePercentToReal, NULL, offsetof( OverchargeBehaviorModuleData, m_healthPercentToDrainPerSecond ) },
{ "NotAllowedWhenHealthBelowPercent", INI::parsePercentToReal, NULL, offsetof( OverchargeBehaviorModuleData, m_notAllowedWhenHealthBelowPercent ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
OverchargeBehavior::OverchargeBehavior( Thing *thing, const ModuleData* moduleData )
: UpdateModule( thing, moduleData )
{
m_overchargeActive = FALSE;
// start off sleeping forever until we become active
setWakeFrame( getObject(), UPDATE_SLEEP_FOREVER );
} // end OverchargeBehavior
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
OverchargeBehavior::~OverchargeBehavior( void )
{
} // end ~OverchargeBehavior
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime OverchargeBehavior::update( void )
{
// if the overcharge is active we need to take away some life
if( m_overchargeActive )
{
Object *us = getObject();
// get mod data
const OverchargeBehaviorModuleData *modData = getOverchargeBehaviorModuleData();
// do some damage
BodyModuleInterface *body = us->getBodyModule();
DamageInfo damageInfo;
damageInfo.in.m_amount = (body->getMaxHealth() * modData->m_healthPercentToDrainPerSecond) / LOGICFRAMES_PER_SECOND;
damageInfo.in.m_sourceID = us->getID();
damageInfo.in.m_damageType = DAMAGE_PENALTY;
damageInfo.in.m_deathType = DEATH_NORMAL;
us->attemptDamage( &damageInfo );
// see if our health is below the allowable threshold
if( body->getHealth() < body->getMaxHealth() * modData->m_notAllowedWhenHealthBelowPercent )
{
// turn off the overcharge
enable( FALSE );
// do some UI info for the local user if this is theirs
if( ThePlayerList->getLocalPlayer() == us->getControllingPlayer() )
{
// print msg
TheInGameUI->message( "GUI:OverchargeExhausted" );
// do radar event
TheRadar->createEvent( us->getPosition(), RADAR_EVENT_INFORMATION );
} // end of
// do nothing else
return UPDATE_SLEEP_NONE;
} // end if
} // end if
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void OverchargeBehavior::onDamage( DamageInfo *damageInfo )
{
} // end onDie
// ------------------------------------------------------------------------------------------------
/** Flip the state of our 'overcharge-ness' */
// ------------------------------------------------------------------------------------------------
void OverchargeBehavior::toggle( void )
{
// just toggle using enable()
enable( !m_overchargeActive );
} // end toggle
// ------------------------------------------------------------------------------------------------
/** Enable or disable an overcharge */
// ------------------------------------------------------------------------------------------------
void OverchargeBehavior::enable( Bool enable )
{
Object *us = getObject();
if( enable == FALSE )
{
// if we're turned on, turn off
if( m_overchargeActive == TRUE )
{
// make sure to NOT extend rods for purpose of maintaining proper model condition
PowerPlantUpdateInterface *ppui;
for( BehaviorModule **umi = getObject()->getBehaviorModules(); *umi; ++umi)
{
ppui = (*umi)->getPowerPlantUpdateInterface();
if( ppui )
ppui->extendRods(FALSE);
}
Player *player = us->getControllingPlayer();
if ( player )
player->removePowerBonus(us);
// we are no longer active
m_overchargeActive = FALSE;
// sleep forever
setWakeFrame( us, UPDATE_SLEEP_FOREVER );
} // end if
} // end if
else
{
// if we're turned off, turn on
if( m_overchargeActive == FALSE )
{
// make sure to extend rods for purpose of maintaining proper model condition
PowerPlantUpdateInterface *ppui;
for( BehaviorModule **umi = getObject()->getBehaviorModules(); *umi; ++umi)
{
ppui = (*umi)->getPowerPlantUpdateInterface();
if( ppui )
ppui->extendRods(TRUE);
}
// add the power bonus
Player *player = us->getControllingPlayer();
if ( player )
player->addPowerBonus(us);
// we are now active
m_overchargeActive = TRUE;
// need to update every frame now
setWakeFrame( us, UPDATE_SLEEP_NONE );
} // end if
} // end else
} // end enable
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void OverchargeBehavior::onDelete( void )
{
// if we haven't been upgraded there is nothing to clean up
if( m_overchargeActive == FALSE )
return;
// remove the power bonus from the player
Player *player = getObject()->getControllingPlayer();
if( player )
player->removePowerBonus( getObject() );
m_overchargeActive = FALSE;
} // end onDelete
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void OverchargeBehavior::onCapture( Player *oldOwner, Player *newOwner )
{
// do nothing if we haven't upgraded yet
if( m_overchargeActive == FALSE )
return;
if (getObject()->isDisabled())
return;
// remove power bonus from old owner
if( oldOwner )
oldOwner->removePowerBonus( getObject() );
// add power bonus to the new owner
if( newOwner )
newOwner->addPowerBonus( getObject() );
} // end onCapture
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void OverchargeBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void OverchargeBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// overcharge active
xfer->xferBool( &m_overchargeActive );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void OverchargeBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
// Our effect is a fire and forget effect, not an upgrade state that is itself saved, so need to re-fire.
if( m_overchargeActive && getObject()->getControllingPlayer() )
getObject()->getControllingPlayer()->addPowerBonus( getObject() );
} // end loadPostProcess

View File

@@ -0,0 +1,157 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: POWTruckBehavior.cpp /////////////////////////////////////////////////////////////////////
// Author: Colin Day
// Desc: POW Truck
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/InGameUI.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/POWTruckAIUpdate.h"
#include "GameLogic/Module/POWTruckBehavior.h"
#ifdef ALLOW_SURRENDER
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
POWTruckBehaviorModuleData::POWTruckBehaviorModuleData( void )
{
} // end POWTruckBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void POWTruckBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
OpenContainModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
POWTruckBehavior::POWTruckBehavior( Thing *thing, const ModuleData *moduleData )
: OpenContain( thing, moduleData )
{
} // end POWTruckBehavior
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
POWTruckBehavior::~POWTruckBehavior( void )
{
} // end ~POWTruckBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void POWTruckBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
{
Object *us = getObject();
// sanity
if( other == NULL )
return;
// if other isn't slated to be picked up by us, ignore
AIUpdateInterface *otherAi = other->getAIUpdateInterface();
if( otherAi == NULL || otherAi->isSurrendered() == FALSE )
return;
// get our AI info
AIUpdateInterface *ourAI = us->getAIUpdateInterface();
DEBUG_ASSERTCRASH( ourAI, ("POWTruckBehavior::onCollide - '%s' has no AI\n",
us->getTemplate()->getName().str()) );
POWTruckAIUpdateInterface *powTruckAI = ourAI->getPOWTruckAIUpdateInterface();
DEBUG_ASSERTCRASH( powTruckAI, ("POWTruckBehavior::onCollide - '%s' has no POWTruckAI\n",
us->getTemplate()->getName().str()) );
// pick up the prisoner
powTruckAI->loadPrisoner( other );
} // end onCollide
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void POWTruckBehavior::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void POWTruckBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void POWTruckBehavior::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess
#endif

View File

@@ -0,0 +1,249 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: PoisonedBehavior.cpp /////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, July 2002
// Desc: Behavior that reacts to poison Damage by continuously damaging us further in an Update
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/Module/PoisonedBehavior.h"
#include "GameLogic/Damage.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// tinting is all handled in drawable, now, Graham look near the bottom of Drawable::UpdateDrawable()
//static const RGBColor poisonedTint = {0.0f, 1.0f, 0.0f};
//-------------------------------------------------------------------------------------------------
PoisonedBehaviorModuleData::PoisonedBehaviorModuleData()
{
m_poisonDamageIntervalData = 0; // How often I retake poison damage dealt me
m_poisonDurationData = 0; // And how long after the last poison dose I am poisoned
}
//-------------------------------------------------------------------------------------------------
/*static*/ void PoisonedBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
static const FieldParse dataFieldParse[] =
{
{ "PoisonDamageInterval", INI::parseDurationUnsignedInt, NULL, offsetof(PoisonedBehaviorModuleData, m_poisonDamageIntervalData) },
{ "PoisonDuration", INI::parseDurationUnsignedInt, NULL, offsetof(PoisonedBehaviorModuleData, m_poisonDurationData) },
{ 0, 0, 0, 0 }
};
UpdateModuleData::buildFieldParse(p);
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
PoisonedBehavior::PoisonedBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
m_poisonDamageFrame = 0;
m_poisonOverallStopFrame = 0;
m_poisonDamageAmount = 0.0f;
m_deathType = DEATH_POISONED;
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
PoisonedBehavior::~PoisonedBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
/** Damage has been dealt, this is an opportunity to react to that damage */
//-------------------------------------------------------------------------------------------------
void PoisonedBehavior::onDamage( DamageInfo *damageInfo )
{
if( damageInfo->in.m_damageType == DAMAGE_POISON )
startPoisonedEffects( damageInfo );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::onHealing( DamageInfo *damageInfo )
{
stopPoisonedEffects();
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime PoisonedBehavior::update()
{
const PoisonedBehaviorModuleData* d = getPoisonedBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
if( m_poisonOverallStopFrame == 0 )
{
DEBUG_CRASH(("hmm, this should not happen"));
return UPDATE_SLEEP_FOREVER;
//we aren't poisoned, so nevermind
}
if (m_poisonDamageFrame != 0 && now >= m_poisonDamageFrame)
{
// If it is time to do damage, then do it and reset the damage timer
DamageInfo damage;
damage.in.m_amount = m_poisonDamageAmount;
damage.in.m_sourceID = INVALID_ID;
damage.in.m_damageType = DAMAGE_UNRESISTABLE; // Not poison, as that will infect us again
damage.in.m_damageFXOverride = DAMAGE_POISON; // but this will ensure that the right effect is played
damage.in.m_deathType = m_deathType;
getObject()->attemptDamage( &damage );
m_poisonDamageFrame = now + d->m_poisonDamageIntervalData;
}
// If we are now at zero we need to turn off our special effects...
// unless the poison killed us, then we continue to be a pulsating toxic pus ball
if( m_poisonOverallStopFrame != 0 &&
now >= m_poisonOverallStopFrame &&
!getObject()->isEffectivelyDead())
{
stopPoisonedEffects();
}
return calcSleepTime();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime PoisonedBehavior::calcSleepTime()
{
// UPDATE_SLEEP requires a count-of-frames, not an absolute-frame, so subtract 'now'
UnsignedInt now = TheGameLogic->getFrame();
if (m_poisonOverallStopFrame == 0 || m_poisonOverallStopFrame == now)
return UPDATE_SLEEP_FOREVER;
return frameToSleepTime(m_poisonDamageFrame, m_poisonOverallStopFrame);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::startPoisonedEffects( const DamageInfo *damageInfo )
{
const PoisonedBehaviorModuleData* d = getPoisonedBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
// We are going to take the damage dealt by the original poisoner every so often for a while.
m_poisonDamageAmount = damageInfo->out.m_actualDamageDealt;
m_poisonOverallStopFrame = now + d->m_poisonDurationData;
// If we are getting re-poisoned, don't reset the damage counter if running, but do set it if unset
if( m_poisonDamageFrame != 0 )
m_poisonDamageFrame = min( m_poisonDamageFrame, now + d->m_poisonDamageIntervalData );
else
m_poisonDamageFrame = now + d->m_poisonDamageIntervalData;
m_deathType = damageInfo->in.m_deathType;
Drawable *myDrawable = getObject()->getDrawable();
if( myDrawable )
myDrawable->setTintStatus( TINT_STATUS_POISONED );// Graham, It has changed, see UpdateDrawable()
setWakeFrame(getObject(), calcSleepTime());
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::stopPoisonedEffects()
{
m_poisonDamageFrame = 0;
m_poisonOverallStopFrame = 0;
m_poisonDamageAmount = 0.0f;
Drawable *myDrawable = getObject()->getDrawable();
if( myDrawable )
myDrawable->clearTintStatus( TINT_STATUS_POISONED );// Graham, It has changed, see UpdateDrawable()
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::xfer( Xfer *xfer )
{
// version
const XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// poisoned damage frame
xfer->xferUnsignedInt( &m_poisonDamageFrame );
// poison overall stop frame
xfer->xferUnsignedInt( &m_poisonOverallStopFrame );
// poison damage amount
xfer->xferReal( &m_poisonDamageAmount );
if (version >= 2)
{
xfer->xferUser(&m_deathType, sizeof(m_deathType));
}
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,478 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: PrisonBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author: Colin Day, August 2002
// Desc: Prison Behavior
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameState.h"
#include "Common/Player.h"
#include "Common/RandomValue.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/InGameUI.h"
#include "GameClient/GameClient.h"
#include "GameClient/Line2D.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/PrisonBehavior.h"
#ifdef ALLOW_SURRENDER
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
class PrisonVisual : public MemoryPoolObject
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( PrisonVisual, "PrisonVisual" )
public:
PrisonVisual( void );
// virtual destructor prototype provied by memory pool object
ObjectID m_objectID; ///< object that is contained
DrawableID m_drawableID; ///< associated visual prisoner drawable
PrisonVisual *m_next; ///< next
};
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PrisonVisual::PrisonVisual( void )
{
m_objectID = INVALID_ID;
m_drawableID = INVALID_DRAWABLE_ID;
m_next = NULL;
} // end PrisonVisual
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PrisonVisual::~PrisonVisual( void )
{
} // end ~PrisonVisual
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PrisonBehaviorModuleData::PrisonBehaviorModuleData( void )
{
m_showPrisoners = FALSE;
} // end PrisonBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void PrisonBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
OpenContainModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "ShowPrisoners", INI::parseBool, NULL, offsetof( PrisonBehaviorModuleData, m_showPrisoners ) },
{ "YardBonePrefix", INI::parseAsciiString, NULL, offsetof( PrisonBehaviorModuleData, m_prisonYardBonePrefix ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PrisonBehavior::PrisonBehavior( Thing *thing, const ModuleData *moduleData )
: OpenContain( thing, moduleData )
{
m_visualList = NULL;
} // end PrisonBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PrisonBehavior::~PrisonBehavior( void )
{
} // end ~PrisonBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::onDelete( void )
{
// extend functionality
OpenContain::onDelete();
// delete our list
Drawable *draw;
PrisonVisual *visual;
while( m_visualList )
{
// delete drawable if found
draw = TheGameClient->findDrawableByID( m_visualList->m_drawableID );
if( draw )
TheGameClient->destroyDrawable( draw );
// delete element and set next to head
visual = m_visualList->m_next;
m_visualList->deleteInstance();
m_visualList = visual;
} // end while
} // end onDelete
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::onContaining( Object *obj, Bool wasSelected )
{
// extend functionality
OpenContain::onContaining( obj, wasSelected );
// objects inside a prison are held
obj->setDisabled( DISABLED_HELD );
// if we show visuals, make one
const PrisonBehaviorModuleData *modData = getPrisonBehaviorModuleData();
if( modData->m_showPrisoners )
addVisual( obj );
} // end onContaining
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::onRemoving( Object *obj )
{
// if we show visuals, remove one
const PrisonBehaviorModuleData *modData = getPrisonBehaviorModuleData();
if( modData->m_showPrisoners )
removeVisual( obj );
// object is no longer held inside a garrisoned building
obj->clearDisabled( DISABLED_HELD );
// extend functionality
OpenContain::onRemoving( obj );
} // end onRemoving
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
/** Pick a random location inside the prison yard */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::pickVisualLocation( Coord3D *pos )
{
Object *us = getObject();
const PrisonBehaviorModuleData *modData = getPrisonBehaviorModuleData();
Int i;
// sanity
if( pos == NULL )
return;
// initialize the picked location to that of the prison center
Coord3D pickedLocation = *us->getPosition();
// get the positions of the bones that make up the prison yard area
const Int MAX_YARD_BONES = 16;
Coord3D yardPositions[ MAX_YARD_BONES ];
Int yardBones = us->getMultiLogicalBonePosition( modData->m_prisonYardBonePrefix.str(),
MAX_YARD_BONES,
yardPositions,
NULL );
//
// we must have at least 3 bone locations to make a yard polygon, otherwise we'll
// default to the object position
//
if( yardBones >= 3 )
{
// find the bounding region of the yard area
Region2D yardRegion;
yardRegion.lo.x = yardPositions[ 0 ].x;
yardRegion.lo.y = yardPositions[ 0 ].y;
yardRegion.hi.x = yardPositions[ 0 ].x;
yardRegion.hi.y = yardPositions[ 0 ].y;
for( i = 1; i < yardBones; i++ )
{
if( yardPositions[ i ].x < yardRegion.lo.x )
yardRegion.lo.x = yardPositions[ i ].x;
if( yardPositions[ i ].y < yardRegion.lo.y )
yardRegion.lo.y = yardPositions[ i ].y;
if( yardPositions[ i ].x > yardRegion.hi.x )
yardRegion.hi.x = yardPositions[ i ].x;
if( yardPositions[ i ].y > yardRegion.hi.y )
yardRegion.hi.y = yardPositions[ i ].y;
} // end for i
//
// now that we have a yard region, the default visual position will be in the middle
// of the yard region instead of the position of our object
//
pickedLocation.x = yardRegion.lo.x + yardRegion.width() / 2.0f;
pickedLocation.y = yardRegion.lo.y + yardRegion.height() / 2.0f;
// NOTE: pickedLocation.z is left alone at the object center Z
// loop till we find a valid location that is inside the yard area
Int maxTries = 32;
Coord3D loc;
for( i = 0; i < maxTries; ++i )
{
// pick a location
loc.x = GameLogicRandomValueReal( yardRegion.lo.x, yardRegion.hi.x );
loc.y = GameLogicRandomValueReal( yardRegion.lo.y, yardRegion.hi.y );
loc.z = pickedLocation.z;
// must be inside the yard polygon
if( PointInsideArea2D( &loc, yardPositions, yardBones ) == TRUE )
{
// use this location, leave Z alone as the center of the prison
pickedLocation = loc;
break; // exit for i
} // end if
} // end for i
} // end if
// return the location picked
*pos = pickedLocation;
} // end pickVisualLocation
// ------------------------------------------------------------------------------------------------
/** Add prisoner visual to the prison yard */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::addVisual( Object *obj )
{
// sanity
if( obj == NULL )
return;
// create a drawable
Drawable *draw = TheThingFactory->newDrawable( obj->getTemplate() );
// set the color of the drawable to that of the object
if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT)
draw->setIndicatorColor( obj->getNightIndicatorColor() );
else
draw->setIndicatorColor( obj->getIndicatorColor() );
// pick a location insid the prison yard
Coord3D pos;
pickVisualLocation( &pos );
// place drawable withing the prison yard area
draw->setPosition( &pos );
draw->setOrientation( GameLogicRandomValueReal( 0, TWO_PI ) );
DrawableInfo *drawInfo=draw->getDrawableInfo();
drawInfo->m_shroudStatusObjectID=getObject()->getID();
// record this object/drawable pair
PrisonVisual *visual = newInstance(PrisonVisual);
visual->m_objectID = obj->getID();
visual->m_drawableID = draw->getID();
visual->m_next = m_visualList;
m_visualList = visual;
} // end addVisual
// ------------------------------------------------------------------------------------------------
/** Remove prisoner visual from the prison yard */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::removeVisual( Object *obj )
{
// sanity
if( obj == NULL )
return;
// initialize a drawable ID to invalid
DrawableID drawableID = INVALID_DRAWABLE_ID;
// find visual info in our list, once found, take this opportunity to remove it from that list
PrisonVisual *visual, *prevVisual = NULL;
for( visual = m_visualList; visual; visual = visual->m_next )
{
// is this the one we're looking for
if( visual->m_objectID == obj->getID() )
{
// record the information we need here
drawableID = visual->m_drawableID;
// remove from list
if( prevVisual )
prevVisual->m_next = visual->m_next;
else
m_visualList = visual->m_next;
// delete the element
visual->deleteInstance();
break; // exit for
} // end if
// keep a pointer to the previous element
prevVisual = visual;
} // end for
// find the drawable visual and destroy it
Drawable *draw = TheGameClient->findDrawableByID( drawableID );
if( draw )
TheGameClient->destroyDrawable( draw );
} // end removeVisual
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// count and data for the prison visuals
UnsignedShort visualCount = 0;
PrisonVisual *visual;
for( visual = m_visualList; visual; visual = visual->m_next )
visualCount++;
xfer->xferUnsignedShort( &visualCount );
if( xfer->getXferMode() == XFER_SAVE )
{
// write all data
for( visual = m_visualList; visual; visual = visual->m_next )
{
// object id
xfer->xferObjectID( &visual->m_objectID );
// drawable id
xfer->xferDrawableID( &visual->m_drawableID );
} // end for, visual
} // end if, save
else
{
// the visual list should be empty
if( m_visualList != NULL )
{
DEBUG_CRASH(( "PrisonBehavior::xfer - the visual list should be empty but is not\n" ));
throw SC_INVALID_DATA;
} // end if
// read each item
for( UnsignedShort i = 0; i < visualCount; ++i )
{
// allocate a new visual and tie to list
visual = newInstance(PrisonVisual);
visual->m_next = m_visualList;
m_visualList = visual;
// read object id
xfer->xferObjectID( &visual->m_objectID );
// read drawable id
xfer->xferDrawableID( &visual->m_drawableID );
} // end for, i
} // end else, load
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess
#endif

View File

@@ -0,0 +1,282 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: PropagandaCenterBehavior.cpp /////////////////////////////////////////////////////////////
// Author: Colin Day, August 2002
// Desc: Propaganda Center Behavior
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/PropagandaCenterBehavior.h"
#ifdef ALLOW_SURRENDER
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaCenterBehaviorModuleData::PropagandaCenterBehaviorModuleData( void )
{
m_brainwashDuration = 0;
} // end PropagandaCenterBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void PropagandaCenterBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
PrisonBehaviorModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "BrainwashDuration", INI::parseDurationUnsignedInt, NULL, offsetof( PropagandaCenterBehaviorModuleData, m_brainwashDuration ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaCenterBehavior::PropagandaCenterBehavior( Thing *thing, const ModuleData *moduleData )
: PrisonBehavior( thing, moduleData )
{
m_brainwashingSubjectID = INVALID_ID;
m_brainwashingSubjectStartFrame = 0;
} // end PropagandaCenterBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaCenterBehavior::~PropagandaCenterBehavior( void )
{
} // end ~PropagandaCenterBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PropagandaCenterBehavior::onDelete( void )
{
// extend functionality
PrisonBehavior::onDelete();
//
// go through our list of brainwashed objects, and if they are still under our
// control, return them to their original owners
//
for( BrainwashedIDListContIterator it = m_brainwashedList.begin();
it != m_brainwashedList.end();
++it )
{
Object *obj;
// get this object
obj = TheGameLogic->findObjectByID( *it );
if( obj )
{
// return this object under the control of the original owner
obj->restoreOriginalTeam();
} // end if
} // end for
// clear the list
m_brainwashedList.clear();
} // end onDelete
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime PropagandaCenterBehavior::update( void )
{
Object *us = getObject();
const PropagandaCenterBehaviorModuleData *modData = getPropagandaCenterBehaviorModuleData();
// extend functionality
PrisonBehavior::update();
// if we have a prisoner inside, continue the brainwashing on them (one at a time)
if( m_brainwashingSubjectID != INVALID_ID )
{
Object *brainwashingSubject = TheGameLogic->findObjectByID( m_brainwashingSubjectID );
if( brainwashingSubject )
{
// if we've been in here long enough, we come out brainwashed
if( TheGameLogic->getFrame() - m_brainwashingSubjectStartFrame >= modData->m_brainwashDuration )
{
// only can exit if the prison allows us to
ExitDoorType exitDoor = reserveDoorForExit(brainwashingSubject->getTemplate(), brainwashingSubject);
if(exitDoor != DOOR_NONE_AVAILABLE)
{
// place this object under the control of the player
Player *player = us->getControllingPlayer();
DEBUG_ASSERTCRASH( player, ("Brainwashing: No controlling player for '%s'\n", us->getTemplate()->getName().str()) );
if( player )
brainwashingSubject->setTemporaryTeam( player->getDefaultTeam() );
// remove any surrender status from this object
AIUpdateInterface *ai = brainwashingSubject->getAIUpdateInterface();
if( ai )
ai->setSurrendered( NULL, FALSE );
// add this object to our brainwashed list if we're not already in it
for( BrainwashedIDListIterator it = m_brainwashedList.begin();
it != m_brainwashedList.end(); ++it )
if( *it == brainwashingSubject->getID() )
break; // exit for
if( it == m_brainwashedList.end() )
m_brainwashedList.push_front( brainwashingSubject->getID() );
// exit the prison
exitObjectViaDoor( brainwashingSubject, exitDoor );
} // end if
} // end if
} // end if,
} // end if
// if we have no brainwashing subject, hook one up if we have people inside us
if( m_brainwashingSubjectID == INVALID_ID )
{
// find the first object in our containment list
if( getContainList().begin() != getContainList().end() )
{
Object *obj = getContainList().front();
if( obj )
{
// assign brainwashing subject on this frame
m_brainwashingSubjectID = obj->getID();
m_brainwashingSubjectStartFrame = TheGameLogic->getFrame();
} // end if
} // end if
} // end if
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PropagandaCenterBehavior::onRemoving( Object *obj )
{
// if we're removing the brainwashing subject, NULL the pointer
if( m_brainwashingSubjectID == obj->getID() )
{
m_brainwashingSubjectID = INVALID_ID;
m_brainwashingSubjectStartFrame = 0;
} // end if
// extend functionality
PrisonBehavior::onRemoving( obj );
} // end onRemoving
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PropagandaCenterBehavior::crc( Xfer *xfer )
{
// extend base class
PrisonBehavior::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PropagandaCenterBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
PrisonBehavior::xfer( xfer );
// brainwashing subject
xfer->xferObjectID( &m_brainwashingSubjectID );
// brainwashing subject start frame
xfer->xferUnsignedInt( &m_brainwashingSubjectStartFrame );
// brainwashed list size and data
xfer->xferSTLObjectIDList( &m_brainwashedList );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PropagandaCenterBehavior::loadPostProcess( void )
{
// extend base class
PrisonBehavior::loadPostProcess();
} // end loadPostProcess
#endif

View File

@@ -0,0 +1,649 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: PropagandaTowerBehavior.cpp //////////////////////////////////////////////////////////////
// Author: Colin Day, August 2002
// Desc: Behavior module for PropagandaTower
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/GameState.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Upgrade.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/FXList.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Weapon.h"
#include "GameLogic/Module/PropagandaTowerBehavior.h"
#include "GameLogic/Module/BodyModule.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// FORWARD REFERENCES /////////////////////////////////////////////////////////////////////////////
enum ObjectID;
// ------------------------------------------------------------------------------------------------
/** This class is used to track objects as they exit our area of influence */
// ------------------------------------------------------------------------------------------------
class ObjectTracker : public MemoryPoolObject
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ObjectTracker, "ObjectTracker" );
public:
ObjectTracker( void ) { objectID = INVALID_ID; next = NULL; }
ObjectID objectID;
ObjectTracker *next;
};
ObjectTracker::~ObjectTracker( void ) { }
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaTowerBehaviorModuleData::PropagandaTowerBehaviorModuleData( void )
{
m_scanRadius = 1.0f;
m_scanDelayInFrames = 100;
m_autoHealPercentPerSecond = 0.01f;
m_upgradedAutoHealPercentPerSecond = 0.02f;
m_pulseFX = NULL;
m_upgradeRequired = NULL;
m_upgradedPulseFX = NULL;
m_affectsSelf = FALSE;
} // end PropagandaTowerBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void PropagandaTowerBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "Radius", INI::parseReal, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_scanRadius ) },
{ "DelayBetweenUpdates", INI::parseDurationUnsignedInt, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_scanDelayInFrames ) },
{ "HealPercentEachSecond", INI::parsePercentToReal, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_autoHealPercentPerSecond ) },
{ "UpgradedHealPercentEachSecond", INI::parsePercentToReal,NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradedAutoHealPercentPerSecond ) },
{ "PulseFX", INI::parseFXList, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_pulseFX ) },
{ "UpgradeRequired", INI::parseAsciiString, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradeRequired ) },
{ "UpgradedPulseFX", INI::parseFXList, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradedPulseFX ) },
{ "AffectsSelf", INI::parseBool, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_affectsSelf ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaTowerBehavior::PropagandaTowerBehavior( Thing *thing, const ModuleData *modData )
: UpdateModule( thing, modData )
{
//Added By Sadullah Nader
//Initializations inserted
m_lastScanFrame = 0;
//
m_insideList = NULL;
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
} // end PropagandaTowerBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaTowerBehavior::~PropagandaTowerBehavior( void )
{
} // end ~PropagandaTowerBehavior
// ------------------------------------------------------------------------------------------------
/** Module is being deleted */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::onDelete( void )
{
// remove any benefits from anybody in our area of influence
removeAllInfluence();
} // end onDelete
// ------------------------------------------------------------------------------------------------
/** Resolve */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::onObjectCreated( void )
{
const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData();
// convert module upgrade name to a pointer
m_upgradeRequired = TheUpgradeCenter->findUpgrade( modData->m_upgradeRequired );
} // end onObjectCreated
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::onCapture( Player *oldOwner, Player *newOwner )
{
// We don't function for the neutral player.
if( newOwner == ThePlayerList->getNeutralPlayer() )
{
removeAllInfluence();
setWakeFrame( getObject(), UPDATE_SLEEP_FOREVER );
}
else
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
}
// ------------------------------------------------------------------------------------------------
/** The update callback */
// ------------------------------------------------------------------------------------------------
UpdateSleepTime PropagandaTowerBehavior::update( void )
{
/// @todo srj use SLEEPY_UPDATE here
const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData();
//Sep 27, 2002 (Kris): Added this code to prevent the tower from working while under construction.
Object *self = getObject();
if( self->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
return UPDATE_SLEEP_NONE;
if( self->testStatus(OBJECT_STATUS_SOLD) )
{
removeAllInfluence();
return UPDATE_SLEEP_FOREVER;
}
if( self->isEffectivelyDead() )
return UPDATE_SLEEP_FOREVER;
if( self->isDisabled() )
{
// We need to let go of everyone if we are EMPd or underpowered or yadda, but not if we are only held
DisabledMaskType allButHeld = MAKE_DISABLED_MASK( DISABLED_HELD );
FLIP_DISABLEDMASK(allButHeld);
if( TEST_DISABLEDMASK_ANY(self->getDisabledFlags(), allButHeld) )
{
removeAllInfluence();
return UPDATE_SLEEP_NONE;
}
}
if( self->getContainedBy() && self->getContainedBy()->getContainedBy() )
{
// If our container is contained, we turn the heck off. Seems like a weird specific check, but all of
// attacking is guarded by the same check in isPassengersAllowedToFire. We similarly work in a container,
// but not in a double container.
removeAllInfluence();
return UPDATE_SLEEP_NONE;
}
// if it's not time to scan, nothing to do
UnsignedInt currentFrame = TheGameLogic->getFrame();
if( currentFrame - m_lastScanFrame >= modData->m_scanDelayInFrames )
{
// do a scan
doScan();
m_lastScanFrame = currentFrame;
} // end if
// go through any objects in our area of influence and do the effect logic on them
Object *obj;
ObjectTracker *curr = NULL, *prev = NULL, *next = NULL;
for( curr = m_insideList; curr; curr = next )
{
// get the next link
next = curr->next;
// find this object
obj = TheGameLogic->findObjectByID( curr->objectID );
if ((obj) &&
(obj->isKindOf(KINDOF_SCORE) || obj->isKindOf(KINDOF_SCORE_CREATE) || obj->isKindOf(KINDOF_SCORE_DESTROY) || obj->isKindOf(KINDOF_MP_COUNT_FOR_VICTORY)))
{
// give any bonus to this object
effectLogic( obj, TRUE, getPropagandaTowerBehaviorModuleData() );
// record this element as the previous one found in the list
prev = curr;
} // end if
else
{
//
// actual object wasn't found, remove this entry from our inside list so we don't
// have to search through it again
//
if( prev )
prev->next = curr->next;
else
m_insideList = curr->next;
curr->deleteInstance();
} // end else
} // end for, curr
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
/** The death callback */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::onDie( const DamageInfo *damageInfo )
{
// remove any benefits from anybody in our area of influence
removeAllInfluence();
} // end onDie
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
/** Grant or remove effect to this object */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::effectLogic( Object *obj, Bool giving,
const PropagandaTowerBehaviorModuleData *modData )
{
Bool effectUpgraded = getObject()->getControllingPlayer()->hasUpgradeComplete( m_upgradeRequired );
// if giving the effect
if( giving )
{
if ( obj->hasAnyDamageWeapon() == TRUE )
{
if( obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC ) == FALSE )
obj->setWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC );
if (effectUpgraded)
{
if (obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL ) == FALSE)
obj->setWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL );
}
} // hasdamageweapon
// grant health to this object as well
BodyModuleInterface *body = obj->getBodyModule();
if( body )
{
Real healthPercent;
if(effectUpgraded)
healthPercent = modData->m_upgradedAutoHealPercentPerSecond;
else
healthPercent = modData->m_autoHealPercentPerSecond;
Real amount = healthPercent / LOGICFRAMES_PER_SECOND * body->getMaxHealth();
// Dustin wants the healing effect not to stack from multiple propaganda towers...
// To accomplish this, I'll give every object a single healing-sender (ID)
// Any given healing recipient (object) can only receive healing from one particular healing sender
// and cannot change healing senders until the previous one expires (its scandelay)
// obj->attemptHealing(amount, getObject()); // the regular way to give healing...
obj->attemptHealingFromSoleBenefactor( amount, getObject(), modData->m_scanDelayInFrames );//the non-stacking way
} // end if
} // end if
else
{
// taking effect away
obj->clearWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC );
obj->clearWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL );
} // end else
} // end effectLogic
// ------------------------------------------------------------------------------------------------
/** Remove all influence from objects we've given bonuses to */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::removeAllInfluence( void )
{
ObjectTracker *o;
// go through all objects we've given bonuses to and remove them
Object *obj;
for( o = m_insideList; o; o = o->next )
{
obj = TheGameLogic->findObjectByID( o->objectID );
if( obj )
effectLogic( obj, FALSE, getPropagandaTowerBehaviorModuleData() );
} // end for
// delete the list of objects under our influence
while( m_insideList )
{
o = m_insideList->next;
m_insideList->deleteInstance();
m_insideList = o;
} // end while
} // end removeAllInfluence
// ------------------------------------------------------------------------------------------------
/** Do a scan */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::doScan( void )
{
const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData();
Object *us = getObject();
ObjectTracker *newInsideList = NULL;
// The act of scanning is when we play our effect
Bool upgradePresent = FALSE;
if( m_upgradeRequired )
{
// see if we have the upgrade
switch( m_upgradeRequired->getUpgradeType() )
{
// ------------------------------------------------------------------------------------------
case UPGRADE_TYPE_PLAYER:
{
Player *player = us->getControllingPlayer();
upgradePresent = player->hasUpgradeComplete( m_upgradeRequired );
break;
} // end player upgrade
// ------------------------------------------------------------------------------------------
case UPGRADE_TYPE_OBJECT:
{
upgradePresent = us->hasUpgrade( m_upgradeRequired );
break;
} // end object upgrade
// ------------------------------------------------------------------------------------------
default:
{
DEBUG_CRASH(( "PropagandaTowerBehavior::doScan - Unknown upgrade type '%d'\n",
m_upgradeRequired->getUpgradeType() ));
break;
} // end default
} // end switch
} // end if
Bool doFX = TRUE;
// if it is us, or if it is he and we do see him
Object *overlord = us->getContainedBy();
if ( overlord )
{
if ( us->getControllingPlayer() != ThePlayerList->getLocalPlayer() )// daling with someone else's tower
{
if ( overlord->testStatus( OBJECT_STATUS_STEALTHED ) && !overlord->testStatus( OBJECT_STATUS_DETECTED ) )
doFX = FALSE;// so they don't give up their position
}
// Sorry, this is a lot of hard coded mishmash, but it must be done... ship it!
// Propaganda towers (proper) never get contained, so they don't care about this code
// The PropTowers that ride on the backs of overlord tanks want to suppress their fx if the overlord is stealthed to the local player
// The PropTowers that ride on the bellies of Helixes also suppress their fx when hiding stealthed (never happens?)
// The PropTowers must do nothing, scanning-or-FX-wise when they are inside something... there are two cases
// 1) When the prop tower is on an Overlord that is in a helicopter (likely a Helix II)
// 2) When the prop tower is the EmperorTank, which is in a helicopter (likely a Helix II).
if ( us->isKindOf( KINDOF_VEHICLE ) && !us->isKindOf( KINDOF_PORTABLE_STRUCTURE ) )//craptacular!
// oh my dear Lord... I am not a propaganda tower, I am an emperorTank! that means the overlord is a helix or something!
return; // proptowers that are riding IN things cannot do their scan!
//okay this is wacky, but this proptower may be on an overlord thank that is riding in a helix... oy!
Object *helix = overlord->getContainedBy();
if ( helix )
return; // proptowers that are riding ON things that are in-turn riding IN things cannot do their scan!
}
if ( us->getControllingPlayer() != ThePlayerList->getLocalPlayer() )// daling with someone else's tower
{
if ( us->testStatus( OBJECT_STATUS_STEALTHED ) && !us->testStatus( OBJECT_STATUS_DETECTED ) )
{
doFX = FALSE;// Certainly don't play if we ourselves are stelthed.
}
}
if ( doFX )
{
// play the right pulse
if( upgradePresent == TRUE )
FXList::doFXObj( modData->m_upgradedPulseFX, us );
else
FXList::doFXObj( modData->m_pulseFX, us );
}
// setup scan filters
PartitionFilterRelationship relationship( us, PartitionFilterRelationship::ALLOW_ALLIES );
PartitionFilterAlive filterAlive;
PartitionFilterSameMapStatus filterMapStatus(us);
PartitionFilterAcceptByKindOf filterOutBuildings(KINDOFMASK_NONE, MAKE_KINDOF_MASK(KINDOF_STRUCTURE));
PartitionFilter *filters[] = { &relationship,
&filterAlive,
&filterMapStatus,
&filterOutBuildings,
NULL
};
// scan objects in our region
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( us->getPosition(),
modData->m_scanRadius,
FROM_CENTER_2D,
filters );
MemoryPoolObjectHolder hold( iter );
Object *obj;
ObjectTracker *newEntry;
for( obj = iter->first(); obj; obj = iter->next() )
{
// ignore ourselves, unless Design wants us to affect ourselves
if( obj == us && !modData->m_affectsSelf)
continue;
// record this object as being in the new "in list"
newEntry = newInstance(ObjectTracker);
newEntry->objectID = obj->getID();
newEntry->next = newInsideList;
newInsideList = newEntry;
} // end for obj
//
// now that we have a list of objects that are in our area of influence, look through
// the objects that were last recorded as in our area of influence and remove any
// bonus we've given them (if they are within the area of effect of another tower it's
// OK, they'll get the bonus back again when that tower does a scan which won't be too long
//
for( ObjectTracker *curr = m_insideList; curr; curr = curr->next )
{
// find this entry in the new list
ObjectTracker *o = NULL;
for( o = newInsideList; o; o = o->next )
if( o->objectID == curr->objectID )
break;
// if entry wasn't there, remove the bonus from this object
if( o == NULL )
{
obj = TheGameLogic->findObjectByID( curr->objectID );
if( obj )
effectLogic( obj, FALSE, modData );
} // end if
} // end for
// delete the inside list we have recoreded
ObjectTracker *next;
while( m_insideList )
{
next = m_insideList->next;
m_insideList->deleteInstance();
m_insideList = next;
} // end while
// set the new inside list to the one we're recording
m_insideList = newInsideList;
} // end doScan
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// last scan frame
xfer->xferUnsignedInt( &m_lastScanFrame );
// inside list tracking
ObjectTracker *trackerEntry;
UnsignedShort insideCount = 0;
for( trackerEntry = m_insideList; trackerEntry; trackerEntry = trackerEntry->next )
insideCount++;
xfer->xferUnsignedShort( &insideCount );
if( xfer->getXferMode() == XFER_SAVE )
{
// write all entries
for( trackerEntry = m_insideList; trackerEntry; trackerEntry = trackerEntry->next )
{
// object id
xfer->xferObjectID( &trackerEntry->objectID );
} // end for
} // end if, save
else
{
// sanity
if( m_insideList != NULL )
{
DEBUG_CRASH(( "PropagandaTowerBehavior::xfer - m_insideList should be empty but is not\n" ));
throw SC_INVALID_DATA;
} // end if
// read all entries
for( UnsignedShort i = 0; i < insideCount; ++i )
{
// allocate new tracker entry and tie to list
trackerEntry = newInstance(ObjectTracker);
trackerEntry->next = m_insideList;
m_insideList = trackerEntry;
// read object id
xfer->xferObjectID( &trackerEntry->objectID );
} // end for i
} // end else, load
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,491 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: RebuildHoleBehavior.cpp //////////////////////////////////////////////////////////////////
// Author: Colin Day, June 2002
// Desc: GLA Hole behavior that reconstructs a building after death
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameState.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/RebuildHoleBehavior.h"
#include "GameLogic/Module/StickyBombUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
RebuildHoleBehaviorModuleData::RebuildHoleBehaviorModuleData( void )
{
m_workerRespawnDelay = 0.0f;
m_holeHealthRegenPercentPerSecond = 0.1f;
} // end RebuildHoleBehaviorModuleData
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void RebuildHoleBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "WorkerObjectName", INI::parseAsciiString, NULL, offsetof( RebuildHoleBehaviorModuleData, m_workerTemplateName ) },
{ "WorkerRespawnDelay", INI::parseDurationReal, NULL, offsetof( RebuildHoleBehaviorModuleData, m_workerRespawnDelay ) },
{ "HoleHealthRegen%PerSecond", INI::parsePercentToReal, NULL, offsetof( RebuildHoleBehaviorModuleData, m_holeHealthRegenPercentPerSecond ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
RebuildHoleBehavior::RebuildHoleBehavior( Thing *thing, const ModuleData* moduleData )
: UpdateModule( thing, moduleData )
{
m_workerID = INVALID_ID;
m_reconstructingID = INVALID_ID;
m_spawnerObjectID = INVALID_ID;
m_workerWaitCounter = 0;
m_workerTemplate = NULL;
m_rebuildTemplate = NULL;
} // end RebuildHoleBehavior
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
RebuildHoleBehavior::~RebuildHoleBehavior( void )
{
// ensure that our generated worker is destroyed,
// just in case someone decides to destroy (not kill) us...
if( m_workerID != INVALID_ID )
{
Object *worker = TheGameLogic->findObjectByID(m_workerID);
if( worker )
{
TheGameLogic->destroyObject(worker);
m_workerID = INVALID_ID;
}
}
} // end ~RebuildHoleBehavior
// ------------------------------------------------------------------------------------------------
/** we need to start all the timers and ID ties to make a new worker at the correct time */
// ------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::newWorkerRespawnProcess( Object *existingWorker )
{
const RebuildHoleBehaviorModuleData *modData = getRebuildHoleBehaviorModuleData();
// if we have an existing worker, get rid of it
if( existingWorker )
{
DEBUG_ASSERTCRASH(existingWorker->getID() == m_workerID, ("m_workerID mismatch in RebuildHole"));
TheGameLogic->destroyObject( existingWorker );
}
m_workerID = INVALID_ID;
// set the timer for the next worker respawn
m_workerWaitCounter = modData->m_workerRespawnDelay;
//
// this method is called when a worker needs to be respawned from the hole. One of those
// situations is where the building was killed. Since during building reconstruction
// we made the hole "effectively not here" we will always want to make the hole
// "here again" (able to be selected, targeted by the AI etc) because it's the
// "focus" of this small area again
//
getObject()->maskObject( FALSE );
} // end newWorkerRespawnProcess
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::startRebuildProcess( const ThingTemplate *rebuild, ObjectID spawnerID )
{
// save what we're gonna do
m_rebuildTemplate = rebuild;
// store the object that spawned this hole (even though it's likely being destroyed)
m_spawnerObjectID = spawnerID;
// start the spawning process for a worker
Object *worker = TheGameLogic->findObjectByID(m_workerID);
newWorkerRespawnProcess( worker ); //Kill the worker if we have one.
} /// end startRebuildProcess
//----------------------------------------------------------------------------------------------
void RebuildHoleBehavior::transferBombs( Object *reconstruction )
{
Object *self = getObject();
Object *obj = TheGameLogic->getFirstObject();
while( obj )
{
if( obj->isKindOf( KINDOF_MINE ) )
{
static NameKeyType key_StickyBombUpdate = NAMEKEY( "StickyBombUpdate" );
StickyBombUpdate *update = (StickyBombUpdate*)obj->findUpdateModule( key_StickyBombUpdate );
if( update && update->getTargetObject() == self )
{
update->setTargetObject( reconstruction );
}
}
obj = obj->getNextObject();
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime RebuildHoleBehavior::update( void )
{
const RebuildHoleBehaviorModuleData *modData = getRebuildHoleBehaviorModuleData();
Object *hole = getObject();
Object *reconstructing = NULL;
Object *worker = NULL;
// get the worker object if we have one
if( m_workerID != 0 )
{
// get the worker
worker = TheGameLogic->findObjectByID( m_workerID );
// if the worker is no longer there, start the respawning process for a worker again
if( worker == NULL )
newWorkerRespawnProcess( NULL );
} // end if
// if we have a reconstructing object built, get the actual object pointer
if( m_reconstructingID != 0 )
{
// get object pointer
reconstructing = TheGameLogic->findObjectByID( m_reconstructingID );
//
// if that object does not exist anymore, we need to kill a worker if we have one
// and start the spawning process over again
//
if( reconstructing == NULL )
{
newWorkerRespawnProcess( worker );
m_reconstructingID = INVALID_ID;
} // end if
} // end if
// see if it's time for us to spawn a worker
if( worker == NULL && m_workerWaitCounter > 0 )
{
// decrement counter and respawn if it's time
if( --m_workerWaitCounter == 0 )
{
// resolve the worker template pointer if necessary
if( m_workerTemplate == NULL )
m_workerTemplate = TheThingFactory->findTemplate( modData->m_workerTemplateName );
// create a worker
worker = TheThingFactory->newObject( m_workerTemplate, hole->getTeam() );
if( worker )
{
// set the position of the worker to that of the hole
worker->setPosition( hole->getPosition() );
// save the ID of the worker spawned
m_workerID = worker->getID();
//
// tell the worker to begin construction of a building if one does not
// exist yet. If one does, have construction resume
//
AIUpdateInterface *ai = worker->getAIUpdateInterface();
if( ai )
{
if( reconstructing == NULL )
reconstructing = ai->construct( m_rebuildTemplate,
hole->getPosition(),
hole->getOrientation(),
hole->getControllingPlayer(),
TRUE );
else
ai->aiResumeConstruction( reconstructing, CMD_FROM_AI );
for ( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
{
// Just like the building transfers attackers to the hole when it creates us, we need to transfer
// attackers to our replacement building before we mask ourselves.
AIUpdateInterface* ai = obj->getAI();
if (!ai)
continue;
ai->transferAttack(hole->getID(), reconstructing->getID());
}
// save the id of what we are reconstructing
m_reconstructingID = reconstructing->getID();
//Kris: Hacking the building to set the hole as the producer... so if the site dies, we
//can transfer the attack back to the hole. The object has OBJECT_STATUS_RECONSTRUCTING
//which we check when the object dies.
reconstructing->setProducer( hole );
// we want to prevent the player from selecting and doing things with this worker
worker->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) );
//
// we want to prevent the player and the AI from selecting or targeting the hole
// cause the focus in this area (while reconstruction is happening) is the
// actual reconstructing building
//
hole->maskObject( TRUE );
transferBombs( reconstructing );
} // end if
} // end if, worker
} // end if, time to spawn a worker
} // end if, check for working respawn
// holes get auto-healed when they're sittin around
BodyModuleInterface *body = hole->getBodyModule();
if( body->getHealth() < body->getMaxHealth() )
{
DamageInfo healingInfo;
// do some healing
healingInfo.in.m_amount = (modData->m_holeHealthRegenPercentPerSecond / LOGICFRAMES_PER_SECOND) *
body->getMaxHealth();
healingInfo.in.m_sourceID = hole->getID();
healingInfo.in.m_damageType = DAMAGE_HEALING;
healingInfo.in.m_deathType = DEATH_NONE;
body->attemptHealing( &healingInfo );
} // end if
// when re-construction is complete, we remove this hole and worker
if( reconstructing && !reconstructing->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
{
// Transfer hole name to new building
TheScriptEngine->transferObjectName( hole->getName(), reconstructing );
// make the worker go away
if( worker )
TheGameLogic->destroyObject( worker );
// make the hole go away
TheGameLogic->destroyObject( hole );
} // end if
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::onDie( const DamageInfo *damageInfo )
{
if( m_workerID != INVALID_ID )
{
// Our rebuilding building and us the hole can be killed in the same frame, which means we may not have
// deleted our generated worker since we do that in our update.
Object *worker = TheGameLogic->findObjectByID(m_workerID);
if( worker )
{
TheGameLogic->destroyObject(worker);
m_workerID = INVALID_ID;
}
}
Object *obj = getObject();
// destroy us
TheGameLogic->destroyObject( obj );
} // end onDie
// ------------------------------------------------------------------------------------------------
/** Helper method to get interface given an object */
// ------------------------------------------------------------------------------------------------
/*static*/ RebuildHoleBehaviorInterface* RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( Object *obj )
{
RebuildHoleBehaviorInterface *rhbi = NULL;
if( obj )
{
for( BehaviorModule **i = obj->getBehaviorModules(); *i; ++i )
{
rhbi = (*i)->getRebuildHoleBehaviorInterface();
if( rhbi )
break; // exit for
} // end for i
} // end if, obj
return rhbi;
} // end getRebuildHoleBehaviorInterfaceFromObject
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version,
* 2: Added spawner id */
// ------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// worker ID
xfer->xferObjectID( &m_workerID );
// reconstructing id
xfer->xferObjectID( &m_reconstructingID );
// spawner ID
if( version >= 2 )
xfer->xferObjectID( &m_spawnerObjectID );
// worker wait counter
xfer->xferUnsignedInt( &m_workerWaitCounter );
// worker template
AsciiString workerName = m_workerTemplate ? m_workerTemplate->getName() : AsciiString::TheEmptyString;
xfer->xferAsciiString( &workerName );
if( xfer->getXferMode() == XFER_LOAD )
{
if( workerName != AsciiString::TheEmptyString )
{
m_workerTemplate = TheThingFactory->findTemplate( workerName );
if( m_workerTemplate == NULL )
{
DEBUG_CRASH(( "RebuildHoleBehavior::xfer - Unable to find template '%s'\n",
workerName.str() ));
throw SC_INVALID_DATA;
} // end if
} // end if
else
m_workerTemplate = NULL;
} // end if
// rebuild template
AsciiString rebuildName = m_rebuildTemplate ? m_rebuildTemplate->getName() : AsciiString::TheEmptyString;
xfer->xferAsciiString( &rebuildName );
if( xfer->getXferMode() == XFER_LOAD )
{
if( rebuildName != AsciiString::TheEmptyString )
{
m_rebuildTemplate = TheThingFactory->findTemplate( rebuildName );
if( m_rebuildTemplate == NULL )
{
DEBUG_CRASH(( "RebuildHoleBehavior::xfer - Unable to find template '%s'\n",
rebuildName.str() ));
throw SC_INVALID_DATA;
} // end if
} // end if
else
m_rebuildTemplate = NULL;
} // end if
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,584 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: SlowDeathBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_SLOWDEATHPHASE_NAMES
#include "Common/GameLOD.h"
#include "Common/INI.h"
#include "Common/RandomValue.h"
#include "Common/Thing.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Module/SlowDeathBehavior.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/SlavedUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
const Real BEGIN_MIDPOINT_RATIO = 0.35f;
const Real END_MIDPOINT_RATIO = 0.65f;
//-------------------------------------------------------------------------------------------------
SlowDeathBehaviorModuleData::SlowDeathBehaviorModuleData()
{
m_sinkRate = 0;
m_probabilityModifier = 10;
m_modifierBonusPerOverkillPercent = 0;
m_sinkDelay = 0;
m_sinkDelayVariance = 0;
m_destructionDelay = 0;
m_destructionDelayVariance = 0;
m_destructionAltitude = -10;
m_maskOfLoadedEffects = 0; //assume no ocl, fx, or weapons.
m_flingForce = 0;
m_flingForceVariance = 0;
m_flingPitch = 0;
m_flingPitchVariance = 0;
// redundant.
//m_fx.clear();
//m_ocls.clear();
//m_weapons.clear();
}
//-------------------------------------------------------------------------------------------------
static void parseFX( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
SlowDeathBehaviorModuleData* self = (SlowDeathBehaviorModuleData*)instance;
SlowDeathPhaseType sdphase = (SlowDeathPhaseType)INI::scanIndexList(ini->getNextToken(), TheSlowDeathPhaseNames);
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const FXList *fxl = TheFXListStore->findFXList((token)); // could be null! this is OK!
self->m_fx[sdphase].push_back(fxl);
if (fxl)
self->m_maskOfLoadedEffects |= SlowDeathBehaviorModuleData::HAS_FX;
}
}
//-------------------------------------------------------------------------------------------------
static void parseOCL( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
SlowDeathBehaviorModuleData* self = (SlowDeathBehaviorModuleData*)instance;
SlowDeathPhaseType sdphase = (SlowDeathPhaseType)INI::scanIndexList(ini->getNextToken(), TheSlowDeathPhaseNames);
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const ObjectCreationList *ocl = TheObjectCreationListStore->findObjectCreationList(token); // could be null! this is OK!
self->m_ocls[sdphase].push_back(ocl);
if (ocl)
self->m_maskOfLoadedEffects |= SlowDeathBehaviorModuleData::HAS_OCL;
}
}
//-------------------------------------------------------------------------------------------------
static void parseWeapon( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
SlowDeathBehaviorModuleData* self = (SlowDeathBehaviorModuleData*)instance;
SlowDeathPhaseType sdphase = (SlowDeathPhaseType)INI::scanIndexList(ini->getNextToken(), TheSlowDeathPhaseNames);
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const WeaponTemplate *wt = TheWeaponStore->findWeaponTemplate(token); // could be null! this is OK!
self->m_weapons[sdphase].push_back(wt);
if (wt)
self->m_maskOfLoadedEffects |= SlowDeathBehaviorModuleData::HAS_WEAPON;
}
}
//-------------------------------------------------------------------------------------------------
/*static*/ void SlowDeathBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
UpdateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "SinkRate", INI::parseVelocityReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_sinkRate ) },
{ "ProbabilityModifier", INI::parseInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_probabilityModifier ) },
{ "ModifierBonusPerOverkillPercent", INI::parsePercentToReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_modifierBonusPerOverkillPercent ) },
{ "SinkDelay", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_sinkDelay ) },
{ "SinkDelayVariance", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_sinkDelayVariance ) },
{ "DestructionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_destructionDelay ) },
{ "DestructionDelayVariance", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_destructionDelayVariance ) },
{ "DestructionAltitude", INI::parseReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_destructionAltitude ) },
{ "FX", parseFX, NULL, 0 },
{ "OCL", parseOCL, NULL, 0 },
{ "Weapon", parseWeapon, NULL, 0 },
{ "FlingForce", INI::parseReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingForce) },
{ "FlingForceVariance", INI::parseReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingForceVariance) },
{ "FlingPitch", INI::parseAngleReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingPitch) },
{ "FlingPitchVariance", INI::parseAngleReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingPitchVariance) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
p.add(DieMuxData::getFieldParse(), offsetof( SlowDeathBehaviorModuleData, m_dieMuxData ));
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SlowDeathBehavior::SlowDeathBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
m_flags = 0;
m_sinkFrame = 0;
m_midpointFrame = 0;
m_destructionFrame = 0;
m_acceleratedTimeScale = 1.0f;
if (getSlowDeathBehaviorModuleData()->m_probabilityModifier < 1)
{
DEBUG_CRASH(("ProbabilityModifer must be >= 1.\n"));
throw INI_INVALID_DATA;
}
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SlowDeathBehavior::~SlowDeathBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Int SlowDeathBehavior::getProbabilityModifier( const DamageInfo *damageInfo ) const
{
// Calculating how far past dead we were allows us to pick more spectacular deaths when
// severly killed, and more sedate ones when only slightly killed.
// eg ( 200 hp max, had 10 left, took 50 damage, 40 overkill, (40/200) * 100 = 20 overkill %)
Int overkillDamage = damageInfo->out.m_actualDamageDealt - damageInfo->out.m_actualDamageClipped;
Real overkillPercent = (float)overkillDamage / (float)getObject()->getBodyModule()->getMaxHealth();
Int overkillModifier = overkillPercent * getSlowDeathBehaviorModuleData()->m_modifierBonusPerOverkillPercent;
return max( getSlowDeathBehaviorModuleData()->m_probabilityModifier + overkillModifier, 1 );
}
//-------------------------------------------------------------------------------------------------
static void calcRandomForce(Real minMag, Real maxMag, Real minPitch, Real maxPitch, Coord3D& force)
{
Real angle = GameLogicRandomValueReal(-PI, PI);
Real pitch = GameLogicRandomValueReal(minPitch, maxPitch);
Real mag = GameLogicRandomValueReal(minMag, maxMag);
Matrix3D mtx(1);
mtx.Scale(mag);
mtx.Rotate_Z(angle);
mtx.Rotate_Y(-pitch);
Vector3 v = mtx.Get_X_Vector();
force.x = v.X;
force.y = v.Y;
force.z = v.Z;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SlowDeathBehavior::beginSlowDeath(const DamageInfo *damageInfo)
{
if (!isSlowDeathActivated())
{
const SlowDeathBehaviorModuleData* d = getSlowDeathBehaviorModuleData();
Object* obj = getObject();
if (d->m_sinkRate && obj->isKindOf(KINDOF_INFANTRY))
{
Drawable *draw = getObject()->getDrawable();
if ( draw )
{
// this object sinks slowly after it dies so don't draw a
// floating shadow decal on the ground above it.
obj->getDrawable()->setShadowsEnabled(false);
draw->setTerrainDecalFadeTarget( 0.0f, -0.2f );
}
}
// Ask game detail manager if we need to speedup all deaths to improve performance
Real timeScale = TheGameLODManager->getSlowDeathScale();
m_acceleratedTimeScale = 1.0f; // assume normal death speed.
if (timeScale == 0.0f && !d->hasNonLodEffects())
{
// Deaths happen instantly so just delete the object and return
TheGameLogic->destroyObject(obj);
return;
}
else
{
// timescale is some non-zero value so we may need to speed up death
if( getObject()->isKindOf( KINDOF_HULK ) && TheGameLogic->getHulkMaxLifetimeOverride() != -1 )
{
//Scripts don't want hulks around, so start sinking immediately!
m_sinkFrame = 1;
m_midpointFrame = (LOGICFRAMES_PER_SECOND/2) + 1;
m_destructionFrame = LOGICFRAMES_PER_SECOND + 1;
m_acceleratedTimeScale = 1.0f;
}
else
{
m_sinkFrame = timeScale * (d->m_sinkDelay + GameLogicRandomValue(0, d->m_sinkDelayVariance));
m_destructionFrame = timeScale * (d->m_destructionDelay + GameLogicRandomValue(0, d->m_destructionDelayVariance));
m_midpointFrame = GameLogicRandomValue( BEGIN_MIDPOINT_RATIO * m_destructionFrame, END_MIDPOINT_RATIO * m_destructionFrame );
m_acceleratedTimeScale = timeScale;
}
}
UnsignedInt now = TheGameLogic->getFrame();
if (d->m_flingForce > 0)
{
//Just in case this is a stingersoldier or other HELD object, lets set them free so they will fly
// with their own physics during slow death
if( obj->isDisabledByType( DISABLED_HELD ) )
{
static NameKeyType key_SlavedUpdate = NAMEKEY( "SlavedUpdate" );
SlavedUpdate* slave = (SlavedUpdate*)obj->findUpdateModule( key_SlavedUpdate );
if( slave )
{
slave->onSlaverDie( NULL );
}
}
PhysicsBehavior* physics = obj->getPhysics();
if (physics)
{
// make sure we are at least a bit above the ground
const Real MIN_ALTITUDE = 1.0f;
Real altitude = obj->getHeightAboveTerrain();
if (altitude < MIN_ALTITUDE)
{
Coord3D pos = *obj->getPosition();
pos.z += MIN_ALTITUDE;
obj->setPosition(&pos);
}
Coord3D force;
calcRandomForce(d->m_flingForce, d->m_flingForce + d->m_flingForceVariance,
d->m_flingPitch, d->m_flingPitch + d->m_flingPitchVariance, force);
physics->setAllowToFall(true);
physics->applyForce(&force);
physics->setExtraBounciness(-1.0); // we don't want this guy to bounce at all
physics->setExtraFriction(-3 * SECONDS_PER_LOGICFRAME_REAL); // reduce his ground friction a bit
physics->setAllowBouncing(true);
Real orientation = atan2(force.y, force.x);
physics->setAngles(orientation, 0, 0);
obj->getDrawable()->setModelConditionState(MODELCONDITION_EXPLODED_FLAILING);
m_flags |= (1<<FLUNG_INTO_AIR);
}
setWakeFrame(obj, UPDATE_SLEEP_NONE);
}
else
{
// we don't need to wake up immediately, but only when the first of these
// counters wants to trigger....
Int whenToWakeTime = m_sinkFrame;
if (whenToWakeTime > m_destructionFrame)
whenToWakeTime = m_destructionFrame;
if (whenToWakeTime > m_midpointFrame)
whenToWakeTime = m_midpointFrame;
setWakeFrame(obj, UPDATE_SLEEP(whenToWakeTime));
}
m_sinkFrame += now;
m_destructionFrame += now;
m_midpointFrame += now;
m_flags |= (1<<SLOW_DEATH_ACTIVATED);
doPhaseStuff(SDPHASE_INITIAL);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SlowDeathBehavior::doPhaseStuff(SlowDeathPhaseType sdphase)
{
const SlowDeathBehaviorModuleData* d = getSlowDeathBehaviorModuleData();
Int idx, listSize;
if (!d->m_maskOfLoadedEffects)
return; //has no ocl, fx, or weapons.
listSize = d->m_fx[sdphase].size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const FXListVec& v = d->m_fx[sdphase];
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const FXList* fxl = v[idx];
FXList::doFXObj(fxl, getObject(), NULL);
}
listSize = d->m_ocls[sdphase].size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const OCLVec& v = d->m_ocls[sdphase];
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const ObjectCreationList* ocl = v[idx];
ObjectCreationList::create(ocl, getObject(), NULL);
}
listSize = d->m_weapons[sdphase].size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const WeaponTemplateVec& v = d->m_weapons[sdphase];
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const WeaponTemplate* wt = v[idx];
if (wt)
{
TheWeaponStore->createAndFireTempWeapon(wt, getObject(), getObject()->getPosition());
}
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime SlowDeathBehavior::update()
{
//DEBUG_LOG(("updating SlowDeathBehavior %08lx\n",this));
DEBUG_ASSERTCRASH(isSlowDeathActivated(), ("hmm, this should not be possible"));
const SlowDeathBehaviorModuleData* d = getSlowDeathBehaviorModuleData();
Object* obj = getObject();
Real timeScale = TheGameLODManager->getSlowDeathScale();
// Check if we have normal time scale but LODManager is requeseting acceleration
if (timeScale != 1.0f && m_acceleratedTimeScale == 1.0f && !d->hasNonLodEffects())
{
// speed of deaths has been increased since beginning of death
// so adjust it to current levels.
if (timeScale == 0)
{
// instant death
TheGameLogic->destroyObject(obj);
return UPDATE_SLEEP_NONE;
}
m_sinkFrame = (Real)m_sinkFrame * timeScale;
m_midpointFrame = (Real)m_midpointFrame * timeScale;
m_destructionFrame = (Real)m_destructionFrame * timeScale;
m_acceleratedTimeScale = timeScale;
};
UnsignedInt now = TheGameLogic->getFrame();
if ((m_flags & (1<<FLUNG_INTO_AIR)) != 0)
{
if ((m_flags & (1<<BOUNCED)) == 0)
{
++m_sinkFrame;
++m_midpointFrame;
++m_destructionFrame;
if (!obj->isAboveTerrain())
{
obj->clearAndSetModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_FLAILING),
MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_BOUNCING));
m_flags |= (1<<BOUNCED);
}
// Here we want to make sure we die if we collide with a tree on the way down
PhysicsBehavior *phys = obj->getPhysics();
if ( phys )
{
ObjectID treeID = phys->getLastCollidee();
Object *tree = TheGameLogic->findObjectByID( treeID );
if ( tree )
{
if (tree->isKindOf( KINDOF_SHRUBBERY ) )
{
obj->setDisabled( DISABLED_HELD );
obj->clearModelConditionFlags( MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_FLAILING) );
obj->clearModelConditionFlags( MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_BOUNCING) );
obj->setModelConditionFlags( MAKE_MODELCONDITION_MASK(MODELCONDITION_PARACHUTING) ); //looks like he is snagged in a tree
obj->setPositionZ( obj->getPosition()->z - (d->m_sinkRate * 50.0f) );// make him sink faster
if ( !obj->isAboveTerrain() )
TheGameLogic->destroyObject(obj);
}
}
}
}
}
if ( (now >= m_sinkFrame && d->m_sinkRate > 0.0f) )
{
// disable Physics (if any) so that we can control the sink...
obj->setDisabled( DISABLED_HELD );
Coord3D pos = *obj->getPosition();
pos.z -= d->m_sinkRate / m_acceleratedTimeScale;
obj->setPosition( &pos );
}
if( now >= m_midpointFrame && (m_flags & (1<<MIDPOINT_EXECUTED)) == 0 )
{
doPhaseStuff(SDPHASE_MIDPOINT);
m_flags |= (1<<MIDPOINT_EXECUTED);
}
if (now >= m_destructionFrame)
{
doPhaseStuff(SDPHASE_FINAL);
TheGameLogic->destroyObject(obj);
}
return UPDATE_SLEEP_NONE;
}
//-------------------------------------------------------------------------------------------------
void SlowDeathBehavior::onDie( const DamageInfo *damageInfo )
{
Object *obj = getObject();
if (!isDieApplicable(damageInfo))
return;
AIUpdateInterface *ai = obj->getAIUpdateInterface();
if (ai)
{
// has another AI already handled us. (hopefully another SlowDeathBehavior)
if (ai->isAiInDeadState())
return;
ai->markAsDead();
}
// deselect this unit for all players.
TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE);
Int total = 0;
for (BehaviorModule** update = obj->getBehaviorModules(); *update; ++update)
{
SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface();
if (sdu != NULL && sdu->isDieApplicable(damageInfo))
{
total += sdu->getProbabilityModifier( damageInfo );
}
}
DEBUG_ASSERTCRASH(total > 0, ("Hmm, this is wrong"));
// this returns a value from 1...total, inclusive
Int roll = GameLogicRandomValue(1, total);
for (/* UpdateModuleInterface** */ update = obj->getBehaviorModules(); *update; ++update)
{
SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface();
if (sdu != NULL && sdu->isDieApplicable(damageInfo))
{
roll -= sdu->getProbabilityModifier( damageInfo );
if (roll <= 0)
{
sdu->beginSlowDeath(damageInfo);
return;
}
}
}
DEBUG_CRASH(("We should never get here"));
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SlowDeathBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SlowDeathBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// sink frame
xfer->xferUnsignedInt( &m_sinkFrame );
// midpoint frame
xfer->xferUnsignedInt( &m_midpointFrame );
// destruction frame
xfer->xferUnsignedInt( &m_destructionFrame );
// accelerated time scale
xfer->xferReal( &m_acceleratedTimeScale );
// flags
xfer->xferUnsignedInt( &m_flags );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SlowDeathBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,184 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: SupplyWarehouseCripplingBehavior.cpp /////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, Septemmber 2002
// Desc: Behavior that Disables the building on ReallyDamaged edge state, and manages an Update timer to heal
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Module/SupplyWarehouseCripplingBehavior.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BodyModule.h"
//-------------------------------------------------------------------------------------------------
SupplyWarehouseCripplingBehaviorModuleData::SupplyWarehouseCripplingBehaviorModuleData()
{
m_selfHealSupression = 0; ///< Time since last damage until I can start to heal
m_selfHealDelay = 0; ///< Once I am okay to heal, how often to do so
m_selfHealAmount = 0; ///< And how much
}
//-------------------------------------------------------------------------------------------------
/*static*/ void SupplyWarehouseCripplingBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
static const FieldParse dataFieldParse[] =
{
{ "SelfHealSupression", INI::parseDurationUnsignedInt, NULL, offsetof(SupplyWarehouseCripplingBehaviorModuleData, m_selfHealSupression) },
{ "SelfHealDelay", INI::parseDurationUnsignedInt, NULL, offsetof(SupplyWarehouseCripplingBehaviorModuleData, m_selfHealDelay) },
{ "SelfHealAmount", INI::parseReal, NULL, offsetof(SupplyWarehouseCripplingBehaviorModuleData, m_selfHealAmount) },
{ 0, 0, 0, 0 }
};
UpdateModuleData::buildFieldParse(p);
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyWarehouseCripplingBehavior::SupplyWarehouseCripplingBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
m_healingSupressedUntilFrame = 0;
m_nextHealingFrame = 0;
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyWarehouseCripplingBehavior::~SupplyWarehouseCripplingBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
/** Damage has been dealt, this is an opportunity to react to that damage */
//-------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::onDamage( DamageInfo *damageInfo )
{
UnsignedInt now = TheGameLogic->getFrame();
resetSelfHealSupression();
setWakeFrame(getObject(), UPDATE_SLEEP(m_healingSupressedUntilFrame - now));// we got hit, time to get up for work after a quick snooze
}
//-------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::onBodyDamageStateChange(const DamageInfo* damageInfo, BodyDamageType oldState, BodyDamageType newState)
{
if( newState == BODY_REALLYDAMAGED )
startCrippledEffects();
else if( oldState == BODY_REALLYDAMAGED )
stopCrippledEffects();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime SupplyWarehouseCripplingBehavior::update()
{
// Supression is handled by sleeping the module, so if I am here, I know it is time to heal.
const SupplyWarehouseCripplingBehaviorModuleData* md = getSupplyWarehouseCripplingBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
m_nextHealingFrame = now + md->m_selfHealDelay;
getObject()->attemptHealing(md->m_selfHealAmount, NULL);
if( getObject()->getBodyModule()->getHealth() == getObject()->getBodyModule()->getMaxHealth() )
return UPDATE_SLEEP_FOREVER;// this can't be in onHealing, as the healing comes from here
// in the update, and sleep settings in onHealing would be overridden by my return value.
// Delay between heals is also handled by sleeping the module. How cool is that?
return UPDATE_SLEEP(m_nextHealingFrame - now);
}
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::resetSelfHealSupression()
{
const SupplyWarehouseCripplingBehaviorModuleData* md = getSupplyWarehouseCripplingBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
m_healingSupressedUntilFrame = now + md->m_selfHealSupression;
m_nextHealingFrame = m_healingSupressedUntilFrame;
}
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::startCrippledEffects()
{
DockUpdateInterface *myDock = getObject()->getDockUpdateInterface();
myDock->setDockCrippled( TRUE );
}
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::stopCrippledEffects()
{
DockUpdateInterface *myDock = getObject()->getDockUpdateInterface();
myDock->setDockCrippled( FALSE );
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// healing supressed until frame
xfer->xferUnsignedInt( &m_healingSupressedUntilFrame );
// next healing frame
xfer->xferUnsignedInt( &m_nextHealingFrame );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,181 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: TechBuildingBehavior.cpp /////////////////////////////////////////////////////////////////
// Author: Colin Day, October 2002
// Desc: Tech building basic behavior
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.H"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/Module/TechBuildingBehavior.h"
#include "GameLogic/Object.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
TechBuildingBehaviorModuleData::TechBuildingBehaviorModuleData( void )
{
m_pulseFX = NULL;
m_pulseFXRate = 0;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void TechBuildingBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "PulseFX", INI::parseFXList, NULL, offsetof( TechBuildingBehaviorModuleData, m_pulseFX ) },
{ "PulseFXRate", INI::parseDurationUnsignedInt, NULL, offsetof( TechBuildingBehaviorModuleData, m_pulseFXRate ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
TechBuildingBehavior::TechBuildingBehavior( Thing *thing, const ModuleData *modData )
: UpdateModule( thing, modData )
{
//
// setup ourselves so we do at least one update evaluation after the module
// is in the world
//
setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
TechBuildingBehavior::~TechBuildingBehavior( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime TechBuildingBehavior::update( void )
{
Object *us = getObject();
const TechBuildingBehaviorModuleData* d = getTechBuildingBehaviorModuleData();
Bool captured = false;
// update our model condition for the captured status
Player *player = us->getControllingPlayer();
if( player && player->isPlayableSide() )
{
us->setModelConditionState( MODELCONDITION_CAPTURED );
captured = true;
}
else
{
us->clearModelConditionState( MODELCONDITION_CAPTURED );
captured = false;
}
// if we have a pulse fx, and are owned, sleep only a little while, otherwise sleep forever
if (d->m_pulseFX != NULL && d->m_pulseFXRate > 0 && captured)
{
FXList::doFXObj( d->m_pulseFX, us );
return UPDATE_SLEEP(d->m_pulseFXRate);
}
else
{
// now sleep forever my dear
return UPDATE_SLEEP_FOREVER;
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TechBuildingBehavior::onDie( const DamageInfo *damageInfo )
{
//
// put us on the team of the neutral player so no player has any bonus from us
//
Object *us = getObject();
us->clearModelConditionState( MODELCONDITION_CAPTURED );
us->setTeam( ThePlayerList->getNeutralPlayer()->getDefaultTeam() );
} // end onDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TechBuildingBehavior::onCapture( Player *oldOwner, Player *newOwner )
{
// wake up next frame so we can re-evaluate our captured status
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
} // end onCapture
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TechBuildingBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TechBuildingBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TechBuildingBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: BodyModule.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Colin Day, September 2002
// Desc: BodyModule base class
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/BodyModule.h"
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BodyModule::crc( Xfer *xfer )
{
// call base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BodyModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// call base class
BehaviorModule::xfer( xfer );
// damage scalar
xfer->xferReal( &m_damageScalar );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BodyModule::loadPostProcess( void )
{
// call base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,101 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: HighlanderBody.cpp ////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, November 2002
// Desc: Takes damage according to armor, but can't die from normal damage. Can die from Unresistable though
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Module/HighlanderBody.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
HighlanderBody::HighlanderBody( Thing *thing, const ModuleData* moduleData )
: ActiveBody( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
HighlanderBody::~HighlanderBody( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void HighlanderBody::attemptDamage( DamageInfo *damageInfo )
{
// Bind to one hitpoint remaining afterwards, unless it is Unresistable damage
if( damageInfo->in.m_damageType != DAMAGE_UNRESISTABLE )
damageInfo->in.m_amount = min( damageInfo->in.m_amount, getHealth() - 1 );
ActiveBody::attemptDamage(damageInfo);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void HighlanderBody::crc( Xfer *xfer )
{
// extend base class
ActiveBody::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void HighlanderBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
ActiveBody::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void HighlanderBody::loadPostProcess( void )
{
// extend base class
ActiveBody::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,171 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: HiveStructureBody.cpp ////////////////////////////////////////////////////////////////////////
// Desc: Hive structure bodies are structure bodies with the ability to propagate specified
// damage types to slaves when available. If there are no slaves, the the structure
// will take the damage.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "Common/ThingTemplate.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Damage.h"
#include "GameLogic/Module/HiveStructureBody.h"
#include "GameLogic/Module/SpawnBehavior.h"
#include "GameLogic/Module/ContainModule.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
HiveStructureBodyModuleData::HiveStructureBodyModuleData()
{
m_damageTypesToPropagateToSlaves = DAMAGE_TYPE_FLAGS_NONE;
m_damageTypesToSwallow = DAMAGE_TYPE_FLAGS_NONE;
}
//-------------------------------------------------------------------------------------------------
HiveStructureBody::HiveStructureBody( Thing *thing, const ModuleData* moduleData )
: StructureBody( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
HiveStructureBody::~HiveStructureBody( void )
{
}
//------------------------------------------------------------------------------------------------
void HiveStructureBody::attemptDamage( DamageInfo *damageInfo )
{
const HiveStructureBodyModuleData *data = getHiveStructureBodyModuleData();
Object *hive = getObject();
if( getDamageTypeFlag( data->m_damageTypesToPropagateToSlaves, damageInfo->in.m_damageType ) )
{
//We have the right type of damage types incoming to propagate to slaves. Do we have slaves?
SpawnBehaviorInterface *spawnInterface = hive->getSpawnBehaviorInterface();
if( spawnInterface )
{
//We found the spawn interface, now get some slaves!
Object *shooter = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
if( shooter )
{
Object *slave = spawnInterface->getClosestSlave( shooter->getPosition() );
if( slave )
{
//Propagate damage and return!
slave->attemptDamage( damageInfo );
return;
}
else if( getDamageTypeFlag( data->m_damageTypesToSwallow, damageInfo->in.m_damageType ) )
{
//no slave to give to, so eat it
damageInfo->out.m_actualDamageDealt = 0.0f;
damageInfo->out.m_actualDamageClipped = 0.0f;
damageInfo->out.m_noEffect = true;
return;
}
}
}
else if ( hive->getContain() )
{
ContainModuleInterface *contain = hive->getContain();
//We found the spawn interface, now get some slaves!
Object *shooter = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
if( shooter )
{
Object *rider = contain->getClosestRider( shooter->getPosition() );
if( rider )
{
//Propagate damage and return!
rider->attemptDamage( damageInfo );
return;
}
else if( getDamageTypeFlag( data->m_damageTypesToSwallow, damageInfo->in.m_damageType ) )
{
//no slave to give to, so eat it
damageInfo->out.m_actualDamageDealt = 0.0f;
damageInfo->out.m_actualDamageClipped = 0.0f;
damageInfo->out.m_noEffect = true;
return;
}
}
}
else
{
DEBUG_CRASH( ("%s has a HiveStructureBody module, which requires a SpawnBehavior module. Thus it is unable to propagate damage to slaves.", hive->getTemplate()->getName().str() ) );
}
}
//Nothing to propagate (either different damage type or no slaves),
//so damage me instead!
StructureBody::attemptDamage( damageInfo );
}
//------------------------------------------------------------------------------------------------
void HiveStructureBody::crc( Xfer *xfer )
{
// extend parent class
StructureBody::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void HiveStructureBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// parent class
StructureBody::xfer( xfer );
}
//------------------------------------------------------------------------------------------------
void HiveStructureBody::loadPostProcess( void )
{
// extend parent class
StructureBody::loadPostProcess();
}

View File

@@ -0,0 +1,106 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: ImmortalBody.cpp ////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, April 2002
// Desc: Just like Active Body, but won't let health drop below 1
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Damage.h"
#include "GameLogic/Module/ImmortalBody.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ImmortalBody::ImmortalBody( Thing *thing, const ModuleData* moduleData )
: ActiveBody( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ImmortalBody::~ImmortalBody( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void ImmortalBody::internalChangeHealth( Real delta )
{
// Don't let anything changes us to below one hit point
delta = max( delta, -getHealth() + 1 );
// extend functionality, but I go first because I can't let you die and then fix it, I must prevent
ActiveBody::internalChangeHealth( delta );
// nothing -- never mark it as dead.
DEBUG_ASSERTCRASH( (getHealth() > 0 && !getObject()->isEffectivelyDead() ), ("Immortal objects should never get marked as dead!"));
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ImmortalBody::crc( Xfer *xfer )
{
// extend base class
ActiveBody::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ImmortalBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
ActiveBody::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ImmortalBody::loadPostProcess( void )
{
// extend base class
ActiveBody::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,205 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: InactiveBody.cpp /////////////////////////////////////////////////////////////////////////
// Author: Colin Day, November 2001
// Desc: An Inactive body module doesn't have any of the data storage for
// health and damage etc ... it's an "Inactive" object that isn't
// affected by matters of the body ... it's all in the mind!!!!
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/InactiveBody.h"
#include "GameLogic/Module/DieModule.h"
#include "GameLogic/Damage.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InactiveBody::InactiveBody( Thing *thing, const ModuleData* moduleData )
: BodyModule( thing, moduleData ), m_dieCalled(false)
{
getObject()->setEffectivelyDead(true);
} // end InactiveBody
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InactiveBody::~InactiveBody( void )
{
} // end ~InactiveBody
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Real InactiveBody::estimateDamage( DamageInfoInput& damageInfo ) const
{
// Inactive bodies have no health so no damage can really be done
Real amount = 0.0f;
// exception!
if (damageInfo.m_damageType == DAMAGE_UNRESISTABLE)
{
amount = damageInfo.m_amount;
}
return amount;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void InactiveBody::attemptDamage( DamageInfo *damageInfo )
{
if( damageInfo == NULL )
return;
if( damageInfo->in.m_damageType == DAMAGE_HEALING )
{
// Healing and Damage are separate, so this shouldn't happen
attemptHealing( damageInfo );
return;
}
// Inactive bodies have no health so no damage can really be done
damageInfo->out.m_actualDamageDealt = 0.0f;
damageInfo->out.m_actualDamageClipped = 0.0f;
damageInfo->out.m_noEffect = true;
// exception: damage type KILL always wipes us out
if (damageInfo->in.m_damageType == DAMAGE_UNRESISTABLE)
{
DEBUG_ASSERTCRASH(!getObject()->getTemplate()->isPrerequisite(), ("Prerequisites should not have InactiveBody"));
damageInfo->out.m_noEffect = false;
// since we have no Health, we do not call DamageModules, nor do DamageFX.
// however, we DO process DieModules.
if (!m_dieCalled)
{
getObject()->onDie( damageInfo );
m_dieCalled = true;
}
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void InactiveBody::attemptHealing( DamageInfo *damageInfo )
{
if( damageInfo == NULL )
return;
if( damageInfo->in.m_damageType != DAMAGE_HEALING )
{
// Healing and Damage are separate, so this shouldn't happen
attemptDamage( damageInfo );
return;
}
// Inactive bodies have no health so no damage can really be done
damageInfo->out.m_actualDamageDealt = 0.0f;
damageInfo->out.m_actualDamageClipped = 0.0f;
damageInfo->out.m_noEffect = true;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void InactiveBody::internalChangeHealth( Real delta )
{
// Inactive bodies have no health to increase or decrease
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Real InactiveBody::getHealth() const
{
// Inactive bodies have no health to get
return 0.0f;
} // end getHealth
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
BodyDamageType InactiveBody::getDamageState() const
{
return BODY_PRISTINE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void InactiveBody::setDamageState( BodyDamageType ) ///< control damage state directly. Will adjust hitpoints.
{
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void InactiveBody::crc( Xfer *xfer )
{
// extend base class
BodyModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void InactiveBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// base class
BodyModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void InactiveBody::loadPostProcess( void )
{
// extend base class
BodyModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,108 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: StructureBody.cpp ////////////////////////////////////////////////////////////////////////
// Author: Colin Day, November 2001
// Desc: Structure bodies are active bodies specifically for structures that are built
// and/or interactable (is that a world) with the player.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Damage.h"
#include "GameLogic/Module/StructureBody.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
StructureBody::StructureBody( Thing *thing, const ModuleData* moduleData )
: ActiveBody( thing, moduleData )
{
m_constructorObjectID = INVALID_ID;
} // end StructureBody
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
StructureBody::~StructureBody( void )
{
} // end ~StructureBody
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void StructureBody::setConstructorObject( Object *obj )
{
if( obj )
m_constructorObjectID = obj->getID();
} // end setConstructorObject
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void StructureBody::crc( Xfer *xfer )
{
// extend base class
ActiveBody::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void StructureBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// base class
ActiveBody::xfer( xfer );
// constructor object id
xfer->xferObjectID( &m_constructorObjectID );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void StructureBody::loadPostProcess( void )
{
// extend base class
ActiveBody::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,187 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: UndeadBody.cpp ////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, June 2003
// Desc: First death is intercepted and sets flags and setMaxHealth. Second death is handled normally.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Module/UndeadBody.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/SlowDeathBehavior.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
void UndeadBodyModuleData::buildFieldParse(MultiIniFieldParse& p)
{
ActiveBodyModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "SecondLifeMaxHealth", INI::parseReal, NULL, offsetof( UndeadBodyModuleData, m_secondLifeMaxHealth ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UndeadBodyModuleData::UndeadBodyModuleData()
{
m_secondLifeMaxHealth = 1;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UndeadBody::UndeadBody( Thing *thing, const ModuleData* moduleData )
: ActiveBody( thing, moduleData )
{
m_isSecondLife = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UndeadBody::~UndeadBody( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void UndeadBody::attemptDamage( DamageInfo *damageInfo )
{
// If we are on our first life, see if this damage will kill us. If it will, bind it to one hitpoint
// remaining, then go ahead and take it.
Bool shouldStartSecondLife = FALSE;
if( damageInfo->in.m_damageType != DAMAGE_UNRESISTABLE
&& !m_isSecondLife
&& damageInfo->in.m_amount >= getHealth()
&& IsHealthDamagingDamage(damageInfo->in.m_damageType)
)
{
damageInfo->in.m_amount = min( damageInfo->in.m_amount, getHealth() - 1 );
shouldStartSecondLife = TRUE;
}
ActiveBody::attemptDamage(damageInfo);
// After we take it (which allows for damaging special effects), we will do our modifications to the body module
if( shouldStartSecondLife )
startSecondLife(damageInfo);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void UndeadBody::startSecondLife(DamageInfo *damageInfo)
{
const UndeadBodyModuleData *data = getUndeadBodyModuleData();
// Flag module as no longer intercepting damage
m_isSecondLife = TRUE;
// Modify ActiveBody's max health and initial health
setMaxHealth(data->m_secondLifeMaxHealth, FULLY_HEAL);
// Set Armor set flag to use second life armor
setArmorSetFlag(ARMORSET_SECOND_LIFE);
// Fire the Slow Death module. The fact that this is not the result of an onDie will cause the special behavior
Int total = 0;
for( BehaviorModule** update = getObject()->getBehaviorModules(); *update; ++update )
{
SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface();
if (sdu != NULL && sdu->isDieApplicable(damageInfo) )
{
total += sdu->getProbabilityModifier( damageInfo );
}
}
DEBUG_ASSERTCRASH(total > 0, ("Hmm, this is wrong"));
// this returns a value from 1...total, inclusive
Int roll = GameLogicRandomValue(1, total);
for( update = getObject()->getBehaviorModules(); *update; ++update)
{
SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface();
if (sdu != NULL && sdu->isDieApplicable(damageInfo))
{
roll -= sdu->getProbabilityModifier( damageInfo );
if (roll <= 0)
{
sdu->beginSlowDeath(damageInfo);
return;
}
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void UndeadBody::crc( Xfer *xfer )
{
// extend base class
ActiveBody::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void UndeadBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
ActiveBody::xfer( xfer );
xfer->xferBool(&m_isSecondLife);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void UndeadBody::loadPostProcess( void )
{
// extend base class
ActiveBody::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,71 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: CollideModule.cpp ////////////////////////////////////////////////////////////////////////
// Author: Colin Day, September 2002
// Desc: Collide module base class implementations
///////////////////////////////////////////////////////////////////////////////////////////////////
// INLCUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/CollideModule.h"
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CollideModule::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void CollideModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// call base class
BehaviorModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CollideModule::loadPostProcess( void )
{
// call base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,200 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: ConvertToCarBombCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that gives a level of experience to all within n distance
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/Radar.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/FXList.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/ConvertToCarBombCrateCollide.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/ExperienceTracker.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ConvertToCarBombCrateCollide::ConvertToCarBombCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ConvertToCarBombCrateCollide::~ConvertToCarBombCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool ConvertToCarBombCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
return FALSE;
}
if( other->isEffectivelyDead() )
{
return FALSE;
}
if( other->isKindOf( KINDOF_AIRCRAFT ) || other->isKindOf( KINDOF_BOAT ) )
{
//Can't make carbombs out of planes and boats!
return FALSE;
}
if( other->getStatusBits().test( OBJECT_STATUS_IS_CARBOMB ) )
{
return FALSE;// oops, sorry, I'll convert the next one.
}
// Check to see if this other object has a carbomb weapon set that isn't in use.
WeaponSetFlags flags;
flags.set( WEAPONSET_CARBOMB );
const WeaponTemplateSet* set = other->getTemplate()->findWeaponTemplateSet( flags );
if( !set )
{
//This unit has no weapon set!
return FALSE;
}
if( !set->testWeaponSetFlag( WEAPONSET_CARBOMB ) )
{
//This unit has a weaponset, but the best match code above chose a different
//weaponset.
return FALSE;
}
// Also make sure that the car isn't already a carbomb!
if( other->testWeaponSetFlag( WEAPONSET_CARBOMB ) )
{
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool ConvertToCarBombCrateCollide::executeCrateBehavior( Object *other )
{
//Check to make sure that the other object is also the goal object in the AIUpdateInterface
//in order to prevent an unintentional conversion simply by having the terrorist walk too close
//to it.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
return false;
if( other && other->checkAndDetonateBoobyTrap(obj) )
{
// Whoops, it was mined. Cancel if it or us is now dead.
if( other->isEffectivelyDead() || getObject()->isEffectivelyDead() )
{
return false;
}
}
other->setWeaponSetFlag( WEAPONSET_CARBOMB );
FXList::doFXObj( getConvertToCarBombCrateCollideModuleData()->m_fxList, other );
other->defect( getObject()->getControllingPlayer()->getDefaultTeam(), 0);
//In order to make things easier for the designers, we are going to transfer the terrorist name
//to the car... so the designer can control the car with their scripts.
TheScriptEngine->transferObjectName( getObject()->getName(), other );
//This is kinda special... we will endow our new ride with our vision and shroud range, since we are driving
other->setVisionRange(getObject()->getVisionRange());
other->setShroudClearingRange(getObject()->getShroudClearingRange());
other->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_IS_CARBOMB ) );
ExperienceTracker *exp = other->getExperienceTracker();
if (exp)
{
exp->setVeterancyLevel(obj->getExperienceTracker()->getVeterancyLevel());
}
TheRadar->removeObject( other );
TheRadar->addObject( other );
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ConvertToCarBombCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ConvertToCarBombCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ConvertToCarBombCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,299 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: ConvertToHijackedVehicleCrateCollide.cpp
// Author: Mark Lorenzen, July 2002
// Desc: A crate (actually a terrorist - mobile crate) that makes the target vehicle switch
// sides, and kills its driver
// @todo Needs to set the science of that vehicle (dozer) so still can build same stuff as always
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ConvertToHijackedVehicleCrateCollide::ConvertToHijackedVehicleCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ConvertToHijackedVehicleCrateCollide::~ConvertToHijackedVehicleCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool ConvertToHijackedVehicleCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
return FALSE;
}
if( other->isEffectivelyDead() )
{
return FALSE;// can't hijack a dead vehicle
}
if( other->isKindOf( KINDOF_IMMUNE_TO_CAPTURE ) )
{
return FALSE; //Kris: Patch 1.03 -- Prevent hijackers from being able to hijack battle buses.
}
if( other->isKindOf( KINDOF_AIRCRAFT ) || other->isKindOf( KINDOF_BOAT ) )
{
//Can't hijack planes and boats!
return FALSE;
}
if( other->isKindOf( KINDOF_DRONE ) )
{
//Can't hijack drones!
return FALSE;
}
if( other->getStatusBits().test( OBJECT_STATUS_HIJACKED ) )
{
return FALSE;// oops, sorry, I'll jack the next one.
}
Relationship r = getObject()->getRelationship( other );
//Only hijack enemy objects
if( r != ENEMIES )
{
return FALSE;
}
if( other->isKindOf( KINDOF_TRANSPORT ) )
{
//Kris: Allow empty transports to be hijacked.
if( other->getContain() && other->getContain()->getContainCount() > 0 )
{
return FALSE;// dustin sez: do not jack vehicles that may carry hostile passengers
}
}
//Kris: Make sure you can't hijack any aircraft (or hijack-enter).
if( other->isKindOf( KINDOF_AIRCRAFT ) )
{
return FALSE;
}
//VeterancyLevel veterancyLevel = other->getVeterancyLevel();
//if( veterancyLevel >= LEVEL_ELITE )
//{
// return FALSE;
//}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool ConvertToHijackedVehicleCrateCollide::executeCrateBehavior( Object *other )
{
//Check to make sure that the other object is also the goal object in the AIUpdateInterface
//in order to prevent an unintentional conversion simply by having the terrorist walk too close
//to it.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
//Before the actual defection takes place, play the "vehicle stolen" EVA
//event if the local player is the victim!
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_VehicleStolen );
}
other->setTeam( obj->getControllingPlayer()->getDefaultTeam() );
other->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_HIJACKED ) );// I claim this car in the name of the GLA
AIUpdateInterface* targetAI = other->getAIUpdateInterface();
targetAI->aiMoveToPosition( other->getPosition(), CMD_FROM_AI );
targetAI->aiIdle( CMD_FROM_AI );
//Just in case this target is a dozer, lets make him stop al his dozer tasks, like building and repairing,
//So the previous owner does not benefit from these tasks
DozerAIInterface * dozerAI = targetAI->getDozerAIInterface();
if ( dozerAI )
{
for (UnsignedInt task = DOZER_TASK_FIRST; task < DOZER_NUM_TASKS; ++task)
{
dozerAI->cancelTask( (DozerTask)task );
}
}
AudioEventRTS hijackEvent( "HijackDriver", obj->getID() );
TheAudio->addAudioEvent( &hijackEvent );
//In order to make things easier for the designers, we are going to transfer the hijacker's name
//to the car... so the designer can control the car with their scripts.
TheScriptEngine->transferObjectName( obj->getName(), other );
ExperienceTracker *targetExp = other->getExperienceTracker();
ExperienceTracker *jackerExp = obj->getExperienceTracker();
if ( targetExp && jackerExp )
{
VeterancyLevel highestLevel = MAX(targetExp->getVeterancyLevel(),jackerExp->getVeterancyLevel());
jackerExp->setVeterancyLevel( highestLevel, FALSE );
targetExp->setVeterancyLevel( highestLevel, FALSE );
}
Bool targetCanEject = FALSE;
BehaviorModule **dmi = NULL;
for( dmi = other->getBehaviorModules(); *dmi; ++dmi )
{
if( (*dmi)->getEjectPilotDieInterface() )
{
targetCanEject = TRUE;
break;
}
} // end for dmi
if ( ! targetCanEject )
{
TheGameLogic->destroyObject( obj );
return TRUE;
}
// I we have made it this far, we are going to ride in this vehicle for a while
// get the name of the hijackerupdate
static NameKeyType key_HijackerUpdate = NAMEKEY( "HijackerUpdate" );
HijackerUpdate *hijackerUpdate = (HijackerUpdate*)obj->findUpdateModule( key_HijackerUpdate );
if( hijackerUpdate )
{
hijackerUpdate->setTargetObject( other );
hijackerUpdate->setIsInVehicle( TRUE );
hijackerUpdate->setUpdate( TRUE );
// flag bits so hijacker won't be selectible or collideable
//while within the vehicle
obj->setStatus( MAKE_OBJECT_STATUS_MASK3( OBJECT_STATUS_NO_COLLISIONS, OBJECT_STATUS_MASKED, OBJECT_STATUS_UNSELECTABLE ) );
}
// THIS BLOCK HIDES THE HIJACKER AND REMOVES HIM FROM PARTITION MANAGER
// remove object from its group (if any)
obj->leaveGroup();
if( ai )
{
//By setting him to idle, we will prevent him from entering the target after this gets called.
ai->aiIdle( CMD_FROM_AI );
}
//This is kinda special... we will endow our new ride with our vision and shroud range, since we are driving
other->setVisionRange(getObject()->getVisionRange());
other->setShroudClearingRange(getObject()->getShroudClearingRange());
// remove rider from partition manager
ThePartitionManager->unRegisterObject( obj );
// hide the drawable associated with rider
if( obj->getDrawable() )
obj->getDrawable()->setDrawableHidden( true );
// By returning FALSE, we will not remove the object (Hijacker)
return FALSE;
// return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ConvertToHijackedVehicleCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ConvertToHijackedVehicleCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ConvertToHijackedVehicleCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,284 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: CrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: Abstract base Class Crate Collide
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/BitFlagsIO.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "Common/GameAudio.h"
#include "Common/MiscAudio.h"
#include "GameClient/Anim2D.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/CrateCollide.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CrateCollideModuleData::CrateCollideModuleData()
{
m_isForbidOwnerPlayer = FALSE;
m_executeAnimationDisplayTimeInSeconds = 0.0f;
m_executeAnimationZRisePerSecond = 0.0f;
m_executeAnimationFades = TRUE;
m_isBuildingPickup = FALSE;
m_isHumanOnlyPickup = FALSE;
m_executeFX = NULL;
m_pickupScience = SCIENCE_INVALID;
// Added By Sadullah Nader
// Initializations missing and needed
m_executionAnimationTemplate = AsciiString::TheEmptyString;
// End Add
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void CrateCollideModuleData::buildFieldParse(MultiIniFieldParse& p)
{
ModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( CrateCollideModuleData, m_kindof ) },
{ "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( CrateCollideModuleData, m_kindofnot ) },
{ "ForbidOwnerPlayer", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isForbidOwnerPlayer ) },
{ "BuildingPickup", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isBuildingPickup ) },
{ "HumanOnly", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isHumanOnlyPickup ) },
{ "PickupScience", INI::parseScience, NULL, offsetof( CrateCollideModuleData, m_pickupScience ) },
{ "ExecuteFX", INI::parseFXList, NULL, offsetof( CrateCollideModuleData, m_executeFX ) },
{ "ExecuteAnimation", INI::parseAsciiString, NULL, offsetof( CrateCollideModuleData, m_executionAnimationTemplate ) },
{ "ExecuteAnimationTime", INI::parseReal, NULL, offsetof( CrateCollideModuleData, m_executeAnimationDisplayTimeInSeconds ) },
{ "ExecuteAnimationZRise", INI::parseReal, NULL, offsetof( CrateCollideModuleData, m_executeAnimationZRisePerSecond ) },
{ "ExecuteAnimationFades", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_executeAnimationFades ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CrateCollide::CrateCollide( Thing *thing, const ModuleData* moduleData ) : CollideModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CrateCollide::~CrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The collide event.
* Note that when other is NULL it means "collide with ground" */
//-------------------------------------------------------------------------------------------------
void CrateCollide::onCollide( Object *other, const Coord3D *, const Coord3D * )
{
const CrateCollideModuleData *modData = getCrateCollideModuleData();
// If the crate can be picked up, perform the game logic and destroy the crate.
if( isValidToExecute( other ) )
{
if( executeCrateBehavior( other ) )
{
if( modData->m_executeFX != NULL )
{
// Note: We pass in other here, because the crate is owned by the neutral player, and
// we want to do things that only the other person can see.
FXList::doFXObj( modData->m_executeFX, other );
}
TheGameLogic->destroyObject( getObject() );
}
// play animation in the world at this spot if there is one
if( TheAnim2DCollection && modData->m_executionAnimationTemplate.isEmpty() == FALSE && TheGameLogic->getDrawIconUI() )
{
Anim2DTemplate *animTemplate = TheAnim2DCollection->findTemplate( modData->m_executionAnimationTemplate );
TheInGameUI->addWorldAnimation( animTemplate,
getObject()->getPosition(),
WORLD_ANIM_FADE_ON_EXPIRE,
modData->m_executeAnimationDisplayTimeInSeconds,
modData->m_executeAnimationZRisePerSecond );
}
}
}
//-------------------------------------------------------------------------------------------------
Bool CrateCollide::isValidToExecute( const Object *other ) const
{
//The ground never picks up a crate
if( other == NULL )
return FALSE;
//Nothing Neutral can pick up any type of crate
if( other->isNeutralControlled() )
return FALSE;
const CrateCollideModuleData* md = getCrateCollideModuleData();
Bool validBuildingAttempt = md->m_isBuildingPickup && other->isKindOf( KINDOF_STRUCTURE );
// Must be a "Unit" type thing. Real Game Object, not just Object
if( other->getAIUpdateInterface() == NULL && !validBuildingAttempt )// Building exception flag for Drop Zone
return FALSE;
// must match our kindof flags (if any)
if (md && !other->isKindOfMulti(md->m_kindof, md->m_kindofnot))
return FALSE;
if( other->isEffectivelyDead() )
return FALSE;
// crates cannot be claimed while in the air, except by buildings
if( getObject()->isAboveTerrain() && !validBuildingAttempt )
return FALSE;
if( md->m_isForbidOwnerPlayer && (getObject()->getControllingPlayer() == other->getControllingPlayer()) )
return FALSE; // Design has decreed this to not be picked up by the dead guy's team.
if( md->m_isHumanOnlyPickup && other->getControllingPlayer() && (other->getControllingPlayer()->getPlayerType() != PLAYER_HUMAN) )
return FALSE; // Human only mission crate
if( (md->m_pickupScience != SCIENCE_INVALID) && other->getControllingPlayer() && !other->getControllingPlayer()->hasScience(md->m_pickupScience) )
return FALSE; // Science required to pick this up
if( other->isKindOf( KINDOF_PARACHUTE ) )
return FALSE;
return TRUE;
}
void CrateCollide::doSabotageFeedbackFX( const Object *other, SabotageVictimType type )
{
if ( ! getObject() )
return;
if ( ! other )
return;
AudioEventRTS soundToPlay;
switch ( type )
{
case CrateCollide::SAB_VICTIM_FAKE_BUILDING:
{
return; // THIS NEEDS NO ADD'L FEEDBACK
}
case CrateCollide::SAB_VICTIM_COMMAND_CENTER:
case CrateCollide::SAB_VICTIM_SUPERWEAPON:
{
soundToPlay = TheAudio->getMiscAudio()->m_sabotageResetTimerBuilding;
break;
}
case CrateCollide::SAB_VICTIM_DROP_ZONE:
case CrateCollide::SAB_VICTIM_SUPPLY_CENTER:
{
soundToPlay = TheAudio->getMiscAudio()->m_moneyWithdrawSound;
break;
}
case CrateCollide::SAB_VICTIM_INTERNET_CENTER:
case CrateCollide::SAB_VICTIM_MILITARY_FACTORY:
case CrateCollide::SAB_VICTIM_POWER_PLANT:
default:
{
soundToPlay = TheAudio->getMiscAudio()->m_sabotageShutDownBuilding;
break;
}
}
soundToPlay.setPosition( other->getPosition() );
TheAudio->addAudioEvent( &soundToPlay );
Drawable *draw = other->getDrawable();
if ( draw )
draw->flashAsSelected();
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CrateCollide::crc( Xfer *xfer )
{
// extend base class
CollideModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CollideModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CrateCollide::loadPostProcess( void )
{
// extend base class
CollideModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,106 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: MoneyCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that heals everything owned by the collider
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/HealCrateCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
HealCrateCollide::HealCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
HealCrateCollide::~HealCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool HealCrateCollide::executeCrateBehavior( Object *other )
{
Player* cratePlayer = other->getControllingPlayer();
cratePlayer->healAllObjects();
//Play a crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateHeal;
soundToPlay.setPosition( other->getPosition() );
TheAudio->addAudioEvent(&soundToPlay);
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void HealCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void HealCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void HealCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,135 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: MoneyCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that gives x money to the collider
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/MoneyCrateCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MoneyCrateCollide::MoneyCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MoneyCrateCollide::~MoneyCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool MoneyCrateCollide::executeCrateBehavior( Object *other )
{
UnsignedInt money = getMoneyCrateCollideModuleData()->m_moneyProvided;
money += getUpgradedSupplyBoost(other);
other->getControllingPlayer()->getMoney()->deposit( money );
other->getControllingPlayer()->getScoreKeeper()->addMoneyEarned( money );
//Play a crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateMoney;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent(&soundToPlay);
return TRUE;
}
//------------------------------------------------------------------------------------------------
Int MoneyCrateCollide::getUpgradedSupplyBoost( Object *other ) const
{
Player *player = other->getControllingPlayer();
if (!player) return 0;
// Loop through the upgrade pairs and see if an upgrade is present that adds supply boost
std::list<upgradePair>::const_iterator it = getMoneyCrateCollideModuleData()->m_upgradeBoost.begin();
while (it != getMoneyCrateCollideModuleData()->m_upgradeBoost.end())
{
upgradePair info = *it;
// Check if the player has the desired upgrade. If so return the boost
static const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( info.type.c_str() );
if (player && upgradeTemplate && player->hasUpgradeComplete(upgradeTemplate))
{
return info.amount;
}
// check next
++it;
}
return 0;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void MoneyCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void MoneyCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void MoneyCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,193 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: SabotageCommandCenterCrateCollide.cpp
// Author: Kris Morness, June 2003
// Desc: A crate (actually a saboteur - mobile crate) that resets the timer on the target supply dropzone.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/SpecialPower.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/OCLUpdate.h"
#include "GameLogic/Module/SabotageCommandCenterCrateCollide.h"
#include "GameLogic/Module/SpecialPowerModule.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageCommandCenterCrateCollide::SabotageCommandCenterCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageCommandCenterCrateCollide::~SabotageCommandCenterCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageCommandCenterCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_COMMANDCENTER ) )
{
//We can only sabotage superweapon structures.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageCommandCenterCrateCollide::executeCrateBehavior( Object *other )
{
//Check to make sure that the other object is also the goal object in the AIUpdateInterface
//in order to prevent an unintentional conversion simply by having the terrorist walk too close
//to it.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_COMMAND_CENTER );
//When the sabotage occurs, play the appropriate EVA
//event if the local player is the victim!
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
//Reset ALL special powers!
for( BehaviorModule **m = other->getBehaviorModules(); *m; ++m )
{
SpecialPowerModuleInterface* sp = (*m)->getSpecialPower();
if( !sp )
{
continue;
}
sp->startPowerRecharge();
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageCommandCenterCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageCommandCenterCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageCommandCenterCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,188 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: SabotageFakeBuildingCrateCollide.cpp
// Author: Kris Morness, July 2003
// Desc: A crate (actually a saboteur - mobile crate) that destroys a fake building.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/SabotageFakeBuildingCrateCollide.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageFakeBuildingCrateCollide::SabotageFakeBuildingCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageFakeBuildingCrateCollide::~SabotageFakeBuildingCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageFakeBuildingCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_FAKE ) )
{
//We can only sabotage fake structures.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageFakeBuildingCrateCollide::executeCrateBehavior( Object *other )
{
//Check to make sure that the other object is also the goal object in the AIUpdateInterface
//in order to prevent an unintentional conversion simply by having the terrorist walk too close
//to it.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_FAKE_BUILDING );
//When the sabotage occurs, play the appropriate EVA
//event if the local player is the victim!
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
Player *player = other->getControllingPlayer();
if( player )
{
DamageInfo damageInfo;
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
damageInfo.in.m_deathType = DEATH_DETONATED;
damageInfo.in.m_sourceID = obj->getID();
damageInfo.in.m_amount = other->getBodyModule()->getMaxHealth();
other->attemptDamage( &damageInfo );
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageFakeBuildingCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageFakeBuildingCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageFakeBuildingCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,231 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: SabotageInternetCenterCrateCollide.cpp
// Author: Kris Morness, July 2003
// Desc: A crate (actually a saboteur - mobile crate) that temporarily disables an internet center
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/GameText.h"
#include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BehaviorModule.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/OCLUpdate.h"
#include "GameLogic/Module/SabotageInternetCenterCrateCollide.h"
#include "GameLogic/Module/SpyVisionUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageInternetCenterCrateCollide::SabotageInternetCenterCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageInternetCenterCrateCollide::~SabotageInternetCenterCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageInternetCenterCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_INTERNET_CENTER ) )
{
//We can only sabotage supply dropzones.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
static void disableHacker( Object *obj, void *userData )
{
UnsignedInt frame = (UnsignedInt)userData;
if( obj )
{
obj->setDisabledUntil( DISABLED_HACKED, frame );
}
}
static void disableInternetCenterSpyVision( Object *obj, void *userData )
{
if( obj && obj->isKindOf( KINDOF_FS_INTERNET_CENTER ) )
{
UnsignedInt frame = (UnsignedInt)userData;
//Loop through all it's SpyVisionUpdates() and wake them all up so they can be shut down. This is weird because
//it's one of the few update modules that is actually properly sleepified.
for( BehaviorModule** u = obj->getBehaviorModules(); *u; ++u )
{
SpyVisionUpdate *svUpdate = (*u)->getSpyVisionUpdate();
if( svUpdate )
{
//Turn off the vision temporarily. When it recovers from being disabled, the
//timer will need to start over from scratch so it won't come on right away unless
//it's a permanent spy vision.
svUpdate->setDisabledUntilFrame( frame );
}
}
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageInternetCenterCrateCollide::executeCrateBehavior( Object *other )
{
//Check to make sure that the other object is also the goal object in the AIUpdateInterface
//in order to prevent an unintentional conversion simply by having the terrorist walk too close
//to it.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_INTERNET_CENTER );
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
//Loop through every internet center to temporarily disable the spy vision upgrades.
UnsignedInt frame = TheGameLogic->getFrame() + getSabotageInternetCenterCrateCollideModuleData()->m_sabotageFrames;
//Disable all internet center spy visions (they stack) without visually disabling the other centers.
//Kris: Note -- Design has changed that we only can have one center at a time... logically, this code
//doesn't need to change.
// This loop goes before the Disabled_Hacked one since that will use the normal disabled code with its cool timers.
// This loop is for the other centers, but it hits the main one too.
other->getControllingPlayer()->iterateObjects( disableInternetCenterSpyVision, (void*)frame );
//Disable the internet center. Note this is purely fluff... because the spy vision update will still run even
//though we are disabling it. We have to disable the spyvision updates manually because other centers need to
//be turned off too but without visually disabling them. Yikes!
other->setDisabledUntil( DISABLED_HACKED, frame );
//Disable all the hackers inside.
ContainModuleInterface *contain = other->getContain();
contain->iterateContained( disableHacker, (void*)frame, FALSE );
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageInternetCenterCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageInternetCenterCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageInternetCenterCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,188 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: SabotageMilitaryFactoryCrateCollide.cpp
// Author: Kris Morness, June 2003
// Desc: A crate (actually a saboteur - mobile crate) that steals cash from the target supply center.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/GameText.h"
#include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/OCLUpdate.h"
#include "GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageMilitaryFactoryCrateCollide::SabotageMilitaryFactoryCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageMilitaryFactoryCrateCollide::~SabotageMilitaryFactoryCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageMilitaryFactoryCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( other->isKindOf( KINDOF_AIRCRAFT_CARRIER ) )
{
//Can't sabotage that!
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_BARRACKS ) && !other->isKindOf( KINDOF_FS_WARFACTORY ) && !other->isKindOf( KINDOF_FS_AIRFIELD ) )
{
//We can only sabotage military factory buildings.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageMilitaryFactoryCrateCollide::executeCrateBehavior( Object *other )
{
//Check to make sure that the other object is also the goal object in the AIUpdateInterface
//in order to prevent an unintentional conversion simply by having the terrorist walk too close
//to it.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_MILITARY_FACTORY );
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
UnsignedInt frame = TheGameLogic->getFrame() + getSabotageMilitaryFactoryCrateCollideModuleData()->m_sabotageFrames;
other->setDisabledUntil( DISABLED_HACKED, frame );
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageMilitaryFactoryCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageMilitaryFactoryCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageMilitaryFactoryCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,190 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: SabotagePowerPlantCrateCollide.cpp
// Author: Kris Morness, June 2003
// Desc: A crate (actually a saboteur - mobile crate) that makes the target powerplant lose power
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/SabotagePowerPlantCrateCollide.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotagePowerPlantCrateCollide::SabotagePowerPlantCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotagePowerPlantCrateCollide::~SabotagePowerPlantCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotagePowerPlantCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_POWER ) )
{
//We can only sabotage power plants.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotagePowerPlantCrateCollide::executeCrateBehavior( Object *other )
{
//Check to make sure that the other object is also the goal object in the AIUpdateInterface
//in order to prevent an unintentional conversion simply by having the terrorist walk too close
//to it.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_POWER_PLANT );
//When the sabotage occurs, play the appropriate EVA
//event if the local player is the victim!
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
Player *player = other->getControllingPlayer();
if( player )
{
//Set the duration inside the player's energy class to record the length of the power outage.
UnsignedInt frame = TheGameLogic->getFrame() + getSabotagePowerPlantCrateCollideModuleData()->m_powerSabotageFrames;
player->getEnergy()->setPowerSabotagedTillFrame( frame );
//Trigger the callback function that will turn everything off.
player->onPowerBrownOutChange( TRUE );
//Note: Player::update() will check to turn it back on again once the timer expires.
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotagePowerPlantCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotagePowerPlantCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotagePowerPlantCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,193 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: SabotageSuperweaponCrateCollide.cpp
// Author: Kris Morness, June 2003
// Desc: A crate (actually a saboteur - mobile crate) that resets the timer on the target supply dropzone.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/SpecialPower.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/OCLUpdate.h"
#include "GameLogic/Module/SabotageSuperweaponCrateCollide.h"
#include "GameLogic/Module/SpecialPowerModule.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSuperweaponCrateCollide::SabotageSuperweaponCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSuperweaponCrateCollide::~SabotageSuperweaponCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSuperweaponCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_SUPERWEAPON ) && !other->isKindOf( KINDOF_FS_STRATEGY_CENTER ) )
{
//We can only sabotage superweapon structures.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSuperweaponCrateCollide::executeCrateBehavior( Object *other )
{
//Check to make sure that the other object is also the goal object in the AIUpdateInterface
//in order to prevent an unintentional conversion simply by having the terrorist walk too close
//to it.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_SUPERWEAPON );
//When the sabotage occurs, play the appropriate EVA
//event if the local player is the victim!
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
//Reset ALL special powers!
for( BehaviorModule **m = other->getBehaviorModules(); *m; ++m )
{
SpecialPowerModuleInterface* sp = (*m)->getSpecialPower();
if( !sp )
{
continue;
}
sp->startPowerRecharge();
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageSuperweaponCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageSuperweaponCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageSuperweaponCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,221 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: SabotageSupplyCenterCrateCollide.cpp
// Author: Kris Morness, June 2003
// Desc: A crate (actually a saboteur - mobile crate) that steals cash from the target supply center.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/GameText.h"
#include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/OCLUpdate.h"
#include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSupplyCenterCrateCollide::SabotageSupplyCenterCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSupplyCenterCrateCollide::~SabotageSupplyCenterCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSupplyCenterCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_SUPPLY_CENTER ) )
{
//We can only sabotage supply dropzones.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSupplyCenterCrateCollide::executeCrateBehavior( Object *other )
{
//Check to make sure that the other object is also the goal object in the AIUpdateInterface
//in order to prevent an unintentional conversion simply by having the terrorist walk too close
//to it.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_SUPPLY_CENTER );
//Steal cash!
Money *targetMoney = other->getControllingPlayer()->getMoney();
Money *objectMoney = obj->getControllingPlayer()->getMoney();
if( targetMoney && objectMoney )
{
UnsignedInt cash = targetMoney->countMoney();
UnsignedInt desiredAmount = getSabotageSupplyCenterCrateCollideModuleData()->m_stealCashAmount;
//Check to see if they have the cash, otherwise, take the remainder!
cash = min( desiredAmount, cash );
if( cash > 0 )
{
//Steal the cash
targetMoney->withdraw( cash );
objectMoney->deposit( cash );
Player* controller = obj->getControllingPlayer();
if (controller)
controller->getScoreKeeper()->addMoneyEarned( cash );
//Play the "cash stolen" EVA event if the local player is the victim!
if( other && other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_CashStolen );
}
//Display cash income floating over the about to be deleted saboteur.
UnicodeString moneyString;
moneyString.format( TheGameText->fetch( "GUI:AddCash" ), cash );
Coord3D pos;
pos.set( obj->getPosition() );
pos.z += 20.0f; //add a little z to make it show up above the unit.
TheInGameUI->addFloatingText( moneyString, &pos, GameMakeColor( 0, 255, 0, 255 ) );
//Display cash lost floating over the target
moneyString.format( TheGameText->fetch( "GUI:LoseCash" ), cash );
pos.set( other->getPosition() );
pos.z += 30.0f; //add a little z to make it show up above the unit.
TheInGameUI->addFloatingText( moneyString, &pos, GameMakeColor( 255, 0, 0, 255 ) );
}
else
{
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
}
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyCenterCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyCenterCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyCenterCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,232 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: SabotageSupplyDropzoneCrateCollide.cpp
// Author: Kris Morness, June 2003
// Desc: A crate (actually a saboteur - mobile crate) that resets the timer on the target supply dropzone.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/GameText.h"
#include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/OCLUpdate.h"
#include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSupplyDropzoneCrateCollide::SabotageSupplyDropzoneCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSupplyDropzoneCrateCollide::~SabotageSupplyDropzoneCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSupplyDropzoneCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_SUPPLY_DROPZONE ) )
{
//We can only sabotage supply dropzones.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSupplyDropzoneCrateCollide::executeCrateBehavior( Object *other )
{
//Check to make sure that the other object is also the goal object in the AIUpdateInterface
//in order to prevent an unintentional conversion simply by having the terrorist walk too close
//to it.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_DROP_ZONE );
//Reset the timer on the dropzone. Um... only the dropzone has an OCLUpdate and one, so
//we can "assume" it's going to be safe. Otherwise, we'll have to write code to search for
//a specific OCLUpdate.
static NameKeyType key_ocl = NAMEKEY( "OCLUpdate" );
OCLUpdate *update = (OCLUpdate*)other->findUpdateModule( key_ocl );
if( update )
{
update->resetTimer();
}
//Steal cash!
Money *targetMoney = other->getControllingPlayer()->getMoney();
Money *objectMoney = obj->getControllingPlayer()->getMoney();
if( targetMoney && objectMoney )
{
UnsignedInt cash = targetMoney->countMoney();
UnsignedInt desiredAmount = getSabotageSupplyDropzoneCrateCollideModuleData()->m_stealCashAmount;
//Check to see if they have the cash, otherwise, take the remainder!
cash = min( desiredAmount, cash );
if( cash > 0 )
{
//Steal the cash
targetMoney->withdraw( cash );
objectMoney->deposit( cash );
Player* controller = obj->getControllingPlayer();
if (controller)
controller->getScoreKeeper()->addMoneyEarned( cash );
//Play the "cash stolen" EVA event if the local player is the victim!
if( other && other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_CashStolen );
}
//Display cash income floating over the about to be deleted saboteur.
UnicodeString moneyString;
moneyString.format( TheGameText->fetch( "GUI:AddCash" ), cash );
Coord3D pos;
pos.set( obj->getPosition() );
pos.z += 20.0f; //add a little z to make it show up above the unit.
TheInGameUI->addFloatingText( moneyString, &pos, GameMakeColor( 0, 255, 0, 255 ) );
//Display cash lost floating over the target
moneyString.format( TheGameText->fetch( "GUI:LoseCash" ), cash );
pos.set( other->getPosition() );
pos.z += 30.0f; //add a little z to make it show up above the unit.
TheInGameUI->addFloatingText( moneyString, &pos, GameMakeColor( 255, 0, 0, 255 ) );
}
else
{
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
}
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyDropzoneCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyDropzoneCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyDropzoneCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,300 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: SalvageCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: The Savlage system can give a Weaponset bonus, a level, or money. Salvagers create them
// by killing marked units, and only WeaponSalvagers can get the WeaponSet bonus
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameLogic/Module/SalvageCrateCollide.h"
#include "Common/AudioEventRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Kindof.h"
#include "Common/RandomValue.h"
#include "Common/Player.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/GameText.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SalvageCrateCollide::SalvageCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SalvageCrateCollide::~SalvageCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::isValidToExecute( const Object *other ) const
{
if( ! CrateCollide::isValidToExecute( other ) )
return FALSE;
// Only salvage units can pick up a Salvage crate
if( ! other->getTemplate()->isKindOf( KINDOF_SALVAGER ) )
return FALSE;
return TRUE;
}
//-------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::executeCrateBehavior( Object *other )
{
if( eligibleForArmorSet(other) )// No percent chance on this one, if you can get it, you get it.
{
doArmorSet(other);
//Play the salvage installation crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateSalvage;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent( &soundToPlay );
}
else if( eligibleForWeaponSet( other ) && testWeaponChance() )
{
doWeaponSet( other );
//Play the salvage installation crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateSalvage;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent( &soundToPlay );
//Play the unit voice acknowledgement for upgrading weapons.
//Already handled by the "move order"
//const AudioEventRTS *soundToPlayPtr = other->getTemplate()->getPerUnitSound( "VoiceSalvage" );
//soundToPlay = *soundToPlayPtr;
//soundToPlay.setObjectID( other->getID() );
//TheAudio->addAudioEvent( &soundToPlay );
}
else if( eligibleForLevel( other ) && testLevelChance() )
{
doLevelGain( other );
//Sound will play in
//soundToPlay = TheAudio->getMiscAudio()->m_unitPromoted;
}
else // just assume the testMoneyChance
{
doMoney( other );
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateMoney;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent(&soundToPlay);
}
other->getControllingPlayer()->getAcademyStats()->recordSalvageCollected();
return TRUE;
}
// ------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::eligibleForWeaponSet( Object *other )
{
if( other == NULL )
return FALSE;
// A kindof marks eligibility, and you must not be fully upgraded
if( !other->isKindOf(KINDOF_WEAPON_SALVAGER) )
return FALSE;
if( other->testWeaponSetFlag(WEAPONSET_CRATEUPGRADE_TWO) )
return FALSE;
return TRUE;
}
// ------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::eligibleForArmorSet( Object *other )
{
if( other == NULL )
return FALSE;
// A kindof marks eligibility, and you must not be fully upgraded
if( !other->isKindOf(KINDOF_ARMOR_SALVAGER) )
return FALSE;
if( other->testArmorSetFlag(ARMORSET_CRATE_UPGRADE_TWO) )
return FALSE;
return TRUE;
}
// ------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::eligibleForLevel( Object *other )
{
if( other == NULL )
return FALSE;
// Sorry, you are max level
if( other->getExperienceTracker()->getVeterancyLevel() == LEVEL_HEROIC )
return FALSE;
// Sorry, you can't gain levels
if( !other->getExperienceTracker()->isTrainable() )
return FALSE;
return TRUE;
}
// ------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::testWeaponChance()
{
const SalvageCrateCollideModuleData *md = getSalvageCrateCollideModuleData();
if( md->m_weaponChance == 1.0f )
return TRUE; // don't waste a random number for a 100%
Real randomNumber = GameLogicRandomValueReal( 0, 1 );
if( randomNumber < md->m_weaponChance )
return TRUE;
return FALSE;
}
// ------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::testLevelChance()
{
const SalvageCrateCollideModuleData *md = getSalvageCrateCollideModuleData();
if( md->m_levelChance == 1.0f )
return TRUE; // don't waste a random number for a 100%
Real randomNumber = GameLogicRandomValueReal( 0, 1 );
if( randomNumber < md->m_levelChance )
return TRUE;
return FALSE;
}
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::doWeaponSet( Object *other )
{
if( other->testWeaponSetFlag( WEAPONSET_CRATEUPGRADE_ONE ) )
{
other->clearWeaponSetFlag( WEAPONSET_CRATEUPGRADE_ONE );
other->setWeaponSetFlag( WEAPONSET_CRATEUPGRADE_TWO );
}
else
{
other->setWeaponSetFlag( WEAPONSET_CRATEUPGRADE_ONE );
}
}
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::doArmorSet( Object *other )
{
if( other->testArmorSetFlag( ARMORSET_CRATE_UPGRADE_ONE ) )
{
other->clearArmorSetFlag( ARMORSET_CRATE_UPGRADE_ONE );
other->setArmorSetFlag( ARMORSET_CRATE_UPGRADE_TWO );
other->clearAndSetModelConditionState(MODELCONDITION_ARMORSET_CRATEUPGRADE_ONE, MODELCONDITION_ARMORSET_CRATEUPGRADE_TWO);
}
else
{
other->setArmorSetFlag( ARMORSET_CRATE_UPGRADE_ONE );
other->setModelConditionState(MODELCONDITION_ARMORSET_CRATEUPGRADE_ONE);
}
}
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::doLevelGain( Object *other )
{
other->getExperienceTracker()->gainExpForLevel( 1 );
}
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::doMoney( Object *other )
{
const SalvageCrateCollideModuleData *md = getSalvageCrateCollideModuleData();
Int money;
if( md->m_minimumMoney != md->m_maximumMoney )// Random value doesn't like to get a constant range
money = GameLogicRandomValue( md->m_minimumMoney, md->m_maximumMoney );
else
money = md->m_minimumMoney;
if( money > 0 )
{
other->getControllingPlayer()->getMoney()->deposit( money );
other->getControllingPlayer()->getScoreKeeper()->addMoneyEarned( money );
//Display cash income floating over the crate. Position is me, everything else is them.
UnicodeString moneyString;
moneyString.format( TheGameText->fetch( "GUI:AddCash" ), money );
Coord3D pos;
pos.set( getObject()->getPosition() );
pos.z += 10.0f; //add a little z to make it show up above the unit.
Color color = other->getControllingPlayer()->getPlayerColor() | GameMakeColor( 0, 0, 0, 230 );
TheInGameUI->addFloatingText( moneyString, &pos, color );
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,105 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: ShroudCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that clears the shroud for the pickerupper
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/ShroudCrateCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ShroudCrateCollide::ShroudCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ShroudCrateCollide::~ShroudCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool ShroudCrateCollide::executeCrateBehavior( Object *other )
{
Player* cratePlayer = other->getControllingPlayer();
ThePartitionManager->revealMapForPlayer( cratePlayer->getPlayerIndex() );
//Play a crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateShroud;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent(&soundToPlay);
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ShroudCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ShroudCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ShroudCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,132 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: UnitCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that gives n units of type m the the pickerupper
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/UnitCrateCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UnitCrateCollide::UnitCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UnitCrateCollide::~UnitCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool UnitCrateCollide::executeCrateBehavior( Object *other )
{
UnsignedInt unitCount = getUnitCrateCollideModuleData()->m_unitCount;
ThingTemplate const *unitType = TheThingFactory->findTemplate( getUnitCrateCollideModuleData()->m_unitType );
if( unitType == NULL )
{
return FALSE;
}
for( Int unitIndex = 0; unitIndex < unitCount; unitIndex++ )
{
Team *creationTeam = other->getControllingPlayer()->getDefaultTeam();
Object *newObj = TheThingFactory->newObject( unitType, creationTeam );
if( newObj )
{
Coord3D creationPoint = *other->getPosition();
/// @todo As a user of the future findLegalPositionAround, I wouldn't mind not having to specify range. I just want a non colliding point.
FindPositionOptions fpOptions;
fpOptions.minRadius = 0.0f;
fpOptions.maxRadius = 20.0f;
ThePartitionManager->findPositionAround( &creationPoint,
&fpOptions,
&creationPoint );
newObj->setOrientation( other->getOrientation() );
newObj->setPosition( &creationPoint );
}
}
//Play a crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateFreeUnit;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent(&soundToPlay);
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void UnitCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void UnitCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void UnitCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,210 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: VeterancyCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that gives a level of experience to all within n distance
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/VeterancyCrateCollide.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
VeterancyCrateCollide::VeterancyCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
VeterancyCrateCollide::~VeterancyCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Int VeterancyCrateCollide::getLevelsToGain() const
{
const VeterancyCrateCollideModuleData* d = getVeterancyCrateCollideModuleData();
if (!d || !d->m_addsOwnerVeterancy)
return 1;
// this requires that "regular" is 0, vet is 1, etc.
return (Int)getObject()->getVeterancyLevel();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool VeterancyCrateCollide::isValidToExecute( const Object *other ) const
{
const VeterancyCrateCollideModuleData* d = getVeterancyCrateCollideModuleData();
if( !d )
{
return false;
}
if(!CrateCollide::isValidToExecute(other))
return false;
if (other->isEffectivelyDead())
return false;
if( other->isSignificantlyAboveTerrain() )
{
return false;
}
Int levelsToGain = getLevelsToGain();
if (levelsToGain <= 0)
return false;
const ExperienceTracker *et = other->getExperienceTracker();
if( !et || !et->isTrainable() )
{
//If the other unit can't gain experience, then we can't help promote it!
return false;
}
if (!et || !et->canGainExpForLevel(levelsToGain))
return false;
if( d->m_isPilot )
{
if( other->getControllingPlayer() != getObject()->getControllingPlayer() )
{
//This is a pilot and we are checking to make sure the pilot is entering a vehicle on
//the same team. If it's not, then don't allow it.. this is particularly the case for
//pilots attempting to enter civilian vehicles.
return false;
}
if( other->isUsingAirborneLocomotor() )
{
// Can't upgrade a helicopter or plane, but we will think we can for a moment while it
// is on the ground from being built.
return false;
}
}
return true;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool VeterancyCrateCollide::executeCrateBehavior( Object *other )
{
//Make sure the pilot is actually *TRYING* to enter the object
//unlike other crates
AIUpdateInterface *ai = (AIUpdateInterface*)getObject()->getAIUpdateInterface();
const VeterancyCrateCollideModuleData *md = getVeterancyCrateCollideModuleData();
if( !ai || ai->getGoalObject() != other )
{
return false;
}
Int levelsToGain = getLevelsToGain();
Real range = md->m_rangeOfEffect;
if (range == 0)
{
// do just the collider
if (other != NULL)
{
other->getExperienceTracker()->gainExpForLevel( levelsToGain, ( ! md->m_isPilot) );
}
}
else
{
PartitionFilterSamePlayer othersPlayerFilter( other->getControllingPlayer() );
PartitionFilterSameMapStatus filterMapStatus(other);
PartitionFilter *filters[] = { &othersPlayerFilter, &filterMapStatus, NULL };
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( other, range, FROM_CENTER_2D, filters, ITER_FASTEST );
MemoryPoolObjectHolder hold(iter);
for( Object *potentialObject = iter->first(); potentialObject; potentialObject = iter->next() )
{
// This function will give just enough exp for the Object to gain a level, if it can
potentialObject->getExperienceTracker()->gainExpForLevel( levelsToGain, ( ! md->m_isPilot) );
}
}
//In order to make things easier for the designers, we are going to transfer the terrorist name
//to the car... so the designer can control the car with their scripts.
if( md->m_isPilot )
{
TheScriptEngine->transferObjectName( getObject()->getName(), other );
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void VeterancyCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void VeterancyCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void VeterancyCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,175 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: FireWeaponCollide.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood April 2002
// Desc: Shoot something that collides with me every frame with my weapon
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_OBJECT_STATUS_NAMES
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/FireWeaponCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void FireWeaponCollideModuleData::buildFieldParse(MultiIniFieldParse& p)
{
CollideModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "CollideWeapon", INI::parseWeaponTemplate, NULL, offsetof( FireWeaponCollideModuleData, m_collideWeaponTemplate ) },
{ "FireOnce", INI::parseBool, NULL, offsetof( FireWeaponCollideModuleData, m_fireOnce ) },
{ "RequiredStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof( FireWeaponCollideModuleData, m_requiredStatus ) },
{ "ForbiddenStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof( FireWeaponCollideModuleData, m_forbiddenStatus ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponCollide::FireWeaponCollide( Thing *thing, const ModuleData* moduleData ) :
CollideModule( thing, moduleData ),
m_collideWeapon(NULL)
{
m_collideWeapon = TheWeaponStore->allocateNewWeapon(getFireWeaponCollideModuleData()->m_collideWeaponTemplate, PRIMARY_WEAPON);
m_everFired = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponCollide::~FireWeaponCollide( void )
{
if (m_collideWeapon)
m_collideWeapon->deleteInstance();
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void FireWeaponCollide::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
{
if( other == NULL )
return; //Don't shoot the ground
Object *me = getObject();
// This will fire at you every frame, because multiple people could be colliding and we want
// to hurt them all. Another solution would be to keep a Map of other->objetIDs and
// delays for each individually. However, this solution here is so quick and simple that it
// warrants the "do it eventually if we need it" clause.
if( shouldFireWeapon() )
{
m_collideWeapon->loadAmmoNow( me );
m_collideWeapon->fireWeapon( me, other );
}
}
//-------------------------------------------------------------------------------------------------
Bool FireWeaponCollide::shouldFireWeapon()
{
const FireWeaponCollideModuleData *d = getFireWeaponCollideModuleData();
ObjectStatusMaskType status = getObject()->getStatusBits();
//We need all required status or else we fail
if( !status.testForAll( d->m_requiredStatus ) )
return FALSE;
//If we have any forbidden statii, then fail
if( status.testForAny( d->m_forbiddenStatus ) )
return FALSE;
if( m_everFired && d->m_fireOnce )
return FALSE;// can only fire once ever
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FireWeaponCollide::crc( Xfer *xfer )
{
// extend base class
CollideModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void FireWeaponCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CollideModule::xfer( xfer );
// weapon
Bool collideWeaponPresent = m_collideWeapon ? TRUE : FALSE;
xfer->xferBool( &collideWeaponPresent );
if( collideWeaponPresent )
{
DEBUG_ASSERTCRASH( m_collideWeapon != NULL,
("FireWeaponCollide::xfer - m_collideWeapon present mismatch\n") );
xfer->xferSnapshot( m_collideWeapon );
} // end else
else
{
DEBUG_ASSERTCRASH( m_collideWeapon == NULL,
("FireWeaponCollide::Xfer - m_collideWeapon missing mismatch\n" ));
} // end else
// ever fired
xfer->xferBool( &m_everFired );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void FireWeaponCollide::loadPostProcess( void )
{
// extend base class
CollideModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,175 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: SquishCollide.cpp //////////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameLogic/Damage.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/SquishCollide.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/SpecialAbilityUpdate.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SquishCollide::SquishCollide( Thing *thing, const ModuleData* moduleData ) : CollideModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SquishCollide::~SquishCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
/** Do the collision */
//-------------------------------------------------------------------------------------------------
void SquishCollide::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
{
// Note that other == null means "collide with ground"
if (other == NULL)
return;
Object *self = getObject();
AIUpdateInterface *ai = self->getAI();
if( ai && ai->getGoalObject() == other )
{
//We are actually targeting the other object to do something to! Don't allow them to crush us in the following
//special circumstances:
//Hijacking!
static NameKeyType key_HijackerUpdate = NAMEKEY( "HijackerUpdate" );
HijackerUpdate *hijackUpdate = (HijackerUpdate*)self->findUpdateModule( key_HijackerUpdate );
if( hijackUpdate )
{
return;
}
//TNT hunter placing a charge!
SpecialAbilityUpdate *saUpdate = self->findSpecialAbilityUpdate( SPECIAL_TANKHUNTER_TNT_ATTACK );
if( saUpdate && saUpdate->isActive() )
{
return;
}
}
// order matters: we want to know if IT considers ME to be an ally (a reversal of the usual situation)
if( other->getCrusherLevel() > 0 && other->getRelationship(getObject()) != ALLIES)
{
PhysicsBehavior *otherPhysics = other->getPhysics();
if (otherPhysics == NULL)
return;
// use a 1.0 crush radius so the tank has to actually hit the infantry.
GeometryInfo myGeom = self->getGeometryInfo();
myGeom.setMajorRadius(1.0f);
myGeom.setMinorRadius(1.0f);
if (!ThePartitionManager->geomCollidesWithGeom(other->getPosition(), other->getGeometryInfo(), other->getOrientation(),
self->getPosition(), myGeom, self->getOrientation())) {
return;
}
// only squish if tank is moving toward victim
const Coord3D *vel = otherPhysics->getVelocity();
const Coord3D *pos = other->getPosition();
const Coord3D *myPos = getObject()->getPosition();
Coord3D to;
to.x = myPos->x - pos->x;
to.y = myPos->y - pos->y;
to.z = myPos->z - pos->z;
if (to.x * vel->x + to.y * vel->y > 0.0f)
{
DamageInfo damageInfo;
damageInfo.in.m_damageType = DAMAGE_CRUSH;
damageInfo.in.m_deathType = DEATH_CRUSHED;
damageInfo.in.m_sourceID = other->getID();
damageInfo.in.m_amount = HUGE_DAMAGE_AMOUNT; // make sure they die
getObject()->attemptDamage( &damageInfo );
getObject()->friend_setUndetectedDefector( FALSE );// My secret is out
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SquishCollide::crc( Xfer *xfer )
{
// extend base class
CollideModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SquishCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CollideModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SquishCollide::loadPostProcess( void )
{
// extend base class
CollideModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,436 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: CaveContain.cpp ////////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, July 2002
// Desc: A version of OpenContain that overrides where the passengers are stored: one of CaveSystem's
// entries. Changing entry is a script or ini command. All queries about capacity and
// contents are also redirected. They change sides like Garrison too.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameState.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Team.h"
#include "Common/ThingTemplate.h"
#include "Common/TunnelTracker.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/CaveContain.h"
#include "GameLogic/CaveSystem.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CaveContain::CaveContain( Thing *thing, const ModuleData* moduleData ) : OpenContain( thing, moduleData )
{
m_needToRunOnBuildComplete = true;
m_caveIndex = 0;
m_originalTeam = NULL;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CaveContain::~CaveContain()
{
}
void CaveContain::addToContainList( Object *obj )
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
myTracker->addToContainList( obj );
}
//-------------------------------------------------------------------------------------------------
/** Remove 'obj' from the m_containList of objects in this module.
* This will trigger an onRemoving event for the object that this module
* is a part of and an onRemovedFrom event for the object being removed */
//-------------------------------------------------------------------------------------------------
void CaveContain::removeFromContain( Object *obj, Bool exposeStealthUnits )
{
// sanity
if( obj == NULL )
return;
//
// we can only remove this object from the contains list of this module if
// it is actually contained by this module
//
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
if( ! myTracker->isInContainer( obj ) )
{
return;
}
// This must come before the onRemov*, because CaveContain's version has a edge-0 triggered event.
// If that were to go first, the number would still be 1 at that time. Noone else cares about
// order.
myTracker->removeFromContain( obj, exposeStealthUnits );
// trigger an onRemoving event for 'm_object' no longer containing 'itemToRemove->m_object'
if (getObject()->getContain())
getObject()->getContain()->onRemoving( obj );
// trigger an onRemovedFrom event for 'remove'
obj->onRemovedFrom( getObject() );
}
//-------------------------------------------------------------------------------------------------
/** Remove all contained objects from the contained list */
//-------------------------------------------------------------------------------------------------
void CaveContain::removeAllContained( Bool exposeStealthUnits )
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
const ContainedItemsList *fullList = myTracker->getContainedItemsList();
Object *obj;
ContainedItemsList::const_iterator it;
it = (*fullList).begin();
while( it != (*fullList).end() )
{
obj = *it;
it++;
removeFromContain( obj, exposeStealthUnits );
}
}
//-------------------------------------------------------------------------------------------------
/** Iterate the contained list and call the callback on each of the objects */
//-------------------------------------------------------------------------------------------------
void CaveContain::iterateContained( ContainIterateFunc func, void *userData, Bool reverse )
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
myTracker->iterateContained( func, userData, reverse );
}
//-------------------------------------------------------------------------------------------------
void CaveContain::onContaining( Object *obj, Bool wasSelected )
{
OpenContain::onContaining( obj, wasSelected );
// objects inside a building are held
obj->setDisabled( DISABLED_HELD );
//
// the team of the building is now the same as those that have garrisoned it, be sure
// to save our original team tho so that we can revert back to it when all the
// occupants are gone
//
recalcApparentControllingPlayer();
}
//-------------------------------------------------------------------------------------------------
void CaveContain::onRemoving( Object *obj )
{
OpenContain::onRemoving(obj);
// object is no longer held inside a garrisoned building
obj->clearDisabled( DISABLED_HELD );
/// place the object in the world at position of the container m_object
ThePartitionManager->registerObject( obj );
obj->setPosition( getObject()->getPosition() );
if( obj->getDrawable() )
{
obj->getDrawable()->setDrawableHidden( false );
}
doUnloadSound();
if( getContainCount() == 0 )
{
// put us back on our original team
// (hokey exception: if our team is null, don't bother -- this
// usually means we are being called during game-teardown and
// the teams are no longer valid...)
if (getObject()->getTeam() != NULL)
{
changeTeamOnAllConnectedCaves( m_originalTeam, FALSE );
m_originalTeam = NULL;
}
// change the state back from garrisoned
Drawable *draw = getObject()->getDrawable();
if( draw )
{
draw->clearModelConditionState( MODELCONDITION_GARRISONED );
}
} // end if
}
Bool CaveContain::isValidContainerFor(const Object* obj, Bool checkCapacity) const
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
return myTracker->isValidContainerFor( obj, checkCapacity );
}
UnsignedInt CaveContain::getContainCount() const
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
return myTracker->getContainCount();
}
Int CaveContain::getContainMax( void ) const
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
return myTracker->getContainMax();
}
const ContainedItemsList* CaveContain::getContainedItemsList() const
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
return myTracker->getContainedItemsList();
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void CaveContain::onDie( const DamageInfo * damageInfo )
{
// override the onDie we inherit from OpenContain. no super call.
if (!getCaveContainModuleData()->m_dieMuxData.isDieApplicable(getObject(), damageInfo))
return;
if( getObject()->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
return;//it never registered itself as a tunnel
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
TheCaveSystem->unregisterCave( m_caveIndex );
myTracker->onTunnelDestroyed( getObject() );
}
//-------------------------------------------------------------------------------------------------
void CaveContain::onCreate( void )
{
m_caveIndex = getCaveContainModuleData()->m_caveIndexData;
}
//-------------------------------------------------------------------------------------------------
void CaveContain::onBuildComplete( void )
{
if( ! shouldDoOnBuildComplete() )
return;
m_needToRunOnBuildComplete = false;
TheCaveSystem->registerNewCave( m_caveIndex );
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
myTracker->onTunnelCreated( getObject() );
}
//-------------------------------------------------------------------------------------------------
void CaveContain::tryToSetCaveIndex( Int newIndex )
{
if( TheCaveSystem->canSwitchIndexToIndex( m_caveIndex, newIndex ) )
{
TunnelTracker *myOldTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
TheCaveSystem->unregisterCave( m_caveIndex );
myOldTracker->onTunnelDestroyed( getObject() );
m_caveIndex = newIndex;
TheCaveSystem->registerNewCave( m_caveIndex );
TunnelTracker *myNewTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
myNewTracker->onTunnelCreated( getObject() );
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void CaveContain::recalcApparentControllingPlayer( void )
{
//Record original team first time through.
if( m_originalTeam == NULL )
{
m_originalTeam = getObject()->getTeam();
}
// (hokey trick: if our team is null, nuke originalTeam -- this
// usually means we are being called during game-teardown and
// the teams are no longer valid...)
if (getObject()->getTeam() == NULL)
m_originalTeam = NULL;
// This is called from onContaining, so a one is the edge trigger to do capture stuff
if( getContainCount() == 1 )
{
ContainedItemsList::const_iterator it = getContainedItemsList()->begin();
Object *rider = *it;
// This also gets called during reset from the PlayerList, so we might not actually have players.
// Check like the hokey trick mentioned above
if( rider->getControllingPlayer() )
changeTeamOnAllConnectedCaves( rider->getControllingPlayer()->getDefaultTeam(), TRUE );
}
else if( getContainCount() == 0 )
{
// And a 0 is the edge trigger to do uncapture stuff
changeTeamOnAllConnectedCaves( m_originalTeam, FALSE );
}
// Handle the team color that is rendered
const Player* controller = getApparentControllingPlayer(ThePlayerList->getLocalPlayer());
if (controller)
{
if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT)
getObject()->getDrawable()->setIndicatorColor( controller->getPlayerNightColor() );
else
getObject()->getDrawable()->setIndicatorColor( controller->getPlayerColor() );
}
}
/////////////////////////////////////////////////////////////////////////////////////
static CaveInterface* findCave(Object* obj)
{
for (BehaviorModule** i = obj->getBehaviorModules(); *i; ++i)
{
CaveInterface* c = (*i)->getCaveInterface();
if (c != NULL)
return c;
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////////
void CaveContain::changeTeamOnAllConnectedCaves( Team *newTeam, Bool setOriginalTeams )
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
const std::list<ObjectID> *allCaves = myTracker->getContainerList();
for( std::list<ObjectID>::const_iterator iter = allCaves->begin(); iter != allCaves->end(); iter++ )
{
// For each ID, look it up and change its team. We all get captured together.
Object *currentCave = TheGameLogic->findObjectByID( *iter );
if( currentCave )
{
// This is a distributed Garrison in terms of capturing, so when one node
// triggers the change, he needs to tell everyone, so anyone can do the un-change.
CaveInterface *caveModule = findCave(currentCave);
if( caveModule == NULL )
continue;
if( setOriginalTeams )
caveModule->setOriginalTeam( currentCave->getTeam() );
else
caveModule->setOriginalTeam( NULL );
// Now do the actual switch for this one.
currentCave->defect( newTeam, 0 );
// currentCave->setTeam( newTeam );
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
void CaveContain::setOriginalTeam( Team *oldTeam )
{
m_originalTeam = oldTeam;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CaveContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CaveContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// need to run on build complete
xfer->xferBool( &m_needToRunOnBuildComplete );
// cave index
xfer->xferInt( &m_caveIndex );
// original team
TeamID teamID = m_originalTeam ? m_originalTeam->getID() : TEAM_ID_INVALID;
xfer->xferUser( &teamID, sizeof( TeamID ) );
if( xfer->getXferMode() == XFER_LOAD )
{
if( teamID != TEAM_ID_INVALID )
{
m_originalTeam = TheTeamFactory->findTeamByID( teamID );
if( m_originalTeam == NULL )
{
DEBUG_CRASH(( "CaveContain::xfer - Unable to find original team by id\n" ));
throw SC_INVALID_DATA;
} // end if
} // end if
else
m_originalTeam = NULL;
} // end if
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CaveContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,222 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: HealContain.cpp //////////////////////////////////////////////////////////////////////////
// Author: Colin Day
// Desc: Objects that are contained inside a heal contain ... get healed! oh my!
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Damage.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/HealContain.h"
#include "GameLogic/Module/UpdateModule.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
HealContainModuleData::HealContainModuleData( void )
{
m_framesForFullHeal = 0;
} // end HealContainModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void HealContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
OpenContainModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "TimeForFullHeal", INI::parseDurationUnsignedInt, NULL, offsetof( HealContainModuleData, m_framesForFullHeal ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
HealContain::HealContain( Thing *thing, const ModuleData *moduleData )
: OpenContain( thing, moduleData )
{
} // end HealContain
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
HealContain::~HealContain( void )
{
} // end ~HealContain
// ------------------------------------------------------------------------------------------------
/** Per frame update */
// ------------------------------------------------------------------------------------------------
UpdateSleepTime HealContain::update( void )
{
// extending functionality
/*UpdateSleepTime result =*/ OpenContain::update();
// get the module data
const HealContainModuleData *modData = getHealContainModuleData();
//
// for each of our objects, we give them a little health each frame so that when the
// the TimeTillHealed is up, the object will exit and be fully healed
//
Bool doneHealing;
Object *obj;
ContainedItemsList::const_iterator it = getContainList().begin();
while( it != getContainList().end() )
{
// get the object
obj = *it;
// increment the iterator, which allows failure to not cause an infinite loop
++it;
// do the healing on this object
doneHealing = doHeal( obj, modData->m_framesForFullHeal );
// if we're done healing, we need to remove us from the healing container
if( doneHealing == TRUE )
{
ExitDoorType exitDoor = reserveDoorForExit(obj->getTemplate(), obj);
if (exitDoor != DOOR_NONE_AVAILABLE)
exitObjectViaDoor( obj, exitDoor );
} // end if
} // end for, it
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
/** Do the healing for a single object for a single frame. */
// ------------------------------------------------------------------------------------------------
Bool HealContain::doHeal( Object *obj, UnsignedInt framesForFullHeal )
{
Bool doneHealing = FALSE;
// setup the healing damageInfo structure with all but the amount
DamageInfo healInfo;
healInfo.in.m_damageType = DAMAGE_HEALING;
healInfo.in.m_deathType = DEATH_NONE;
healInfo.in.m_sourceID = getObject()->getID();
// get body module of the thing to heal
BodyModuleInterface *body = obj->getBodyModule();
// if we've been in here long enough ... set our health to max
if( TheGameLogic->getFrame() - obj->getContainedByFrame() >= framesForFullHeal )
{
// set the amount to max just to be sure we're at the top
healInfo.in.m_amount = body->getMaxHealth();
// set max health
body->attemptHealing( &healInfo );
// we're done healing
doneHealing = TRUE;
} // end if
else
{
//
// given the *whole* time it would take to heal this object, lets pretend that the
// object is at zero health ... and give it a sliver of health as if it were at 0 health
// and would be fully healed at 'framesForFullHeal'
//
healInfo.in.m_amount = body->getMaxHealth() / (Real)framesForFullHeal;
// do the healing
body->attemptHealing( &healInfo );
} // end else
// return if we're done healing
return doneHealing;
} // end doHeal
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void HealContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void HealContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void HealContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,481 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// FILE: HelixContain.cpp ////////////////////////////////////////////////////////////////////////
// Author: Mark Lorenzen, April, 2003
//
// Desc:
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "GameClient/ControlBar.h"
#include "GameClient/Drawable.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/HelixContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Weapon.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
HelixContainModuleData::HelixContainModuleData()
{
// m_initialPayload.count = 0;
m_drawPips = TRUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void HelixContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
TransportContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "PayloadTemplateName", INI::parseAsciiStringVectorAppend, NULL, offsetof(HelixContainModuleData, m_payloadTemplateNameData) },
{"ShouldDrawPips", INI::parseBool, NULL, offsetof(HelixContainModuleData, m_drawPips) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
//void HelixContainModuleData::parseInitialPayload( INI* ini, void *instance, void *store, const void* /*userData*/ )
//{
// HelixContainModuleData* self = (HelixContainModuleData*)instance;
// const char* name = ini->getNextToken();
// const char* countStr = ini->getNextTokenOrNull();
// Int count = countStr ? INI::scanInt(countStr) : 1;
// self->m_initialPayload.name.set(name);
// self->m_initialPayload.count = count;
//}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
HelixContain::HelixContain( Thing *thing, const ModuleData *moduleData ) :
TransportContain( thing, moduleData )
{
m_payloadCreated = FALSE;
m_portableStructureID = INVALID_ID;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
HelixContain::~HelixContain( void )
{
}
void HelixContain::onObjectCreated( void )
{
HelixContain::createPayload();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime HelixContain::update()
{
Object *portable = getPortableStructure();
if ( portable )
{
portable->setPosition( getObject()->getPosition());
portable->setOrientation( getObject()->getOrientation());
}
return TransportContain::update(); // extend base
}
void HelixContain::redeployOccupants( void )
{
Coord3D firePos = *getObject()->getPosition();
firePos.z += 8;
for (ContainedItemsList::iterator it = m_containList.begin(); it != m_containList.end(); ++it)
{
Object* rider = *it;
if (rider)
rider->setPosition( &firePos );
}
}
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void HelixContain::createPayload()
{
HelixContainModuleData* self = (HelixContainModuleData*)getHelixContainModuleData();
// Any number of different passengers can be loaded here at init time
Object* object = getObject();
ContainModuleInterface *contain = object->getContain();
if( contain )
{
contain->enableLoadSounds( FALSE );
TemplateNameList list = self->m_payloadTemplateNameData;
TemplateNameIterator iter = list.begin();
while ( iter != list.end() )
{
const ThingTemplate* temp = TheThingFactory->findTemplate( *iter );
if (temp)
{
Object* payload = TheThingFactory->newObject( temp, object->getTeam() );
if( contain->isValidContainerFor( payload, true ) )
{
contain->addToContain( payload );
}
else
{
DEBUG_CRASH( ( "HelixContain::createPayload: %s is full, or not valid for the payload %s!", object->getName().str(), self->m_initialPayload.name.str() ) );
}
}
++iter;
}
contain->enableLoadSounds( TRUE );
} // endif contain
m_payloadCreated = TRUE;
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void HelixContain::onBodyDamageStateChange( const DamageInfo* damageInfo,
BodyDamageType oldState,
BodyDamageType newState) ///< state change callback
{
// Need to apply state change to the portable structure
Object *portable = getPortableStructure();
if ( newState != BODY_RUBBLE && portable )
{
portable->getBodyModule()->setDamageState( newState );
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Object* HelixContain::getPortableStructure( void )
{
return TheGameLogic->findObjectByID( m_portableStructureID );
}
//-------------------------------------------------------------------------------------------------
void HelixContain::onDie( const DamageInfo *damageInfo )
{
Object *portable = getPortableStructure();
if ( portable )
portable->kill();
TransportContain::onDie( damageInfo );//extend base class
}
//-------------------------------------------------------------------------------------------------
void HelixContain::onDelete( void )
{
Object *portable = getPortableStructure();
if ( portable )
TheGameLogic->destroyObject( portable );
TransportContain::onDelete( );
}
// ------------------------------------------------------------------------------------------------
void HelixContain::onCapture( Player *oldOwner, Player *newOwner )
{
// Need to setteam() the portable structure, that's all;
Object *portable = getPortableStructure();
if ( portable )
portable->setTeam( newOwner->getDefaultTeam() );
}
//-------------------------------------------------------------------------------------------------
void HelixContain::addToContainList( Object *obj )
{
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && m_portableStructureID == INVALID_ID)
{
Object *portable = getPortableStructure();
if ( portable )
TheGameLogic->destroyObject( portable );
m_portableStructureID = obj->getID();
obj->friend_setContainedBy( getObject() );//fool portable into thinking my object is his container
}
else
TransportContain::addToContainList( obj );
}
//-------------------------------------------------------------------------------------------------
void HelixContain::addToContain( Object *obj )
{
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && m_portableStructureID == INVALID_ID)
{
Object *portable = getPortableStructure();
if ( portable )
TheGameLogic->destroyObject( portable );
m_portableStructureID = obj->getID();
obj->friend_setContainedBy( getObject() );//fool portable into thinking my object is his container
}
else
TransportContain::addToContain( obj );
}
//-------------------------------------------------------------------------------------------------
void HelixContain::removeFromContain( Object *obj, Bool exposeStealthUnits )
{
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && obj->getID() == m_portableStructureID )
{
Object *portable = getPortableStructure();
if ( portable )
m_portableStructureID = INVALID_ID;
//portable->kill();
}
else
{
TransportContain::removeFromContain( obj, exposeStealthUnits );
}
}
//-------------------------------------------------------------------------------------------------
Bool HelixContain::isValidContainerFor(const Object* obj, Bool checkCapacity) const
{
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && INVALID_ID == m_portableStructureID )
return TRUE;
return TransportContain::isValidContainerFor( obj, checkCapacity );
}
//-------------------------------------------------------------------------------------------------
const Object *HelixContain::friend_getRider() const
{
// The draw order dependency bug for riders means that our draw module needs to cheat to get around it.
if ( m_portableStructureID != INVALID_ID )
{
const Object *portableAsRider = TheGameLogic->findObjectByID( m_portableStructureID );
return portableAsRider;
}
return NULL;
}
//-------------------------------------------------------------------------------------------------
Bool HelixContain::isEnclosingContainerFor( const Object *obj ) const
{
if ( m_portableStructureID == obj->getID() )
{
const Object *portableAsRider = TheGameLogic->findObjectByID( m_portableStructureID );
if ( portableAsRider == obj )
return FALSE;
}
return TransportContain::isEnclosingContainerFor( obj );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
// if my object gets selected, then my visible passengers should, too
// this gets called from
void HelixContain::clientVisibleContainedFlashAsSelected()
{
if ( m_portableStructureID != INVALID_ID)
{
Object *portable = getPortableStructure();
if ( portable && portable->isKindOf(KINDOF_PORTABLE_STRUCTURE) )
{
Drawable *draw = portable->getDrawable();
if ( draw )
{
draw->flashAsSelected(); //WOW!
}
}
}
}
Bool HelixContain::isPassengerAllowedToFire( ObjectID id ) const
{
// WHETHER WE ARE ALLOWED TO FIRE DEPENDS ON WHO WE ARE
// PASSENGERS (PROPER) MAY ONLY IF THE FLAG IS TRUE
// RIDERS ARE ALWAYS ALLOWED TO FIRE (GATTLING CANNONS)
if ( getObject() && getObject()->getContainedBy() ) // nested containment voids firing, always
return FALSE;
if ( m_portableStructureID != INVALID_ID && m_portableStructureID == id )
return TRUE;
else
{
const Object *rider = TheGameLogic->findObjectByID( id );
if ( rider && rider->isKindOf( KINDOF_INFANTRY ))
return TransportContain::isPassengerAllowedToFire( id );//extend
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
void HelixContain::onContaining( Object *obj, Bool wasSelected )
{
// extend base class
TransportContain::onContaining( obj, wasSelected );
// give the object a garrisoned version of its weapon
obj->setWeaponBonusCondition( WEAPONBONUSCONDITION_GARRISONED );
obj->setDisabled( DISABLED_HELD );
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && getObject()->testStatus( OBJECT_STATUS_STEALTHED ) )
{
StealthUpdate *myStealth = obj->getStealth();
if ( myStealth )
{
myStealth->receiveGrant( true );
// note to anyone... once stealth is granted to this gattlingcannon ( or such )
// let its own stealthupdate govern the allowedtostealth cases
// a portable structure never gets removed, so...
}
}
} // end onContaining
void HelixContain::onRemoving( Object *obj )
{
// extend base class
TransportContain::onRemoving(obj);
// give the object back a regular weapon
obj->clearWeaponBonusCondition( WEAPONBONUSCONDITION_GARRISONED );
obj->clearDisabled( DISABLED_HELD );
} // end onRemoving
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void HelixContain::crc( Xfer *xfer )
{
// extend base class
TransportContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void HelixContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
if (version >= 2)
xfer->xferObjectID( &m_portableStructureID );
// extend base class
TransportContain::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void HelixContain::loadPostProcess( void )
{
// extend base class
TransportContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,131 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: InternetHackContain.cpp //////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, June 2003
// Desc: Contain module that just gives aiHackInternet command to passengers
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameLogic/Module/InternetHackContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
InternetHackContainModuleData::InternetHackContainModuleData()
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void InternetHackContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
TransportContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InternetHackContain::InternetHackContain( Thing *thing, const ModuleData *moduleData ) :
TransportContain( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InternetHackContain::~InternetHackContain( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void InternetHackContain::onContaining( Object *rider, Bool wasSelected )
{
TransportContain::onContaining( rider, wasSelected );
if( rider->getAI() )
rider->getAI()->aiHackInternet(CMD_FROM_AI);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void InternetHackContain::crc( Xfer *xfer )
{
// extend base class
TransportContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void InternetHackContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
TransportContain::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void InternetHackContain::loadPostProcess( void )
{
// extend base class
TransportContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,497 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: MobNexusContain.cpp //////////////////////////////////////////////////////////////////////
// Author: Mark Lorenzen, August 2002
// Desc: Contain module for mob units.
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/StealthUpdate.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/MobNexusContain.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Object.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
MobNexusContainModuleData::MobNexusContainModuleData()
{
m_slotCapacity = 0;
m_scatterNearbyOnExit = true;
m_orientLikeContainerOnExit = false;
m_keepContainerVelocityOnExit = false;
m_exitPitchRate = 0.0f;
m_initialPayload.count = 0;
m_healthRegen = 0.0f;
//
// by default we say that MobNexae can have infantry inside them, this will be totally
// overwritten by any data provided from the INI entry tho
//
m_allowInsideKindOf = MAKE_KINDOF_MASK( KINDOF_INFANTRY );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void MobNexusContainModuleData::parseInitialPayload( INI* ini, void *instance, void *store, const void* /*userData*/ )
{
MobNexusContainModuleData* self = (MobNexusContainModuleData*)instance;
const char* name = ini->getNextToken();
const char* countStr = ini->getNextTokenOrNull();
Int count = countStr ? INI::scanInt(countStr) : 1;
self->m_initialPayload.name.set(name);
self->m_initialPayload.count = count;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void MobNexusContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
OpenContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "Slots", INI::parseInt, NULL, offsetof( MobNexusContainModuleData, m_slotCapacity ) },
{ "ScatterNearbyOnExit", INI::parseBool, NULL, offsetof( MobNexusContainModuleData, m_scatterNearbyOnExit ) },
{ "OrientLikeContainerOnExit", INI::parseBool, NULL, offsetof( MobNexusContainModuleData, m_orientLikeContainerOnExit ) },
{ "KeepContainerVelocityOnExit", INI::parseBool, NULL, offsetof( MobNexusContainModuleData, m_keepContainerVelocityOnExit ) },
{ "ExitBone", INI::parseAsciiString, NULL, offsetof( MobNexusContainModuleData, m_exitBone ) },
{ "ExitPitchRate", INI::parseAngularVelocityReal, NULL, offsetof( MobNexusContainModuleData, m_exitPitchRate ) },
{ "InitialPayload", parseInitialPayload, NULL, 0 },
{ "HealthRegen%PerSec", INI::parseReal, NULL, offsetof( MobNexusContainModuleData, m_healthRegen ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Int MobNexusContain::getContainMax( void ) const
{
if (getMobNexusContainModuleData())
return getMobNexusContainModuleData()->m_slotCapacity;
return 0;
}
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MobNexusContain::MobNexusContain( Thing *thing, const ModuleData *moduleData ) :
OpenContain( thing, moduleData )
{
m_extraSlotsInUse = 0;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MobNexusContain::~MobNexusContain( 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 MobNexusContain::isValidContainerFor(const Object* rider, Bool checkCapacity) const
{
// sanity
if (!rider)
return false;
//The point of this new code is to determine when something has a negative 1 contain max, to
//look at the object inside of it to use that as the valid check. There is a case, when a
//paratrooper (an infantry contained in a parachute). In this case, when we pass this object
//to contain in a --- plane, we want to check the infantry, not the parachute.
// const MobNexusContainModuleData *modData = getMobNexusContainModuleData();
//Check if we are a fake container, and if so, get an object inside it to see what kind this object *is*.
if( rider->getContain() && rider->getContain()->isSpecialZeroSlotContainer() )
{
//Report the first thing inside it!
const ContainedItemsList *items = rider->getContain()->getContainedItemsList();
if( items )
{
ContainedItemsList::const_iterator it;
it = items->begin();
if( *it )
{
//Replace the object we are checking with the *first* object contained within it.
rider = *it;
}
}
}
else
{
//blech! This case may or may not occur... in which case, just use the supplied object.
}
// extend functionality
if( OpenContain::isValidContainerFor( rider, checkCapacity ) == false )
return false;
// only allied objects can be MobNexused.
// order matters: we want to know if IT considers ME to be an ally (a reversal of the usual situation)
if (rider->getRelationship(getObject()) != ALLIES)
return false;
Int mobNexusSlotCount = rider->getTransportSlotCount();
// if 0, this object isn't MobNexusable.
if (mobNexusSlotCount == 0)
return false;
if (checkCapacity)
{
return (m_extraSlotsInUse + getContainCount() + mobNexusSlotCount <= getContainMax());
}
else
{
return true;
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MobNexusContain::onContaining( Object *rider, Bool wasSelected )
{
OpenContain::onContaining( rider, wasSelected );
// objects inside a MobNexus are held
rider->setDisabled( DISABLED_HELD );
Int mobNexusSlotCount = rider->getTransportSlotCount();
DEBUG_ASSERTCRASH(mobNexusSlotCount > 0, ("Hmm, this object isnt MobNexusable"));
m_extraSlotsInUse += mobNexusSlotCount - 1;
DEBUG_ASSERTCRASH(m_extraSlotsInUse >= 0 && m_extraSlotsInUse + getContainCount() <= getContainMax(), ("Hmm, bad slot count"));
//
// when we go from holding nothing to holding something we have a model condition
// to visually show the change
//
if( getContainCount() == 1 )
{
Drawable *draw = getObject()->getDrawable();
if( draw )
draw->setModelConditionState( MODELCONDITION_LOADED );
} // end if
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MobNexusContain::onRemoving( Object *rider )
{
OpenContain::onRemoving(rider);
// object is no longer held inside a MobNexus
rider->clearDisabled( DISABLED_HELD );
const MobNexusContainModuleData* d = getMobNexusContainModuleData();
if (!d->m_exitBone.isEmpty())
{
Drawable* draw = getObject()->getDrawable();
if (draw)
{
Coord3D bonePos, worldPos;
if (draw->getPristineBonePositions(d->m_exitBone.str(), 0, &bonePos, NULL, 1) == 1)
{
getObject()->convertBonePosToWorldPos(&bonePos, NULL, &worldPos, NULL);
rider->setPosition(&worldPos);
}
}
}
if (d->m_orientLikeContainerOnExit)
{
rider->setOrientation(getObject()->getOrientation());
}
if (d->m_keepContainerVelocityOnExit)
{
PhysicsBehavior* parent = getObject()->getPhysics();
PhysicsBehavior* child = rider->getPhysics();
if (parent && child)
{
Coord3D startingForce = *parent->getVelocity();
Real mass = child->getMass();
startingForce.x *= mass;
startingForce.y *= mass;
startingForce.z *= mass;
child->applyMotiveForce( &startingForce );
Real pitchRate = child->getCenterOfMassOffset() * d->m_exitPitchRate;
child->setPitchRate( pitchRate );
}
}
if (d->m_scatterNearbyOnExit)
scatterToNearbyPosition(rider);
Int mobNexusSlotCount = rider->getTransportSlotCount();
DEBUG_ASSERTCRASH(mobNexusSlotCount > 0, ("This object isnt MobNexusable"));
m_extraSlotsInUse -= mobNexusSlotCount - 1;
DEBUG_ASSERTCRASH(m_extraSlotsInUse >= 0 && m_extraSlotsInUse + getContainCount() <= getContainMax(), ("Bad slot count, MobNexus"));
// when we are empty again, clear the model condition for loaded
if( getContainCount() == 0 )
{
Drawable *draw = getObject()->getDrawable();
if( draw )
draw->clearModelConditionState( MODELCONDITION_LOADED );
} // end if
if (getObject()->isAboveTerrain())
{
// temporarily mark the guy as being allowed to fall
// (overriding his locomotor's stick-to-ground attribute).
// this will be reset (by PhysicsBehavior) when he touches the ground.
PhysicsBehavior* physics = rider->getPhysics();
if (physics)
physics->setAllowToFall(true);
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void MobNexusContain::onObjectCreated()
{
MobNexusContainModuleData* self = (MobNexusContainModuleData*)getMobNexusContainModuleData();
Int count = self->m_initialPayload.count;
const ThingTemplate* payloadTemplate = TheThingFactory->findTemplate( self->m_initialPayload.name );
Object* object = getObject();
for( int i = 0; i < count; i++ )
{
//We are creating a MobNexus that comes with a initial payload, so add it now!
Object* payload = TheThingFactory->newObject( payloadTemplate, object->getControllingPlayer()->getDefaultTeam() );
if( object->getContain() && object->getContain()->isValidContainerFor( payload, true ) )
{
object->getContain()->addToContain( payload );
}
else
{
DEBUG_CRASH( ( "DeliverPayload: PutInContainer %s is full, or not valid for the payload %s!", object->getName().str(), self->m_initialPayload.name.str() ) );
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime MobNexusContain::update()
{
MobNexusContainModuleData *moduleData = (MobNexusContainModuleData*)getModuleData();
if( moduleData && moduleData->m_healthRegen )
{
ContainModuleInterface *contain = getObject()->getContain();
if( contain )
{
//This MobNexus has a health regeneration value, so go through and heal all inside.
const ContainedItemsList* items = contain->getContainedItemsList();
if( items )
{
ContainedItemsList::const_iterator it;
it = items->begin();
while( *it )
{
Object *object = *it;
//Advance to the next iterator
it++;
//Determine if we need healing or not.
BodyModuleInterface *body = object->getBodyModule();
if( body->getHealth() < body->getMaxHealth() )
{
//Calculate the health to be regenerated on each unit.
Real regen = body->getMaxHealth() * moduleData->m_healthRegen / 100.0f * SECONDS_PER_LOGICFRAME_REAL;
//Perform the actual healing for this frame.
// DamageInfo damageInfo;
// damageInfo.in.m_damageType = DAMAGE_HEALING;
// damageInfo.in.m_deathType = DEATH_NONE;
// damageInfo.in.m_sourceID = getObject()->getID();
// damageInfo.in.m_amount = regen;
// object->attemptDamage( &damageInfo );
object->attemptHealing( regen, getObject() );
}
}
}
}
}
return OpenContain::update(); //extend
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ExitDoorType MobNexusContain::reserveDoorForExit( const ThingTemplate* objType, Object *specificObject )
{
if( specificObject == NULL )
return DOOR_1;// I can, in general, exit people.
// This is an override, not an extend. I will check for game legality for
// okaying the call to exitObjectViaDoor.
Object *me = getObject();
// this is present solely for some MobNexuss to override, so that they can land before
// allowing people to exit...
AIUpdateInterface* ai = me->getAIUpdateInterface();
if (ai && ai->getAiFreeToExit(me) != FREE_TO_EXIT)
return DOOR_NONE_AVAILABLE;
// I can always kick people out if I am in the air, I know what I'm doing
if( me->isUsingAirborneLocomotor() )
return DOOR_1;
const Coord3D *myPosition = me->getPosition();
if( !specificObject->getAIUpdateInterface() )
{
return DOOR_NONE_AVAILABLE;
}
const Locomotor *hisLocomotor = specificObject->getAIUpdateInterface()->getCurLocomotor();
// He can't get to this spot naturally, so I can't force him there. (amphib MobNexus)
if( ! TheAI->pathfinder()->validMovementTerrain(me->getLayer(), hisLocomotor, myPosition ) )
return DOOR_NONE_AVAILABLE;
return DOOR_1;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void MobNexusContain::unreserveDoorForExit( ExitDoorType exitDoor )
{
/* nothing */
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool MobNexusContain::tryToEvacuate( Bool exposeStealthedUnits )
{
Bool exitedAnyone = false;
ContainedItemsList::const_iterator it = getContainList().begin();
while( it != getContainList().end() )
{
// get the object
Object *obj = *it;
// increment the iterator, since removal will pull this guy from the list somewhere else
// and we might not actually kick anyone so we don't want to loop forever.
++it;
ExitDoorType exitDoor = reserveDoorForExit(obj->getTemplate(), obj);
if(exitDoor != DOOR_NONE_AVAILABLE)
{
exitObjectViaDoor( obj, exitDoor );
exitedAnyone = true;
if( obj->isKindOf( KINDOF_STEALTH_GARRISON ) && exposeStealthedUnits )
{
StealthUpdate* stealth = obj->getStealth();
if( stealth )
{
stealth->markAsDetected();
}
}
}
}
return exitedAnyone;
}
// ------------------------------------------------------------------------------------------------
/** CRC - I like the word nexus */
// ------------------------------------------------------------------------------------------------
void MobNexusContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void MobNexusContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// extra slots in use
xfer->xferInt( &m_extraSlotsInUse );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void MobNexusContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,638 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: OverlordContain.cpp ////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, September, 2002
// Desc: Contain module that acts as transport normally, but when full it redirects queries to the first passenger
// All of this redirection stuff makes it so that while I am normally a transport
// for Overlord subObjects, once I have a passenger, _I_ become a transport of their type.
// So, the answer to this question depends on if it is my passenger asking, or theirs.
// As always, I can't use convience functions that get redirected on a ? like this.
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "GameClient/ControlBar.h"
#include "GameClient/Drawable.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/OverlordContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
OverlordContainModuleData::OverlordContainModuleData()
{
// m_initialPayload.count = 0;
m_experienceSinkForRider = TRUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void OverlordContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
TransportContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "PayloadTemplateName", INI::parseAsciiStringVectorAppend, NULL, offsetof(OverlordContainModuleData, m_payloadTemplateNameData) },
{ "ExperienceSinkForRider", INI::parseBool, NULL, offsetof(OverlordContainModuleData, m_experienceSinkForRider) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
//void OverlordContainModuleData::parseInitialPayload( INI* ini, void *instance, void *store, const void* /*userData*/ )
//{
// OverlordContainModuleData* self = (OverlordContainModuleData*)instance;
// const char* name = ini->getNextToken();
// const char* countStr = ini->getNextTokenOrNull();
// Int count = countStr ? INI::scanInt(countStr) : 1;
// self->m_initialPayload.name.set(name);
// self->m_initialPayload.count = count;
//}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
OverlordContain::OverlordContain( Thing *thing, const ModuleData *moduleData ) :
TransportContain( thing, moduleData )
{
m_redirectionActivated = FALSE;
m_payloadCreated = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
OverlordContain::~OverlordContain( void )
{
}
void OverlordContain::onObjectCreated( void )
{
OverlordContain::createPayload();
}
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void OverlordContain::createPayload()
{
OverlordContainModuleData* self = (OverlordContainModuleData*)getOverlordContainModuleData();
// Any number of different passengers can be loaded here at init time
Object* object = getObject();
ContainModuleInterface *contain = object->getContain();
if( contain )
{
contain->enableLoadSounds( FALSE );
TemplateNameList list = self->m_payloadTemplateNameData;
TemplateNameIterator iter = list.begin();
while ( iter != list.end() )
{
const ThingTemplate* temp = TheThingFactory->findTemplate( *iter );
if (temp)
{
Object* payload = TheThingFactory->newObject( temp, object->getTeam() );
if( contain->isValidContainerFor( payload, true ) )
{
contain->addToContain( payload );
}
else
{
DEBUG_CRASH( ( "OverlordContain::createPayload: %s is full, or not valid for the payload %s!", object->getName().str(), self->m_initialPayload.name.str() ) );
}
}
++iter;
}
contain->enableLoadSounds( TRUE );
} // endif contain
m_payloadCreated = TRUE;
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void OverlordContain::onBodyDamageStateChange( const DamageInfo* damageInfo,
BodyDamageType oldState,
BodyDamageType newState) ///< state change callback
{
// I can't use any convienience functions, as they will all get routed to the bunker I may carry.
// I want just me.
// Oh, and I don't want this function trying to do death. That is more complicated and will be handled
// on my death.
if( newState != BODY_RUBBLE && m_containListSize == 1 )
{
Object *myGuy = m_containList.front();
myGuy->getBodyModule()->setDamageState( newState );
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ContainModuleInterface *OverlordContain::getRedirectedContain() const
{
// Naturally, I can not use a redirectible convienience function
// to answer if I am redirecting yet.
// If I am empty, say no.
if( m_containListSize < 1 )
return NULL;
if( !m_redirectionActivated )
return NULL;// Shut off early to allow death to happen without my bunker having
// trouble finding me to say goodbye as messages get sucked up the pipe to him.
Object *myGuy = m_containList.front();
if( myGuy )
return myGuy->getContain();
return NULL;// Or say no if they have no contain.
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void OverlordContain::onDie( const DamageInfo *damageInfo )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::onDie( damageInfo );
return;
}
//Everything is fine if I am empty or carrying a regular guy. If I have a redirected contain
// set up, then I need to handle the order of death explicitly, or things will become confused
// when I stop redirecting in the middle of the process. Or I will get confused as my commands
// get sucked up the pipe.
// So this is an extend that lets me control the order of death.
deactivateRedirectedContain();
Object *myGuy = m_containList.front();
myGuy->kill();
TransportContain::onDie( damageInfo );
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::onDelete( void )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::onDelete( );
return;
}
// Without my throwing the redirect switch, teardown deletion will get confused and fire off a bunch of asserts
getRedirectedContain()->removeAllContained();
deactivateRedirectedContain();
removeAllContained();
TransportContain::onDelete( );
}
// ------------------------------------------------------------------------------------------------
void OverlordContain::onCapture( Player *oldOwner, Player *newOwner )
{
if( m_containListSize < 1 )
return;
// Need to capture our specific rider. He will then kick passengers out if he is a Transport
Object *myGuy = m_containList.front();
myGuy->setTeam( newOwner->getDefaultTeam() );
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::isGarrisonable() const
{
if( getRedirectedContain() == NULL )
return FALSE;
return getRedirectedContain()->isGarrisonable();
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::isKickOutOnCapture()
{
if( getRedirectedContain() == NULL )
return FALSE;// Me the Overlord doesn't want to
return getRedirectedContain()->isKickOutOnCapture();
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::addToContainList( Object *obj )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::addToContainList( obj );
return;
}
getRedirectedContain()->addToContainList( obj );
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::addToContain( Object *obj )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::addToContain( obj );
return;
}
getRedirectedContain()->addToContain( obj );
}
//-------------------------------------------------------------------------------------------------
/** Remove 'obj' from the m_containList of objects in this module.
* This will trigger an onRemoving event for the object that this module
* is a part of and an onRemovedFrom event for the object being removed */
//-------------------------------------------------------------------------------------------------
void OverlordContain::removeFromContain( Object *obj, Bool exposeStealthUnits )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::removeFromContain( obj, exposeStealthUnits );
return;
}
getRedirectedContain()->removeFromContain( obj, exposeStealthUnits );
}
//-------------------------------------------------------------------------------------------------
/** Remove all contained objects from the contained list */
//-------------------------------------------------------------------------------------------------
void OverlordContain::removeAllContained( Bool exposeStealthUnits )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::removeAllContained( exposeStealthUnits );
return;
}
const ContainedItemsList *fullList = getRedirectedContain()->getContainedItemsList();
Object *obj;
ContainedItemsList::const_iterator it;
it = (*fullList).begin();
while( it != (*fullList).end() )
{
obj = *it;
it++;
removeFromContain( obj, exposeStealthUnits );
}
}
//-------------------------------------------------------------------------------------------------
/** Iterate the contained list and call the callback on each of the objects */
//-------------------------------------------------------------------------------------------------
void OverlordContain::iterateContained( ContainIterateFunc func, void *userData, Bool reverse )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::iterateContained( func, userData, reverse );
return;
}
getRedirectedContain()->iterateContained( func, userData, reverse );
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::onContaining( Object *obj, Bool wasSelected )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::onContaining( obj, wasSelected );
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) )
{
activateRedirectedContain();//Am now carrying something
// And this contain style explicitly sucks XP from our little friend.
if( getOverlordContainModuleData()->m_experienceSinkForRider && obj->getExperienceTracker() )
obj->getExperienceTracker()->setExperienceSink(getObject()->getID());
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && getObject()->testStatus( OBJECT_STATUS_STEALTHED ) )
{
StealthUpdate *myStealth = obj->getStealth();
if ( myStealth )
{
myStealth->receiveGrant( true );
// note to anyone... once stealth is granted to this gattlingcannon ( or such )
// let its own stealthupdate govern the allowedtostealth cases
// a portable structure never gets removed, so...
}
}
}
return;
}
OpenContain::onContaining( obj, wasSelected );
getRedirectedContain()->onContaining( obj, wasSelected );
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::killAllContained( void )
{
// This is a game call meant to clear actual passengers. We don't want it to kill our turret. That'd be wierd.
if( getRedirectedContain() )
{
getRedirectedContain()->killAllContained();
}
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::onRemoving( Object *obj )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::onRemoving( obj );
return;
}
OpenContain::onRemoving(obj);
getRedirectedContain()->onRemoving( obj );
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::isValidContainerFor(const Object* obj, Bool checkCapacity) const
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
return TransportContain::isValidContainerFor( obj, checkCapacity );
return getRedirectedContain()->isValidContainerFor( obj, checkCapacity );
}
//-------------------------------------------------------------------------------------------------
UnsignedInt OverlordContain::getContainCount() const
{
ContainModuleInterface* redir = getRedirectedContain();
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( redir == NULL )
return TransportContain::getContainCount( );
return redir->getContainCount();
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::getContainerPipsToShow(Int& numTotal, Int& numFull)
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
numTotal = 0;
numFull = 0;
return false;
}
else
{
return getRedirectedContain()->getContainerPipsToShow(numTotal, numFull);
}
}
//-------------------------------------------------------------------------------------------------
Int OverlordContain::getContainMax( ) const
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
return TransportContain::getContainMax( );
return getRedirectedContain()->getContainMax();
}
//-------------------------------------------------------------------------------------------------
const ContainedItemsList* OverlordContain::getContainedItemsList() const
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
return TransportContain::getContainedItemsList( );
return getRedirectedContain()->getContainedItemsList();
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::isEnclosingContainerFor( const Object *obj ) const
{
// All of this redirection stuff makes it so that while I am normally a transport
// for Overlord subObjects, once I have a passenger, _I_ become a transport of their type.
// So, the answer to this question depends on if it is my passenger asking, or theirs.
// As always, I can't use convience functions that get redirected on a ? like this.
if( m_containListSize > 0 && obj == m_containList.front() )
return FALSE;
return TRUE;
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::isDisplayedOnControlBar() const
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
return FALSE;//No need to call up inheritance, this is a module based question, and I say no.
return getRedirectedContain()->isDisplayedOnControlBar();
}
//-------------------------------------------------------------------------------------------------
const Object *OverlordContain::friend_getRider() const
{
// The draw order dependency bug for riders means that our draw module needs to cheat to get
//around it. So this is another function that knows it is getting around redirection to ask
// an Overlord specific function.
if( m_containListSize > 0 )
return m_containList.front();
return NULL;
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::activateRedirectedContain()
{
m_redirectionActivated = TRUE;
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::deactivateRedirectedContain()
{
m_redirectionActivated = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
// if my object gets selected, then my visible passengers should, too
// this gets called from
void OverlordContain::clientVisibleContainedFlashAsSelected()
{
// THIS OVERRIDES GRAHAMS NASTY OVERRIDE THING
// SO WE CAN FLASH THE PORTABLE BUNKER INSTEAD OF ITS OCCUPANTS
const ContainedItemsList* items = TransportContain::getContainedItemsList();
if( items )
{
ContainedItemsList::const_iterator it;
it = items->begin();
while( *it )
{
Object *object = *it;
if ( object && object->isKindOf( KINDOF_PORTABLE_STRUCTURE ) )
{
Drawable *draw = object->getDrawable();
if ( draw )
{
draw->flashAsSelected(); //WOW!
}
}
++it;
}
}
}
Bool OverlordContain::isPassengerAllowedToFire( ObjectID id ) const
{
Object *passenger = TheGameLogic->findObjectByID(id);
if(passenger != NULL)
{
//only allow infantry, and turrets and such. no vehicles.
if(passenger->isKindOf(KINDOF_INFANTRY) == FALSE && passenger->isKindOf(KINDOF_PORTABLE_STRUCTURE) == FALSE)
return FALSE;
}
if ( getObject() && getObject()->getContainedBy() ) // nested containment voids firing, always
return FALSE;
return TransportContain::isPassengerAllowedToFire();
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void OverlordContain::crc( Xfer *xfer )
{
// extend base class
TransportContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void OverlordContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
TransportContain::xfer( xfer );
// redirection activated
xfer->xferBool( &m_redirectionActivated );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void OverlordContain::loadPostProcess( void )
{
// extend base class
TransportContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,791 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: ParachuteContain.cpp //////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, March 2002
// Desc: Contain module for transport units.
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/CRCDebug.h"
#include "Common/Player.h"
#include "Common/RandomValue.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ParachuteContain.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameClient/Drawable.h"
const Real NO_START_Z = 1e10;
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
ParachuteContainModuleData::ParachuteContainModuleData() :
m_pitchRateMax(0),
m_rollRateMax(0),
m_lowAltitudeDamping(0.2f),
m_paraOpenDist(0.0f),
m_freeFallDamagePercent(0.5f),
m_killWhenLandingInWaterSlop(10.0f)
{
}
//-------------------------------------------------------------------------------------------------
void ParachuteContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
OpenContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "PitchRateMax", INI::parseAngularVelocityReal, NULL, offsetof( ParachuteContainModuleData, m_pitchRateMax ) },
{ "RollRateMax", INI::parseAngularVelocityReal, NULL, offsetof( ParachuteContainModuleData, m_rollRateMax ) },
{ "LowAltitudeDamping", INI::parseReal, NULL, offsetof( ParachuteContainModuleData, m_lowAltitudeDamping ) },
{ "ParachuteOpenDist", INI::parseReal, NULL, offsetof( ParachuteContainModuleData, m_paraOpenDist ) },
{ "KillWhenLandingInWaterSlop", INI::parseReal, NULL, offsetof( ParachuteContainModuleData, m_killWhenLandingInWaterSlop ) },
{ "FreeFallDamagePercent", INI::parsePercentToReal, NULL, offsetof( ParachuteContainModuleData, m_freeFallDamagePercent ) },
{ "ParachuteOpenSound", INI::parseAudioEventRTS, NULL, offsetof( ParachuteContainModuleData, m_parachuteOpenSound ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ParachuteContain::ParachuteContain( Thing *thing, const ModuleData *moduleData ) :
OpenContain( thing, moduleData )
{
m_opened = false;
m_needToUpdateParaBones = true;
m_needToUpdateRiderBones = true;
m_pitch = 0;
m_roll = 0;
m_pitchRate = 0;
m_rollRate = 0;
m_isLandingOverrideSet = FALSE;
m_startZ = NO_START_Z;
//Added By Sadullah Nader
//Initializations
m_landingOverride.zero();
m_paraAttachBone.zero();
m_paraAttachOffset.zero();
m_paraSwayBone.zero();
m_paraSwayOffset.zero();
m_riderAttachBone.zero();
m_riderAttachOffset.zero();
m_riderSwayBone.zero();
m_riderSwayOffset.zero();
//
const ParachuteContainModuleData* d = getParachuteContainModuleData();
if (d)
{
m_pitchRate = GameLogicRandomValueReal(-d->m_pitchRateMax, d->m_pitchRateMax);
m_rollRate = GameLogicRandomValueReal(-d->m_rollRateMax, d->m_rollRateMax);
}
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_PARACHUTING ) );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ParachuteContain::~ParachuteContain( void )
{
}
//-------------------------------------------------------------------------------------------------
/**
this is called whenever a drawable is bound to the object.
drawable is NOT guaranteed to be non-null.
*/
void ParachuteContain::onDrawableBoundToObject()
{
Drawable* draw = getObject()->getDrawable();
if (draw)
draw->setDrawableHidden(!m_opened);
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::calcSwayMtx(const Coord3D* offset, Matrix3D* mtx)
{
mtx->Make_Identity();
mtx->Translate(offset->x, offset->y, offset->z);
mtx->In_Place_Pre_Rotate_X(m_roll);
mtx->In_Place_Pre_Rotate_Y(m_pitch);
mtx->Translate(-offset->x, -offset->y, -offset->z);
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::updateBonePositions()
{
if (m_needToUpdateParaBones)
{
m_needToUpdateParaBones = false; // yeah, even if not found.
Drawable* parachuteDraw = getObject()->getDrawable();
if (parachuteDraw)
{
if (parachuteDraw->getPristineBonePositions( "PARA_COG", 0, &m_paraSwayBone, NULL, 1) != 1)
{
DEBUG_CRASH(("PARA_COG not found\n"));
m_paraSwayBone.zero();
}
if (parachuteDraw->getPristineBonePositions( "PARA_ATTCH", 0, &m_paraAttachBone, NULL, 1 ) != 1)
{
DEBUG_CRASH(("PARA_ATTCH not found\n"));
m_paraAttachBone.zero();
}
}
//DEBUG_LOG(("updating para bone positions %d...\n",TheGameLogic->getFrame()));
}
if (m_needToUpdateRiderBones)
{
m_needToUpdateRiderBones = false; // yeah, even if not found.
Object* rider = (getContainCount() > 0) ? getContainList().front() : NULL;
Drawable* riderDraw = rider ? rider->getDrawable() : NULL;
if (riderDraw)
{
if (riderDraw->getPristineBonePositions( "PARA_MAN", 0, &m_riderAttachBone, NULL, 1) != 1)
{
//DEBUG_LOG(("*** No parachute-attach bone... using object height!\n"));
m_riderAttachBone.zero();
m_riderAttachBone.z += riderDraw->getDrawableGeometryInfo().getMaxHeightAbovePosition();
}
}
//DEBUG_LOG(("updating rider bone positions %d...\n",TheGameLogic->getFrame()));
}
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::updateOffsetsFromBones()
{
const Coord3D* objPos = getObject()->getPosition();
getObject()->convertBonePosToWorldPos(&m_paraSwayBone, NULL, &m_paraSwayOffset, NULL);
m_paraSwayOffset.x -= objPos->x;
m_paraSwayOffset.y -= objPos->y;
m_paraSwayOffset.z -= objPos->z;
getObject()->convertBonePosToWorldPos(&m_paraAttachBone, NULL, &m_paraAttachOffset, NULL);
m_paraAttachOffset.x -= objPos->x;
m_paraAttachOffset.y -= objPos->y;
m_paraAttachOffset.z -= objPos->z;
Object* rider = (getContainCount() > 0) ? getContainList().front() : NULL;
if (rider)
{
const Coord3D* riderPos = rider->getPosition();
rider->convertBonePosToWorldPos(&m_riderAttachBone, NULL, &m_riderAttachOffset, NULL);
m_riderAttachOffset.x -= riderPos->x;
m_riderAttachOffset.y -= riderPos->y;
m_riderAttachOffset.z -= riderPos->z;
m_riderAttachOffset.x = m_paraAttachOffset.x - m_riderAttachOffset.x;
m_riderAttachOffset.y = m_paraAttachOffset.y - m_riderAttachOffset.y;
m_riderAttachOffset.z = m_paraAttachOffset.z - m_riderAttachOffset.z;
m_riderSwayOffset.x = m_paraSwayOffset.x - m_riderAttachOffset.x;
m_riderSwayOffset.y = m_paraSwayOffset.y - m_riderAttachOffset.y;
m_riderSwayOffset.z = m_paraSwayOffset.z - m_riderAttachOffset.z;
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/**
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 ParachuteContain::isValidContainerFor(const Object* rider, Bool checkCapacity) const
{
if (!rider)
return false;
// extend functionality
if( OpenContain::isValidContainerFor( rider, checkCapacity ) == false )
return false;
Int transportSlotCount = rider->getTransportSlotCount();
// if 0, this object isn't transportable.
// (exception: infantry are always transportable by parachutes, regardless
// of this.... this allows us to paradrop pilots, but not transport them
// by other means)
if (transportSlotCount == 0 && !rider->isKindOf(KINDOF_INFANTRY) && !rider->isKindOf(KINDOF_PARACHUTABLE))
return false;
// we can only "hold" one item at a time.
if (getContainCount() > 0)
return false;
return true;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void ParachuteContain::containReactToTransformChange()
{
// a bit of a cheese festival here... hidden is a flag, not a counter, so when we are dumped
// from a plane, we might get drawn for a frame before our update is called, meaning we
// should be briefly visible. put this here to ensure we stay hidden appropriately.
Drawable* draw = getObject()->getDrawable();
if (draw)
draw->setDrawableHidden(!m_opened);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime ParachuteContain::update( void )
{
OpenContain::update();
Object* parachute = getObject();
if( parachute->isDisabledByType( DISABLED_HELD ) )
{
return UPDATE_SLEEP_NONE; // my, that was easy
}
AIUpdateInterface *parachuteAI = parachute->getAI();
Drawable* draw = parachute->getDrawable();
const ParachuteContainModuleData* d = getParachuteContainModuleData();
Object* rider = (getContainCount() > 0) ? getContainList().front() : NULL;
if (m_startZ == NO_START_Z) {
m_startZ = parachute->getPosition()->z;
Real groundHeight = TheTerrainLogic->getGroundHeight(parachute->getPosition()->x, parachute->getPosition()->y);
if (m_startZ-groundHeight < 2*d->m_paraOpenDist) {
// Oh dear - we ejected too close to the ground, and there isn't enough
// room to open the chute. Well, since it's only a game, we'll fudge
// a little so that the pilot doesn't slam into the ground & stick.
m_startZ = groundHeight+2*d->m_paraOpenDist;
}
}
if (!m_opened)
{
// see if we need to open.
if (fabs(m_startZ - parachute->getPosition()->z) >= d->m_paraOpenDist)
{
m_opened = true;
parachute->clearAndSetModelConditionState(MODELCONDITION_FREEFALL, MODELCONDITION_PARACHUTING);
m_needToUpdateParaBones = true;
if (rider)
{
rider->clearAndSetModelConditionState(MODELCONDITION_FREEFALL, MODELCONDITION_PARACHUTING);
m_needToUpdateRiderBones = true;
AudioEventRTS soundToPlay = d->m_parachuteOpenSound;
soundToPlay.setObjectID( rider->getID() );
TheAudio->addAudioEvent( &soundToPlay );
}
// When a parachute opens, it should look for a good place to land. This could be explicitly set
// by a DeliverPayload, otherwise any place clear is good.
if( parachuteAI )
{
Coord3D target = *parachute->getPosition();
if( m_isLandingOverrideSet )
{
target = m_landingOverride;
if( parachuteAI->getCurLocomotor() )
parachuteAI->getCurLocomotor()->setUltraAccurate( TRUE );
}
else
{
FindPositionOptions fpOptions;
fpOptions.minRadius = 0.0f;
fpOptions.maxRadius = 100.0f;
fpOptions.relationshipObject = NULL;
fpOptions.flags = FPF_NONE;
ThePartitionManager->findPositionAround( &target, &fpOptions, &target );
}
parachuteAI->aiMoveToPosition( &target, CMD_FROM_AI );
}
}
else if ( rider )
rider->clearAndSetModelConditionState( MODELCONDITION_PARACHUTING, MODELCONDITION_FREEFALL );
}
draw->setDrawableHidden(!m_opened);
if (!m_opened || getContainCount() == 0)
{
// unopened, or empty, chutes, don't collide with anything, to simplify
// ejections, paradrops, landings, etc...
parachute->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
if (rider)
rider->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
}
else
{
// opened/nonempty chutes DO collide...
parachute->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
if (rider)
rider->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
}
AIUpdateInterface* ai = parachute->getAIUpdateInterface();
if (ai && !parachute->isEffectivelyDead())
{
ai->chooseLocomotorSet(m_opened ? LOCOMOTORSET_NORMAL : LOCOMOTORSET_FREEFALL);
Locomotor* locomotor = ai->getCurLocomotor();
if (locomotor)
{
// damp the swaying a bunch when we get close, so that things land vertically (or nearly so)
Real altitudeDamping = 0;
if (getContainCount() > 0)
{
Object* rider = getContainList().front();
const Real ALTITUDE_DAMP_START = 20.0f;
if (rider->getHeightAboveTerrain() <= ALTITUDE_DAMP_START)
altitudeDamping = d->m_lowAltitudeDamping;
}
if (m_opened)
{
const Real PITCH_STIFFNESS = locomotor->getPitchStiffness();
const Real ROLL_STIFFNESS = locomotor->getRollStiffness();
const Real PITCH_DAMPING = locomotor->getPitchDamping() + altitudeDamping;
const Real ROLL_DAMPING = locomotor->getRollDamping() + altitudeDamping;
m_pitchRate += ((-PITCH_STIFFNESS * m_pitch) + (-PITCH_DAMPING * m_pitchRate)); // spring/damper
m_rollRate += ((-ROLL_STIFFNESS * m_roll) + (-ROLL_DAMPING * m_rollRate)); // spring/damper
m_pitch += m_pitchRate;
m_roll += m_rollRate;
if( m_isLandingOverrideSet )
{
// Need to wait until after opening to do this, or else we are sending the message to the Freefall locomotor.
locomotor->setCloseEnoughDist( 10.0 );
locomotor->setCloseEnoughDist3D( FALSE );
}
}
if (draw)
{
updateBonePositions();
updateOffsetsFromBones();
Matrix3D tmp;
calcSwayMtx(&m_paraSwayOffset, &tmp);
draw->setInstanceMatrix(&tmp);
}
positionContainedObjectsRelativeToContainer();
}
}
// allow us to land on bridges!
const Coord3D* paraPos = getObject()->getPosition();
PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(paraPos);
getObject()->setLayer(newLayer);
if (rider)
rider->setLayer(newLayer);
// If we have lost our passenger for whatever reason, die early. Otherwise we just sit around forever.
if( getContainCount() == 0 )
getObject()->kill();
// the collide system doesn't always collide us with the ground if we fall into water.
// so force the issue.
Real waterZ;
if (!getObject()->isEffectivelyDead()
&& getObject()->getLayer() == LAYER_GROUND
&& TheTerrainLogic->isUnderwater(paraPos->x, paraPos->y, &waterZ)
&& (paraPos->z - waterZ) < d->m_killWhenLandingInWaterSlop)
{
getObject()->kill();
}
return UPDATE_SLEEP_NONE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void ParachuteContain::onContaining( Object *rider, Bool wasSelected )
{
OpenContain::onContaining( rider, wasSelected );
// objects inside a transport are held
rider->setDisabled( DISABLED_HELD );
rider->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_PARACHUTING ) );
rider->clearAndSetModelConditionState(MODELCONDITION_PARACHUTING, MODELCONDITION_FREEFALL);
m_needToUpdateRiderBones = true;
// position him correctly.
positionRider(rider);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void ParachuteContain::onRemoving( Object *rider )
{
OpenContain::onRemoving(rider);
const ParachuteContainModuleData* d = getParachuteContainModuleData();
// object is no longer held inside a transport
rider->clearDisabled( DISABLED_HELD );
rider->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_PARACHUTING ) );
// mark parachute as "no-collisions"... it is just ephemeral at this point,
// and having the chute collide with the soldier (and both bounce apart) is
// just dumb-lookin'...
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
// position him correctly.
positionRider(rider);
rider->clearModelConditionFlags(MAKE_MODELCONDITION_MASK2(MODELCONDITION_FREEFALL, MODELCONDITION_PARACHUTING));
m_needToUpdateRiderBones = true;
// temporarily mark the guy as being allowed to fall
// (overriding his locomotor's stick-to-ground attribute).
// this will be reset (by PhysicsBehavior) when he touches the ground.
PhysicsBehavior* physics = rider->getPhysics();
if (physics)
{
physics->setAllowToFall(true);
Coord3D force;
force.zero();
physics->applyForce(&force); // force its physics to wake up... should be done when DISABLED_HELD is cleared, but it not, and scared to do it now.
}
AIUpdateInterface* riderAI = rider->getAIUpdateInterface();
if (riderAI)
{
Player* controller = rider->getControllingPlayer();
if (controller && controller->isSkirmishAIPlayer())
{
riderAI->aiHunt(CMD_FROM_AI); // hunt, as per Dustin's request.
}
else
{
bool hasRallyPoint = false;
// Get the transport of the rider
Object *transport = TheGameLogic->findObjectByID(rider->getProducerID());
if (transport)
{
// Get the building that produced the transport
Object *transportProducer = TheGameLogic->findObjectByID(transport->getProducerID());
if (transportProducer)
{
// See if we need to set a rally point for the object being parachuted
ExitInterface* exitInterface = transportProducer->getObjectExitInterface();
if (exitInterface && exitInterface->useSpawnRallyPoint())
{
exitInterface->exitObjectViaDoor(rider, DOOR_1);
hasRallyPoint = true;
}
}
}
if (!hasRallyPoint)
riderAI->aiIdle(CMD_FROM_AI); // become idle.
}
}
// if we land in the water, we die. alas.
const Coord3D* riderPos = rider->getPosition();
Real waterZ, terrainZ;
if (TheTerrainLogic->isUnderwater(riderPos->x, riderPos->y, &waterZ, &terrainZ)
&& riderPos->z <= waterZ + d->m_killWhenLandingInWaterSlop
&& rider->getLayer() == LAYER_GROUND)
{
// don't call kill(); do it manually, so we can specify DEATH_FLOODED
DamageInfo damageInfo;
damageInfo.in.m_damageType = DAMAGE_WATER; // use this instead of UNRESISTABLE so we don't get a dusty damage effect
damageInfo.in.m_deathType = DEATH_FLOODED;
damageInfo.in.m_sourceID = INVALID_ID;
damageInfo.in.m_amount = HUGE_DAMAGE_AMOUNT;
rider->attemptDamage( &damageInfo );
}
// Kill if we landed on impassable ground
Int cellX = REAL_TO_INT( rider->getPosition()->x / PATHFIND_CELL_SIZE );
Int cellY = REAL_TO_INT( rider->getPosition()->y / PATHFIND_CELL_SIZE );
PathfindCell* cell = TheAI->pathfinder()->getCell( rider->getLayer(), cellX, cellY );
PathfindCell::CellType cellType = cell ? cell->getType() : PathfindCell::CELL_IMPASSABLE;
// If we land outside the map from a faulty parachute, we die too.
// Otherwise we exist outside the PartitionManger like a cheater.
if( rider->isOffMap()
|| (cellType == PathfindCell::CELL_CLIFF)
|| (cellType == PathfindCell::CELL_WATER)
|| (cellType == PathfindCell::CELL_IMPASSABLE) )
{
// The Paradrop command was legal, the parachute destination was legal, but the parachute
// can still fail to adjust back on the map. SO this is the place to cap the cheater.
rider->kill();
}
// Note: for future enhancement of this feature, we should test the object against the cell type he is on,
// using obj->getAI()->hasLocomotorForSurface( __ ). We cshould not assume here that the parachutist can not
// find happiness on cliffs or water or whatever.
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::positionRider(Object* rider)
{
updateBonePositions();
updateOffsetsFromBones();
Coord3D pos = *getObject()->getPosition();
///DUMPCOORD3D(&pos);
pos.x += m_riderAttachOffset.x;
pos.y += m_riderAttachOffset.y;
pos.z += m_riderAttachOffset.z;
//DUMPCOORD3D(&pos);
rider->setPosition(&pos);
Real alt = rider->getHeightAboveTerrain();
if (alt < 0.0f)
{
// don't let him go below ground.
pos.z -= alt;
rider->setPosition(&pos);
}
rider->setOrientation(getObject()->getOrientation());
Drawable* draw = rider->getDrawable();
if (draw)
{
if( rider->isDisabledByType( DISABLED_HELD ) )
{
Matrix3D tmp;
calcSwayMtx(&m_riderSwayOffset, &tmp);
draw->setInstanceMatrix(&tmp);
}
else
{
draw->setInstanceMatrix(NULL);
}
}
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::positionContainedObjectsRelativeToContainer()
{
for(ContainedItemsList::const_iterator it = getContainList().begin(); it != getContainList().end(); ++it)
{
positionRider(*it);
}
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::setOverrideDestination( const Coord3D *override )
{
// Instead of trying to float straight down, I am going to nail this spot.
m_landingOverride = *override;
m_isLandingOverrideSet = TRUE;
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::onDie( const DamageInfo * damageInfo )
{
// if we are airborne when killed, the guy falls screaming to his death...
if (getObject()->isSignificantlyAboveTerrain())
{
Object* rider = (getContainCount() > 0) ? getContainList().front() : NULL;
if (rider)
{
removeAllContained();
const ParachuteContainModuleData* d = getParachuteContainModuleData();
if (d->m_freeFallDamagePercent > 0.0f)
{
// do some damage just for losing your parachute.
// not very realistic, but practical to help ensure that
// you really do die from going "splat" on the ground.
DamageInfo extraDamageInfo;
extraDamageInfo.in.m_damageType = DAMAGE_FALLING;
extraDamageInfo.in.m_deathType = DEATH_SPLATTED;
extraDamageInfo.in.m_sourceID = damageInfo->in.m_sourceID;
extraDamageInfo.in.m_amount = rider->getBodyModule()->getMaxHealth() * d->m_freeFallDamagePercent;
rider->attemptDamage(&extraDamageInfo);
}
PhysicsBehavior* physics = rider->getPhysics();
if (physics)
{
physics->setAllowToFall(true);
physics->setIsInFreeFall(true); // bwah ha ha
Coord3D force;
force.zero();
physics->applyForce(&force); // force its physics to wake up... should be done when DISABLED_HELD is cleared, but it not, and scared to do it now.
}
}
}
OpenContain::onDie(damageInfo);
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
{
// Note that other == null means "collide with ground"
if( other == NULL )
{
// if we're in a container (eg, a transport plane), just ignore this...
if( getObject()->getContainedBy() != NULL )
return;
removeAllContained();
// TheGameLogic->destroyObject(obj);
// kill it, so that the chute's SlowDeath will trigger!
getObject()->kill();
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ParachuteContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ParachuteContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// pitch
xfer->xferReal( &m_pitch );
// roll
xfer->xferReal( &m_roll );
// pitch rage
xfer->xferReal( &m_pitchRate );
// roll rate
xfer->xferReal( &m_rollRate );
// start Z
xfer->xferReal( &m_startZ );
// is landing override set
xfer->xferBool( &m_isLandingOverrideSet );
// landing override
xfer->xferCoord3D( &m_landingOverride );
// rider attach bone
xfer->xferCoord3D( &m_riderAttachBone );
// rider sway bone
xfer->xferCoord3D( &m_riderSwayBone );
// para attach bone
xfer->xferCoord3D( &m_paraAttachBone );
// para sway bone
xfer->xferCoord3D( &m_paraSwayBone );
// rider attach offset
xfer->xferCoord3D( &m_riderAttachOffset );
// rider sway offset
xfer->xferCoord3D( &m_riderSwayOffset );
// para attach offset
xfer->xferCoord3D( &m_paraAttachOffset );
// para sway offset
xfer->xferCoord3D( &m_paraSwayOffset );
// need to update rider bones
xfer->xferBool( &m_needToUpdateRiderBones );
// need to update para bones
xfer->xferBool( &m_needToUpdateParaBones );
// opened
xfer->xferBool( &m_opened );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ParachuteContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,158 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: RailedTransportContain.cpp ///////////////////////////////////////////////////////////////
// Author: Colin Day, August 2002
// Desc: Railed Transport Contain Module
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/RailedTransportContain.h"
#include "GameLogic/Module/RailedTransportDockUpdate.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
static RailedTransportDockUpdateInterface *getRailedTransportDockUpdateInterface( Object *obj )
{
// sanity
if( obj == NULL )
return NULL;
// find us our dock interface
RailedTransportDockUpdateInterface *rtdui = NULL;
for( BehaviorModule **u = obj->getBehaviorModules(); *u; ++u )
if( (rtdui = (*u)->getRailedTransportDockUpdateInterface()) != NULL )
break;
return rtdui;
} // end getRailedTransportDockUpdateInterface
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
RailedTransportContain::RailedTransportContain( Thing *thing, const ModuleData *moduleData )
: TransportContain( thing, moduleData )
{
} // end RailedTransportContain
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
RailedTransportContain::~RailedTransportContain( void )
{
} // end ~RailedTransportContain
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RailedTransportContain::onRemoving( Object *obj )
{
// extend functionality
TransportContain::onRemoving( obj );
// once we have removed all our contents we are "open" for docking again for more transportation
if( getContainCount() == 0 )
{
DockUpdateInterface *dui = getObject()->getDockUpdateInterface();
if( dui )
dui->setDockOpen( TRUE );
} // end if
} // end onRemoving
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool RailedTransportContain::isSpecificRiderFreeToExit( Object *obj )
{
Object *us = getObject();
// if we are in transit we cannot exit, when we're in transit, the dock is closed
DockUpdateInterface *dui = us->getDockUpdateInterface();
if( dui && dui->isDockOpen() == FALSE )
return FALSE;
// we can now exit, note we're not extending the base class cause *we* handle it all
return TRUE;
} // end isSpecificRiderFreeToExit
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RailedTransportContain::exitObjectViaDoor( Object *newObj, ExitDoorType exitDoor )
{
RailedTransportDockUpdateInterface *rtdui = getRailedTransportDockUpdateInterface();
if( rtdui == NULL )
return;
// tell the railed dock to exit ONE object, this one
rtdui->unloadSingleObject( newObj );
} // end exitObjectViaDoor
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void RailedTransportContain::crc( Xfer *xfer )
{
// extend base class
TransportContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void RailedTransportContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
TransportContain::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void RailedTransportContain::loadPostProcess( void )
{
// extend base class
TransportContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,495 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (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! <Design Spec>
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

View File

@@ -0,0 +1,709 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: TransportContain.cpp //////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, March 2002
// Desc: Contain module for transport units.
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.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/TransportContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/Weapon.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
TransportContainModuleData::TransportContainModuleData()
{
m_slotCapacity = 0;
m_scatterNearbyOnExit = true;
m_orientLikeContainerOnExit = false;
m_keepContainerVelocityOnExit = false;
m_goAggressiveOnExit = FALSE;
m_armedRidersUpgradeWeaponSet = FALSE;
m_resetMoodCheckTimeOnExit = true;
m_destroyRidersWhoAreNotFreeToExit = false;
m_exitPitchRate = 0.0f;
m_initialPayload.count = 0;
m_healthRegen = 0.0f;
m_exitDelay = 0;
m_isDelayExitInAir = FALSE;
//
// by default we say that transports can have infantry inside them, this will be totally
// overwritten by any data provided from the INI entry tho
//
m_allowInsideKindOf = MAKE_KINDOF_MASK( KINDOF_INFANTRY );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContainModuleData::parseInitialPayload( INI* ini, void *instance, void *store, const void* /*userData*/ )
{
TransportContainModuleData* self = (TransportContainModuleData*)instance;
const char* name = ini->getNextToken();
const char* countStr = ini->getNextTokenOrNull();
Int count = countStr ? INI::scanInt(countStr) : 1;
self->m_initialPayload.name.set(name);
self->m_initialPayload.count = count;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
OpenContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "Slots", INI::parseInt, NULL, offsetof( TransportContainModuleData, m_slotCapacity ) },
{ "ScatterNearbyOnExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_scatterNearbyOnExit ) },
{ "OrientLikeContainerOnExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_orientLikeContainerOnExit ) },
{ "KeepContainerVelocityOnExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_keepContainerVelocityOnExit ) },
{ "GoAggressiveOnExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_goAggressiveOnExit ) },
{ "ResetMoodCheckTimeOnExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_resetMoodCheckTimeOnExit ) },
{ "DestroyRidersWhoAreNotFreeToExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_destroyRidersWhoAreNotFreeToExit ) },
{ "ExitBone", INI::parseAsciiString, NULL, offsetof( TransportContainModuleData, m_exitBone ) },
{ "ExitPitchRate", INI::parseAngularVelocityReal, NULL, offsetof( TransportContainModuleData, m_exitPitchRate ) },
{ "InitialPayload", parseInitialPayload, NULL, 0 },
{ "HealthRegen%PerSec", INI::parseReal, NULL, offsetof( TransportContainModuleData, m_healthRegen ) },
{ "ExitDelay", INI::parseDurationUnsignedInt, NULL, offsetof( TransportContainModuleData, m_exitDelay ) },
{ "ArmedRidersUpgradeMyWeaponSet", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_armedRidersUpgradeWeaponSet ) },
{ "DelayExitInAir", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_isDelayExitInAir ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Int TransportContain::getContainMax( void ) const
{
if (getTransportContainModuleData())
return getTransportContainModuleData()->m_slotCapacity;
return 0;
}
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TransportContain::TransportContain( Thing *thing, const ModuleData *moduleData ) :
OpenContain( thing, moduleData )
{
m_extraSlotsInUse = 0;
m_frameExitNotBusy = 0;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TransportContain::~TransportContain( 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 TransportContain::isValidContainerFor(const Object* rider, Bool checkCapacity) const
{
// sanity
if (!rider)
return false;
// The point of this new code is to determine when something is a "fake" container, to
// look at the object inside of it to use that as the valid check. There is a case, when a
// paratrooper (an infantry contained in a parachute). In this case, when we pass this object
// to contain in a transport plane, we want to check the infantry, not the parachute.
if (rider->getContain() && rider->getContain()->isSpecialZeroSlotContainer())
{
// Report the first thing inside it!
const ContainedItemsList *items = rider->getContain()->getContainedItemsList();
if (items && !items->empty())
{
if (items->front())
{
// Replace the object we are checking with the *first* object contained within it.
rider = items->front();
}
}
}
// extend functionality
if( OpenContain::isValidContainerFor( rider, checkCapacity ) == false )
return false;
// // only allied objects can be transported.
// // order matters: we want to know if I consider it to be an ally, not vice versa
// if (getObject()->getRelationship(rider) != ALLIES)
// return false;
// no... actually, only OUR OWN units can be transported.
if (rider->getControllingPlayer() != getObject()->getControllingPlayer())
return false;
Int transportSlotCount = rider->getTransportSlotCount();
// if 0, this object isn't transportable.
if (transportSlotCount == 0)
return false;
if (checkCapacity)
{
Int containMax = getContainMax();
Int containCount = getContainCount();
return (m_extraSlotsInUse + containCount + transportSlotCount <= containMax);
}
else
{
return true;
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void TransportContain::letRidersUpgradeWeaponSet( void )
{
const TransportContainModuleData * d = getTransportContainModuleData();
if ( ! d->m_armedRidersUpgradeWeaponSet )
return;
Object *self = getObject();
if ( self == NULL )
return;
Bool anyRiderHasViableWeapon = FALSE;
const ContainedItemsList* riderList = getContainedItemsList();
if( riderList )
{
ContainedItemsList::const_iterator it;
it = riderList->begin();
while( *it )
{
Object *rider = *it;
//Advance to the next iterator
it++;
if ( rider )
{
if(rider->isKindOf(KINDOF_INFANTRY) == false)
continue;
Weapon *weapon = NULL;
for ( Int w = PRIMARY_WEAPON; w < WEAPONSLOT_COUNT; ++ w )
{
weapon = rider->getWeaponInWeaponSlot( (WeaponSlotType)w );
if ( weapon )
{
if ( weapon->getTemplate()->isContactWeapon() == FALSE && weapon->isDamageWeapon() == TRUE ) // THIS MAY NEED TO CHECK MORE WEAPON ATTRIBUTES TO WORK BEST
{
anyRiderHasViableWeapon = TRUE;
break;
}
}
}
}//end if rider
}
}
if ( anyRiderHasViableWeapon )
self->setWeaponSetFlag( WEAPONSET_PLAYER_UPGRADE );
else
self->clearWeaponSetFlag( WEAPONSET_PLAYER_UPGRADE );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void TransportContain::onContaining( Object *rider, Bool wasSelected )
{
OpenContain::onContaining( rider, wasSelected );
// objects inside a transport are held
rider->setDisabled( DISABLED_HELD );
Int transportSlotCount = rider->getTransportSlotCount();
DEBUG_ASSERTCRASH(transportSlotCount > 0, ("Hmm, this object isnt transportable"));
m_extraSlotsInUse += transportSlotCount - 1;
DEBUG_ASSERTCRASH(m_extraSlotsInUse >= 0 && m_extraSlotsInUse + getContainCount() <= getContainMax(), ("Hmm, bad slot count"));
//
// when we go from holding nothing to holding something we have a model condition
// to visually show the change
//
if( getContainCount() == 1 )
{
Drawable *draw = getObject()->getDrawable();
if( draw )
draw->setModelConditionState( MODELCONDITION_LOADED );
} // end if
if ( getTransportContainModuleData()->m_armedRidersUpgradeWeaponSet )
letRidersUpgradeWeaponSet();
//Kris: October 20, 2003 - Patch 1.01
//Force Jarmen Kell to transfer weapon timer for snipe to and from the combat bike.
if( getObject()->isKindOf( KINDOF_CLIFF_JUMPER ) && rider->isKindOf( KINDOF_HERO ) && rider->isKindOf( KINDOF_SALVAGER ) )
{
//Admittedly brutal hack, but considering the state of the game (post-ship), this is a particularly surgical fix.
Weapon *bikeWeapon = getObject()->getWeaponInWeaponSlot( SECONDARY_WEAPON );
Weapon *riderWeapon = rider->getWeaponInWeaponSlot( SECONDARY_WEAPON );
if( bikeWeapon && riderWeapon )
{
//Transfer the reload time from the rider to the bike
bikeWeapon->transferNextShotStatsFrom( *riderWeapon );
}
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void TransportContain::onRemoving( Object *rider )
{
OpenContain::onRemoving(rider);
// object is no longer held inside a transport
rider->clearDisabled( DISABLED_HELD );
const TransportContainModuleData* d = getTransportContainModuleData();
if (!d->m_exitBone.isEmpty())
{
Drawable* draw = getObject()->getDrawable();
if (draw)
{
Coord3D bonePos, worldPos;
if (draw->getPristineBonePositions(d->m_exitBone.str(), 0, &bonePos, NULL, 1) == 1)
{
getObject()->convertBonePosToWorldPos(&bonePos, NULL, &worldPos, NULL);
rider->setPosition(&worldPos);
}
}
}
if (d->m_orientLikeContainerOnExit)
{
rider->setOrientation(getObject()->getOrientation());
}
if (d->m_keepContainerVelocityOnExit)
{
PhysicsBehavior* parent = getObject()->getPhysics();
PhysicsBehavior* child = rider->getPhysics();
if (parent && child)
{
Coord3D startingForce = *parent->getVelocity();
Real mass = child->getMass();
startingForce.x *= mass;
startingForce.y *= mass;
startingForce.z *= mass;
child->applyMotiveForce( &startingForce );
Real pitchRate = child->getCenterOfMassOffset() * d->m_exitPitchRate;
child->setPitchRate( pitchRate );
}
}
Int transportSlotCount = rider->getTransportSlotCount();
DEBUG_ASSERTCRASH(transportSlotCount > 0, ("Hmm, this object isnt transportable"));
m_extraSlotsInUse -= transportSlotCount - 1;
#if (defined(_DEBUG) || defined(_INTERNAL))
UnsignedInt containCount = getContainCount();
UnsignedInt containMax = getContainMax();
DEBUG_ASSERTCRASH(m_extraSlotsInUse >= 0 && m_extraSlotsInUse + containCount <= containMax, ("Hmm, bad slot count"));
#endif
// when we are empty again, clear the model condition for loaded
if( getContainCount() == 0 )
{
Drawable *draw = getObject()->getDrawable();
if( draw )
draw->clearModelConditionState( MODELCONDITION_LOADED );
} // end if
if (getObject()->isAboveTerrain())
{
// temporarily mark the guy as being allowed to fall
// (overriding his locomotor's stick-to-ground attribute).
// this will be reset (by PhysicsBehavior) when he touches the ground.
PhysicsBehavior* physics = rider->getPhysics();
if (physics)
physics->setAllowToFall(true);
}
// AI might need help using this transport in a good way. Make the passengers aggressive.
//There is no computer player check since Aggressive only means something for computer players anyway
if( d->m_goAggressiveOnExit && rider->getAI() )
{
rider->getAI()->setAttitude( AI_AGGRESSIVE );
}
if (getObject()->isEffectivelyDead()) {
scatterToNearbyPosition(rider);
}
if( d->m_resetMoodCheckTimeOnExit && rider->getAI() )
{
rider->getAI()->wakeUpAndAttemptToTarget();
}
m_frameExitNotBusy = TheGameLogic->getFrame() + d->m_exitDelay;
if ( d->m_armedRidersUpgradeWeaponSet )
letRidersUpgradeWeaponSet();
//Kris: October 20, 2003 - Patch 1.01
//Force Jarmen Kell to transfer weapon timer for snipe to and from the combat bike.
if( getObject()->isKindOf( KINDOF_CLIFF_JUMPER ) && rider->isKindOf( KINDOF_HERO ) && rider->isKindOf( KINDOF_SALVAGER ) )
{
//Admittedly brutal hack, but considering the state of the game (post-ship), this is a particularly surgical fix.
Weapon *bikeWeapon = getObject()->getWeaponInWeaponSlot( SECONDARY_WEAPON );
Weapon *riderWeapon = rider->getWeaponInWeaponSlot( SECONDARY_WEAPON );
if( bikeWeapon && riderWeapon )
{
//Transfer the reload time from the bike to the rider
riderWeapon->transferNextShotStatsFrom( *bikeWeapon );
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContain::createPayload()
{
TransportContainModuleData* self = (TransportContainModuleData*)getTransportContainModuleData();
Int count = self->m_initialPayload.count;
const ThingTemplate* payloadTemplate = TheThingFactory->findTemplate( self->m_initialPayload.name );
Object* object = getObject();
ContainModuleInterface *contain = object->getContain();
if( contain )
{
contain->enableLoadSounds( FALSE );
for( int i = 0; i < count; i++ )
{
//We are creating a transport that comes with a initial payload, so add it now!
Object* payload = TheThingFactory->newObject( payloadTemplate, object->getControllingPlayer()->getDefaultTeam() );
if( contain->isValidContainerFor( payload, true ) )
{
contain->addToContain( payload );
}
else
{
DEBUG_CRASH( ( "DeliverPayload: PutInContainer %s is full, or not valid for the payload %s!", object->getName().str(), self->m_initialPayload.name.str() ) );
}
}
contain->enableLoadSounds( TRUE );
}
m_payloadCreated = TRUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime TransportContain::update()
{
const TransportContainModuleData *moduleData = getTransportContainModuleData();
if( m_payloadCreated == FALSE )
createPayload();
if( moduleData && moduleData->m_healthRegen )
{
ContainModuleInterface *contain = getObject()->getContain();
if( contain )
{
//This transport has a health regeneration value, so go through and heal all inside.
const ContainedItemsList* items = contain->getContainedItemsList();
if( items )
{
ContainedItemsList::const_iterator it;
it = items->begin();
while( *it )
{
Object *object = *it;
//Advance to the next iterator
it++;
//Determine if we need healing or not.
BodyModuleInterface *body = object->getBodyModule();
if( body->getHealth() < body->getMaxHealth() )
{
//Calculate the health to be regenerated on each unit.
Real regen = body->getMaxHealth() * moduleData->m_healthRegen / 100.0f * SECONDS_PER_LOGICFRAME_REAL;
//Perform the actual healing for this frame.
// DamageInfo damageInfo;
// damageInfo.in.m_damageType = DAMAGE_HEALING;
// damageInfo.in.m_deathType = DEATH_NONE;
// damageInfo.in.m_sourceID = getObject()->getID();
// damageInfo.in.m_amount = regen;
// object->attemptDamage( &damageInfo );
object->attemptHealing( regen, getObject() );
}
}
}
}
}
return OpenContain::update(); //extend
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContain::unreserveDoorForExit( ExitDoorType exitDoor )
{
/* nothing */
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContain::killRidersWhoAreNotFreeToExit()
{
const TransportContainModuleData* d = getTransportContainModuleData();
ContainedItemsList::const_iterator it = getContainList().begin();
while( it != getContainList().end() )
{
// get the object
Object *obj = *it;
// increment the iterator, since death will pull this guy from the list somewhere else
++it;
if (!isSpecificRiderFreeToExit(obj)) // only TransportContain has a meaningful failure for isFreeToExit
{
// If we cannot exit this guy legally, kill the bastard before removeAllContained forces him out.
if (d->m_destroyRidersWhoAreNotFreeToExit)
TheGameLogic->destroyObject(obj);
else
obj->kill();
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool TransportContain::isSpecificRiderFreeToExit(Object* specificObject)
{
if( specificObject == NULL )
return TRUE; // I can, in general, exit people.
// This is a override, not an extend. I will check for game legality for
// okaying the call to exitObjectViaDoor.
const Object* me = getObject();
// this is present solely for some transports to override, so that they can land before
// allowing people to exit...
const AIUpdateInterface* ai = me->getAIUpdateInterface();
if (ai && ai->getAiFreeToExit(specificObject) != FREE_TO_EXIT)
return FALSE;
// I can always kick people out if I am in the air, I know what I'm doing
if (me->isUsingAirborneLocomotor())
return TRUE;
const Coord3D *myPosition = me->getPosition();
if (!specificObject->getAIUpdateInterface())
return FALSE;
const Locomotor *hisLocomotor = specificObject->getAIUpdateInterface()->getCurLocomotor();
if( hisLocomotor == FALSE )
return FALSE;
// He can't get to this spot naturally, so I can't force him there. (amphib transport)
if (!TheAI->pathfinder()->validMovementTerrain(me->getLayer(), hisLocomotor, myPosition))
return FALSE;
return TRUE;
}
Bool TransportContain::isPassengerAllowedToFire( ObjectID id ) const
{
Object *passenger = TheGameLogic->findObjectByID(id);
if( passenger != NULL )
{
//only allow infantry, and turrets and such. no vehicles.
if( passenger->isKindOf(KINDOF_INFANTRY) == FALSE )
return FALSE;
}
if ( ! getObject() )
return FALSE;
// but wait! I may be riding on an Overlord
// This code detects the case of whether the contained passenger is in a bunker riding on an overlord, inside a helix!
// Oh my God.
const Object *heWhoContainsMe = getObject()->getContainedBy();
if ( heWhoContainsMe)
{
ContainModuleInterface *hisContain = heWhoContainsMe->getContain();
DEBUG_ASSERTCRASH( hisContain,("TransportContain::isPassengerAllowedToFire()... CONTAINER WITHOUT A CONTAIN! AARRGH!") );
if ( hisContain && hisContain->isSpecialOverlordStyleContainer() )
return hisContain->isPassengerAllowedToFire( id );
}
return OpenContain::isPassengerAllowedToFire();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ExitDoorType TransportContain::reserveDoorForExit( const ThingTemplate* objType, Object *specificObject )
{
return isSpecificRiderFreeToExit(specificObject) ? DOOR_1 : DOOR_NONE_AVAILABLE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool TransportContain::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.
{
const TransportContainModuleData *data = getTransportContainModuleData();
if( data->m_isDelayExitInAir && getObject()->isAboveTerrain() )
return TRUE;
return TheGameLogic->getFrame() < m_frameExitNotBusy;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContain::onCapture( Player *oldOwner, Player *newOwner )
{
if( oldOwner != newOwner )
{
if( getObject()->isDisabledByType( DISABLED_UNMANNED ) )
{
//If this vehicle was sniped, then instantly eject everyone (otherwise, the next guy to eject will recapture it).
removeAllContained();
}
else
{
//Use standard
orderAllPassengersToExit( CMD_FROM_AI, FALSE );
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void TransportContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void TransportContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::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 TransportContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,552 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: TunnelContain.cpp ////////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A version of OpenContain that overrides where the passengers are stored: the Owning Player's
// TunnelTracker. All queries about capacity and contents are also redirected.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/RandomValue.h"
#include "Common/ThingTemplate.h"
#include "Common/TunnelTracker.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/OpenContain.h"
#include "GameLogic/Module/TunnelContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TunnelContain::TunnelContain( Thing *thing, const ModuleData* moduleData ) : OpenContain( thing, moduleData )
{
m_needToRunOnBuildComplete = true;
m_isCurrentlyRegistered = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TunnelContain::~TunnelContain()
{
}
void TunnelContain::addToContainList( Object *obj )
{
Player *owningPlayer = getObject()->getControllingPlayer();
owningPlayer->getTunnelSystem()->addToContainList( obj );
}
//-------------------------------------------------------------------------------------------------
/** Remove 'obj' from the m_containList of objects in this module.
* This will trigger an onRemoving event for the object that this module
* is a part of and an onRemovedFrom event for the object being removed */
//-------------------------------------------------------------------------------------------------
void TunnelContain::removeFromContain( Object *obj, Bool exposeStealthUnits )
{
// sanity
if( obj == NULL )
return;
// trigger an onRemoving event for 'm_object' no longer containing 'itemToRemove->m_object'
if( getObject()->getContain() )
{
getObject()->getContain()->onRemoving( obj );
}
// trigger an onRemovedFrom event for 'remove'
obj->onRemovedFrom( getObject() );
//
// we can only remove this object from the contains list of this module if
// it is actually contained by this module
//
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return; //game tear down. We do the onRemove* stuff first because this is allowed to fail but that still needs to be done
if( ! owningPlayer->getTunnelSystem()->isInContainer( obj ) )
{
return;
}
owningPlayer->getTunnelSystem()->removeFromContain( obj, exposeStealthUnits );
}
//--------------------------------------------------------------------------------------------------------
/** Force all contained objects in the contained list to exit, and kick them in the pants on the way out*/
//--------------------------------------------------------------------------------------------------------
void TunnelContain::harmAndForceExitAllContained( DamageInfo *info )
{
Player *owningPlayer = getObject()->getControllingPlayer();
const ContainedItemsList *fullList = owningPlayer->getTunnelSystem()->getContainedItemsList();
Object *obj;
ContainedItemsList::const_iterator it;
//Kris: Patch 1.01 -- November 6, 2003
//No longer advances the iterator and saves it. The iterator is fetched from the beginning after
//each loop. This is to prevent a crash where dropping a bunker buster on a tunnel network containing
//multiple units (if they have the suicide bomb upgrade - demo general). In this case, multiple bunker
//busters would hit the tunnel network in close succession. Missile #1 would iterate the list, killing
//infantry #1. Infantry #1 would explode and destroy Missile #2. Missile #2 would start iterating the
//same list, killing the remaining units. When Missile #1 picked up and continued processing the list
//it would crash because it's iterator was deleted from under it.
it = (*fullList).begin();
while( it != (*fullList).end() )
{
obj = *it;
removeFromContain( obj, true );
obj->attemptDamage( info );
it = (*fullList).begin();
} // end while
} // end removeAllContained
//-------------------------------------------------------------------------------------------------
/** Remove all contained objects from the contained list */
//-------------------------------------------------------------------------------------------------
void TunnelContain::killAllContained( void )
{
Player *owningPlayer = getObject()->getControllingPlayer();
const ContainedItemsList *fullList = owningPlayer->getTunnelSystem()->getContainedItemsList();
Object *obj;
ContainedItemsList::const_iterator it;
it = (*fullList).begin();
while( it != (*fullList).end() )
{
obj = *it;
it++;
removeFromContain( obj, true );
obj->kill();
}
}
//-------------------------------------------------------------------------------------------------
/** Remove all contained objects from the contained list */
//-------------------------------------------------------------------------------------------------
void TunnelContain::removeAllContained( Bool exposeStealthUnits )
{
Player *owningPlayer = getObject()->getControllingPlayer();
const ContainedItemsList *fullList = owningPlayer->getTunnelSystem()->getContainedItemsList();
Object *obj;
ContainedItemsList::const_iterator it;
it = (*fullList).begin();
while( it != (*fullList).end() )
{
obj = *it;
it++;
removeFromContain( obj, exposeStealthUnits );
}
}
//-------------------------------------------------------------------------------------------------
/** Iterate the contained list and call the callback on each of the objects */
//-------------------------------------------------------------------------------------------------
void TunnelContain::iterateContained( ContainIterateFunc func, void *userData, Bool reverse )
{
Player *owningPlayer = getObject()->getControllingPlayer();
owningPlayer->getTunnelSystem()->iterateContained( func, userData, reverse );
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onContaining( Object *obj, Bool wasSelected )
{
OpenContain::onContaining( obj, wasSelected );
// objects inside a building are held
obj->setDisabled( DISABLED_HELD );
obj->getControllingPlayer()->getAcademyStats()->recordUnitEnteredTunnelNetwork();
obj->handlePartitionCellMaintenance();
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onRemoving( Object *obj )
{
OpenContain::onRemoving(obj);
// object is no longer held inside a garrisoned building
obj->clearDisabled( DISABLED_HELD );
/// place the object in the world at position of the container m_object
ThePartitionManager->registerObject( obj );
obj->setPosition( getObject()->getPosition() );
if( obj->getDrawable() )
{
obj->setSafeOcclusionFrame(TheGameLogic->getFrame()+obj->getTemplate()->getOcclusionDelay());
obj->getDrawable()->setDrawableHidden( false );
}
doUnloadSound();
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onSelling()
{
// A TunnelContain tells everyone to leave if this is the last tunnel
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return;
TunnelTracker *tunnelTracker = owningPlayer->getTunnelSystem();
if( tunnelTracker == NULL )
return;
// We are the last tunnel, so kick everyone out. This makes tunnels act like Palace and Bunker
// rather than killing the occupants as if the last tunnel died.
if( tunnelTracker->friend_getTunnelCount() == 1 )
removeAllContained(FALSE);// Can't be order to exit, as I have no time to organize their exits.
// If they don't go right now, I will delete them in a moment
// Unregister after the kick out, or else the unregistering will activate a cavein-kill.
// We need to do this in case someone sells their last two tunnels at the same time.
if( m_isCurrentlyRegistered )
{
tunnelTracker->onTunnelDestroyed( getObject() );
m_isCurrentlyRegistered = FALSE;
}
}
//-------------------------------------------------------------------------------------------------
Bool TunnelContain::isValidContainerFor(const Object* obj, Bool checkCapacity) const
{
Player *owningPlayer = getObject()->getControllingPlayer();
return owningPlayer->getTunnelSystem()->isValidContainerFor( obj, checkCapacity );
}
UnsignedInt TunnelContain::getContainCount() const
{
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer && owningPlayer->getTunnelSystem() )
{
return owningPlayer->getTunnelSystem()->getContainCount();
}
return 0;
}
Int TunnelContain::getContainMax( void ) const
{
Player *owningPlayer = getObject()->getControllingPlayer();
return owningPlayer->getTunnelSystem()->getContainMax();
}
const ContainedItemsList* TunnelContain::getContainedItemsList() const
{
Player *owningPlayer = getObject()->getControllingPlayer();
return owningPlayer->getTunnelSystem()->getContainedItemsList();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
void TunnelContain::scatterToNearbyPosition(Object* obj)
{
Object *theContainer = getObject();
//
// for now we will just set the position of the object that is being removed from us
// at a random angle away from our center out some distance
//
//
// pick an angle that is in the view of the current camera position so that
// the thing will come out "toward" the player and they can see it
// NOPE, can't do that ... all players screen angles will be different, unless
// we maintain the angle of each players screen in the player structure or something
//
Real angle = GameLogicRandomValueReal( 0.0f, 2.0f * PI );
// angle = TheTacticalView->getAngle();
// angle -= GameLogicRandomValueReal( PI / 3.0f, 2.0f * (PI / 3.0F) );
Real minRadius = theContainer->getGeometryInfo().getBoundingCircleRadius();
Real maxRadius = minRadius + minRadius / 2.0f;
const Coord3D *containerPos = theContainer->getPosition();
Real dist = GameLogicRandomValueReal( minRadius, maxRadius );
Coord3D pos;
pos.x = dist * Cos( angle ) + containerPos->x;
pos.y = dist * Sin( angle ) + containerPos->y;
pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y );
// set orientation
obj->setOrientation( angle );
AIUpdateInterface *ai = obj->getAIUpdateInterface();
if( ai )
{
// set position of the object at center of building and move them toward pos
obj->setPosition( theContainer->getPosition() );
ai->ignoreObstacle(theContainer);
ai->aiMoveToPosition( &pos, CMD_FROM_AI );
} // end if
else
{
// no ai, just set position at the target pos
obj->setPosition( &pos );
} // end else
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void TunnelContain::onDie( const DamageInfo * damageInfo )
{
// override the onDie we inherit from OpenContain. no super call.
if (!getTunnelContainModuleData()->m_dieMuxData.isDieApplicable(getObject(), damageInfo))
return;
if( !m_isCurrentlyRegistered )
return;//it isn't registered as a tunnel
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return;
TunnelTracker *tunnelTracker = owningPlayer->getTunnelSystem();
if( tunnelTracker == NULL )
return;
tunnelTracker->onTunnelDestroyed( getObject() );
m_isCurrentlyRegistered = FALSE;
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onDelete( void )
{
// Being sold is a straight up delete. no death
if( !m_isCurrentlyRegistered )
return;//it isn't registered as a tunnel
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return;
TunnelTracker *tunnelTracker = owningPlayer->getTunnelSystem();
if( tunnelTracker == NULL )
return;
tunnelTracker->onTunnelDestroyed( getObject() );
m_isCurrentlyRegistered = FALSE;
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onObjectCreated()
{
//Kris: July 29, 2003
//Added this function to support the sneak attack (which doesn't call onBuildComplete).
if( ! shouldDoOnBuildComplete() )
return;
m_needToRunOnBuildComplete = false;
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return;
TunnelTracker *tunnelTracker = owningPlayer->getTunnelSystem();
if( tunnelTracker == NULL )
return;
tunnelTracker->onTunnelCreated( getObject() );
m_isCurrentlyRegistered = TRUE;
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onBuildComplete( void )
{
//Kris: July 29, 2003
//Obsolete -- onObjectCreated handles it before this function gets called.
/*
if( ! shouldDoOnBuildComplete() )
return;
m_needToRunOnBuildComplete = false;
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return;
TunnelTracker *tunnelTracker = owningPlayer->getTunnelSystem();
if( tunnelTracker == NULL )
return;
tunnelTracker->onTunnelCreated( getObject() );
m_isCurrentlyRegistered = TRUE;
*/
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TunnelContain::onCapture( Player *oldOwner, Player *newOwner )
{
if( m_isCurrentlyRegistered )
{
TunnelTracker *oldTunnelTracker = oldOwner->getTunnelSystem();
if( oldTunnelTracker )
{
DEBUG_ASSERTCRASH( oldTunnelTracker->getContainCount() == 0, ("You shouldn't force a capture of a Tunnel with people in it. Future ExitFromContainer scripts will fail."));
oldTunnelTracker->onTunnelDestroyed(getObject());
}
TunnelTracker *newTunnelTracker = newOwner->getTunnelSystem();
if( newTunnelTracker )
{
newTunnelTracker->onTunnelCreated(getObject());
}
}
// extend base class
OpenContain::onCapture( oldOwner, newOwner );
}
// ------------------------------------------------------------------------------------------------
/** Per frame update */
// ------------------------------------------------------------------------------------------------
UpdateSleepTime TunnelContain::update( void )
{
// extending functionality to heal the units within the tunnel system
OpenContain::update();
const TunnelContainModuleData *modData = getTunnelContainModuleData();
Object *obj = getObject();
Player *controllingPlayer = NULL;
if (obj)
{
controllingPlayer = obj->getControllingPlayer();
}
if (controllingPlayer)
{
TunnelTracker *tunnelSystem = controllingPlayer->getTunnelSystem();
if (tunnelSystem)
{
tunnelSystem->healObjects(modData->m_framesForFullHeal);
}
// check for attacked.
BodyModuleInterface *body = obj->getBodyModule();
if (body) {
const DamageInfo *info = body->getLastDamageInfo();
if (info) {
if (body->getLastDamageTimestamp() + LOGICFRAMES_PER_SECOND > TheGameLogic->getFrame()) {
// winner.
ObjectID attackerID = info->in.m_sourceID;
Object *attacker = TheGameLogic->findObjectByID(attackerID);
if( attacker )
{
if (obj->getRelationship(attacker) == ENEMIES) {
tunnelSystem->updateNemesis(attacker);
}
}
}
}
}
}
return UPDATE_SLEEP_NONE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void TunnelContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void TunnelContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// need to run on build complete
xfer->xferBool( &m_needToRunOnBuildComplete );
// Currently registered with owning player
xfer->xferBool( &m_isCurrentlyRegistered );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void TunnelContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,94 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: CreateModule.cpp /////////////////////////////////////////////////////////////////////////
// Author: Colin Day, October 2002
// Desc: Create module base class
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/CreateModule.h"
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CreateModule::CreateModule( Thing *thing, const ModuleData* moduleData )
: BehaviorModule( thing, moduleData ),
m_needToRunOnBuildComplete(TRUE)
{
} // end createModule
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
CreateModule::~CreateModule()
{
} // end ~CreateModule
//-------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CreateModule::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CreateModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
BehaviorModule::xfer( xfer );
// need to run on build complete
xfer->xferBool( &m_needToRunOnBuildComplete );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CreateModule::loadPostProcess( void )
{
// extend base class
BehaviorModule::loadPostProcess();
} // ene loadPostProcess

View File

@@ -0,0 +1,182 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: GrantUpgradeCreate.cpp ////////////////////////////////////////////////////////////////////////
// Author: Kris Morness, April 2002
// Desc: GrantUpgrade create module
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_OBJECT_STATUS_NAMES
#include "Common/Player.h"
#include "Common/Upgrade.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/GrantUpgradeCreate.h"
#include "GameLogic/Object.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
GrantUpgradeCreateModuleData::GrantUpgradeCreateModuleData()
{
m_upgradeName = "";
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GrantUpgradeCreateModuleData::buildFieldParse(MultiIniFieldParse& p)
{
CreateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "UpgradeToGrant", INI::parseAsciiString, NULL, offsetof( GrantUpgradeCreateModuleData, m_upgradeName ) },
{ "ExemptStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof( GrantUpgradeCreateModuleData, m_exemptStatus ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GrantUpgradeCreate::GrantUpgradeCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
} // end GrantUpgradeCreate
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GrantUpgradeCreate::~GrantUpgradeCreate( void )
{
} // end ~GrantUpgradeCreate
//-------------------------------------------------------------------------------------------------
/** The create callback. */
//-------------------------------------------------------------------------------------------------
void GrantUpgradeCreate::onCreate( void )
{
ObjectStatusMaskType exemptStatus = getGrantUpgradeCreateModuleData()->m_exemptStatus;
ObjectStatusMaskType currentStatus = getObject()->getStatusBits();
if( exemptStatus.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
{
if( !currentStatus.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
{
const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( getGrantUpgradeCreateModuleData()->m_upgradeName );
if( !upgradeTemplate )
{
DEBUG_ASSERTCRASH( 0, ("GrantUpdateCreate for %s can't find upgrade template %s.", getObject()->getName(), getGrantUpgradeCreateModuleData()->m_upgradeName ) );
return;
}
Player *player = getObject()->getControllingPlayer();
if( upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER )
{
// get the player
player->addUpgrade( upgradeTemplate, UPGRADE_STATUS_COMPLETE );
}
else
{
getObject()->giveUpgrade( upgradeTemplate );
}
player->getAcademyStats()->recordUpgrade( upgradeTemplate, TRUE );
}
}
} // end onCreate
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GrantUpgradeCreate::onBuildComplete( void )
{
if( ! shouldDoOnBuildComplete() )
return;
CreateModule::onBuildComplete(); // extend
const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( getGrantUpgradeCreateModuleData()->m_upgradeName );
if( !upgradeTemplate )
{
DEBUG_ASSERTCRASH( 0, ("GrantUpdateCreate for %s can't find upgrade template %s.", getObject()->getName(), getGrantUpgradeCreateModuleData()->m_upgradeName ) );
return;
}
if( upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER )
{
// get the player
Player *player = getObject()->getControllingPlayer();
player->addUpgrade( upgradeTemplate, UPGRADE_STATUS_COMPLETE );
}
else
{
getObject()->giveUpgrade( upgradeTemplate );
}
} // end onBuildComplete
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void GrantUpgradeCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void GrantUpgradeCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void GrantUpgradeCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,134 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: LockWeaponCreate.cpp //////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2003
// Desc: Locks the weapon choice to the slot specified on creation
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_WEAPONSLOTTYPE_NAMES
#include "Common/Xfer.h"
#include "GameLogic/Module/LockWeaponCreate.h"
#include "GameLogic/Object.h"
#include "GameLogic/WeaponSet.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
LockWeaponCreateModuleData::LockWeaponCreateModuleData()
{
m_slotToLock = PRIMARY_WEAPON;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void LockWeaponCreateModuleData::buildFieldParse(MultiIniFieldParse& p)
{
CreateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "SlotToLock", INI::parseLookupList, TheWeaponSlotTypeNamesLookupList, offsetof( LockWeaponCreateModuleData, m_slotToLock ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
LockWeaponCreate::LockWeaponCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
} // end GrantUpgradeCreate
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
LockWeaponCreate::~LockWeaponCreate( void )
{
} // end ~GrantUpgradeCreate
//-------------------------------------------------------------------------------------------------
/** The create callback. */
//-------------------------------------------------------------------------------------------------
void LockWeaponCreate::onCreate( void )
{
} // end onCreate
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void LockWeaponCreate::onBuildComplete( void )
{
CreateModule::onBuildComplete(); // extend
Object *me = getObject();
WeaponSlotType slot = getLockWeaponCreateModuleData()->m_slotToLock;
me->setWeaponLock( slot, LOCKED_PERMANENTLY );
} // end onBuildComplete
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void LockWeaponCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void LockWeaponCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void LockWeaponCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,114 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: PreorderCreate.cpp ///////////////////////////////////////////////////////////////////////
// Author: Matthew D. Campbell, December 2002
// Desc: When a building is created, set the preorder status if necessary
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/PreorderCreate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
PreorderCreate::PreorderCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
PreorderCreate::~PreorderCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void PreorderCreate::onCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void PreorderCreate::onBuildComplete( void )
{
if (getObject()->getControllingPlayer()->didPlayerPreorder())
{
getObject()->setModelConditionState( MODELCONDITION_PREORDER );
}
else
{
getObject()->clearModelConditionState( MODELCONDITION_PREORDER );
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PreorderCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PreorderCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PreorderCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,113 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: SpecialPowerCreate.h /////////////////////////////////////////////////////////////////////////////
// Author: Matthew D. Campbell, May 2002
// Desc: When a building is created, tell special powers to start counting down
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/SpecialPowerCreate.h"
#include "GameLogic/Module/SpecialPowerModule.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SpecialPowerCreate::SpecialPowerCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SpecialPowerCreate::~SpecialPowerCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void SpecialPowerCreate::onCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void SpecialPowerCreate::onBuildComplete( void )
{
if( ! shouldDoOnBuildComplete() )
return;
CreateModule::onBuildComplete(); // extend
for (BehaviorModule** m = getObject()->getBehaviorModules(); *m; ++m)
{
SpecialPowerModuleInterface* sp = (*m)->getSpecialPower();
if (!sp)
continue;
sp->onSpecialPowerCreation();
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,119 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: SupplyCenterCreate.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood Feb 2002
// Desc: When a Supply Center is created, it needs to update all the Resource brains in all players
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ResourceGatheringManager.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/SupplyCenterCreate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyCenterCreate::SupplyCenterCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyCenterCreate::~SupplyCenterCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void SupplyCenterCreate::onCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void SupplyCenterCreate::onBuildComplete( void )
{
if( ! shouldDoOnBuildComplete() )
return;
CreateModule::onBuildComplete(); // extend
if( ThePlayerList == NULL )
return;
for( Int playerIndex = ThePlayerList->getPlayerCount() - 1; playerIndex >= 0; playerIndex-- )
{
Player *currentPlayer = ThePlayerList->getNthPlayer( playerIndex );
if( currentPlayer == NULL )
continue;
ResourceGatheringManager *manager = currentPlayer->getResourceGatheringManager();
if( manager == NULL )
continue;
manager->addSupplyCenter( getObject() );
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SupplyCenterCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SupplyCenterCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SupplyCenterCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,110 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: SupplyWarehouseCreate.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood Feb 2002
// Desc: When a Supply Center is created, it needs to update all the Resource brains in all players
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ResourceGatheringManager.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/SupplyWarehouseCreate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyWarehouseCreate::SupplyWarehouseCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyWarehouseCreate::~SupplyWarehouseCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void SupplyWarehouseCreate::onCreate( void )
{
// Warehouses are never Built.
if( ThePlayerList == NULL )
return;
for( Int playerIndex = ThePlayerList->getPlayerCount() - 1; playerIndex >= 0; playerIndex-- )
{
Player *currentPlayer = ThePlayerList->getNthPlayer( playerIndex );
if( currentPlayer == NULL )
continue;
ResourceGatheringManager *manager = currentPlayer->getResourceGatheringManager();
if( manager == NULL )
continue;
manager->addSupplyWarehouse( getObject() );
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,139 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: VeterancyGainCreate.cpp //////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, August 2002
// Desc: On creation, will set Object's veterancy level if required Science is present.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_VETERANCY_NAMES // for TheVeterancyNames[]
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/VeterancyGainCreate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
VeterancyGainCreateModuleData::VeterancyGainCreateModuleData()
{
m_startingLevel = LEVEL_REGULAR;
m_scienceRequired = SCIENCE_INVALID;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void VeterancyGainCreateModuleData::buildFieldParse(MultiIniFieldParse& p)
{
CreateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "StartingLevel", INI::parseIndexList, TheVeterancyNames, offsetof( VeterancyGainCreateModuleData, m_startingLevel ) },
{ "ScienceRequired", INI::parseScience, NULL, offsetof( VeterancyGainCreateModuleData, m_scienceRequired ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
VeterancyGainCreate::VeterancyGainCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
VeterancyGainCreate::~VeterancyGainCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The create callback. */
//-------------------------------------------------------------------------------------------------
void VeterancyGainCreate::onCreate( void )
{
// When produced normally, this Object will ask the Player if the correct Science is known for it
// to set its level to the given level
const VeterancyGainCreateModuleData *md = getVeterancyGainCreateModuleData();
Player *myPlayer = getObject()->getControllingPlayer();
if( myPlayer && (md->m_scienceRequired == SCIENCE_INVALID ||
myPlayer->hasScience( md->m_scienceRequired )) )
{
ExperienceTracker* myExp = getObject()->getExperienceTracker();
if( myExp && myExp->isTrainable() )
{
// srj sez: use "setMin" here so that we never lose levels
myExp->setMinVeterancyLevel( md->m_startingLevel );// sVL can override isTrainable, but this module should not.
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void VeterancyGainCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void VeterancyGainCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void VeterancyGainCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,118 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: BoneFXDamage.cpp ///////////////////////////////////////////////////////////////////
// Author: Bryan Cleveland, March 2002
// Desc: Damage module that goes with BoneFX update
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BoneFXDamage.h"
#include "GameLogic/Module/BoneFXUpdate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
BoneFXDamage::BoneFXDamage( Thing *thing, const ModuleData* moduleData )
: DamageModule( thing, moduleData )
{
} // end BoneFXDamage
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
BoneFXDamage::~BoneFXDamage( void )
{
} // end ~BoneFXDamage
//-------------------------------------------------------------------------------------------------
void BoneFXDamage::onObjectCreated()
{
static NameKeyType key_BoneFXUpdate = NAMEKEY("BoneFXUpdate");
BoneFXUpdate* bfxu = (BoneFXUpdate*)getObject()->findUpdateModule(key_BoneFXUpdate);
if (bfxu == NULL)
{
DEBUG_ASSERTCRASH(bfxu != NULL, ("BoneFXDamage requires BoneFXUpdate"));
throw INI_INVALID_DATA;
}
}
//-------------------------------------------------------------------------------------------------
/** Switching damage states */
//-------------------------------------------------------------------------------------------------
void BoneFXDamage::onBodyDamageStateChange( const DamageInfo *damageInfo,
BodyDamageType oldState,
BodyDamageType newState )
{
static NameKeyType key_BoneFXUpdate = NAMEKEY("BoneFXUpdate");
BoneFXUpdate* bfxu = (BoneFXUpdate*)getObject()->findUpdateModule(key_BoneFXUpdate);
if (bfxu)
bfxu->changeBodyDamageState(oldState, newState);
} // end onBodyDamageStateChange
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BoneFXDamage::crc( Xfer *xfer )
{
// extend base class
DamageModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BoneFXDamage::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DamageModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BoneFXDamage::loadPostProcess( void )
{
// extend base class
DamageModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,71 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: DamageModule.cpp /////////////////////////////////////////////////////////////////////////
// Author: Colin Day, September 2002
// Desc: Damage Module base class
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/DamageModule.h"
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DamageModule::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DamageModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// call base class
BehaviorModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DamageModule::loadPostProcess( void )
{
// call base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,469 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: TransitionDamageFX.cpp ///////////////////////////////////////////////////////////////////
// Author: Colin Day, March 2002
// Desc: Damage module capable of launching various effects on damage transitions
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/FXList.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Module/TransitionDamageFX.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TransitionDamageFXModuleData::TransitionDamageFXModuleData( void )
{
Int i, j;
for( i = 0; i < BODYDAMAGETYPE_COUNT; i++ )
{
for( j = 0; j < DAMAGE_MODULE_MAX_FX; j++ )
{
m_fxList[ i ][ j ].fx = NULL;
m_fxList[ i ][ j ].locInfo.loc.x = 0.0f;
m_fxList[ i ][ j ].locInfo.loc.y = 0.0f;
m_fxList[ i ][ j ].locInfo.loc.z = 0.0f;
m_fxList[ i ][ j ].locInfo.locType = FX_DAMAGE_LOC_TYPE_COORD;
m_fxList[ i ][ j ].locInfo.randomBone = FALSE;
m_OCL[ i ][ j ].ocl = NULL;
m_OCL[ i ][ j ].locInfo.loc.x = 0.0f;
m_OCL[ i ][ j ].locInfo.loc.y = 0.0f;
m_OCL[ i ][ j ].locInfo.loc.z = 0.0f;
m_OCL[ i ][ j ].locInfo.locType = FX_DAMAGE_LOC_TYPE_COORD;
m_OCL[ i ][ j ].locInfo.randomBone = FALSE;
m_particleSystem[ i ][ j ].particleSysTemplate = NULL;
m_particleSystem[ i ][ j ].locInfo.loc.x = 0.0f;
m_particleSystem[ i ][ j ].locInfo.loc.y = 0.0f;
m_particleSystem[ i ][ j ].locInfo.loc.z = 0.0f;
m_particleSystem[ i ][ j ].locInfo.locType = FX_DAMAGE_LOC_TYPE_COORD;
m_particleSystem[ i ][ j ].locInfo.randomBone = FALSE;
} // end for j
} // end for i
m_damageFXTypes = DAMAGE_TYPE_FLAGS_NONE;
m_damageFXTypes.flip();
m_damageOCLTypes = DAMAGE_TYPE_FLAGS_NONE;
m_damageOCLTypes.flip();
m_damageParticleTypes = DAMAGE_TYPE_FLAGS_NONE;
m_damageParticleTypes.flip();
} // end TransitionDamageFXModuleData
//-------------------------------------------------------------------------------------------------
/** Parse fx location info ... that is a named bone or a coord3d position */
//-------------------------------------------------------------------------------------------------
static void parseFXLocInfo( INI *ini, void *instance, FXLocInfo *locInfo )
{
const char *token = ini->getNextToken( ini->getSepsColon() );
if( stricmp( token, "bone" ) == 0 )
{
// save bone name and location type
locInfo->boneName = AsciiString( ini->getNextToken() );
locInfo->locType = FX_DAMAGE_LOC_TYPE_BONE;
//
// bones are followed by RandomBone:<Yes|No>, if random bone is yes, the bone name is
// assumed to be a "base bone name" and we will find all the bones with that prefix
// when picking an effect position. If it's no, the bone name is assumed to be explicit
//
token = ini->getNextToken( ini->getSepsColon() );
if( stricmp( token, "randombone" ) != 0 )
{
DEBUG_CRASH(( "parseFXLocInfo: Bone name not followed by RandomBone specifier\nPress IGNORE to see which INI file and line # is incorrect." ));
throw INI_INVALID_DATA;
} // end if
// parse the Bool definition
ini->parseBool( ini, instance, &locInfo->randomBone, NULL );
} // end if
else if( stricmp( token, "loc" ) == 0 )
{
// save location and location type
locInfo->loc.x = ini->scanReal( ini->getNextSubToken("X") );
locInfo->loc.y = ini->scanReal( ini->getNextSubToken("Y") );
locInfo->loc.z = ini->scanReal( ini->getNextSubToken("Z") );
locInfo->locType = FX_DAMAGE_LOC_TYPE_COORD;
} // end else
else
{
// error
throw INI_INVALID_DATA;
} // end else
} // end parseFXLocInfo
//-------------------------------------------------------------------------------------------------
/** In the form of:
* FXListSlot = <<Bone:BoneName BoneRandom:<Yes|No>> | <Loc: X:x Y:y Z:z>> FXList:FXListName */
//-------------------------------------------------------------------------------------------------
void TransitionDamageFXModuleData::parseFXList( INI *ini, void *instance,
void *store, const void *userData )
{
const char *token;
FXDamageFXListInfo *info = (FXDamageFXListInfo *)store;
// parse the location bone or location
parseFXLocInfo( ini, instance, &info->locInfo );
// make sure we have an "FXList:" token
token = ini->getNextToken( ini->getSepsColon() );
if( stricmp( token, "fxlist" ) != 0 )
{
// error
throw INI_INVALID_DATA;
} // end if
// parse the fx list name
ini->parseFXList( ini, instance, &info->fx, NULL );
} // end parseFXList
//-------------------------------------------------------------------------------------------------
/** In the form of:
* OCLSlot = <<Bone:BoneName BoneRandom:<Yes|No>> | <Loc: X:x Y:y Z:z>> OCL:OCLName */
//-------------------------------------------------------------------------------------------------
void TransitionDamageFXModuleData::parseObjectCreationList( INI *ini, void *instance,
void *store, const void *userData )
{
const char *token;
FXDamageOCLInfo *info = (FXDamageOCLInfo *)store;
// parse the location bone or location
parseFXLocInfo( ini, instance, &info->locInfo );
// make sure we have an "OCL:" token
token = ini->getNextToken( ini->getSepsColon() );
if( stricmp( token, "ocl" ) != 0 )
{
// error
throw INI_INVALID_DATA;
} // end if
// parse the ocl name
ini->parseObjectCreationList( ini, instance, store, &info->ocl );
} // end parseObjectCreationList
//-------------------------------------------------------------------------------------------------
/** In the form of:
* ParticleSlot = <<Bone:BoneName BoneRandom:<Yes|No>> | <Loc: X:x Y:y Z:z>> PSys:PSysName */
//-------------------------------------------------------------------------------------------------
void TransitionDamageFXModuleData::parseParticleSystem( INI *ini, void *instance,
void *store, const void *userData )
{
const char *token;
FXDamageParticleSystemInfo *info = (FXDamageParticleSystemInfo *)store;
// parse the location bone or location
parseFXLocInfo( ini, instance, &info->locInfo );
// make sure we have an "PSys:" token
token = ini->getNextToken( ini->getSepsColon() );
if( stricmp( token, "psys" ) != 0 )
{
// error
throw INI_INVALID_DATA;
} // end if
// parse the particle system name
ini->parseParticleSystemTemplate( ini, instance, store, &info->particleSysTemplate );
} // end parseParticleSystem
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TransitionDamageFX::TransitionDamageFX( Thing *thing, const ModuleData* moduleData )
: DamageModule( thing, moduleData )
{
Int i, j;
for( i = 0; i < BODYDAMAGETYPE_COUNT; i++ )
for( j = 0; j < DAMAGE_MODULE_MAX_FX; j++ )
m_particleSystemID[ i ][ j ] = INVALID_PARTICLE_SYSTEM_ID;
} // end TransitionDamageFX
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TransitionDamageFX::~TransitionDamageFX( void )
{
} // end ~TransitionDamageFX
/*
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void TransitionDamageFX::onDelete( void )
{
//
// we would in theory delete any particle systems we have created and attached, but the
// particle system will automatically delete itself when the object is destroyed
//
} // end onDelete
*/
//-------------------------------------------------------------------------------------------------
/** Given an FXLoc info struct, return the effect position that we are supposed to use.
* The position is local to to the object */
//-------------------------------------------------------------------------------------------------
static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw )
{
DEBUG_ASSERTCRASH( locInfo, ("getLocalEffectPos: locInfo is NULL\n") );
if( locInfo->locType == FX_DAMAGE_LOC_TYPE_BONE && draw )
{
if( locInfo->randomBone == FALSE )
{
Coord3D pos;
// get the bone position
Int count = draw->getPristineBonePositions( locInfo->boneName.str(), 0, &pos, NULL, 1 );
// sanity, if bone not found revert back to location defined in struct (which is 0,0,0)
if( count == 0 )
return locInfo->loc;
// return the position retrieved
return pos;
} // end if
else
{
const Int MAX_BONES = 32;
Coord3D positions[ MAX_BONES ];
// get the bone positions
Int boneCount;
boneCount = draw->getPristineBonePositions( locInfo->boneName.str(), 1, positions, NULL, MAX_BONES );
// sanity, if bone not found revert back to location defined in struct (which is 0,0,0)
if( boneCount == 0 )
return locInfo->loc;
// pick one of the bone positions
Int pick = GameLogicRandomValue( 0, boneCount - 1 );
return positions[ pick ];
} // end else
} // end if
else
return locInfo->loc;
} // end getLocalEffectPos
//-------------------------------------------------------------------------------------------------
/** Switching damage states */
//-------------------------------------------------------------------------------------------------
void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo,
BodyDamageType oldState,
BodyDamageType newState )
{
Object *damageSource = NULL;
Int i;
Drawable *draw = getObject()->getDrawable();
const TransitionDamageFXModuleData *modData = getTransitionDamageFXModuleData();
// get the source of the damage if present
if( damageInfo )
damageSource = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
// remove any particle systems that might be emitting from our old state
for( i = 0; i < DAMAGE_MODULE_MAX_FX; i++ )
{
if( m_particleSystemID[ oldState ][ i ] != INVALID_PARTICLE_SYSTEM_ID )
{
TheParticleSystemManager->destroyParticleSystemByID( m_particleSystemID[ oldState ][ i ] );
m_particleSystemID[ oldState ][ i ] = INVALID_PARTICLE_SYSTEM_ID;
} // end if
} // end for i
//
// when we are transitioning to a "worse" state we will play a set of effects for that
// new state to make the transition
//
if( IS_CONDITION_WORSE( newState, oldState ) )
{
const ParticleSystemTemplate *pSystemT;
Coord3D pos;
// if we are restricted by the damage type executing effect, bail out of here
const DamageInfo *lastDamageInfo = getObject()->getBodyModule()->getLastDamageInfo();
for( i = 0; i < DAMAGE_MODULE_MAX_FX; i++ )
{
// play fx list for our new state
if( modData->m_fxList[ newState ][ i ].fx )
{
if( lastDamageInfo == NULL ||
getDamageTypeFlag( modData->m_damageFXTypes, lastDamageInfo->in.m_damageType ) )
{
pos = getLocalEffectPos( &modData->m_fxList[ newState ][ i ].locInfo, draw );
getObject()->convertBonePosToWorldPos( &pos, NULL, &pos, NULL );
FXList::doFXPos( modData->m_fxList[ newState ][ i ].fx, &pos );
} // end if
} // end if
// do any object creation list for our new state
if( modData->m_OCL[ newState ][ i ].ocl )
{
if( lastDamageInfo == NULL ||
getDamageTypeFlag( modData->m_damageOCLTypes, lastDamageInfo->in.m_damageType ) )
{
pos = getLocalEffectPos( &modData->m_OCL[ newState ][ i ].locInfo, draw );
getObject()->convertBonePosToWorldPos( &pos, NULL, &pos, NULL );
ObjectCreationList::create( modData->m_OCL[ newState ][ i ].ocl,
getObject(), &pos, damageSource->getPosition(), INVALID_ANGLE );
} // end if
} // end if
// get the template of the system to create
pSystemT = modData->m_particleSystem[ newState ][ i ].particleSysTemplate;
if( pSystemT )
{
if( lastDamageInfo == NULL ||
getDamageTypeFlag( modData->m_damageParticleTypes, lastDamageInfo->in.m_damageType ) )
{
// create a new particle system based on the template provided
ParticleSystem* pSystem = TheParticleSystemManager->createParticleSystem( pSystemT );
if( pSystem )
{
// get the what is the position we're going to playe the effect at
pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw );
//
// set position on system given any bone position provided, the bone position is
// local to the object and that's what we want for the particle system ... the
// transormation into world space using the object position is taken care of in
// the particle system attachToObject method
//
pSystem->setPosition( &pos );
// attach to object
pSystem->attachToObject( getObject() );
// save the id of this particle system so we can remove it later if it still exists
m_particleSystemID[ newState ][ i ] = pSystem->getSystemID();
} // end if
} // end if
} // end if
} // end for i
} // end if
} // end onBodyDamageStateChange
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void TransitionDamageFX::crc( Xfer *xfer )
{
// extend base class
DamageModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void TransitionDamageFX::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DamageModule::xfer( xfer );
// particle systems ids
xfer->xferUser( m_particleSystemID, sizeof( ParticleSystemID ) * BODYDAMAGETYPE_COUNT * DAMAGE_MODULE_MAX_FX );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void TransitionDamageFX::loadPostProcess( void )
{
// extend base class
DamageModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,89 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: DestroyModule.cpp ////////////////////////////////////////////////////////////////////////
// Author: Colin Day, October 2002
// Desc: Destroy module base class
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/DestroyModule.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DestroyModule::DestroyModule( Thing *thing, const ModuleData* moduleData )
: BehaviorModule( thing, moduleData )
{
} // end DestroyModule
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DestroyModule::~DestroyModule( void )
{
} // end ~DestroyModule
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DestroyModule::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void DestroyModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
BehaviorModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DestroyModule::loadPostProcess( void )
{
// extend base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,293 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: CreateCrateDie.cpp /////////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, February 2002
// Desc: A chance to create a crate on death according to certain condition checks
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/PlayerList.h"
#include "Common/Player.h"
#include "Common/RandomValue.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/CrateSystem.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/CreateCrateDie.h"
#include "GameLogic/Module/AIUpdate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CreateCrateDie::CreateCrateDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CreateCrateDie::~CreateCrateDie( void )
{
}
void CreateCrateDieModuleData::parseCrateData( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
CreateCrateDieModuleData* self = (CreateCrateDieModuleData*)instance;
AsciiString crateName = ini->getNextToken();
self->m_crateNameList.push_back( crateName );
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void CreateCrateDie::onDie( const DamageInfo * damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
CrateTemplate const *currentCrateData = NULL;
Object *killer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
Object *me = getObject();
if( killer && killer->getRelationship( me ) == ALLIES )
return; //Nope, no crate for killing ally at all.
for( AsciiStringListConstIterator iter = getCreateCrateDieModuleData()->m_crateNameList.begin();
iter != getCreateCrateDieModuleData()->m_crateNameList.end();
iter++
)
{
currentCrateData = TheCrateSystem->findCrateTemplate( *iter );
if( currentCrateData )
{
if( ! testCreationChance( currentCrateData ) )
continue;// always test this
if( (currentCrateData->m_veterancyLevel != LEVEL_INVALID) && ! testVeterancyLevel( currentCrateData ) )
continue; //If this is set up to test and it fails
if( KINDOFMASK_ANY_SET(currentCrateData->m_killedByTypeKindof) && !testKillerType( currentCrateData, killer ) )
continue; //If this is set up to test and it fails
if( (currentCrateData->m_killerScience != SCIENCE_INVALID) && !testKillerScience( currentCrateData, killer ) )
continue; //If this is set up to test and it fails
Object *crate = createCrate( currentCrateData );//Make the crate
if( crate )
{
// Design needs to set ownership of crates sometimes
if( currentCrateData->m_isOwnedByMaker )
{
crate->setTeam( me->getControllingPlayer()->getDefaultTeam() );
}
if (killer)
{
// If the killer is a computer controlled player, notify that the crate exists.
if (killer->getControllingPlayer() &&
killer->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)
{
AIUpdateInterface *ai = killer->getAIUpdateInterface();
if (ai)
{
ai->notifyCrate( crate->getID() );
}
}
}
}
}
}
}
Bool CreateCrateDie::testCreationChance( CrateTemplate const *currentCrateData )
{
Real testAgainst = currentCrateData->m_creationChance;
Real testWith = GameLogicRandomValueReal( 0, 1 );
return testWith < testAgainst;
}
Bool CreateCrateDie::testVeterancyLevel( CrateTemplate const *currentCrateData )
{
VeterancyLevel testAgainst = currentCrateData->m_veterancyLevel;
VeterancyLevel testWith = getObject()->getVeterancyLevel();
return testAgainst == testWith;
}
Bool CreateCrateDie::testKillerType( CrateTemplate const *currentCrateData, Object *killer )
{
if( killer == NULL )
return FALSE;
// Must match the whole group of bits set in the KilledBy description (most likely One).
if( ! killer->getTemplate()->isKindOfMulti( currentCrateData->m_killedByTypeKindof, KINDOFMASK_NONE ) )
return FALSE;
return TRUE;
}
Bool CreateCrateDie::testKillerScience( CrateTemplate const *currentCrateData, Object *killer )
{
if( killer == NULL )
return FALSE;
// killer's player must have the listed science
Player *killerPlayer = killer->getControllingPlayer();
if( killerPlayer == NULL )
return FALSE;
if( ! killerPlayer->hasScience( currentCrateData->m_killerScience ) )
return FALSE;
return TRUE;
}
Object *CreateCrateDie::createCrate( CrateTemplate const *currentCrateData )
{
Coord3D centerPoint = *getObject()->getPosition();
PathfindLayerEnum layer = getObject()->getLayer();
// CreationChance is used for the success of this block, but this block can have any number of potential actual crates
Real multipleCratePick = GameLogicRandomValueReal( 0, 1 );
Real multipleCrateRunningTotal = 0;
AsciiString crateName = "";
for( crateCreationEntryConstIterator iter = currentCrateData->m_possibleCrates.begin();
iter != currentCrateData->m_possibleCrates.end();
iter++
)
{
multipleCrateRunningTotal += (*iter).crateChance;
if( multipleCrateRunningTotal > multipleCratePick )
{
// Run through the list of possibles, and if the sum of the chances is greater than my random pick,
// then this is the correct one. (This simulates contiguous %, or weighted distribution)
crateName = (*iter).crateName;
break;
}
}
// At this point, I could very well have a "" for the type, if the Designer didn't make the sum of chances 1
ThingTemplate const *crateType = TheThingFactory->findTemplate( crateName );
if( crateType == NULL )
return NULL;
Bool spotFound = FALSE;
Coord3D creationPoint;
FindPositionOptions fpOptions;
fpOptions.minRadius = 0.0f;
fpOptions.maxRadius = 5.0f;
fpOptions.relationshipObject = getObject();
fpOptions.flags = FPF_IGNORE_ALLY_OR_NEUTRAL_UNITS; // So the dead guy won't block, nor will his dead hulk.
if (layer != LAYER_GROUND) {
creationPoint = centerPoint;
spotFound = true;
} else if( ThePartitionManager->findPositionAround( &centerPoint, &fpOptions, &creationPoint ) )
{
spotFound = TRUE;
}
else
{
// If the tight ignore units scan fails, then try a great big scan so we appear on the edge
// of the large dead thing (building rubble)
fpOptions.minRadius = 0.0f;
fpOptions.maxRadius = 125.0f;
fpOptions.relationshipObject = NULL;
fpOptions.flags = FPF_NONE;
if( ThePartitionManager->findPositionAround( &centerPoint, &fpOptions, &creationPoint ) )
{
spotFound = TRUE;
}
}
if( spotFound )
{
Object *newCrate = TheThingFactory->newObject( crateType, NULL );
newCrate->setPosition( &creationPoint );
newCrate->setOrientation( GameLogicRandomValueReal( 0, 2*PI ) );
newCrate->setLayer(layer);
Drawable *crateDrawable = newCrate->getDrawable();
if( crateDrawable )
{
crateDrawable->setTerrainDecal(TERRAIN_DECAL_CRATE);
crateDrawable->setTerrainDecalSize(2.5f * newCrate->getGeometryInfo().getMajorRadius(),
2.5f * newCrate->getGeometryInfo().getMajorRadius() ) ;
crateDrawable->setTerrainDecalFadeTarget(1.0f, 0.03f);
}
return newCrate;
}
return NULL;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CreateCrateDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CreateCrateDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CreateCrateDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,189 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: CreateObjectDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Michael S. Booth, January 2002
// Desc: Create an object upon this object's death
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_OBJECT_STATUS_NAMES
#include "GameLogic/Module/AIUpdate.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/CreateObjectDie.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Module/BodyModule.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
CreateObjectDieModuleData::CreateObjectDieModuleData()
{
m_ocl = NULL;
m_transferPreviousHealth = FALSE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void CreateObjectDieModuleData::buildFieldParse(MultiIniFieldParse& p)
{
DieModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "CreationList", INI::parseObjectCreationList, NULL, offsetof( CreateObjectDieModuleData, m_ocl ) },
{ "TransferPreviousHealth", INI::parseBool, NULL ,offsetof( CreateObjectDieModuleData, m_transferPreviousHealth ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CreateObjectDie::CreateObjectDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CreateObjectDie::~CreateObjectDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void CreateObjectDie::onDie( const DamageInfo * damageInfo )
{
const CreateObjectDieModuleData *data = getCreateObjectDieModuleData();
if (!isDieApplicable(damageInfo))
return;
Object *damageDealer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
Object *newObject = ObjectCreationList::create( data->m_ocl, getObject(), damageDealer );
//If we're transferring previous health, we're transfering the last known
//health before we died. In the case of the sneak attack tunnel network, it
//is killed after the lifetime update expires.
if( newObject && data->m_transferPreviousHealth )
{
//Convert old health to new health.
Object *oldObject = getObject();
BodyModuleInterface *oldBody = oldObject->getBodyModule();
BodyModuleInterface *newBody = newObject->getBodyModule();
if( oldBody && newBody )
{
//First transfer subdual damage
DamageInfo damInfo;
Real subdualDamageAmount = oldBody->getCurrentSubdualDamageAmount();
if( subdualDamageAmount > 0.0f )
{
damInfo.in.m_amount = subdualDamageAmount;
damInfo.in.m_damageType = DAMAGE_SUBDUAL_UNRESISTABLE;
damInfo.in.m_sourceID = INVALID_ID;
newBody->attemptDamage( &damInfo );
}
//Now transfer the previous health from the old object to the new.
damInfo.in.m_amount = oldBody->getMaxHealth() - oldBody->getPreviousHealth();
damInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
damInfo.in.m_sourceID = oldBody->getLastDamageInfo()->in.m_sourceID;
if( damInfo.in.m_amount > 0.0f )
{
newBody->attemptDamage( &damInfo );
}
}
//Transfer attackers.
for( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
{
AIUpdateInterface* ai = obj->getAI();
if (!ai)
continue;
ai->transferAttack( oldObject->getID(), newObject->getID() );
}
}
} // end onDie
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CreateObjectDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CreateObjectDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CreateObjectDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,248 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: CrushDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Colin Day, November 2001
// Desc: Crush Die module
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/RandomValue.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/CrushDie.h"
//-------------------------------------------------------------------------------------------------
// Figure out which crush point was hit so the correct crushed object can be swapped in
// Figure out which crush point was hit so the correct crushed object can be swapped in
static CrushEnum crushLocationCheck( Object* crusherObject, Object* victimObject )
{
if( (crusherObject == NULL) || (victimObject == NULL) )
return NO_CRUSH;
Bool frontCrushed = victimObject->getBodyModule()->getFrontCrushed();
Bool backCrushed = victimObject->getBodyModule()->getBackCrushed();
// const Coord3D *dir = crusherObject->getUnitDirectionVector2D();
const Coord3D *otherDir = victimObject->getUnitDirectionVector2D();
const Coord3D *pos = crusherObject->getPosition();
const Coord3D *otherPos = victimObject->getPosition();
Real crushPointOffsetDistance = victimObject->getGeometryInfo().getMajorRadius() * 0.5;
Coord3D crushPointOffset;
crushPointOffset.x = otherDir->x * crushPointOffsetDistance;
crushPointOffset.y = otherDir->y * crushPointOffsetDistance;
crushPointOffset.z = 0;
Coord3D comparisonCoord;
Real dx, dy;
CrushEnum retval = NO_CRUSH;
Real bestDist = 99999;
// PhysicsCollide has already done the logic of which point to smoosh and waited until we crossed that point
// so at this point we just need to know which crush point is closest.
if( !frontCrushed && !backCrushed )
{
// Check the middle crush point
comparisonCoord = *otherPos;//copy so can move to each crush point
dx = comparisonCoord.x - pos->x;
dy = comparisonCoord.y - pos->y;
Real dist = (Real)( dx*dx + dy*dy );
//otherwise we want to make sure we get the closest valid crush point
retval = TOTAL_CRUSH;
bestDist = dist;
}
if( !frontCrushed )
{
// Check the front point.
comparisonCoord = *otherPos;
comparisonCoord.x += crushPointOffset.x;
comparisonCoord.y += crushPointOffset.y;
dx = comparisonCoord.x - pos->x;
dy = comparisonCoord.y - pos->y;
Real dist = (Real)( dx*dx + dy*dy );
if( dist < bestDist )//closer
{
if( backCrushed )
{
retval = TOTAL_CRUSH;
bestDist = dist;
}
else
{
retval = FRONT_END_CRUSH;
bestDist = dist;
}
}
}
if( !backCrushed )
{
// Check back point
comparisonCoord = *otherPos;
comparisonCoord.x -= crushPointOffset.x;
comparisonCoord.y -= crushPointOffset.y;
dx = comparisonCoord.x - pos->x;
dy = comparisonCoord.y - pos->y;
Real dist = (Real)( dx*dx + dy*dy );
if( dist < bestDist )//closer
{
if( frontCrushed )
{
retval = TOTAL_CRUSH;
bestDist = dist;
}
else
{
retval = BACK_END_CRUSH;
bestDist = dist;
}
}
}
return retval;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CrushDie::CrushDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CrushDie::~CrushDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void CrushDie::onDie( const DamageInfo * damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
DEBUG_ASSERTCRASH(damageInfo->in.m_damageType == DAMAGE_CRUSH, ("this should only be used for crush damage\n"));
if (damageInfo->in.m_damageType != DAMAGE_CRUSH)
return;
Object *damageDealer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
DEBUG_ASSERTCRASH(damageDealer,("You must have a damageDealer source for this effect"));
CrushEnum crushType = damageDealer ? crushLocationCheck(damageDealer, getObject()) : TOTAL_CRUSH;
if (crushType != NO_CRUSH)
{
if (getCrushDieModuleData()->m_crushSounds[crushType].getEventName().isEmpty() == false)
{
// be sure that 0==never, 100==always
// MDC: moving to GameLogicRandomValue. This does not need to be synced, but having it so makes searches *so* much nicer.
if (GameLogicRandomValue(0, 99) < getCrushDieModuleData()->m_crushSoundPercent[crushType])
{
AudioEventRTS crushSound(getCrushDieModuleData()->m_crushSounds[crushType]);
crushSound.setObjectID(getObject()->getID());
TheAudio->addAudioEvent(&crushSound);
}
}
{
Object *me = getObject();
if (me)
{
me->getBodyModule()->setFrontCrushed(crushType == TOTAL_CRUSH || crushType == FRONT_END_CRUSH);
me->getBodyModule()->setBackCrushed(crushType == TOTAL_CRUSH || crushType == BACK_END_CRUSH);
ModelConditionFlags newCrushed;
newCrushed.set(MODELCONDITION_FRONTCRUSHED, (crushType == TOTAL_CRUSH || crushType == FRONT_END_CRUSH));
newCrushed.set(MODELCONDITION_BACKCRUSHED, crushType == TOTAL_CRUSH || crushType == BACK_END_CRUSH);
me->getDrawable()->clearAndSetModelConditionFlags(
MAKE_MODELCONDITION_MASK2(MODELCONDITION_BACKCRUSHED, MODELCONDITION_FRONTCRUSHED),
newCrushed);
}
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CrushDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CrushDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CrushDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,149 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: DamDie.cpp ///////////////////////////////////////////////////////////////////////////////
// Author: Colin Day, April 2002
// Desc: The big water dam dying
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/RandomValue.h"
#include "Common/Xfer.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/TerrainVisual.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/DamDie.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DamDieModuleData::DamDieModuleData( void )
{
} // end DamDieModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void DamDieModuleData::buildFieldParse(MultiIniFieldParse& p)
{
DieModuleData::buildFieldParse( p );
// static const FieldParse dataFieldParse[] =
// {
// { 0, 0, 0, 0 }
// };
//
// p.add(dataFieldParse);
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DamDie::DamDie( Thing *thing, const ModuleData *moduleData )
:DieModule( thing, moduleData )
{
} // end DamDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DamDie::~DamDie( void )
{
} // end ~DamDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void DamDie::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
// enable all the water wave objects on the map
Object *obj;
for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
{
// only care aboue water waves
if( obj->isKindOf( KINDOF_WAVEGUIDE ) == FALSE )
continue;
// clear any disabled status of the water wave
obj->clearDisabled( DISABLED_DEFAULT );
} // end for, obj
} // end onDie
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DamDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void DamDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DamDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,105 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: DestroyDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Colin Day, November 2001
// Desc: Default Die module
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/DestroyDie.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DestroyDie::DestroyDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DestroyDie::~DestroyDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void DestroyDie::onDie( const DamageInfo *damageInfo )
{
Object *obj = getObject();
if (!isDieApplicable(damageInfo))
return;
TheGameLogic->destroyObject( obj );
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DestroyDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void DestroyDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DestroyDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,129 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: DieModule.cpp ////////////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_OBJECT_STATUS_NAMES
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/DieModule.h"
#include "GameLogic/Object.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
DieMuxData::DieMuxData() :
m_deathTypes(DEATH_TYPE_FLAGS_ALL),
m_veterancyLevels(VETERANCY_LEVEL_FLAGS_ALL)
{
}
//-------------------------------------------------------------------------------------------------
const FieldParse* DieMuxData::getFieldParse()
{
static const FieldParse dataFieldParse[] =
{
{ "DeathTypes", INI::parseDeathTypeFlags, NULL, offsetof( DieMuxData, m_deathTypes ) },
{ "VeterancyLevels", INI::parseVeterancyLevelFlags, NULL, offsetof( DieMuxData, m_veterancyLevels ) },
{ "ExemptStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof( DieMuxData, m_exemptStatus ) },
{ "RequiredStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof( DieMuxData, m_requiredStatus ) },
{ 0, 0, 0, 0 }
};
return dataFieldParse;
}
//-------------------------------------------------------------------------------------------------
Bool DieMuxData::isDieApplicable(const Object* obj, const DamageInfo *damageInfo) const
{
// wrong death type? punt
if (!getDeathTypeFlag(m_deathTypes, damageInfo->in.m_deathType))
return false;
// wrong vet level? punt
if (!getVeterancyLevelFlag(m_veterancyLevels, obj->getVeterancyLevel()))
return false;
// all 'exempt' bits must be clear for us to run.
if( m_exemptStatus.any() && obj->getStatusBits().testForAny( m_exemptStatus ) )
return false;
// all 'required' bits must be set for us to run.
// But only if we have a required status to check
if( m_requiredStatus.any() && !obj->getStatusBits().testForAll( m_requiredStatus ) )
return false;
return true;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DieModule::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DieModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// call base class
BehaviorModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DieModule::loadPostProcess( void )
{
// call base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,152 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: EjectPilotDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, April 2002
// Desc: Create an object upon this object's death
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/Player.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/EjectPilotDie.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
EjectPilotDieModuleData::EjectPilotDieModuleData() :
m_oclInAir(NULL),
m_oclOnGround(NULL),
m_invulnerableTime(0)
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void EjectPilotDieModuleData::buildFieldParse(MultiIniFieldParse& p)
{
DieModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "AirCreationList", INI::parseObjectCreationList, NULL, offsetof( EjectPilotDieModuleData, m_oclInAir ) },
{ "GroundCreationList", INI::parseObjectCreationList, NULL, offsetof( EjectPilotDieModuleData, m_oclOnGround ) },
{ "InvulnerableTime", INI::parseDurationUnsignedInt, NULL, offsetof(EjectPilotDieModuleData, m_invulnerableTime ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
EjectPilotDie::EjectPilotDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
EjectPilotDie::~EjectPilotDie( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/*static*/ void EjectPilotDie::ejectPilot(const ObjectCreationList* ocl, const Object* dyingObject, const Object* damageDealer)
{
if (!ocl || !dyingObject)
return; // it's OK for damageDealer to be null
ObjectCreationList::create(ocl, dyingObject, damageDealer);
AudioEventRTS voiceEject = *(dyingObject->getTemplate()->getPerUnitSound("VoiceEject"));
voiceEject.setPosition( dyingObject->getPosition() );
voiceEject.setPlayerIndex( dyingObject->getControllingPlayer()->getPlayerIndex() );
TheAudio->addAudioEvent(&voiceEject);
AudioEventRTS soundEject = *(dyingObject->getTemplate()->getPerUnitSound("SoundEject"));
soundEject.setPosition( dyingObject->getPosition() );
TheAudio->addAudioEvent(&soundEject);
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void EjectPilotDie::onDie( const DamageInfo * damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
Object* damageDealer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
const EjectPilotDieModuleData* d = getEjectPilotDieModuleData();
const ObjectCreationList* ocl = getObject()->isSignificantlyAboveTerrain() ? d->m_oclInAir : d->m_oclOnGround;
ejectPilot(ocl, getObject(), damageDealer);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void EjectPilotDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void EjectPilotDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void EjectPilotDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,137 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: FXListDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, Jan 2002
// Desc: Simple Die module
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/INI.h"
#include "Common/Player.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/FXList.h"
#include "GameLogic/Damage.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/FXListDie.h"
#include "GameLogic/Module/AIUpdate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FXListDie::FXListDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
if( getFXListDieModuleData()->m_initiallyActive )
{
giveSelfUpgrade();
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FXListDie::~FXListDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void FXListDie::onDie( const DamageInfo *damageInfo )
{
if (!isUpgradeActive())
return;
if (!isDieApplicable(damageInfo))
return;
const FXListDieModuleData* d = getFXListDieModuleData();
UpgradeMaskType activation, conflicting;
getUpgradeActivationMasks( activation, conflicting );
Object *obj = getObject();
if( obj->getObjectCompletedUpgradeMask().testForAny( conflicting ) )
{
return;
}
if( obj->getControllingPlayer() && obj->getControllingPlayer()->getCompletedUpgradeMask().testForAny( conflicting ) )
{
return;
}
if (d->m_defaultDeathFX)
{
if (d->m_orientToObject)
{
Object *damageDealer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
FXList::doFXObj(getFXListDieModuleData()->m_defaultDeathFX, getObject(), damageDealer);
}
else
{
FXList::doFXPos(getFXListDieModuleData()->m_defaultDeathFX, getObject()->getPosition());
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FXListDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void FXListDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void FXListDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,102 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: KeepObjectDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Kris Morness, November 2002
// Desc: Die module for things that want to leave rubble in the world and don't have other die
// modules. This fixes civilian buildings that don't have garrison contains. Garrison
// contains have a die module built in, so these buildings need something. Without it
// they default to the destroydie module which outright removes the object.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/KeepObjectDie.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
KeepObjectDie::KeepObjectDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
KeepObjectDie::~KeepObjectDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void KeepObjectDie::onDie( const DamageInfo *damageInfo )
{
if( !isDieApplicable(damageInfo) )
{
return;
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void KeepObjectDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void KeepObjectDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void KeepObjectDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,231 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: RebuildHoleExposeDie.cpp /////////////////////////////////////////////////////////////////
// Author: Colin Day, June 2002
// Desc: When a structure dies with this module, a rebuild hole will be created in place
// of the structure that will automatically rebuild the structure
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDE FILES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/RebuildHoleBehavior.h"
#include "GameLogic/Module/RebuildHoleExposeDie.h"
#include "GameLogic/Object.h"
#include "GameLogic/ScriptEngine.h"
#include "GameClient/SelectionXlat.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
RebuildHoleExposeDieModuleData::RebuildHoleExposeDieModuleData()
{
m_holeMaxHealth = 0.0f;
m_transferAttackers = true;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void RebuildHoleExposeDieModuleData::buildFieldParse( MultiIniFieldParse &p )
{
DieModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "HoleName", INI::parseAsciiString, NULL, offsetof( RebuildHoleExposeDieModuleData, m_holeName ) },
{ "HoleMaxHealth", INI::parseReal, NULL, offsetof( RebuildHoleExposeDieModuleData, m_holeMaxHealth ) },
{ "TransferAttackers", INI::parseBool, NULL, offsetof( RebuildHoleExposeDieModuleData, m_transferAttackers ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
RebuildHoleExposeDie::RebuildHoleExposeDie( Thing *thing, const ModuleData* moduleData )
: DieModule( thing, moduleData )
{
} // end RebuildHoleExposeDie
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
RebuildHoleExposeDie::~RebuildHoleExposeDie( void )
{
} // end ~RebuildHoleExposeDie
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void RebuildHoleExposeDie::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
#if defined(_DEBUG) || defined(_INTERNAL) || defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)
if(TheSelectionTranslator->isHandOfGodSelectionMode())
{
if ( getObject()->isKindOf( KINDOF_STRUCTURE ) )
{
if ( damageInfo->in.m_damageType == DAMAGE_UNRESISTABLE )
return;
}
}
#endif
const RebuildHoleExposeDieModuleData *modData = getRebuildHoleExposeDieModuleData();
Object *us = getObject();
//
// if we are being constructed from either the first time or from a hole reconstruction
// we do not "spawn" a hole object
//
if( us->getControllingPlayer() != ThePlayerList->getNeutralPlayer()
&& us->getControllingPlayer()->isPlayerActive()
&& !us->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
{
Object *hole;
// create the hole
hole = TheThingFactory->newObject( TheThingFactory->findTemplate( modData->m_holeName ),
getObject()->getTeam() );
// put the hole at our position and angle
hole->setPosition( us->getPosition() );
hole->setOrientation( us->getOrientation() );
//
// modify the hole extents to be the same as ours because we need to preserve the
// same amount of space for the rebuilding process
//
hole->setGeometryInfo( us->getGeometryInfo() );
//
// Transfer the building's name to the hole
//
TheScriptEngine->transferObjectName( us->getName(), hole );
//
// add to pathfind map, this really should be wrapped up somewhere in the creation
// pipeline of the object automagically!
//
TheAI->pathfinder()->addObjectToPathfindMap( hole );
// set the health of the hole to that defined by our data
BodyModuleInterface *body = hole->getBodyModule();
body->setMaxHealth( modData->m_holeMaxHealth );
// set the information in the hole about what to build
RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( hole );
// sanity
DEBUG_ASSERTCRASH( rhbi, ("RebuildHoleExposeDie: No Rebuild Hole Behavior interface on hole\n") );
// start the rebuild process
if( rhbi )
rhbi->startRebuildProcess( us->getTemplate(), us->getID() );
if (modData->m_transferAttackers)
{
for ( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
{
AIUpdateInterface* ai = obj->getAI();
if (!ai)
continue;
ai->transferAttack(us->getID(), hole->getID());
}
}
} // end if
} // end onDie
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void RebuildHoleExposeDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void RebuildHoleExposeDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void RebuildHoleExposeDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,131 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: SpecialPowerCompletionDie.cpp ////////////////////////////////////////////////////////////
// Author: Matthew D. Campbell, May 2002
// Desc: Die method responsible for telling TheScriptEngine that a special power has been completed
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/SpecialPower.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/SpecialPowerCompletionDie.h"
#include "GameLogic/Object.h"
#include "GameLogic/ScriptEngine.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SpecialPowerCompletionDie::SpecialPowerCompletionDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
m_creatorID = INVALID_ID;
m_creatorSet = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SpecialPowerCompletionDie::~SpecialPowerCompletionDie( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
notifyScriptEngine();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::notifyScriptEngine( void )
{
if (m_creatorID != INVALID_ID)
{
TheScriptEngine->notifyOfCompletedSpecialPower(
getObject()->getControllingPlayer()->getPlayerIndex(),
getSpecialPowerCompletionDieModuleData()->m_specialPowerTemplate->getName(),
m_creatorID);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::setCreator( ObjectID creatorID )
{
if (!m_creatorSet)
{
m_creatorSet = TRUE;
m_creatorID = creatorID;
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
// creator id
xfer->xferObjectID( &m_creatorID );
// creator set
xfer->xferBool( &m_creatorSet );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,127 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: UpgradeDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Kris Morness, August 2002
// Desc: When object dies, the parent object is freed of the specified object upgrade field.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/ThingTemplate.h"
#include "Common/Upgrade.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/UpgradeDie.h"
#include "GameLogic/Object.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpgradeDie::UpgradeDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpgradeDie::~UpgradeDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void UpgradeDie::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
//Look for the object that created me.
Object *producer = TheGameLogic->findObjectByID( getObject()->getProducerID() );
if( producer )
{
//Okay, we found our parent... now look for the upgrade.
const UpgradeTemplate *upgrade = TheUpgradeCenter->findUpgrade( getUpgradeDieModuleData()->m_upgradeName );
if( upgrade )
{
//We found the upgrade, now see if the parent object has it set...
if( producer->hasUpgrade( upgrade ) )
{
//Cool, now remove it.
producer->removeUpgrade( upgrade );
}
else
{
DEBUG_ASSERTCRASH( 0, ("Object %s just died, but is trying to free upgrade %s in it's producer %s%s",
getObject()->getTemplate()->getName().str(),
getUpgradeDieModuleData()->m_upgradeName.str(),
producer->getTemplate()->getName().str(),
", which the producer doesn't have. This is used in cases where the producer builds an upgrade that can die... like ranger building scout drones.") );
}
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void UpgradeDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void UpgradeDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void UpgradeDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,276 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: ExperienceTracker.cpp //////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, February 2002
// Desc: Keeps track of experience points so Veterance levels can be gained
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "Common/ThingTemplate.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
ExperienceTracker::ExperienceTracker(Object *parent) :
m_parent(parent),
m_currentLevel(LEVEL_REGULAR),
m_experienceSink(INVALID_ID),
m_experienceScalar( 1.0f ),
m_currentExperience(0) // Added By Sadullah Nader
{
}
//-------------------------------------------------------------------------------------------------
ExperienceTracker::~ExperienceTracker()
{
}
//-------------------------------------------------------------------------------------------------
Int ExperienceTracker::getExperienceValue( const Object* killer ) const
{
// No experience for killing an ally, cheater.
if( killer->getRelationship( m_parent ) == ALLIES )
return 0;
return m_parent->getTemplate()->getExperienceValue(m_currentLevel);
}
//-------------------------------------------------------------------------------------------------
Bool ExperienceTracker::isTrainable() const
{
return m_parent->getTemplate()->isTrainable();
}
//-------------------------------------------------------------------------------------------------
Bool ExperienceTracker::isAcceptingExperiencePoints() const
{
return isTrainable() || (m_experienceSink != INVALID_ID);
}
//-------------------------------------------------------------------------------------------------
void ExperienceTracker::setExperienceSink( ObjectID sink )
{
m_experienceSink = sink;
}
//-------------------------------------------------------------------------------------------------
// Set Level to AT LEAST this... if we are already >= this level, do nothing.
void ExperienceTracker::setMinVeterancyLevel( VeterancyLevel newLevel )
{
// This does not check for IsTrainable, because this function is for explicit setting,
// so the setter is assumed to know what they are doing. The game function
// of addExperiencePoints cares about Trainability.
if (m_currentLevel < newLevel)
{
VeterancyLevel oldLevel = m_currentLevel;
m_currentLevel = newLevel;
m_currentExperience = m_parent->getTemplate()->getExperienceRequired(m_currentLevel); //Minimum for this level
if (m_parent)
m_parent->onVeterancyLevelChanged( oldLevel, newLevel );
}
}
//-------------------------------------------------------------------------------------------------
void ExperienceTracker::setVeterancyLevel( VeterancyLevel newLevel, Bool provideFeedback )
{
// This does not check for IsTrainable, because this function is for explicit setting,
// so the setter is assumed to know what they are doing. The game function
// of addExperiencePoints cares about Trainability, if flagged thus.
if (m_currentLevel != newLevel)
{
VeterancyLevel oldLevel = m_currentLevel;
m_currentLevel = newLevel;
m_currentExperience = m_parent->getTemplate()->getExperienceRequired(m_currentLevel); //Minimum for this level
if (m_parent)
m_parent->onVeterancyLevelChanged( oldLevel, newLevel, provideFeedback );
}
}
//-------------------------------------------------------------------------------------------------
Bool ExperienceTracker::gainExpForLevel(Int levelsToGain, Bool canScaleForBonus)
{
Int newLevel = (Int)m_currentLevel + levelsToGain;
if (newLevel > LEVEL_LAST)
newLevel = LEVEL_LAST;
// gain what levels we can, even if we can't use 'em all
if (newLevel > m_currentLevel)
{
Int experienceNeeded = m_parent->getTemplate()->getExperienceRequired(newLevel) - m_currentExperience;
addExperiencePoints( experienceNeeded, canScaleForBonus );
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------
Bool ExperienceTracker::canGainExpForLevel(Int levelsToGain) const
{
Int newLevel = (Int)m_currentLevel + levelsToGain;
// return true if we can gain levels, even if we can't gain ALL the levels requested
if (newLevel > LEVEL_LAST)
newLevel = LEVEL_LAST;
return (newLevel > m_currentLevel);
}
//-------------------------------------------------------------------------------------------------
void ExperienceTracker::addExperiencePoints( Int experienceGain, Bool canScaleForBonus)
{
if( m_experienceSink != INVALID_ID )
{
// I have been set up to give my experience to someone else
Object *sinkPointer = TheGameLogic->findObjectByID( m_experienceSink );
if( sinkPointer )
{
// Not a fatal failure if not valid, he died when I was in the air.
sinkPointer->getExperienceTracker()->addExperiencePoints( experienceGain * m_experienceScalar, canScaleForBonus );
return;
}
}
if( !isTrainable() )
return; //safety
VeterancyLevel oldLevel = m_currentLevel;
Int amountToGain = experienceGain;
if ( canScaleForBonus )
amountToGain *= m_experienceScalar;
m_currentExperience += amountToGain;
Int levelIndex = 0;
while( ( (levelIndex + 1) < LEVEL_COUNT)
&& m_currentExperience >= m_parent->getTemplate()->getExperienceRequired(levelIndex + 1)
)
{
// If there is a higher level to qualify for, and I qualify for it, advance the index
levelIndex++;
}
m_currentLevel = (VeterancyLevel)levelIndex;
if( oldLevel != m_currentLevel )
{
// Edge trigger special level gain effects.
m_parent->onVeterancyLevelChanged( oldLevel, m_currentLevel );
}
}
//-------------------------------------------------------------------------------------------------
void ExperienceTracker::setExperienceAndLevel( Int experienceIn, Bool provideFeedback )
{
if( m_experienceSink != INVALID_ID )
{
// I have been set up to give my experience to someone else
Object *sinkPointer = TheGameLogic->findObjectByID( m_experienceSink );
if( sinkPointer )
{
// Not a fatal failure if not valid, he died when I was in the air.
sinkPointer->getExperienceTracker()->setExperienceAndLevel( experienceIn, provideFeedback );
return;
}
}
if( !isTrainable() )
return; //safety
VeterancyLevel oldLevel = m_currentLevel;
m_currentExperience = experienceIn;
Int levelIndex = 0;
while( ( (levelIndex + 1) < LEVEL_COUNT)
&& m_currentExperience >= m_parent->getTemplate()->getExperienceRequired(levelIndex + 1)
)
{
// If there is a level to qualify for, and I qualify for it, advance the index
levelIndex++;
}
m_currentLevel = (VeterancyLevel)levelIndex;
if( oldLevel != m_currentLevel )
{
// Edge trigger special level gain effects.
m_parent->onVeterancyLevelChanged( oldLevel, m_currentLevel, provideFeedback ); //<<== paradox! this may be a level lost!
}
}
//-----------------------------------------------------------------------------
void ExperienceTracker::crc( Xfer *xfer )
{
xfer->xferInt( &m_currentExperience );
xfer->xferUser( &m_currentLevel, sizeof( VeterancyLevel ) );
} // end crc
//-----------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version
*/
// ----------------------------------------------------------------------------
void ExperienceTracker::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// no need to save the m_parent pointer, it is connected on allocation time
// m_parent
// current level
xfer->xferUser( &m_currentLevel, sizeof( VeterancyLevel ) );
// current experience
xfer->xferInt( &m_currentExperience );
// experience sink
xfer->xferObjectID( &m_experienceSink );
// experience scalar
xfer->xferReal( &m_experienceScalar );
} // end xfer
//-----------------------------------------------------------------------------
void ExperienceTracker::loadPostProcess( void )
{
} // end loadPostProcess

View File

@@ -0,0 +1,395 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: FiringTracker.cpp //////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, February 2002
// Desc: Keeps track of shots fired and people targeted for weapons that want a history of such a thing
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioHandleSpecialValues.h"
#include "Common/GameType.h"
#include "Common/GameAudio.h"
#include "Common/PerfTimer.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/FiringTracker.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/ObjectHelper.h"
#include "GameLogic/Object.h"
#include "GameLogic/Weapon.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
FiringTracker::FiringTracker(Thing* thing, const ModuleData *modData) : UpdateModule( thing, modData )
{
m_consecutiveShots = 0;
m_victimID = INVALID_ID;
m_frameToStartCooldown = 0;
m_frameToForceReload = 0;
m_frameToStopLoopingSound = 0;
m_audioHandle = AHSV_NoSound;
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
//-------------------------------------------------------------------------------------------------
FiringTracker::~FiringTracker()
{
// no need to protect this.
TheAudio->removeAudioEvent( m_audioHandle );
m_audioHandle = AHSV_NoSound;
}
//-------------------------------------------------------------------------------------------------
Int FiringTracker::getNumConsecutiveShotsAtVictim( const Object *victim ) const
{
if( victim == NULL )
return 0;// safety, this function is for asking about shots at a victim
if( victim->getID() != m_victimID )
return 0;// nope, not shooting at him
return m_consecutiveShots;// this is how any times I have shot at this hoser right now
}
//-------------------------------------------------------------------------------------------------
void FiringTracker::shotFired(const Weapon* weaponFired, ObjectID victimID)
{
UnsignedInt now = TheGameLogic->getFrame();
Object *me = getObject();
const Object *victim = TheGameLogic->findObjectByID(victimID); // May be null for ground shot
if( victim && victim->testStatus(OBJECT_STATUS_FAERIE_FIRE) )
{
if( !me->testWeaponBonusCondition(WEAPONBONUSCONDITION_TARGET_FAERIE_FIRE) )
{
// We shoot faster at guys marked thusly
me->setWeaponBonusCondition(WEAPONBONUSCONDITION_TARGET_FAERIE_FIRE);
}
}
else
{
// A ground shot or the lack of the status on the target will clear this
if( me->testWeaponBonusCondition(WEAPONBONUSCONDITION_TARGET_FAERIE_FIRE) )
{
me->clearWeaponBonusCondition(WEAPONBONUSCONDITION_TARGET_FAERIE_FIRE);
}
}
if( victimID == m_victimID )
{
// Shooting at the same guy
++m_consecutiveShots;
}
else if( now < m_frameToStartCooldown )
{
// Switching targets within the coast time is valid, and we will not spin down
++m_consecutiveShots;
m_victimID = victimID;
}
else
{
// Start the count over for the new guy
m_consecutiveShots = 1;
m_victimID = victimID;
}
// Push back the time that we should force a reload with each shot
UnsignedInt autoReloadDelay = weaponFired->getAutoReloadWhenIdleFrames();
if( autoReloadDelay > 0 )
m_frameToForceReload = now + autoReloadDelay;
UnsignedInt coast = weaponFired->getContinuousFireCoastFrames();
if (coast)
m_frameToStartCooldown = weaponFired->getPossibleNextShotFrame() + coast;
else
m_frameToStartCooldown = 0;
Int shotsNeededOne = weaponFired->getContinuousFireOneShotsNeeded();
Int shotsNeededTwo = weaponFired->getContinuousFireTwoShotsNeeded();
if( me->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ) )
{
// Can either go up or down from here.
if( m_consecutiveShots < shotsNeededOne )
coolDown();
else if( m_consecutiveShots > shotsNeededTwo )
speedUp();
}
else if( me->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ) )
{
// Only place I can go here from here is all the way down
if( m_consecutiveShots < shotsNeededTwo )
{
coolDown();
}
}
else
{
// Check to go up
if( m_consecutiveShots > shotsNeededOne )
speedUp();
}
UnsignedInt fireSoundLoopTime = weaponFired->getFireSoundLoopTime();
if (fireSoundLoopTime != 0)
{
// If the sound has stopped playing, then we need to re-add it.
if (m_frameToStopLoopingSound == 0 || !TheAudio->isCurrentlyPlaying(m_audioHandle))
{
AudioEventRTS audio = weaponFired->getFireSound();
audio.setObjectID(getObject()->getID());
m_audioHandle = TheAudio->addAudioEvent( &audio );
}
m_frameToStopLoopingSound = now + fireSoundLoopTime;
}
else
{
AudioEventRTS fireAndForgetSound = weaponFired->getFireSound();
fireAndForgetSound.setObjectID(getObject()->getID());
TheAudio->addAudioEvent(&fireAndForgetSound);
m_frameToStopLoopingSound = 0;
}
setWakeFrame(me, calcTimeToSleep());
}
//-------------------------------------------------------------------------------------------------
UpdateSleepTime FiringTracker::update()
{
//DEBUG_ASSERTCRASH(m_frameToStartCooldown != 0 || m_frameToStopLoopingSound != 0, ("hmm, should be asleep"));
UnsignedInt now = TheGameLogic->getFrame();
// I have been idle long enough that I should reload, so I do not hang around with a near empty clip forever.
if( m_frameToForceReload != 0 && now >= m_frameToForceReload )
{
getObject()->reloadAllAmmo(TRUE);
m_frameToForceReload = 0;
}
// If it has been too long since I fired. I have to start over.
// (don't call if we don't need to cool down... it's expensive!)
if (m_frameToStopLoopingSound != 0)
{
if (now >= m_frameToStopLoopingSound)
{
TheAudio->removeAudioEvent( m_audioHandle );
m_audioHandle = AHSV_NoSound;
m_frameToStopLoopingSound = 0;
}
}
if( m_frameToStartCooldown != 0 && now > m_frameToStartCooldown )
{
m_frameToStartCooldown = now + LOGICFRAMES_PER_SECOND;
coolDown();// if this is the coolest call to cooldown, it will set m_frameToStartCooldown to zero
return UPDATE_SLEEP(LOGICFRAMES_PER_SECOND);
}
UpdateSleepTime sleepTime = calcTimeToSleep();
return sleepTime;
}
//-------------------------------------------------------------------------------------------------
UpdateSleepTime FiringTracker::calcTimeToSleep()
{
// Figure out the longest amount of time we can sleep as unneeded
// If all the timers are off, then we aren't needed at all
if (m_frameToStopLoopingSound == 0 && m_frameToStartCooldown == 0 && m_frameToForceReload == 0)
return UPDATE_SLEEP_FOREVER;
// Otherwise, we need to wake up to service the shortest timer
UnsignedInt now = TheGameLogic->getFrame();
UnsignedInt sleepTime = UPDATE_SLEEP_FOREVER;
if( m_frameToStopLoopingSound != 0 )
{
if( m_frameToStopLoopingSound <= now )
sleepTime = UPDATE_SLEEP_NONE;
else if( (m_frameToStopLoopingSound - now) < sleepTime )
sleepTime = m_frameToStopLoopingSound - now;
}
if( m_frameToStartCooldown != 0 )
{
if( m_frameToStartCooldown <= now )
sleepTime = UPDATE_SLEEP_NONE;
else if( (m_frameToStartCooldown - now) < sleepTime )
sleepTime = m_frameToStartCooldown - now;
}
if( m_frameToForceReload != 0 )
{
if( m_frameToForceReload <= now )
sleepTime = UPDATE_SLEEP_NONE;
else if( (m_frameToForceReload - now) < sleepTime )
sleepTime = m_frameToForceReload - now;
}
return UPDATE_SLEEP(sleepTime);
}
//-------------------------------------------------------------------------------------------------
void FiringTracker::speedUp()
{
ModelConditionFlags clr, set;
Object *self = getObject();
if( self->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ) )
{
//self->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN );
//self->clearModelConditionState( MODELCONDITION_CONTINUOUS_FIRE_MEAN );
//self->clearModelConditionState( MODELCONDITION_CONTINUOUS_FIRE_SLOW );
}
else if(self->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ) )
{
const AudioEventRTS *soundToPlayPtr = self->getTemplate()->getPerUnitSound( "VoiceRapidFire" );
AudioEventRTS soundToPlay = *soundToPlayPtr;
soundToPlay.setObjectID( self->getID() );
TheAudio->addAudioEvent( &soundToPlay );
// These flags are exclusive, not cumulative
self->setWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST );
set.set(MODELCONDITION_CONTINUOUS_FIRE_FAST);
// These flags are exclusive, not cumulative
self->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN );
clr.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN);
clr.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW);
}
else
{
self->setWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN );
set.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN);
// These flags are exclusive, not cumulative
self->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST );
clr.set(MODELCONDITION_CONTINUOUS_FIRE_FAST);
clr.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW);
}
self->clearAndSetModelConditionFlags(clr, set);
}
//-------------------------------------------------------------------------------------------------
void FiringTracker::coolDown()
{
ModelConditionFlags clr, set;
if( getObject()->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST )
|| getObject()->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ))
{
// Straight to zero from wherever it is
set.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW);
getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST );
getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN );
clr.set(MODELCONDITION_CONTINUOUS_FIRE_FAST);
clr.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN);
}
else // we stop, now
{
getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST );
getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN );
// Just chillin, nothing to change
clr.set(MODELCONDITION_CONTINUOUS_FIRE_FAST);
clr.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN);
clr.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW);
m_frameToStartCooldown = 0;
}
getObject()->clearAndSetModelConditionFlags(clr, set);
// Start everything over
m_consecutiveShots = 0;
m_victimID = INVALID_ID;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FiringTracker::crc( Xfer *xfer )
{
// object helper base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void FiringTracker::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// object helper base class
UpdateModule::xfer( xfer );
// consecutive shots
xfer->xferInt( &m_consecutiveShots );
// victim id
xfer->xferObjectID( &m_victimID );
// frame to start cooldown
xfer->xferUnsignedInt( &m_frameToStartCooldown );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void FiringTracker::loadPostProcess( void )
{
// object helper back class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,224 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE GhostObject.cpp ///////////////////////////////////////////////////////////////////////////
// Simple base object
// Author: Michael S. Booth, October 2000
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/GhostObject.h"
#include "GameLogic/Object.h"
GhostObjectManager *TheGhostObjectManager=NULL;
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GhostObject::GhostObject(void):
//Added By Sadullah Nader
//Initializations missing and needed
m_parentAngle(0.0f),
m_parentGeometryIsSmall(0.0f),
m_parentGeometryMajorRadius(0.0f),
m_parentGeometryminorRadius(0.0f),
m_parentObject(NULL),
m_partitionData(NULL)
{
m_parentPosition.zero();
// End Initializations
} // end Object
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
GhostObject::~GhostObject()
{
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void GhostObject::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void GhostObject::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// parent object
ObjectID parentObjectID = INVALID_ID;
if( m_parentObject )
parentObjectID = m_parentObject->getID();
xfer->xferObjectID( &parentObjectID );
if( xfer->getXferMode() == XFER_LOAD )
{
// tie up parent object pointer
m_parentObject = TheGameLogic->findObjectByID( parentObjectID );
// sanity
if( parentObjectID != INVALID_ID && m_parentObject == NULL )
{
DEBUG_CRASH(( "GhostObject::xfer - Unable to connect m_parentObject\n" ));
throw INI_INVALID_DATA;
} // end if
} // end if
// parent geometry type
xfer->xferUser( &m_parentGeometryType, sizeof( GeometryType ) );
// parent geometry is small
xfer->xferBool( &m_parentGeometryIsSmall );
// parent geometry major radius
xfer->xferReal( &m_parentGeometryMajorRadius );
// parent geometry minor radius
xfer->xferReal( &m_parentGeometryminorRadius );
// parent angle
xfer->xferReal( &m_parentAngle );
// parent position
xfer->xferCoord3D( &m_parentPosition );
// partition data
///@todo write me ---> !!!!!
// PartitionData *m_partitionData; ///< our PartitionData
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void GhostObject::loadPostProcess( void )
{
} // end loadPostProcess
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
GhostObjectManager::GhostObjectManager(void)
{
m_lockGhostObjects = FALSE;
m_saveLockGhostObjects = FALSE;
m_localPlayer = 0;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
GhostObjectManager::~GhostObjectManager()
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::reset(void)
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
GhostObject *GhostObjectManager::addGhostObject(Object *object, PartitionData *pd)
{
return 0;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::removeGhostObject(GhostObject *mod)
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::updateOrphanedObjects(int *playerIndexList, int numNonLocalPlayers)
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::releasePartitionData(void)
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::restorePartitionData(void)
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method:
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// local player
xfer->xferInt( &m_localPlayer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::loadPostProcess( void )
{
} // end loadPostProcess

View File

@@ -0,0 +1,185 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: ObjectDefectionHelper.cpp ////////////////////////////////////////////////////////////////
// Author: Colin Day, Steven Johnson - September 2002
// Desc: Object helper module
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/ObjectDefectionHelper.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ObjectDefectionHelper::~ObjectDefectionHelper( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime ObjectDefectionHelper::update()
{
Object* obj = getObject();
Drawable* draw = obj->getDrawable();
// if we are here, we should be an undetected defector, but
// just in case someone has changed us behind our backs...
if (!obj->getIsUndetectedDefector())
return UPDATE_SLEEP_FOREVER;
UnsignedInt now = TheGameLogic->getFrame();
if (now >= m_defectionDetectionEnd)
{
obj->friend_setUndetectedDefector(FALSE);
// timer has reached zero, so we flash white once!!!! -- lorenzen
if (draw && m_doDefectorFX)
{
RGBColor white = {1,1,1};
if (draw)
draw->flashAsSelected( &white ); //Whew! that's easier, now, isn't it!
AudioEventRTS defectorVulnerableSound = TheAudio->getMiscAudio()->m_defectorTimerDingSound;
defectorVulnerableSound.setObjectID( obj->getID() );
TheAudio->addAudioEvent(&defectorVulnerableSound);
}
return UPDATE_SLEEP_FOREVER; // hey, we're done.
}
// dead or attacking... our cover is blown.
if( obj->isEffectivelyDead() || obj->getStatusBits().test( OBJECT_STATUS_IS_FIRING_WEAPON ) )
{
// PLEASE NOTE:
// checking the is_attacking statusbit above, only handles weapon related attacks...
// I also set m_undetectedDefector = FALSE in the groupDoSpecialPower[...]( ) functions;
obj->friend_setUndetectedDefector(FALSE);
return UPDATE_SLEEP_FOREVER; // hey, we're done.
}
if (draw && m_doDefectorFX) // skip fx if merely 'invulnerable'
{
Bool lastPhase = ( ((Int)m_defectionDetectionFlashPhase) & 1 );// were we in a flashy phase last frame?
UnsignedInt timeLeft = m_defectionDetectionEnd - now;
m_defectionDetectionFlashPhase += 0.5f * ( 1.0f - ((Real)timeLeft / DEFECTION_DETECTION_TIME_MAX ) );
Bool thisPhase = ( ((Int)m_defectionDetectionFlashPhase) & 1 );// are we in a flashy phase this frame?
if ( lastPhase && ( ! thisPhase ) )
{
draw->flashAsSelected(); //Whew! that's easier, now, isn't it!
AudioEventRTS defectorTimerSound = TheAudio->getMiscAudio()->m_defectorTimerTickSound;
defectorTimerSound.setObjectID( obj->getID() );
TheAudio->addAudioEvent(&defectorTimerSound);
}
}
return UPDATE_SLEEP_NONE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void ObjectDefectionHelper::startDefectionTimer(UnsignedInt numFrames, Bool withDefectorFX)
{
Object* obj = getObject();
if (!obj->getIsUndetectedDefector())
{
setWakeFrame(obj, UPDATE_SLEEP_FOREVER);
return;
}
UnsignedInt now = TheGameLogic->getFrame();
m_defectionDetectionStart = now;
m_defectionDetectionEnd = now + numFrames;
m_defectionDetectionFlashPhase = 0.0f;
m_doDefectorFX = withDefectorFX;
setWakeFrame(obj, UPDATE_SLEEP_NONE);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ObjectDefectionHelper::crc( Xfer *xfer )
{
// object helper crc
ObjectHelper::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info;
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ObjectDefectionHelper::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// object helper base class
ObjectHelper::xfer( xfer );
// detection start
xfer->xferUnsignedInt( &m_defectionDetectionStart );
// detection end
xfer->xferUnsignedInt( &m_defectionDetectionEnd );
// flash phase
xfer->xferReal( &m_defectionDetectionFlashPhase );
// do defector fx
xfer->xferBool( &m_doDefectorFX );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ObjectDefectionHelper::loadPostProcess( void )
{
// object helper base class
ObjectHelper::loadPostProcess();
} // end loadPostProcess

Some files were not shown because too many files have changed in this diff Show More