/*
** Command & Conquer Generals(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: MissileAIUpdate.cpp
// Author: Michael S. Booth, December 2001
// Desc: Implementation of missile behavior
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Thing.h"
#include "Common/ThingTemplate.h"
#include "Common/RandomValue.h"
#include "Common/BitFlagsIO.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/MissileAIUpdate.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/TerrainLogic.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
#include "GameClient/FXList.h"
#include "GameClient/ParticleSys.h"
const Real BIGNUM = 99999.0f;
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MissileAIUpdateModuleData::MissileAIUpdateModuleData()
{
m_tryToFollowTarget = true;
m_fuelLifetime = 0;
m_ignitionDelay = 0;
m_initialVel = 0;
m_initialDist = 0.0f;
m_diveDistance = 0.0f;
m_ignitionFX = NULL;
m_useWeaponSpeed = false;
m_detonateOnNoFuel = FALSE;
m_garrisonHitKillCount = 0;
m_garrisonHitKillFX = NULL;
m_lockDistance = 75.0f;
}
//-----------------------------------------------------------------------------
void MissileAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
{
AIUpdateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "TryToFollowTarget", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_tryToFollowTarget ) },
{ "FuelLifetime", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_fuelLifetime ) },
{ "IgnitionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_ignitionDelay ) },
{ "InitialVelocity", INI::parseVelocityReal, NULL, offsetof( MissileAIUpdateModuleData, m_initialVel) },
{ "DistanceToTravelBeforeTurning", INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_initialDist ) },
{ "DistanceToTargetBeforeDiving", INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_diveDistance ) },
{ "DistanceToTargetForLock",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_lockDistance ) },
{ "IgnitionFX", INI::parseFXList, NULL, offsetof( MissileAIUpdateModuleData, m_ignitionFX ) },
{ "UseWeaponSpeed", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_useWeaponSpeed ) },
{ "DetonateOnNoFuel", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateOnNoFuel ) },
{ "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindof ) },
{ "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindofNot ) },
{ "GarrisonHitKillCount", INI::parseUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillCount ) },
{ "GarrisonHitKillFX", INI::parseFXList, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillFX ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MissileAIUpdate::MissileAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData )
{
const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
m_state = PRELAUNCH;
m_stateTimestamp = TheGameLogic->getFrame();
m_nextTargetTrackTime = 0x7fffffff; // so that we never recalc target pos, by default
m_launcherID = INVALID_ID;
m_victimID = INVALID_ID;
m_isArmed = false;
m_fuelExpirationDate = 0;
m_noTurnDistLeft = d->m_initialDist;
m_prevPos = *getObject()->getPosition();
m_maxAccel = BIGNUM;
m_detonationWeaponTmpl = NULL;
m_exhaustSysTmpl = NULL;
m_isTrackingTarget = FALSE;
m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
m_extraBonusFlags = 0;
m_originalTargetPos.zero();
}
//-------------------------------------------------------------------------------------------------
MissileAIUpdate::~MissileAIUpdate()
{
tossExhaust();
}
//-------------------------------------------------------------------------------------------------
void MissileAIUpdate::onDelete( void )
{
//
// there is no need to destroy the attached particle systems here because the particle
// systems themselves detect that their "parent" object is gone and destroy themselves.
// but to be clean, for the exhaust particle system, we will invalidate the ID so that
// we can't do anything else with it since we're being deleted
//
m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
}
//-------------------------------------------------------------------------------------------------
void MissileAIUpdate::tossExhaust()
{
if (m_exhaustID != INVALID_PARTICLE_SYSTEM_ID)
{
TheParticleSystemManager->destroyParticleSystemByID(m_exhaustID);
m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;
}
}
//-------------------------------------------------------------------------------------------------
void MissileAIUpdate::switchToState(MissileStateType s)
{
if (m_state != s)
{
m_state = s;
m_stateTimestamp = TheGameLogic->getFrame();
}
}
//-------------------------------------------------------------------------------------------------
// Prepares the missile for launch via proper weapon-system channels.
//-------------------------------------------------------------------------------------------------
void MissileAIUpdate::projectileLaunchAtObjectOrPosition(
const Object *victim,
const Coord3D* victimPos,
const Object *launcher,
WeaponSlotType wslot,
Int specificBarrelToUse,
const WeaponTemplate* detWeap,
const ParticleSystemTemplate* exhaustSysOverride
)
{
DEBUG_ASSERTCRASH(specificBarrelToUse>=0, ("specificBarrelToUse must now be explicit"));
m_launcherID = launcher ? launcher->getID() : INVALID_ID;
m_detonationWeaponTmpl = detWeap;
m_extraBonusFlags = launcher ? launcher->getWeaponBonusCondition() : 0;
Weapon::positionProjectileForLaunch(getObject(), launcher, wslot, specificBarrelToUse);
projectileFireAtObjectOrPosition( victim, victimPos, detWeap, exhaustSysOverride );
}
#define APPROACH_HEIGHT 10.0f
//-------------------------------------------------------------------------------------------------
// The actual firing of the missile once setup.
//-------------------------------------------------------------------------------------------------
void MissileAIUpdate::projectileFireAtObjectOrPosition( const Object *victim, const Coord3D *victimPos, const WeaponTemplate *detWeap, const ParticleSystemTemplate* exhaustSysOverride )
{
const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData();
Object *obj = getObject();
m_exhaustSysTmpl = exhaustSysOverride;
m_detonationWeaponTmpl = detWeap;
Real initialVelToUse = d->m_initialVel;
if (d->m_useWeaponSpeed)
{
Real weaponSpeed = detWeap->getWeaponSpeed();
initialVelToUse = weaponSpeed;
Locomotor* curLoco = getCurLocomotor();
if (curLoco)
{
m_maxAccel = weaponSpeed;
curLoco->setMaxSpeed(weaponSpeed);
curLoco->setMaxAcceleration(m_maxAccel);
}
}
Real deltaZ = victimPos->z - obj->getPosition()->z;
Real dx = victimPos->x - obj->getPosition()->x;
Real dy = victimPos->y - obj->getPosition()->y;
Real xyDist = sqrt(sqr(dx)+sqr(dy));
if (xyDist<1) xyDist = 1;
Real zFactor = 0;
if (deltaZ>0) {
zFactor = deltaZ/xyDist;
}
Vector3 dir = getObject()->getTransformMatrix()->Get_X_Vector();
dir.Normalize();
dir.Z += 2*zFactor;
dir.Normalize();
PhysicsBehavior* physics = getObject()->getPhysics();
if (physics && initialVelToUse > 0)
{
Real forceMag = physics->getMass() * initialVelToUse;
Coord3D force;
force.x = forceMag * dir.X;
force.y = forceMag * dir.Y;
force.z = forceMag * dir.Z;
physics->applyMotiveForce( &force );
}
Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z);
Matrix3D newXform;
newXform.buildTransformMatrix( objPos, dir );
obj->setTransformMatrix( &newXform );
switchToState(LAUNCH);
m_isTrackingTarget = false;
// Missiles do their thing by colliding with something and exploding. So they Move to the target
// instead of Attacking the target.
if (victim && d->m_tryToFollowTarget)
{
getStateMachine()->setGoalPosition(victim->getPosition());
// ick. const-cast is evil. fix. (srj)
aiMoveToObject(const_cast