Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2097,7 +2097,9 @@ W3DModelDraw::~W3DModelDraw()
//-------------------------------------------------------------------------------------------------
void W3DModelDraw::doStartOrStopParticleSys()
{
Bool hidden = getDrawable()->isDrawableEffectivelyHidden() || m_fullyObscuredByShroud;
const Drawable* draw = getDrawable();
Bool hidden = (draw->isDrawableEffectivelyHidden() && !draw->testDrawableStatus(DRAWABLE_STATUS_KEEP_PARTICLES_WHEN_HIDDEN))
|| m_fullyObscuredByShroud;

for (std::vector<ParticleSysTrackerType>::const_iterator it = m_particleSystemIDs.begin(); it != m_particleSystemIDs.end(); ++it)
//for (std::vector<ParticleSystemID>::const_iterator it = m_particleSystemIDs.begin(); it != m_particleSystemIDs.end(); ++it)
Expand Down Expand Up @@ -3088,7 +3090,8 @@ void W3DModelDraw::recalcBonesForClientParticleSystems()
// important: mark it as do-not-save, since we'll just re-create it when we reload.
sys->setSaveable(FALSE);

if (drawable->isDrawableEffectivelyHidden() || m_fullyObscuredByShroud)
if ((drawable->isDrawableEffectivelyHidden() && !drawable->testDrawableStatus(DRAWABLE_STATUS_KEEP_PARTICLES_WHEN_HIDDEN))
|| m_fullyObscuredByShroud)
{
sys->stop(); // don't start the systems for drawables that are hidden.
}
Expand Down
1 change: 1 addition & 0 deletions Generals/Code/GameEngine/Include/GameClient/Drawable.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ enum DrawableStatus CPP_11(: DrawableStatusBits)
DRAWABLE_STATUS_NONE = 0x00000000, ///< no status
DRAWABLE_STATUS_DRAWS_IN_MIRROR = 0x00000001, ///< drawable can reflect
DRAWABLE_STATUS_SHADOWS = 0x00000002, ///< use setShadowsEnabled() access method
DRAWABLE_STATUS_KEEP_PARTICLES_WHEN_HIDDEN = 0x00000004, ///< model-condition particle systems keep emitting while the drawable is hidden
DRAWABLE_STATUS_NO_STATE_PARTICLES = 0x00000008, ///< do *not* auto-create particle systems based on model condition
DRAWABLE_STATUS_NO_SAVE = 0x00000010, ///< do *not* save this drawable (UI fluff only). ignored (error, actually) if attached to an object

Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ enum DrawableStatus CPP_11(: DrawableStatusBits)
DRAWABLE_STATUS_NONE = 0x00000000, ///< no status
DRAWABLE_STATUS_DRAWS_IN_MIRROR = 0x00000001, ///< drawable can reflect
DRAWABLE_STATUS_SHADOWS = 0x00000002, ///< use setShadowsEnabled() access method
DRAWABLE_STATUS_KEEP_PARTICLES_WHEN_HIDDEN = 0x00000004, ///< model-condition particle systems keep emitting while the drawable is hidden
DRAWABLE_STATUS_NO_STATE_PARTICLES = 0x00000008, ///< do *not* auto-create particle systems based on model condition
DRAWABLE_STATUS_NO_SAVE = 0x00000010, ///< do *not* save this drawable (UI fluff only). ignored (error, actually) if attached to an object

Expand Down Expand Up @@ -324,6 +325,7 @@ class Drawable : public Thing,
DrawableInfo *getDrawableInfo() {return &m_drawableInfo;}

void setDrawableHidden( Bool hidden ); ///< hide or unhide drawable
void setKeepParticlesWhenHidden( Bool keep ); ///< let model-condition particle systems keep emitting while hidden
//
// note that this is not necessarily the 'get' reflection of setDrawableHidden, since drawables
// can spontaneously hide via stealth. (srj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ class DumbProjectileBehaviorModuleData : public UpdateModuleData
Real m_dynamicHeightMinScale;
Real m_dynamicHeightMinRange;

Bool m_linearFlightPath; ///< fly a straight line at constant speed instead of the Bezier curve
UnsignedInt m_killSelfDelay; ///< frames to linger (hidden, particles still emitting) after detonation before dying

DumbProjectileBehaviorModuleData();

static void buildFieldParse(MultiIniFieldParse& p);
Expand All @@ -88,6 +91,8 @@ class DumbProjectileBehavior : public UpdateModule, public ProjectileUpdateInter

// UpdateModuleInterface
virtual UpdateSleepTime update() override;
// we hold ourselves (DISABLED_HELD) while lingering after detonation (KillSelfDelay) and must keep updating to die
virtual DisabledMaskType getDisabledTypesToProcess() const override { return MAKE_DISABLED_MASK( DISABLED_HELD ); }
virtual ProjectileUpdateInterface* getProjectileUpdateInterface() override { return this; }

// ProjectileUpdateInterface
Expand All @@ -104,11 +109,13 @@ class DumbProjectileBehavior : public UpdateModule, public ProjectileUpdateInter
virtual Object* getTargetObject();
virtual const Coord3D* getTargetPosition();
virtual Bool projectileShouldCollideWithWater() const override;
virtual Bool projectileIsLingering() const override { return m_hasDetonated && getDumbProjectileBehaviorModuleData()->m_killSelfDelay > 0; }

protected:

void positionForLaunch(const Object *launcher, WeaponSlotType wslot, Int specificBarrelToUse);
void detonate();
void doDetonationDeath();

private:

Expand All @@ -129,6 +136,7 @@ class DumbProjectileBehavior : public UpdateModule, public ProjectileUpdateInter
ParticleSystemID m_exhaustID;

Bool m_hasDetonated; ///<
UnsignedInt m_killSelfFrame; ///< while lingering after detonation: frame at which we die (0 = not lingering)

Bool calcFlightPath(Bool recalcNumSegments);
#if defined(RTS_DEBUG)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class KodiakUpdateModuleData : public ModuleData
UnsignedInt m_numSideTurrets;
UnsignedInt m_numAATurrets;

Bool m_mainTurretsFireInSequence; ///< main turrets take turns firing one shot each
UnsignedInt m_mainTurretSequenceDelay; ///< min frames between consecutive main turret shots in sequence mode

Real m_missileScatterRadius;
std::vector<Coord2D> m_scatterTargets; ///< targets for missile attack, amount of targets should match clip size of primary weapon

Expand Down Expand Up @@ -116,6 +119,9 @@ class KodiakUpdate : public SpecialPowerUpdateModule

void friend_enableAfterburners(Bool v);

void resetMainTurretSequence(const std::vector<Object*>& turrets);
void updateMainTurretSequence(const std::vector<Object*>& turrets);




Expand All @@ -129,6 +135,9 @@ class KodiakUpdate : public SpecialPowerUpdateModule

UnsignedInt m_orbitEscapeFrame;

UnsignedInt m_seqTurretIndex; ///< main turret that currently has the turn (MainTurretsFireInSequence)
UnsignedInt m_seqHolderShotFrame; ///< turn holder's last shot frame when it got the turn; a change means it fired

RadiusDecal m_attackAreaDecal;
RadiusDecal m_targetingReticleDecal;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ class ProjectileUpdateInterface
virtual void setFramesTillCountermeasureDiversionOccurs( UnsignedInt frames ) = 0; ///< Number of frames till missile diverts to countermeasures.
virtual void projectileNowJammed() = 0;
virtual Bool projectileShouldCollideWithWater() const { return false; };
virtual Bool projectileIsLingering() const { return false; } ///< true while detonated but kept alive (hidden) so attached particles can catch up

virtual Object* getTargetObject() = 0;
virtual const Coord3D* getTargetPosition() = 0;
Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,7 @@ class Weapon : public MemoryPoolObject,

// do not ever use this unless you are weaponset.cpp
void setPossibleNextShotFrame( UnsignedInt frameNum ) { m_whenWeCanFireAgain = frameNum; }
void holdFiringUntil( UnsignedInt frameNum ); ///< don't fire before frameNum (never shortens an existing delay)
void setPreAttackFinishedFrame( UnsignedInt frameNum ) { m_whenPreAttackFinished = frameNum; }
void setLastReloadStartedFrame( UnsignedInt frameNum ) { m_whenLastReloadStarted = frameNum; }
void setNextPreAttackFXFrame(UnsignedInt frameNum) { m_nextPreAttackFXFrame = frameNum; }
Expand Down
17 changes: 17 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4947,6 +4947,23 @@ void Drawable::setDrawableHidden( Bool hidden )
}
}

