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 @@ -195,6 +195,7 @@ static PoolSizeRec PoolSizes[] =
{ "PilotFindVehicleUpdate", 256, 32 },
{ "DemoTrapUpdate", 32, 32 },
{ "ParticleUplinkCannonUpdate", 16, 16 },
{ "ChronoSphereUpdateModule", 8, 8 },
{ "SpectreGunshipUpdate", 8, 8 },
{ "SpectreGunshipDeploymentUpdate", 8, 8 },
{ "KodiakUpdate", 8, 8 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ class W3DModelDraw : public DrawModule, public ObjectDrawInterface

virtual Bool isVisible() const;
virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle);
virtual void reactToTeleport();
virtual void reactToGeometryChange() { }

// this method must ONLY be called from the client, NEVER From the logic, not even indirectly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class TerrainTracksRenderObjClass : public W3DMPO, public RenderObjClass
void addEdgeToTrack(Real x, Real y); ///< add a new segment to the track
void addCapEdgeToTrack(Real x, Real y); ///< cap the existing segment so we can resume at an unconnected position.
void setAirborne(void) {m_airborne = true; } ///< Starts a new section of track, generally after going airborne.
void breakTrack(void); ///< end current strip (last edge feathered) so the next edge starts a fresh, unconnected anchor (teleport - keeps existing edges, no bridging quad)
void setOwnerDrawable(const Drawable *owner) {m_ownerDrawable = owner;}

protected:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4168,6 +4168,16 @@ Int W3DModelDraw::getCurrentBonePositions(
return posCount;
}

//-------------------------------------------------------------------------------------------------
void W3DModelDraw::reactToTeleport()
{
// The object was instantly relocated. Break the tread-mark strip so the next edge starts a fresh,
// unconnected anchor at the destination - no stretched bridging quad. Existing edges (the tracks
// laid before the teleport) are kept.
if (m_trackRenderObject)
m_trackRenderObject->breakTrack();
}

//-------------------------------------------------------------------------------------------------
void W3DModelDraw::reactToTransformChange( const Matrix3D* oldMtx,
const Coord3D* oldPos,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,28 @@ void TerrainTracksRenderObjClass::addCapEdgeToTrack(Real x, Real y)
m_haveAnchor=false;
}

//=============================================================================
// TerrainTracksRenderObjClass::breakTrack
//=============================================================================
/** End the current strip so the next edge starts a fresh, unconnected anchor (used on teleport).
* The edge buffer is one continuous ring, so the existing edges are kept but the last one is
* feathered to transparent - that way the quad joining it to the first edge of the new strip is
* alpha0->alpha0 (invisible) and no line bridges the teleport distance. */
//=============================================================================
void TerrainTracksRenderObjClass::breakTrack(void)
{
if (m_activeEdgeCount > 0)
{
Int maxEdgeCount = TheTerrainTracksRenderObjClassSystem->m_maxTankTrackEdges;
Int lastAddedEdge = m_topIndex - 1;
if (lastAddedEdge < 0)
lastAddedEdge = maxEdgeCount - 1;
m_edges[lastAddedEdge].alpha = 0.0f; //feather the tip of the old strip
}
m_haveAnchor = false; //next edge starts a new anchor
m_haveCap = TRUE;
}

//=============================================================================
// TerrainTracksRenderObjClass::addEdgeToTrack
//=============================================================================
Expand Down
2 changes: 2 additions & 0 deletions Generals/Code/GameEngine/Include/Common/DrawModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class DrawModule : public DrawableModule
virtual void setTerrainDecalSize(Real x, Real y) {};
virtual void setTerrainDecalOpacity(Real o) {};

virtual void reactToTeleport() {}; ///< object was instantly relocated (e.g. chronosphere) - break tread marks etc.

virtual void setFullyObscuredByShroud(Bool fullyObscured) = 0;

virtual Bool isVisible() const { return true; } ///< for limiting tree sway, etc to visible objects
Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/CaveContain.h
Include/GameLogic/Module/CheckpointUpdate.h
Include/GameLogic/Module/ChinookAIUpdate.h
Include/GameLogic/Module/ChronoSphereUpdateModule.h
Include/GameLogic/Module/CleanupAreaPower.h
Include/GameLogic/Module/CleanupHazardUpdate.h
Include/GameLogic/Module/CollideModule.h
Expand Down Expand Up @@ -1058,6 +1059,7 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Update/BattlePlanUpdate.cpp
Source/GameLogic/Object/Update/BoneFXUpdate.cpp
Source/GameLogic/Object/Update/CheckpointUpdate.cpp
Source/GameLogic/Object/Update/ChronoSphereUpdateModule.cpp
Source/GameLogic/Object/Update/CleanupHazardUpdate.cpp
Source/GameLogic/Object/Update/CommandButtonHuntUpdate.cpp
Source/GameLogic/Object/Update/DeletionUpdate.cpp
Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class DrawModule : public DrawableModule
virtual void setTerrainDecalSize(Real x, Real y) {};
virtual void setTerrainDecalOpacity(Real o) {};

