diff --git a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
index 5d736a6b92e..baa31f2de83 100644
--- a/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
+++ b/Core/GameEngine/Source/Common/System/GameMemoryInitPools_GeneralsMD.inl
@@ -135,6 +135,7 @@ static PoolSizeRec PoolSizes[] =
{ "MissileAIUpdate", 512, 32 },
{ "DumbProjectileBehavior", 64, 32 },
{ "FreeFallProjectileBehavior", 32, 32 },
+ { "JumpjetMissileAIUpdate", 16, 16 },
{ "DestroyDie", 1024, 32 },
{ "UpgradeDie", 128, 32 },
{ "KeepObjectDie", 128, 32 },
@@ -278,6 +279,7 @@ static PoolSizeRec PoolSizes[] =
{ "TransitionDamageFX", 384, 128 },
{ "TransportAIUpdate", 64, 32 },
{ "TransportContain", 128, 32 },
+ { "JumpjetContain", 16, 16 },
{ "RiderChangeContain", 128, 32 },
{ "InternetHackContain", 16, 16 },
{ "TunnelContain", 8, 8 },
diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt
index 9055ccecbe2..39d410a8303 100644
--- a/GeneralsMD/Code/GameEngine/CMakeLists.txt
+++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt
@@ -371,6 +371,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/MaxHealthUpgrade.h
Include/GameLogic/Module/MinefieldBehavior.h
Include/GameLogic/Module/MissileAIUpdate.h
+ Include/GameLogic/Module/JumpjetMissileAIUpdate.h
Include/GameLogic/Module/MissileLauncherBuildingUpdate.h
Include/GameLogic/Module/MobMemberSlavedUpdate.h
Include/GameLogic/Module/MobNexusContain.h
@@ -483,6 +484,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/TransportAIUpdate.h
Include/GameLogic/Module/TransportContain.h
Include/GameLogic/Module/TunnelContain.h
+ Include/GameLogic/Module/JumpjetContain.h
Include/GameLogic/Module/UndeadBody.h
Include/GameLogic/Module/UnitCrateCollide.h
Include/GameLogic/Module/UnpauseSpecialPowerUpgrade.h
@@ -960,6 +962,7 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Contain/RiderChangeContain.cpp
Source/GameLogic/Object/Contain/TransportContain.cpp
Source/GameLogic/Object/Contain/TunnelContain.cpp
+ Source/GameLogic/Object/Contain/JumpjetContain.cpp
Source/GameLogic/Object/Contain/DroneCarrierContain.cpp
Source/GameLogic/Object/Create/CreateModule.cpp
Source/GameLogic/Object/Create/GrantUpgradeCreate.cpp
@@ -1026,6 +1029,7 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Update/AIUpdate/HackInternetAIUpdate.cpp
Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp
Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp
+ Source/GameLogic/Object/Update/AIUpdate/JumpjetMissileAIUpdate.cpp
Source/GameLogic/Object/Update/AIUpdate/POWTruckAIUpdate.cpp
Source/GameLogic/Object/Update/AIUpdate/RailedTransportAIUpdate.cpp
Source/GameLogic/Object/Update/AIUpdate/RailroadGuideAIUpdate.cpp
diff --git a/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h b/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h
index 5f968b4fbf8..eb8e184a515 100644
--- a/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h
+++ b/GeneralsMD/Code/GameEngine/Include/Common/SpecialPowerType.h
@@ -222,6 +222,8 @@ enum SpecialPowerType CPP_11(: Int)
SPECIAL_TOGGLE_DRAWBRIDGE,
+ SPECIAL_JUMPJET,
+
SPECIALPOWER_COUNT,
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NEW CONSTANTS NEED A BEHAVIORTYPE DEFINED IN THE SPECIALPOWER OR return one in getFallbackBehaviorType in ActionManager.cpp
diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/ControlBar.h b/GeneralsMD/Code/GameEngine/Include/GameClient/ControlBar.h
index f2da56d89dd..ad97421c322 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameClient/ControlBar.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameClient/ControlBar.h
@@ -99,6 +99,7 @@ enum CommandOption CPP_11(: Int)
USES_MINE_CLEARING_WEAPONSET= 0x00200000, // uses the special mine-clearing weaponset, even if not current
CAN_USE_WAYPOINTS = 0x00400000, // button has option to use a waypoint path
MUST_BE_STOPPED = 0x00800000, // Unit must be stopped in order to be able to use button.
+ FORMATION_LAUNCH = 0x01000000, // code-only: jumpjet group launch, keep formation offset instead of random scatter.
};
#ifdef DEFINE_COMMAND_OPTION_NAMES
@@ -132,6 +133,7 @@ static const char *const TheCommandOptionNames[] =
"USES_MINE_CLEARING_WEAPONSET",
"CAN_USE_WAYPOINTS",
"MUST_BE_STOPPED",
+ "FORMATION_LAUNCH",
nullptr
};
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h
index a9857c972ef..2a648ec51f3 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h
@@ -264,6 +264,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot
AsciiString getTemplateName() const { return m_template->m_name;}
Real getMinSpeed() const { return m_template->m_minSpeed;}
+ Real getMinTurnSpeed() const { return m_template->m_minTurnSpeed;} ///< must be going >= this speed to turn (0 = can turn in place)
Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.)
Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.)
Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up.
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JumpjetContain.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JumpjetContain.h
new file mode 100644
index 00000000000..a1fca9f5782
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JumpjetContain.h
@@ -0,0 +1,128 @@
+/*
+** 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 .
+*/
+
+////////////////////////////////////////////////////////////////////////////////
+// //
+// (c) 2001-2003 Electronic Arts Inc. //
+// //
+////////////////////////////////////////////////////////////////////////////////
+
+// FILE: ParachuteContain.h ////////////////////////////////////////////////////////////////////////
+// Author: Steven Johnson, March 2002
+// Desc: Contain module for transport units.
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#ifndef __JumpjetContain_H_
+#define __JumpjetContain_H_
+
+// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
+#include "GameLogic/Module/OpenContain.h"
+
+enum ModelConditionFlagType;
+
+//-------------------------------------------------------------------------------------------------
+class JumpjetContainModuleData : public OpenContainModuleData
+{
+public:
+ //Real m_pitchRateMax;
+ //Real m_rollRateMax;
+ //Real m_lowAltitudeDamping;
+ //Real m_paraOpenDist; ///< deploy the parachute when we have traveled this far
+ //Real m_freeFallDamagePercent;
+ ModelConditionFlagType m_conditionFlagFlying; ///< ModelConditionState to use when flying
+ ModelConditionFlagType m_conditionFlagLanding; ///< ModelConditionState to use before landing
+ Real m_landingDistance; ///< Distance from target to initiate landing state
+ Bool m_killWhenLandingInWater; ///< Kill the Rider when landing on water
+ Bool m_killWhenLandingOnCliff; ///< Kill the Rider when landing on cliffs
+ Bool m_killWhenLandingOnImpassable; ///< Kill the Rider when landing on impassable terrain
+ Real m_killWhenLandingInWaterSlop; ///< Water Surface offset when killing the rider
+ //AudioEventRTS m_parachuteOpenSound;
+
+ JumpjetContainModuleData();
+ static void buildFieldParse(MultiIniFieldParse& p);
+};
+
+//-------------------------------------------------------------------------------------------------
+class JumpjetContain : public OpenContain
+{
+
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JumpjetContain, "JumpjetContain")
+ MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(JumpjetContain, JumpjetContainModuleData)
+
+public:
+
+ JumpjetContain(Thing* thing, const ModuleData* moduleData);
+ // virtual destructor prototype provided by memory pool declaration
+
+ //virtual void onDrawableBoundToObject();
+
+ virtual Bool isValidContainerFor(const Object* obj, bool checkCapacity) const;
+ virtual Bool isEnclosingContainerFor(const Object* obj) const { return FALSE; } ///< Does this type of Contain Visibly enclose its contents?
+ virtual Bool isSpecialZeroSlotContainer() const { return true; }
+
+ virtual void onContaining(Object* obj, Bool wasSelected); ///< object now contains 'obj'
+ virtual void onRemoving(Object* obj); ///< object no longer contains 'obj'
+
+ virtual UpdateSleepTime update(); ///< called once per frame
+
+ // virtual void containReactToTransformChange();
+
+ virtual void onCollide(Object* other, const Coord3D* loc, const Coord3D* normal);
+ virtual void onDie(const DamageInfo* damageInfo);
+
+ // virtual void setOverrideDestination( const Coord3D *override ); ///< Instead of falling peacefully towards a clear spot, I will now aim here
+
+protected:
+
+ virtual Bool isFullyEnclosingContainer() const { return false; }
+ virtual void positionContainedObjectsRelativeToContainer();
+
+private:
+
+ void positionRider(Object* obj);
+ /*void updateBonePositions();
+ void updateOffsetsFromBones();
+ void calcSwayMtx(const Coord3D* offset, Matrix3D* mtx);
+ void setSwayMatrices();
+
+ Real m_pitch;
+ Real m_roll;
+ Real m_pitchRate;
+ Real m_rollRate;
+ Real m_startZ;
+ Bool m_isLandingOverrideSet;
+ Coord3D m_landingOverride;
+ Coord3D m_riderAttachBone;
+ Coord3D m_riderSwayBone;
+ Coord3D m_paraAttachBone;
+ Coord3D m_paraSwayBone;
+ Coord3D m_riderAttachOffset;
+ Coord3D m_riderSwayOffset;
+ Coord3D m_paraAttachOffset;
+ Coord3D m_paraSwayOffset;
+ Bool m_needToUpdateRiderBones;
+ Bool m_needToUpdateParaBones;
+ Bool m_opened;*/
+
+ Bool m_landing;
+};
+
+#endif // __JumpjetContain_H_
+
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JumpjetMissileAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JumpjetMissileAIUpdate.h
new file mode 100644
index 00000000000..7c0a4f83b60
--- /dev/null
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JumpjetMissileAIUpdate.h
@@ -0,0 +1,126 @@
+/*
+** 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 .
+*/
+
+////////////////////////////////////////////////////////////////////////////////
+// //
+// (c) 2001-2003 Electronic Arts Inc. //
+// //
+////////////////////////////////////////////////////////////////////////////////
+
+// FILE: MissileAIUpdate.h
+// Author: Michael S. Booth, December 2001
+// Desc: Missile behavior
+
+#pragma once
+
+#ifndef _JUMPJET_MISSILE_AI_UPDATE_H_
+#define _JUMPJET_MISSILE_AI_UPDATE_H_
+
+#include "Common/GameType.h"
+#include "Common/GlobalData.h"
+#include "GameLogic/Module/MissileAIUpdate.h"
+//#include "GameLogic/WeaponBonusConditionFlags.h"
+#include "Common/INI.h"
+//#include "WWMath/Matrix3D.h"
+
+enum ParticleSystemID;
+//class FXList;
+
+
+//-------------------------------------------------------------------------------------------------
+class JumpjetMissileAIUpdateModuleData : public MissileAIUpdateModuleData
+{
+public:
+ Real m_initialPitchAngle; ///< initial pitch angle the missile will be launched at.
+ Real m_scatterRadius; ///< max random offset from target
+ Real m_maxSearchRadius; ///< max radius to search for possible target location
+ /*Bool m_avoidImpassable; ///< try to avoid impassable cells when finding target
+ Bool m_avoidWater; ///< try to avoid water when finding target
+ Bool m_avoidObjects; ///< try to objects when finding target*/
+ JumpjetMissileAIUpdateModuleData();
+
+ static void buildFieldParse(MultiIniFieldParse& p);
+
+};
+
+//-------------------------------------------------------------------------------------------------
+class JumpjetMissileAIUpdate : public MissileAIUpdate
+{
+ MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JumpjetMissileAIUpdate, "JumpjetMissileAIUpdate")
+ MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(JumpjetMissileAIUpdate, JumpjetMissileAIUpdateModuleData);
+
+public:
+ JumpjetMissileAIUpdate(Thing* thing, const ModuleData* moduleData);
+
+ virtual void projectileFireAtObjectOrPosition(const Object* victim, const Coord3D* victimPos, const WeaponTemplate* detWeap, const ParticleSystemTemplate* exhaustSysOverride);
+ virtual Bool projectileHandleCollision(Object* other);
+ // virtual Bool processCollision(PhysicsBehavior *physics, Object *other); ///< Returns true if the physics collide should apply the force. Normally not. jba.
+
+ virtual Bool canLaunchToPosition(const Coord3D* targetPos, Coord3D* newPos, Bool keepFormation = false);
+
+ // virtual UpdateSleepTime update();
+ virtual void onDelete(void);
+
+ // Bool isLanding( void );
+ Real getGoalDistance(void);
+
+protected:
+
+ void detonate();
+
+private:
+
+ /*MissileStateType m_state; ///< the behavior state of the missile
+ UnsignedInt m_stateTimestamp; ///< time of state change
+ UnsignedInt m_nextTargetTrackTime; ///< if nonzero, how often we update our target pos
+ ObjectID m_launcherID; ///< ID of object that launched us (INVALID_ID if not yet launched)
+ ObjectID m_victimID; ///< ID of object that I am rocketing towards (INVALID_ID if not yet launched)
+ UnsignedInt m_fuelExpirationDate; ///< how long 'til we run out of fuel
+ Real m_noTurnDistLeft; ///< when zero, ok to start turning
+ Real m_maxAccel;
+ Coord3D m_originalTargetPos; ///< When firing uphill, we aim high to clear the brow of the hill. jba.
+ Coord3D m_prevPos;
+ WeaponBonusConditionFlags m_extraBonusFlags;
+ const WeaponTemplate* m_detonationWeaponTmpl; ///< weapon to fire at end (or null)
+ const ParticleSystemTemplate* m_exhaustSysTmpl;
+ ParticleSystemID m_exhaustID; ///< our exhaust particle system (if any)
+ UnsignedInt m_framesTillDecoyed; ///< Number of frames before missile will get distracted by decoy countermeasures.
+ Bool m_isTrackingTarget; ///< Was I originally shot at a moving object?
+ Bool m_isArmed; ///< if true, missile will explode on contact
+ Bool m_noDamage; ///< if true, missile will not cause damage when it detonates. (Used for flares).
+ Bool m_isJammed; ///< No target, just shooting at a scattered position
+
+ void doPrelaunchState();
+ void doLaunchState();
+ void doIgnitionState();
+ void doAttackState(Bool turnOK);
+ void doKillState();
+ void doKillSelfState();
+ void doDeadState();
+
+ void airborneTargetGone(); ///< My airborne target has died, so I have to do something cool to make up for that
+
+ void tossExhaust();
+ void switchToState(MissileStateType s);*/
+
+ Bool findOffsetPosition(const Coord3D* targetPos, Coord3D* newPos, Real minRadius, Real maxRadius);
+
+};
+
+#endif // _JUMPJET_MISSILE_AI_UPDATE_H_
+
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h
index 6f6375fd7a1..490468cda89 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h
@@ -118,10 +118,13 @@ class MissileAIUpdate : public AIUpdateInterface, public ProjectileUpdateInterfa
virtual UpdateSleepTime update();
virtual void onDelete( void );
+ virtual void switchToState(MissileStateType s);
+
+ virtual MissileStateType getMissileState() { return m_state; }
protected:
- void detonate();
+ virtual void detonate();
private:
@@ -157,7 +160,5 @@ class MissileAIUpdate : public AIUpdateInterface, public ProjectileUpdateInterfa
void airborneTargetGone(); ///< My airborne target has died, so I have to do something cool to make up for that
void tossExhaust();
- void switchToState(MissileStateType s);
-
};
diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialAbilityUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialAbilityUpdate.h
index 2d0d11bc6ec..d58e027d9d5 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialAbilityUpdate.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpecialAbilityUpdate.h
@@ -76,6 +76,8 @@ class SpecialAbilityUpdateModuleData : public UpdateModuleData
Bool m_approachRequiresLOS;
Bool m_needToFaceTarget;
Bool m_persistenceRequiresRecharge;
+ Bool m_requiresMoveToTurn; ///< if set, orient by moving toward target (for locomotors that can't turn in place) instead of facing in place
+ Real m_facingAngleTolerance; ///< heading delta (radians) considered "facing target" when m_requiresMoveToTurn
const ParticleSystemTemplate *m_disableFXParticleSystem;
AudioEventRTS m_packSound;
@@ -113,6 +115,8 @@ class SpecialAbilityUpdateModuleData : public UpdateModuleData
m_preTriggerUnstealthFrames = 0;
m_needToFaceTarget = TRUE;
m_persistenceRequiresRecharge = FALSE;
+ m_requiresMoveToTurn = FALSE;
+ m_facingAngleTolerance = 0.1f; // ~5.7 degrees
}
static void buildFieldParse(MultiIniFieldParse& p)
@@ -159,6 +163,8 @@ class SpecialAbilityUpdateModuleData : public UpdateModuleData
{ "ApproachRequiresLOS", INI::parseBool, nullptr, offsetof( SpecialAbilityUpdateModuleData, m_approachRequiresLOS ) },
{ "NeedToFaceTarget", INI::parseBool, nullptr, offsetof( SpecialAbilityUpdateModuleData, m_needToFaceTarget ) },
{ "PersistenceRequiresRecharge",INI::parseBool, nullptr, offsetof( SpecialAbilityUpdateModuleData, m_persistenceRequiresRecharge ) },
+ { "RequiresMoveToTurn", INI::parseBool, nullptr, offsetof( SpecialAbilityUpdateModuleData, m_requiresMoveToTurn ) },
+ { "FacingAngleTolerance", INI::parseAngleReal, nullptr, offsetof( SpecialAbilityUpdateModuleData, m_facingAngleTolerance ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
@@ -270,6 +276,7 @@ class SpecialAbilityUpdate : public SpecialPowerUpdateModule
UnsignedInt m_animFrames; //Used for packing/unpacking unit before or after using ability.
ObjectID m_targetID;
Coord3D m_targetPos;
+ UnsignedInt m_commandOptions; //Command option flags this ability was triggered with (e.g. FORMATION_LAUNCH).
Int m_locationCount;
std::list m_specialObjectIDList; //The list of special objects
UnsignedInt m_specialObjectEntries; //The size of the list of member Objects
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp
index 4fa79b6bc4b..166d9e2fcec 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp
@@ -1638,6 +1638,13 @@ Bool ActionManager::canDoSpecialPowerAtLocation( const Object *obj, const Coord3
if( TheTerrainLogic->isUnderwater( loc->x, loc->y ) )
return FALSE;
}
+ case SPECIAL_JUMPJET:
+ {
+ if (TheTerrainLogic->isUnderwater(loc->x, loc->y)
+ || TheTerrainLogic->isCliffCell(loc->x, loc->y)) {
+ return FALSE;
+ }
+ }
}
// Last check is shroudedness, if it is cared about
@@ -1688,6 +1695,7 @@ Bool ActionManager::canDoSpecialPowerAtLocation( const Object *obj, const Coord3
case SPECIAL_CLEANUP_AREA:
case SPECIAL_SNEAK_ATTACK:
case SPECIAL_BATTLESHIP_BOMBARDMENT:
+ case SPECIAL_JUMPJET:
//Don't allow "damaging" special powers in shrouded areas, but Fogged are okay.
return ThePartitionManager->getShroudStatusForPlayer( obj->getControllingPlayer()->getPlayerIndex(), loc ) != CELLSHROUD_SHROUDED;
@@ -1952,6 +1960,7 @@ Bool ActionManager::canDoSpecialPowerAtObject( const Object *obj, const Object *
case SPECIAL_LAUNCH_BAIKONUR_ROCKET:
case SPECIAL_SNEAK_ATTACK:
case SPECIAL_TOGGLE_DRAWBRIDGE:
+ case SPECIAL_JUMPJET:
return false;
case SPECIAL_REMOTE_CHARGES:
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp
index 8f1fd1456f9..2690e574d67 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/SpecialPower.cpp
@@ -221,6 +221,8 @@ const char* const SpecialPowerMaskType::s_bitNameList[] =
"SPECIAL_TOGGLE_DRAWBRIDGE",
+ "SPECIAL_JUMPJET",
+
nullptr
};
static_assert(ARRAY_SIZE(SpecialPowerMaskType::s_bitNameList) == SpecialPowerMaskType::NumBits + 1, "Incorrect array size");
diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
index 75106e257ed..3d28ceae6c9 100644
--- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp
@@ -69,6 +69,7 @@
#include "GameLogic/Module/RailedTransportContain.h"
#include "GameLogic/Module/RiderChangeContain.h"
#include "GameLogic/Module/TransportContain.h"
+#include "GameLogic/Module/JumpjetContain.h"
#include "GameLogic/Module/MobNexusContain.h"
#include "GameLogic/Module/TunnelContain.h"
#include "GameLogic/Module/OverlordContain.h"
@@ -159,6 +160,7 @@
#include "GameLogic/Module/SpecialPowerDesignatorUpdate.h"
#include "GameLogic/Module/AutoDepositUpdate.h"
#include "GameLogic/Module/MissileAIUpdate.h"
+#include "GameLogic/Module/JumpjetMissileAIUpdate.h"
#include "GameLogic/Module/NeutronMissileUpdate.h"
#include "GameLogic/Module/OCLUpdate.h"
#include "GameLogic/Module/PhysicsUpdate.h"
@@ -376,6 +378,7 @@ void ModuleFactory::init( void )
addModule( GarrisonContain );
addModule( InternetHackContain );
addModule( TransportContain );
+ addModule( JumpjetContain );
addModule( RiderChangeContain );
addModule( RailedTransportContain );
addModule( MobNexusContain );
@@ -447,6 +450,7 @@ void ModuleFactory::init( void )
addModule( BuffUpdate );
addModule( ArmorDamageScalarUpdate );
addModule( MissileAIUpdate );
+ addModule( JumpjetMissileAIUpdate );
addModule( NeutronMissileUpdate );
addModule( FireSpreadUpdate );
addModule( FireWeaponUpdate );
diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp
index 303b3056d0d..201cf472c43 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp
@@ -2748,6 +2748,12 @@ void AIGroup::groupDoSpecialPowerAtLocation( UnsignedInt specialPowerID, const C
//This one requires a position
+ // Precompute the group center/formation state once so SPECIAL_JUMPJET members can each
+ // launch to their own formation-relative target instead of all piling on the click point.
+ Coord2D fMin, fMax;
+ Coord3D fCenter;
+ Bool isFormation = getMinMaxAndCenter( &fMin, &fMax, &fCenter );
+
std::list