diff --git a/Core/GameEngine/Include/GameLogic/Damage.h b/Core/GameEngine/Include/GameLogic/Damage.h index 33e3ef92352..a78c5001d3a 100644 --- a/Core/GameEngine/Include/GameLogic/Damage.h +++ b/Core/GameEngine/Include/GameLogic/Damage.h @@ -100,6 +100,7 @@ enum DamageType CPP_11(: Int) DAMAGE_JET_TORPEDO, DAMAGE_ANTI_SHIP, DAMAGE_CRYO, + DAMAGE_BLACK_HOLE, // Specific damage types with special logic attached DAMAGE_TORPEDO, ///< can only attack units over water diff --git a/Core/GameEngine/Source/GameLogic/System/Damage.cpp b/Core/GameEngine/Source/GameLogic/System/Damage.cpp index 7fded581a34..593e7beca07 100644 --- a/Core/GameEngine/Source/GameLogic/System/Damage.cpp +++ b/Core/GameEngine/Source/GameLogic/System/Damage.cpp @@ -96,6 +96,7 @@ const char* const DamageTypeFlags::s_bitNameList[] = "JET_TORPEDO", "ANTI_SHIP", "CRYO", + "BLACK_HOLE", // Specific damage types with special logic attached "TORPEDO", "ANTI_TORPEDO", diff --git a/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp b/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp index 949fb3c0247..f18bfa94738 100644 --- a/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp +++ b/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp @@ -3069,6 +3069,14 @@ U32 AILCALLBACK streamingFileRead(void *file_handle, void *buffer, U32 bytes) //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- +// Holds the Miles global lock for a scope. It is recursive, so this is safe on the Miles callback +// thread, which already holds it. +struct MilesLockScope +{ + MilesLockScope() { AIL_lock(); } + ~MilesLockScope() { AIL_unlock(); } +}; + //------------------------------------------------------------------------------------------------- AudioFileCache::AudioFileCache() : m_maxSize(0), m_currentlyUsedSize(0), m_mutexName("AudioFileCacheMutex") { @@ -3100,6 +3108,12 @@ AudioFileCache::~AudioFileCache() //------------------------------------------------------------------------------------------------- void *AudioFileCache::openFile( AudioEventRTS *eventToOpenFrom ) { + // Take the Miles lock before our own mutex. Miles runs the end-of-sample callbacks while holding + // its lock, and those callbacks reach closeFile/openFile and wait for our mutex. Making room in + // the cache stops playing samples, which waits for the Miles lock, so taking the two in the + // opposite order deadlocks as soon as an eviction meets a sample finishing at the same time. + MilesLockScope milesLock; + // Protect the entire openFile function ScopedMutex mut(m_mutex); diff --git a/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h b/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h index eb8e184a515..7eb503a7303 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h @@ -138,6 +138,7 @@ enum SpecialPowerType CPP_11(: Int) SPECIAL_PUNISHER_CANNON, SPECIAL_CHEMICAL_MISSILE, SPECIAL_CHRONOSPHERE, + SPECIAL_RIFT_GENERATOR, AIRF_SPECIAL_HOLO_PLANES, AIRF_SPECIAL_PARADROP_AMERICA, diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Eva.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Eva.h index 07fb4774257..ed01d250f42 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Eva.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Eva.h @@ -166,6 +166,16 @@ enum EvaMessage CPP_11(: Int) Eva_SuperweaponReady_Ally_Chronosphere, Eva_SuperweaponReady_Enemy_Chronosphere, + Eva_SuperweaponDetected_Own_Rift_Generator, + Eva_SuperweaponDetected_Ally_Rift_Generator, + Eva_SuperweaponDetected_Enemy_Rift_Generator, + Eva_SuperweaponLaunched_Own_Rift_Generator, + Eva_SuperweaponLaunched_Ally_Rift_Generator, + Eva_SuperweaponLaunched_Enemy_Rift_Generator, + Eva_SuperweaponReady_Own_Rift_Generator, + Eva_SuperweaponReady_Ally_Rift_Generator, + Eva_SuperweaponReady_Enemy_Rift_Generator, + EVA_COUNT, EVA_FIRST = 0, }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RiftSlowDeathUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RiftSlowDeathUpdate.h index b6325edd2b9..54fdda8b263 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RiftSlowDeathUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RiftSlowDeathUpdate.h @@ -81,7 +81,10 @@ class RiftSlowDeathBehaviorModuleData : public SlowDeathBehaviorModuleData Real m_spinRate; ///< Tumble budget, rads per frame Real m_hugeVehiclePullScalar; ///< Suction multiplier for KINDOF_HUGE_VEHICLE - Real m_damagePerSecond; ///< Damage dealt per second of exposure at full strength + Real m_damagePerSecond; ///< Damage dealt per second of exposure at full strength, inside MaximumDamageRadius + Real m_maximumDamageRadius; ///< Victims within this 2D distance take the full DamagePerSecond + Real m_outerDamagePerSecond; ///< Damage per second at Radius, falling off linearly from MaximumDamageRadius; + ///< negative means same as DamagePerSecond (no falloff) UnsignedInt m_damageInterval; ///< Frames between damage ticks DamageType m_damageType; ///< Damage type used for the continuous damage DeathType m_deathType; ///< Death type of the continuous damage outside ConsumeDeathRadius @@ -130,11 +133,14 @@ class RiftSlowDeathBehavior : public SlowDeathBehavior void doRiftTick(Real strength, UnsignedInt currFrame); ///< one pass over everything in range void doCollapse(void); ///< release the held victims and blow them outwards + Real getDamagePerSecondAt(Real dist2D) const; ///< damage falloff between MaximumDamageRadius and Radius Bool isPullable(const Object* victim, Bool isDebris = FALSE) const; ///< can this object be dragged around at all + Bool isInvulnerable(const Object* victim) const; ///< the rift can never destroy it Bool usesLocomotorCapture(const Object* victim) const; ///< flyer whose locomotor keeps running while disabled RiftVictim* findVictim(ObjectID id); ///< NULL if we are not holding it void captureVictim(Object* victim); ///< one-shot setup when a victim is first grabbed void beginPhysicsCapture(RiftVictim& v, Object* victim); ///< hand the victim from its AI over to physics + void markVictimDying(RiftVictim& v, Object* obj); ///< keep pulling it until it is destroyed void steerVictim(RiftVictim& victim, Object* obj, const Coord3D& toRift, Real dist, Real strength); void releaseVictim(const RiftVictim& victim, Object* obj); ///< undo what capture cannot self-heal void releaseAllVictims(void); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp index f45609df156..ccfb0d12e97 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp @@ -137,6 +137,7 @@ const char* const SpecialPowerMaskType::s_bitNameList[] = "SPECIAL_PUNISHER_CANNON", "SPECIAL_CHEMICAL_MISSILE", "SPECIAL_CHRONOSPHERE", + "SPECIAL_RIFT_GENERATOR", "AIRF_SPECIAL_HOLO_PLANES", "AIRF_SPECIAL_PARADROP_AMERICA", diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Eva.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Eva.cpp index 3fd36786f15..cf34b7cd33b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Eva.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Eva.cpp @@ -163,6 +163,16 @@ const char *const TheEvaMessageNames[] = "SUPERWEAPONREADY_ALLY_CHRONOSPHERE", "SUPERWEAPONREADY_ENEMY_CHRONOSPHERE", + "SUPERWEAPONDETECTED_OWN_RIFT_GENERATOR", + "SUPERWEAPONDETECTED_ALLY_RIFT_GENERATOR", + "SUPERWEAPONDETECTED_ENEMY_RIFT_GENERATOR", + "SUPERWEAPONLAUNCHED_OWN_RIFT_GENERATOR", + "SUPERWEAPONLAUNCHED_ALLY_RIFT_GENERATOR", + "SUPERWEAPONLAUNCHED_ENEMY_RIFT_GENERATOR", + "SUPERWEAPONREADY_OWN_RIFT_GENERATOR", + "SUPERWEAPONREADY_ALLY_RIFT_GENERATOR", + "SUPERWEAPONREADY_ENEMY_RIFT_GENERATOR", + //**************************************************************************** //Kris: Don't forget to add another handler below -- it's ghey-ly implemented. //**************************************************************************** @@ -288,6 +298,15 @@ const ShouldPlayFunc Eva::s_shouldPlayFuncs[] = Eva::shouldPlayGenericHandler, Eva::shouldPlayGenericHandler, Eva::shouldPlayGenericHandler, + Eva::shouldPlayGenericHandler, + Eva::shouldPlayGenericHandler, + Eva::shouldPlayGenericHandler, + Eva::shouldPlayGenericHandler, + Eva::shouldPlayGenericHandler, + Eva::shouldPlayGenericHandler, + Eva::shouldPlayGenericHandler, + Eva::shouldPlayGenericHandler, + Eva::shouldPlayGenericHandler, }; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RiftSlowDeathUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RiftSlowDeathUpdate.cpp index 516a0b8c714..658f0ab80c8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RiftSlowDeathUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RiftSlowDeathUpdate.cpp @@ -42,6 +42,8 @@ RiftSlowDeathBehaviorModuleData::RiftSlowDeathBehaviorModuleData(void) m_hugeVehiclePullScalar = 1.0f; m_damagePerSecond = 0.0f; + m_maximumDamageRadius = 0.0f; + m_outerDamagePerSecond = -1.0f; m_damageInterval = LOGICFRAMES_PER_SECOND; m_damageType = DAMAGE_EXPLOSION; m_deathType = DEATH_NORMAL; @@ -100,6 +102,8 @@ RiftSlowDeathBehaviorModuleData::RiftSlowDeathBehaviorModuleData(void) { "HugeVehiclePullScalar",INI::parseReal, NULL, offsetof(RiftSlowDeathBehaviorModuleData, m_hugeVehiclePullScalar) }, { "DamagePerSecond", INI::parseReal, NULL, offsetof(RiftSlowDeathBehaviorModuleData, m_damagePerSecond) }, + { "MaximumDamageRadius", INI::parseReal, NULL, offsetof(RiftSlowDeathBehaviorModuleData, m_maximumDamageRadius) }, + { "OuterDamagePerSecond", INI::parseReal, NULL, offsetof(RiftSlowDeathBehaviorModuleData, m_outerDamagePerSecond) }, { "DamageInterval", INI::parseDurationUnsignedInt, NULL, offsetof(RiftSlowDeathBehaviorModuleData, m_damageInterval) }, { "DamageType", INI::parseIndexList, DamageTypeFlags::s_bitNameList, offsetof(RiftSlowDeathBehaviorModuleData, m_damageType) }, { "DeathType", INI::parseIndexList, TheDeathNames, offsetof(RiftSlowDeathBehaviorModuleData, m_deathType) }, @@ -291,11 +295,11 @@ void RiftSlowDeathBehavior::doRiftTick(Real strength, UnsignedInt currFrame) Object* self = getObject(); Bool damageThisFrame = FALSE; - Real damageAmount = 0.0f; - if (d->m_damagePerSecond > 0.0f && currFrame >= m_nextDamageFrame) + Real damageTickScale = 0.0f; // turns a damage per second into this tick's damage + if ((d->m_damagePerSecond > 0.0f || d->m_outerDamagePerSecond > 0.0f) && currFrame >= m_nextDamageFrame) { UnsignedInt interval = d->m_damageInterval > 0 ? d->m_damageInterval : 1; - damageAmount = d->m_damagePerSecond * ((Real)interval / (Real)LOGICFRAMES_PER_SECOND) * strength; + damageTickScale = ((Real)interval / (Real)LOGICFRAMES_PER_SECOND) * strength; m_nextDamageFrame = currFrame + interval; damageThisFrame = TRUE; } @@ -304,7 +308,7 @@ void RiftSlowDeathBehavior::doRiftTick(Real strength, UnsignedInt currFrame) damageInfo.in.m_damageType = d->m_damageType; damageInfo.in.m_deathType = d->m_deathType; damageInfo.in.m_sourceID = self->getID(); - damageInfo.in.m_amount = damageAmount; + damageInfo.in.m_amount = 0.0f; // no shockwave here: we move victims through physics directly, and attemptDamage refuses to // shove anything airborne anyway, which is exactly what we have lifted damageInfo.in.m_shockWaveAmount = 0.0f; @@ -346,6 +350,10 @@ void RiftSlowDeathBehavior::doRiftTick(Real strength, UnsignedInt currFrame) if (damageThisFrame) { + // falloff is measured on the ground, like Radius itself + Real dist2D = sqrtf(toRift.x * toRift.x + toRift.y * toRift.y); + damageInfo.in.m_amount = getDamagePerSecondAt(dist2D) * damageTickScale; + // only things that can actually fall in may die the consume death damageInfo.in.m_deathType = (pullable && dist <= d->m_consumeDeathRadius) ? d->m_consumeDeathType : d->m_deathType; // the same DamageInfo is reused for every victim, so clear what the last one reported @@ -354,6 +362,7 @@ void RiftSlowDeathBehavior::doRiftTick(Real strength, UnsignedInt currFrame) if (d->m_OCLstructureDebris != NULL && victim->isKindOf(KINDOF_STRUCTURE) + && !isInvulnerable(victim) && !damageInfo.out.m_noEffect && damageInfo.out.m_actualDamageDealt > 0.0f && GameLogicRandomValueReal(0.0f, 1.0f) < d->m_structureDebrisChance) @@ -442,18 +451,7 @@ void RiftSlowDeathBehavior::doRiftTick(Real strength, UnsignedInt currFrame) if ((v.m_savedFlags & RiftVictim::RIFTSAVE_DYING) != 0 || dist <= d->m_consumeDeathRadius) { - if ((v.m_savedFlags & RiftVictim::RIFTSAVE_DYING) == 0) - { - v.m_savedFlags |= RiftVictim::RIFTSAVE_DYING; - - // a dead flyer's AI no longer drives its locomotor, so take it over through physics - if ((v.m_savedFlags & RiftVictim::RIFTSAVE_USES_LOCOMOTOR) != 0) - { - v.m_savedFlags &= ~RiftVictim::RIFTSAVE_USES_LOCOMOTOR; - beginPhysicsCapture(v, obj); - } - } - + markVictimDying(v, obj); steerVictim(v, obj, toRift, dist, strength); continue; } @@ -476,14 +474,17 @@ void RiftSlowDeathBehavior::doRiftTick(Real strength, UnsignedInt currFrame) if (victim == NULL) continue; - for (Int i = (Int)m_victims.size() - 1; i >= 0; --i) + // Keep holding it: its death (e.g. a shrinking ChronoDeathBehavior) plays out over time, and + // letting go here would drop it out of the singularity under gravity instead of being + // swallowed. The dead branch above keeps steering it until it is destroyed. + RiftVictim* held = findVictim(*it); + if (held == NULL) { - if (m_victims[i].m_id == *it) - { - m_victims.erase(m_victims.begin() + i); - break; - } + captureVictim(victim); + held = findVictim(*it); } + if (held != NULL) + markVictimDying(*held, victim); FXList::doFXPos(d->m_FXconsume, victim->getPosition()); ObjectCreationList::create(d->m_OCLconsume, self, victim->getPosition(), NULL, 0.0f); @@ -492,6 +493,30 @@ void RiftSlowDeathBehavior::doRiftTick(Real strength, UnsignedInt currFrame) } // end doRiftTick +// ------------------------------------------------------------------------------------------------ +/** Full DamagePerSecond out to MaximumDamageRadius, then linear down to OuterDamagePerSecond at + * Radius. */ +// ------------------------------------------------------------------------------------------------ +Real RiftSlowDeathBehavior::getDamagePerSecondAt(Real dist2D) const +{ + const RiftSlowDeathBehaviorModuleData* d = getRiftSlowDeathBehaviorModuleData(); + + if (d->m_outerDamagePerSecond < 0.0f || dist2D <= d->m_maximumDamageRadius) + return d->m_damagePerSecond; + + // guarded: MaximumDamageRadius >= Radius leaves no falloff band to divide by + Real band = d->m_radius - d->m_maximumDamageRadius; + if (band <= 0.0f) + return d->m_damagePerSecond; + + Real t = (dist2D - d->m_maximumDamageRadius) / band; + if (t > 1.0f) + t = 1.0f; + + return d->m_damagePerSecond + (d->m_outerDamagePerSecond - d->m_damagePerSecond) * t; + +} // end getDamagePerSecondAt + // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Bool RiftSlowDeathBehavior::isPullable(const Object* victim, Bool isDebris) const @@ -508,22 +533,8 @@ Bool RiftSlowDeathBehavior::isPullable(const Object* victim, Bool isDebris) cons // invulnerable things are left alone. Our own debris is exempt: it usually has an InactiveBody, // which always reports itself as indestructible. - if (!isDebris) - { - const BodyModuleInterface* body = victim->getBodyModule(); - if ((body != NULL && body->isIndestructible()) || victim->getIsUndetectedDefector()) - return FALSE; - - // these take damage but can never die, so the event horizon could never consume them - static const NameKeyType key_ImmortalBody = NAMEKEY("ImmortalBody"); - static const NameKeyType key_HighlanderBody = NAMEKEY("HighlanderBody"); - for (BehaviorModule** m = victim->getBehaviorModules(); *m; ++m) - { - NameKeyType key = (*m)->getModuleNameKey(); - if (key == key_ImmortalBody || key == key_HighlanderBody) - return FALSE; - } - } + if (!isDebris && isInvulnerable(victim)) + return FALSE; if (victim->isAnyKindOf(d->m_noPullKindOf)) return FALSE; @@ -535,6 +546,30 @@ Bool RiftSlowDeathBehavior::isPullable(const Object* victim, Bool isDebris) cons } // end isPullable +// ------------------------------------------------------------------------------------------------ +/** Things the rift can never destroy. Note that ImmortalBody and HighlanderBody still report the + * damage they take, e.g. the ImmortalBody of GenericBridge and its towers. */ +// ------------------------------------------------------------------------------------------------ +Bool RiftSlowDeathBehavior::isInvulnerable(const Object* victim) const +{ + const BodyModuleInterface* body = victim->getBodyModule(); + if ((body != NULL && body->isIndestructible()) || victim->getIsUndetectedDefector()) + return TRUE; + + // these take damage but can never die + static const NameKeyType key_ImmortalBody = NAMEKEY("ImmortalBody"); + static const NameKeyType key_HighlanderBody = NAMEKEY("HighlanderBody"); + for (BehaviorModule** m = victim->getBehaviorModules(); *m; ++m) + { + NameKeyType key = (*m)->getModuleNameKey(); + if (key == key_ImmortalBody || key == key_HighlanderBody) + return TRUE; + } + + return FALSE; + +} // end isInvulnerable + // ------------------------------------------------------------------------------------------------ /** Flyers whose locomotor template is flagged LocomotorWorksWhenDisabled keep steering themselves * even while disabled, and their handleBehaviorZ rewrites the z position every frame. Fighting @@ -639,6 +674,25 @@ void RiftSlowDeathBehavior::beginPhysicsCapture(RiftVictim& v, Object* victim) } // end beginPhysicsCapture +// ------------------------------------------------------------------------------------------------ +/** From now on the victim is pulled until it is destroyed, dead or not. */ +// ------------------------------------------------------------------------------------------------ +void RiftSlowDeathBehavior::markVictimDying(RiftVictim& v, Object* obj) +{ + if ((v.m_savedFlags & RiftVictim::RIFTSAVE_DYING) != 0) + return; + + v.m_savedFlags |= RiftVictim::RIFTSAVE_DYING; + + // a dead flyer's AI no longer drives its locomotor, so take it over through physics + if ((v.m_savedFlags & RiftVictim::RIFTSAVE_USES_LOCOMOTOR) != 0) + { + v.m_savedFlags &= ~RiftVictim::RIFTSAVE_USES_LOCOMOTOR; + beginPhysicsCapture(v, obj); + } + +} // end markVictimDying + // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void RiftSlowDeathBehavior::steerVictim(RiftVictim& victim, Object* obj, const Coord3D& toRift,