virtual void reactToTeleport() {}; ///< object was instantly relocated (e.g. chronosphere) - break tread marks etc.

virtual void setFullyObscuredByShroud(Bool fullyObscured) = 0;

virtual Bool isVisible() const { return true; } ///< for limiting tree sway, etc to visible objects
Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ class GameMessage : public MemoryPoolObject
MSG_LOGIC_CRC, ///< CRC from the logic passed around in a network game :)
MSG_SET_MINE_CLEARING_DETAIL, ///< CRC from the logic passed around in a network game :)
MSG_ENABLE_RETALIATION_MODE, ///< Turn retaliation mode on or off for the specified player.
MSG_DO_SPECIAL_POWER_AT_TWO_LOCATIONS, ///< chrono-style: (spID, srcLoc, destLoc, options, sourceID) - both points committed in one message

MSG_BEGIN_DEBUG_NETWORK_MESSAGES = 1900, ///< network messages that exist only in debug/internal builds. all grouped separately.

Expand Down
10 changes: 10 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/GameClient/ControlBar.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Object;
class ThingTemplate;
class WeaponTemplate;
class SpecialPowerTemplate;
class FXList;
class WindowVideoManager;
class WindowVideoManager;
class AnimateWindowManager;
Expand Down Expand Up @@ -100,6 +101,7 @@ enum CommandOption CPP_11(: Int)
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.
NEED_TWO_TARGET_POS = 0x02000000, // command needs the user to select two target positions; only the 2nd click commits (chronosphere).
};

#ifdef DEFINE_COMMAND_OPTION_NAMES
Expand Down Expand Up @@ -134,6 +136,7 @@ static const char *const TheCommandOptionNames[] =
"CAN_USE_WAYPOINTS",
"MUST_BE_STOPPED",
"FORMATION_LAUNCH",
"NEED_TWO_TARGET_POS",

nullptr
};
Expand All @@ -145,6 +148,7 @@ const UnsignedInt COMMAND_OPTION_NEED_TARGET =
NEED_TARGET_NEUTRAL_OBJECT |
NEED_TARGET_ALLY_OBJECT |
NEED_TARGET_POS |
NEED_TWO_TARGET_POS |
CONTEXTMODE_COMMAND;

const UnsignedInt COMMAND_OPTION_NEED_OBJECT_TARGET =
Expand Down Expand Up @@ -323,6 +327,7 @@ class CommandButton : public Overridable

const AsciiString& getName() const { return m_name; }
const AsciiString& getCursorName() const { return m_cursorName; }
const AsciiString& getSecondCursorName() const { return m_secondCursorName; }
const AsciiString& getInvalidCursorName() const { return m_invalidCursorName; }
const AsciiString& getTextLabel() const { return m_textLabel; }
const AsciiString& getDescriptionLabel() const { return m_descriptionLabel; }
Expand All @@ -333,6 +338,8 @@ class CommandButton : public Overridable
GUICommandType getCommandType() const { return m_command; }
UnsignedInt getOptions() const { return m_options; }
OVERRIDE<ThingTemplate> getThingTemplate() const { return m_thingTemplate; }
const ThingTemplate* getMarkerObject() const { return m_markerTemplate; }
const FXList* getMarkerFX() const { return m_markerFX; }
const UpgradeTemplate* getUpgradeTemplate() const { return m_upgradeTemplate; }
const SpecialPowerTemplate* getSpecialPowerTemplate() const { return m_specialPower; }
RadiusCursorType getRadiusCursorType() const { return m_radiusCursor; }
Expand Down Expand Up @@ -371,10 +378,13 @@ class CommandButton : public Overridable
CommandButton* m_next;
UnsignedInt m_options; ///< command options (see CommandOption enum)
const ThingTemplate* m_thingTemplate; ///< for commands that use thing templates in command data
const ThingTemplate* m_markerTemplate; ///< client-only marker model shown at the 1st pick of a two-point (chronosphere) power
const FXList* m_markerFX; ///< one-shot client FX played at the 1st pick of a two-point power
const UpgradeTemplate* m_upgradeTemplate; ///< for commands that use upgrade templates in command data
const SpecialPowerTemplate* m_specialPower; ///< actual special power template
RadiusCursorType m_radiusCursor; ///< radius cursor, if any
AsciiString m_cursorName; ///< cursor name for placement (NEED_TARGET_POS) or valid version (CONTEXTMODE_COMMAND)
AsciiString m_secondCursorName; ///< cursor name for the 2nd pick of a two-point (NEED_TWO_TARGET_POS) power; falls back to m_cursorName if empty
AsciiString m_invalidCursorName; ///< cursor name for invalid version