//-------------------------------------------------------------------------------------------------
/** Allow model-condition particle systems to keep emitting while the drawable is hidden */
//-------------------------------------------------------------------------------------------------
void Drawable::setKeepParticlesWhenHidden( Bool keep )
{
if (keep == testDrawableStatus(DRAWABLE_STATUS_KEEP_PARTICLES_WHEN_HIDDEN))
return;

if (keep)
setDrawableStatus(DRAWABLE_STATUS_KEEP_PARTICLES_WHEN_HIDDEN);
else
clearDrawableStatus(DRAWABLE_STATUS_KEEP_PARTICLES_WHEN_HIDDEN);

// let the draw modules re-evaluate whether their particle systems run
updateHiddenStatus();
}

//-------------------------------------------------------------------------------------------------
void Drawable::updateDrawableClipStatus( UnsignedInt shotsRemaining, UnsignedInt maxShots, WeaponSlotType slot )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ DumbProjectileBehaviorModuleData::DumbProjectileBehaviorModuleData() :
m_flightPathAdjustDistPerFrame(0.0f),
m_applyLauncherBonus(FALSE),
m_dynamicHeightMinScale(1.0),
m_dynamicHeightMinRange(1.0)
m_dynamicHeightMinRange(1.0),
m_linearFlightPath(FALSE),
m_killSelfDelay(0)
{
}

