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 @@ -58,6 +58,13 @@ class MultiLocationSpecialPowerUpdateModuleData : public ModuleData
UnsignedInt m_initialDelay; ///< delay before the first OCL fires (INI in ms, stored as frames)
UnsignedInt m_delay; ///< delay between successive OCLs (INI in ms, stored as frames)

// An OCL containing an Attack nugget makes the caster attack the point with one of its own weapon
// slots, and aiAttackPosition replaces the previous order rather than queueing behind it. With a
// fixed m_delay the next point cuts the previous barrage short, so offer a mode that advances only
// once the caster has actually stopped attacking.
Bool m_waitForAttackComplete; ///< advance only when the caster stops attacking
UnsignedInt m_maxWaitPerTarget; ///< safety cap per point, so an unattackable point cannot stall the sequence

MultiLocationSpecialPowerUpdateModuleData();
static void buildFieldParse(MultiIniFieldParse& p);

Expand Down Expand Up @@ -110,10 +117,13 @@ class MultiLocationSpecialPowerUpdate : public SpecialPowerUpdateModule

const ObjectCreationList* findOCL() const; ///< science-upgraded OCL, else the default
void fireOclAtLocation( const Coord3D *loc ); ///< spawn the OCL at one captured point, honoring CreateLocation
UnsignedInt getPollInterval() const; ///< frames between update() wake-ups during the sequence

SpecialPowerModuleInterface* m_specialPowerModule; ///< cached paired power module (recharge/cost/timer)

std::vector<Coord3D> m_locations; ///< all captured target points (in click order)
Bool m_active; ///< TRUE while the OCL spawn sequence is running
Int m_spawnIndex; ///< index of the next point to spawn an OCL at
Bool m_attackSeen; ///< the caster actually entered its attack state for the point just fired
UnsignedInt m_waitDeadline; ///< logic frame at which we give up waiting on the point just fired
};
Original file line number Diff line number Diff line change
Expand Up @@ -322,11 +322,8 @@ void ChronoSphereUpdateModule::loadPostProcess( void )
// extend base class
SpecialPowerUpdateModule::loadPostProcess();

// If a teleport was pending when the game was saved, re-arm the wake so it still fires.
if( m_active )
{
UnsignedInt now = TheGameLogic->getFrame();
UnsignedInt delay = (m_teleportFrame > now) ? (m_teleportFrame - now) : 0;
setWakeFrame( getObject(), delay > 0 ? UPDATE_SLEEP( delay ) : UPDATE_SLEEP_NONE );
}
// Do not call setWakeFrame() here. It is not safe from the xfer system: the sleepy update heap
// has not been rebuilt yet, so our index in the logic is still -1 and friend_awakenUpdateModule
// would RELEASE_CRASH. Nothing needs re-arming anyway - UpdateModule::xfer already restored our
// wake frame, and GameLogic::loadPostProcess rebuilds the heap from it.
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/TerrainLogic.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/SpecialPowerModule.h"
#include "GameLogic/Module/MultiLocationSpecialPowerUpdate.h"

Expand Down Expand Up @@ -81,6 +82,8 @@ MultiLocationSpecialPowerUpdateModuleData::MultiLocationSpecialPowerUpdateModule
m_createLoc = CREATE_AT_EDGE_NEAR_SOURCE;
m_initialDelay = 0;
m_delay = 0;
m_waitForAttackComplete = FALSE;
m_maxWaitPerTarget = 0;
}

//-------------------------------------------------------------------------------------------------
Expand All @@ -96,6 +99,8 @@ MultiLocationSpecialPowerUpdateModuleData::MultiLocationSpecialPowerUpdateModule
{ "UpgradeOCL", parseMultiLocUpgradeOCL, nullptr, offsetof( MultiLocationSpecialPowerUpdateModuleData, m_upgradeOCL ) },
{ "InitialDelay", INI::parseDurationUnsignedInt, nullptr, offsetof( MultiLocationSpecialPowerUpdateModuleData, m_initialDelay ) },
{ "Delay", INI::parseDurationUnsignedInt, nullptr, offsetof( MultiLocationSpecialPowerUpdateModuleData, m_delay ) },
{ "WaitForAttackComplete", INI::parseBool, nullptr, offsetof( MultiLocationSpecialPowerUpdateModuleData, m_waitForAttackComplete ) },
{ "MaxWaitPerTarget", INI::parseDurationUnsignedInt, nullptr, offsetof( MultiLocationSpecialPowerUpdateModuleData, m_maxWaitPerTarget ) },
{ nullptr, nullptr, nullptr, 0 }
};
p.add(dataFieldParse);
Expand All @@ -109,6 +114,8 @@ MultiLocationSpecialPowerUpdate::MultiLocationSpecialPowerUpdate( Thing *thing,
m_locations.clear();
m_active = FALSE;
m_spawnIndex = 0;
m_attackSeen = FALSE;
m_waitDeadline = 0;
}

//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -202,6 +209,19 @@ void MultiLocationSpecialPowerUpdate::fireOclAtLocation( const Coord3D *loc )
}
}