// bleah. shouldn't be mutable, but is. sue me. (Kris) -snork!
Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ class Drawable : public Thing,
// Override.
void setPosition( const Coord3D *pos );
void reactToGeometryChange();
void reactToTeleport(); ///< object was instantly relocated - break interpolated visuals (tread marks)

const GeometryInfo& getDrawableGeometryInfo() const;

Expand Down
12 changes: 12 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,13 @@ friend class Drawable; // for selection/deselection transactions
virtual void setGUICommand( const CommandButton *command ); ///< the command has been clicked in the UI and needs additional data
virtual const CommandButton *getGUICommand( void ) const; ///< get the pending gui command

// two-point (chronosphere) special power selection: the first click is captured client-side
// and committed nothing; only the second click dispatches. Right-click cancels (via setGUICommand).
virtual void setPendingSpecialPowerFirstLocation( const Coord3D *loc ); ///< store the first-click source location
virtual const Coord3D *getPendingSpecialPowerFirstLocation( void ) const { return &m_pendingSpecialPowerFirstLocation; }
virtual Bool hasPendingSpecialPowerFirstLocation( void ) const { return m_hasPendingSpecialPowerFirstLocation; }
virtual void clearPendingSpecialPowerFirstLocation( void ) { m_hasPendingSpecialPowerFirstLocation = FALSE; destroySpecialPowerSourceMarker(); }

// build interface
virtual void placeBuildAvailable( const ThingTemplate *build, Drawable *buildDrawable ); ///< built thing being placed
virtual const ThingTemplate *getPendingPlaceType( void ); ///< get item we're trying to place
Expand Down Expand Up @@ -628,6 +635,8 @@ friend class Drawable; // for selection/deselection transactions

protected:

void destroySpecialPowerSourceMarker( void ); ///< remove the two-point special power source marker drawable if present

// ----------------------------------------------------------------------------------------------
// Protected Types ------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -739,6 +748,9 @@ friend class Drawable; // for selection/deselection transactions
MoveHintStruct m_moveHint[ MAX_MOVE_HINTS ];
Int m_nextMoveHint;
const CommandButton * m_pendingGUICommand; ///< GUI command that needs additional interaction from the user
Coord3D m_pendingSpecialPowerFirstLocation; ///< first-click source for a two-point special power
Bool m_hasPendingSpecialPowerFirstLocation; ///< TRUE once the first click of a two-point special power is captured
Drawable * m_specialPowerSourceMarker; ///< client-only marker drawable shown at the first-click source (nullptr if none)
BuildProgress m_buildProgress[ MAX_BUILD_PROGRESS ]; ///< progress for building units
const ThingTemplate * m_pendingPlaceType; ///< type of built thing we're trying to place
ObjectID m_pendingPlaceSourceObjectID; ///< source object of the thing constructing the item
Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,7 @@ class AIGroup : public MemoryPoolObject, public Snapshot
void groupDoSpecialPower( UnsignedInt specialPowerID, UnsignedInt commandOptions );
void groupDoSpecialPowerAtObject( UnsignedInt specialPowerID, Object *object, UnsignedInt commandOptions );
void groupDoSpecialPowerAtLocation( UnsignedInt specialPowerID, const Coord3D *location, Real angle, const Object *object, UnsignedInt commandOptions );
void groupDoSpecialPowerAtTwoLocations( UnsignedInt specialPowerID, const Coord3D *source, const Coord3D *dest, UnsignedInt commandOptions );
#ifdef ALLOW_SURRENDER
void groupSurrender( const Object *objWeSurrenderedTo, Bool surrender, CommandSourceType cmdSource );
#endif
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
** 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 <http://www.gnu.org/licenses/>.
*/

// FILE: ChronoSphereUpdateModule.h /////////////////////////////////////////////////////////////////
// Desc: Special power update module for a Red Alert 2 style chronosphere. The player selects two
// locations (source and destination); the second selection can be canceled. This first
// increment only captures the two points and wires the module - the teleport effect is TODO.
///////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "Common/KindOf.h"
#include "Common/Science.h"
#include "GameLogic/Module/UpdateModule.h"
#include "GameLogic/Module/SpecialPowerUpdateModule.h"