Expand Down Expand Up @@ -103,6 +105,9 @@ void DumbProjectileBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{ "DynamicHeightMinScale", INI::parseReal, NULL, offsetof(DumbProjectileBehaviorModuleData, m_dynamicHeightMinScale) },
{ "DynamicHeightMinRange", INI::parseReal, NULL, offsetof(DumbProjectileBehaviorModuleData, m_dynamicHeightMinRange) },

{ "LinearFlightPath", INI::parseBool, nullptr, offsetof(DumbProjectileBehaviorModuleData, m_linearFlightPath) },
{ "KillSelfDelay", INI::parseDurationUnsignedInt, nullptr, offsetof(DumbProjectileBehaviorModuleData, m_killSelfDelay) },

{ nullptr, nullptr, nullptr, 0 }
};

Expand Down Expand Up @@ -132,6 +137,7 @@ DumbProjectileBehavior::DumbProjectileBehavior( Thing *thing, const ModuleData*
m_exhaustID = INVALID_PARTICLE_SYSTEM_ID;

m_hasDetonated = FALSE;
m_killSelfFrame = 0;
}

//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -448,6 +454,45 @@ Bool DumbProjectileBehavior::calcFlightPath(Bool recalcNumSegments)
return false;
}

if (d->m_linearFlightPath)
{
// Straight line at constant speed: evenly spaced points from start to end (inclusive),
// same sampling convention as BezierSegment::getSegmentPoints.
Coord3D delta;
delta.x = controlPoints[3].x - controlPoints[0].x;
delta.y = controlPoints[3].y - controlPoints[0].y;
delta.z = controlPoints[3].z - controlPoints[0].z;

if (recalcNumSegments)
{
m_flightPathSegments = ceil( delta.length() / m_flightPathSpeed );
}

m_flightPath.clear();
m_flightPath.resize(m_flightPathSegments);
if (m_flightPathSegments == 1)
{
m_flightPath[0] = controlPoints[3];
}
else
{
for (Int i = 0; i < m_flightPathSegments; ++i)
{
Real t = (Real)i / (Real)(m_flightPathSegments - 1);
m_flightPath[i].x = controlPoints[0].x + delta.x * t;
m_flightPath[i].y = controlPoints[0].y + delta.y * t;
m_flightPath[i].z = controlPoints[0].z + delta.z * t;
}
}

#if defined(RTS_DEBUG)
if( TheGlobalData->m_debugProjectilePath )
displayFlightPath();
#endif

return true;
}