//-------------------------------------------------------------------------------------------------
// Frames between update() wake-ups. In WaitForAttackComplete mode Delay is the poll granularity and
// the minimum gap between points, rather than the whole spacing.
//-------------------------------------------------------------------------------------------------
UnsignedInt MultiLocationSpecialPowerUpdate::getPollInterval() const
{
const MultiLocationSpecialPowerUpdateModuleData *d = getMultiLocationSpecialPowerUpdateModuleData();
if( d->m_delay > 0 )
return d->m_delay;

return d->m_waitForAttackComplete ? (LOGICFRAMES_PER_SECOND / 4) : 1;
}

//-------------------------------------------------------------------------------------------------
// First click: the first target point arrives here as targetPos. The rest of the points arrive
// together via setSpecialPowerMultiLocations() once the final click commits.
Expand Down Expand Up @@ -242,6 +262,8 @@ void MultiLocationSpecialPowerUpdate::setSpecialPowerMultiLocations( const std::
const MultiLocationSpecialPowerUpdateModuleData *d = getMultiLocationSpecialPowerUpdateModuleData();
m_spawnIndex = 0;
m_active = TRUE;
m_attackSeen = FALSE;
m_waitDeadline = 0;
setWakeFrame( getObject(), UPDATE_SLEEP( d->m_initialDelay > 0 ? d->m_initialDelay : 1 ) );
}

Expand All @@ -254,11 +276,34 @@ UpdateSleepTime MultiLocationSpecialPowerUpdate::update()
return UPDATE_SLEEP_FOREVER;

const MultiLocationSpecialPowerUpdateModuleData *d = getMultiLocationSpecialPowerUpdateModuleData();
const UnsignedInt pollInterval = getPollInterval();

// If the OCL makes us attack (an Attack nugget orders our own AI), the next point's order would
// cancel the barrage still in progress. In that mode hold here until the caster is done shooting.
if( d->m_waitForAttackComplete && m_spawnIndex > 0 && m_spawnIndex < (Int)m_locations.size() )
{
const AIUpdateInterface *ai = getObject()->getAIUpdateInterface();
const Bool attacking = ai != nullptr && ai->isAttacking();

// The AI needs a frame or two to enter the attack state after aiAttackPosition, so "not
// attacking" only means "finished" once we have actually seen it attack.
if( attacking )
m_attackSeen = TRUE;

const Bool finished = m_attackSeen && !attacking;
const Bool timedOut = TheGameLogic->getFrame() >= m_waitDeadline;
if( !finished && !timedOut )
return UPDATE_SLEEP( pollInterval );
}

if( m_spawnIndex < (Int)m_locations.size() )
{
fireOclAtLocation( &m_locations[m_spawnIndex] );
++m_spawnIndex;

m_attackSeen = FALSE;
const UnsignedInt maxWait = d->m_maxWaitPerTarget > 0 ? d->m_maxWaitPerTarget : (30 * LOGICFRAMES_PER_SECOND);
m_waitDeadline = TheGameLogic->getFrame() + maxWait;
}

if( m_spawnIndex >= (Int)m_locations.size() )
Expand All @@ -268,7 +313,7 @@ UpdateSleepTime MultiLocationSpecialPowerUpdate::update()
return UPDATE_SLEEP_FOREVER;
}

return UPDATE_SLEEP( d->m_delay > 0 ? d->m_delay : 1 );
return UPDATE_SLEEP( pollInterval );
}

// ------------------------------------------------------------------------------------------------
Expand All @@ -284,12 +329,13 @@ void MultiLocationSpecialPowerUpdate::crc( Xfer *xfer )
/** Xfer method
* Version Info:
* 1: Initial version
* 2: Added m_spawnIndex (timed OCL spawn sequence) */
* 2: Added m_spawnIndex (timed OCL spawn sequence)
* 3: Added m_attackSeen / m_waitDeadline (WaitForAttackComplete sequencing) */
// ------------------------------------------------------------------------------------------------
void MultiLocationSpecialPowerUpdate::xfer( Xfer *xfer )
{
// version
const XferVersion currentVersion = 2;
const XferVersion currentVersion = 3;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );

Expand All @@ -311,6 +357,12 @@ void MultiLocationSpecialPowerUpdate::xfer( Xfer *xfer )

if( version >= 2 )
xfer->xferInt( &m_spawnIndex );

if( version >= 3 )
{
xfer->xferBool( &m_attackSeen );
xfer->xferUnsignedInt( &m_waitDeadline );
}
}

// ------------------------------------------------------------------------------------------------
Expand All @@ -321,10 +373,9 @@ void MultiLocationSpecialPowerUpdate::loadPostProcess( void )
// extend base class
SpecialPowerUpdateModule::loadPostProcess();

// resume an in-flight spawn sequence
if( m_active && m_spawnIndex < (Int)m_locations.size() )
{
const MultiLocationSpecialPowerUpdateModuleData *d = getMultiLocationSpecialPowerUpdateModuleData();
setWakeFrame( getObject(), UPDATE_SLEEP( d->m_delay > 0 ? d->m_delay : 1 ) );
}
// Do not call setWakeFrame() here to resume an in-flight spawn sequence. It is not safe from the
// xfer system: the sleepy update heap has not been rebuilt yet, so our index in the logic is
// still -1 and friend_awakenUpdateModule would RELEASE_CRASH. The sequence resumes on its own -
// UpdateModule::xfer already restored our wake frame, and GameLogic::loadPostProcess rebuilds the
// heap from it, which also preserves the sleep we had banked instead of restarting a full one.
}
Loading