// FORWARD REFERENCES /////////////////////////////////////////////////////////////////////////////
class SpecialPowerModuleInterface;
class FXList;
class ObjectCreationList;

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
class ChronoSphereUpdateModuleData : public ModuleData
{
public:
SpecialPowerTemplate *m_specialPowerTemplate;

UnsignedInt m_teleportDelayFrames; ///< delay before the teleport happens
KindOfMaskType m_requiredKindOf; ///< whitelist: units must match to be affected
KindOfMaskType m_forbiddenKindOf; ///< blacklist: units matching are never affected
Real m_radius; ///< radius of the affected area at each point

FXList *m_sourceFX; ///< FX at the source area
FXList *m_targetFX; ///< FX at the destination area
FXList *m_unitSourceFX; ///< FX on each teleported unit at the source
FXList *m_unitTargetFX; ///< FX on each teleported unit at the destination

const ObjectCreationList *m_sourceOCL; ///< OCL fired at the source when the power activates (instant, ignores TeleportDelay)
const ObjectCreationList *m_targetOCL; ///< OCL fired at the destination when the power activates (instant)

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

private:

};

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
class ChronoSphereUpdateModule : public SpecialPowerUpdateModule
{

MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ChronoSphereUpdateModule, "ChronoSphereUpdateModule" )
MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( ChronoSphereUpdateModule, ChronoSphereUpdateModuleData );

public:

ChronoSphereUpdateModule( Thing *thing, const ModuleData* moduleData );
// virtual destructor prototype provided by memory pool declaration

// SpecialPowerUpdateInterface
virtual Bool initiateIntentToDoSpecialPower(const SpecialPowerTemplate *specialPowerTemplate, const Object *targetObj, const Coord3D *targetPos, const Waypoint *way, UnsignedInt commandOptions );
virtual Bool isSpecialAbility() const { return false; }
virtual Bool isSpecialPower() const { return true; }
virtual Bool isActive() const { return m_active; }
virtual SpecialPowerUpdateInterface* getSpecialPowerUpdateInterface() { return this; }
virtual CommandOption getCommandOption() const { return (CommandOption)0; }
virtual Bool isPowerCurrentlyInUse( const CommandButton *command = nullptr ) const { return m_active; }
virtual ScienceType getExtraRequiredScience() const { return SCIENCE_INVALID; }

// The chronosphere delivers its destination (the 2nd click) through the existing
// overridable-destination channel - see Object::doSpecialPowerAtTwoLocations.
virtual Bool doesSpecialPowerHaveOverridableDestinationActive() const { return m_active; }
virtual Bool doesSpecialPowerHaveOverridableDestination() const { return true; }
virtual void setSpecialPowerOverridableDestination( const Coord3D *loc );

virtual void onObjectCreated();
virtual UpdateSleepTime update();

// termination conditions
virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK4( DISABLED_SUBDUED, DISABLED_UNDERPOWERED, DISABLED_EMP, DISABLED_HACKED ); }

protected:

void doChronoTeleport(); ///< relocate all matching objects from source to destination, playing FX

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

Coord3D m_sourceLocation; ///< first click - where things teleport FROM
Coord3D m_destLocation; ///< second click - where things teleport TO
UnsignedInt m_teleportFrame; ///< logic frame at which the teleport fires (activation + TeleportDelay)
Bool m_active; ///< TRUE while a teleport is pending (armed, not yet fired)
};
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ class Object : public Thing, public Snapshot
void doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced = false ); ///< execute power
void doSpecialPowerAtObject( const SpecialPowerTemplate *specialPowerTemplate, Object *obj, UnsignedInt commandOptions, Bool forced = false ); ///< execute power
void doSpecialPowerAtLocation( const SpecialPowerTemplate *specialPowerTemplate, const Coord3D *loc, Real angle, UnsignedInt commandOptions, Bool forced = false ); ///< execute power
void doSpecialPowerAtTwoLocations( const SpecialPowerTemplate *specialPowerTemplate, const Coord3D *source, const Coord3D *dest, UnsignedInt commandOptions, Bool forced = false ); ///< execute chrono-style power (source + destination)
void doSpecialPowerUsingWaypoints( const SpecialPowerTemplate *specialPowerTemplate, const Waypoint *way, UnsignedInt commandOptions, Bool forced = false ); ///< execute power

void doCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource );
Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Source/Common/MessageStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,7 @@ const char *GameMessage::getCommandTypeAsString(GameMessage::Type t)
CASE_LABEL(MSG_OBJECT_JOINED_TEAM)
CASE_LABEL(MSG_SET_MINE_CLEARING_DETAIL)
CASE_LABEL(MSG_ENABLE_RETALIATION_MODE)
CASE_LABEL(MSG_DO_SPECIAL_POWER_AT_TWO_LOCATIONS)
}

#undef CASE_LABEL
Expand Down
Loading
Loading