// 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
Expand Down Expand Up @@ -584,7 +629,51 @@ void DumbProjectileBehavior::detonate()
if (m_detonationWeaponTmpl)
{
TheWeaponStore->handleProjectileDetonation(m_detonationWeaponTmpl, obj, obj->getPosition(), m_extraBonusFlags);
}

const UnsignedInt killSelfDelay = getDumbProjectileBehaviorModuleData()->m_killSelfDelay;
if (killSelfDelay > 0)
{
// Linger hidden at the impact point for a few frames so attached particle trails
// (weapon exhaust and model-condition particle systems) can catch up before we die.
m_killSelfFrame = TheGameLogic->getFrame() + killSelfDelay;
obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );

// Freeze in place: Physics has been accumulating gravity while our flight path overrode the
// position each frame. Clear it, and hold the object so physics doesn't move it while lingering.
if (PhysicsBehavior* physics = obj->getPhysics())
physics->resetDynamicPhysics();
obj->setDisabled( DISABLED_HELD );

if (Drawable* draw = obj->getDrawable())
{
draw->setKeepParticlesWhenHidden(true);
draw->setDrawableHidden(true);
}
}
else
{
doDetonationDeath();

if (obj->getDrawable())
obj->getDrawable()->setDrawableHidden(true);
}

m_hasDetonated = TRUE;

}

//-------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::doDetonationDeath()
{
Object* obj = getObject();

// stop any particle systems we kept alive while lingering
if (Drawable* draw = obj->getDrawable())
draw->setKeepParticlesWhenHidden(false);

if (m_detonationWeaponTmpl)
{
if ( getDumbProjectileBehaviorModuleData()->m_detonateCallsKill )
{
// don't call kill(); do it manually, so we can specify DEATH_DETONATED
Expand All @@ -611,12 +700,6 @@ void DumbProjectileBehavior::detonate()
damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth();
obj->attemptDamage( &damageInfo );
}

if (obj->getDrawable())
obj->getDrawable()->setDrawableHidden(true);

m_hasDetonated = TRUE;

}

//-------------------------------------------------------------------------------------------------
Expand All @@ -627,6 +710,18 @@ UpdateSleepTime DumbProjectileBehavior::update()
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();

if (m_hasDetonated && d->m_killSelfDelay > 0)
{
// lingering at the impact point after detonation (KillSelfDelay); die once the delay is up.
// (without KillSelfDelay we keep the original behavior below)
if (m_killSelfFrame != 0 && TheGameLogic->getFrame() >= m_killSelfFrame)
{
m_killSelfFrame = 0;
doDetonationDeath();
}
return UPDATE_SLEEP_NONE;
}

if (m_lifespanFrame != 0 && TheGameLogic->getFrame() >= m_lifespanFrame)
{
// lifetime demands detonation
Expand Down Expand Up @@ -816,17 +911,18 @@ void DumbProjectileBehavior::crc( Xfer *xfer )
* Version Info:
* 1: Initial version
* 2: TheSuperHackers @bugfix Added m_currentFlightPathStep for mid-flight save/load.
* Also added m_launchVeterancy (for veterancy FX/OCL selection)
* 3: Added m_hasDetonated and m_killSelfFrame (KillSelfDelay lingering)
*/
// ------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::xfer( Xfer *xfer )
{

// version
// 2: Added m_launchVeterancy (for veterancy FX/OCL selection)
#if RETAIL_COMPATIBLE_XFER_SAVE
XferVersion currentVersion = 1;
#else
XferVersion currentVersion = 2;
XferVersion currentVersion = 3;
#endif
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
Expand Down Expand Up @@ -899,6 +995,12 @@ void DumbProjectileBehavior::xfer( Xfer *xfer )
xfer->xferInt( &m_currentFlightPathStep );
}

if( version >= 3 )
{
xfer->xferBool( &m_hasDetonated );
xfer->xferUnsignedInt( &m_killSelfFrame );
}

}

// ------------------------------------------------------------------------------------------------
Expand Down
Loading
Loading