Skip to content
Open
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
4 changes: 4 additions & 0 deletions Core/GameEngine/Include/Common/GameDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@
#define RETAIL_COMPATIBLE_XFER_SAVE (1) // Game is expected to be Xfer Save compatible with retail Generals 1.08, Zero Hour 1.04
#endif

#ifndef RETAIL_COMPATIBLE_WORLDBUILDER
#define RETAIL_COMPATIBLE_WORLDBUILDER (1) // WorldBuilder output is expected to remain readable by retail tools
#endif

// This is here to easily toggle between the retail compatible with fixed pathfinding fallback and pure fixed pathfinding mode
#ifndef RETAIL_COMPATIBLE_PATHFINDING
#define RETAIL_COMPATIBLE_PATHFINDING (1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#define K_TRIGGERS_VERSION_1 1
#define K_TRIGGERS_VERSION_2 2 // Added m_isWaterArea
#define K_TRIGGERS_VERSION_3 3 // Added m_isRiver & m_riverStart
#define K_TRIGGERS_VERSION_4 4 // Added layer name.
#define K_LIGHTING_VERSION_1 1
#define K_LIGHTING_VERSION_2 2 // Added 2 additional global lights for objects.
#define K_LIGHTING_VERSION_3 3 // Added 2 additional global lights for terrain.
Expand Down
5 changes: 5 additions & 0 deletions Generals/Code/GameEngine/Include/Common/ThingTemplate.h
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ class ThingTemplate : public Overridable

// Only Object can ask this. Everyone else should ask the Object. In fact, you really should ask the Object everything.
Real friend_getVisionRange() const { return m_visionRange; } ///< get vision range
Real friend_calcVisionRange() const { return m_visionRange; } ///< WorldBuilder compatibility alias
Real friend_getShroudClearingRange() const { return m_shroudClearingRange; } ///< get vision range for Shroud ONLY (Design requested split)

// This function is only for use by the AIUpdateModuleData::parseLocomotorSet function.
Expand Down Expand Up @@ -505,6 +506,8 @@ class ThingTemplate : public Overridable
const AudioEventRTS *getSoundPromotedHero() const { return getAudio(TTAUDIO_soundPromotedHero); }
const AudioEventRTS *getSoundFalling() const { return getAudio(TTAUDIO_soundFalling); }

Bool hasSoundAmbient() const { return m_audioarray.m_audio[TTAUDIO_soundAmbient] != nullptr; }

const AudioEventRTS *getPerUnitSound(const AsciiString& soundName) const;
const FXList* getPerUnitFX(const AsciiString& fxName) const;

Expand Down Expand Up @@ -610,6 +613,8 @@ class ThingTemplate : public Overridable

AsciiString getUpgradeCameoName( Int n)const{ return m_upgradeCameoUpgradeNames[n]; }

const WeaponTemplateSetVector& getWeaponTemplateSets() const {return m_weaponTemplateSets;}

protected:

//
Expand Down
72 changes: 72 additions & 0 deletions Generals/Code/GameEngine/Include/Common/WellKnownKeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,78 @@ DEFINE_KEY(objectGrantUpgrade)
*/
DEFINE_KEY(uniqueID)

/**
Which: MapObject Properties
Type: AsciiString
Usage: What ambient sound does this object have attached to it?
Missing means "Use the default sound for object type from INI"
Blank means "No ambient sound"
*/
DEFINE_KEY(objectSoundAmbient)

/**
Which: MapObject Properties
Type: Bool
Usage: Does the ambient sound have customized flags & properties? Blank or false - use INI parameters for sound
*/
DEFINE_KEY(objectSoundAmbientCustomized)

/**
Which: MapObject Properties
Type: Bool
Usage: Does the ambient sound start off playing?
Blank -- use default of true for looping sounds, false for non-looping sounds
*/
DEFINE_KEY(objectSoundAmbientEnabled)

/**
Which: MapObject Properties
Type: Bool
Usage: Does the ambient sound loop? Blank -- use default for sound
*/
DEFINE_KEY(objectSoundAmbientLooping)

/**
Which: MapObject Properties
Type: Int
Usage: How many times does the sound loop (0 = forever)? Blank - use default for sound
*/
DEFINE_KEY(objectSoundAmbientLoopCount)

/**
Which: MapObject Properties
Type: Real
Usage: Minimum volume of sound. Blank - use default for sound
*/
DEFINE_KEY(objectSoundAmbientMinVolume)

/**
Which: MapObject Properties
Type: Real
Usage: Base volume of sound. Blank - use default for sound
*/
DEFINE_KEY(objectSoundAmbientVolume)

/**
Which: MapObject Properties
Type: Real
Usage: Minimum range of sound. Within this area, sound plays at full volume. Blank - use default for sound
*/
DEFINE_KEY(objectSoundAmbientMinRange)

/**
Which: MapObject Properties
Type: Real
Usage: Maximum range of sound. Sound drops to zero at this range Blank - use default for sound
*/
DEFINE_KEY(objectSoundAmbientMaxRange)

/**
Which: MapObject Properties
Type: Int
Usage: Priority of sound using the enum AudioPriority Blank - use default for sound
*/
DEFINE_KEY(objectSoundAmbientPriority)

// ---------------------------------------------------------------------------------------
// well-known keys in Player dicts.
Expand Down
12 changes: 12 additions & 0 deletions Generals/Code/GameEngine/Include/GameLogic/PolygonTrigger.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class PolygonTrigger : public MemoryPoolObject,
Bool m_exportWithScripts;
Bool m_isWaterArea; ///< Used to specify water areas in the map.
Bool m_isRiver; ///< Used to specify that a water area is a river.
AsciiString m_layerName; ///< Used to specify the layer in the World Builder.
Bool m_shouldRender;
Bool m_selected;

static PolygonTrigger* ThePolygonTriggerListPtr;
static Int s_currentID; ///< Current id for new triggers.
Expand Down Expand Up @@ -116,6 +119,15 @@ class PolygonTrigger : public MemoryPoolObject,
void deletePoint(Int ndx);
void setTriggerName(AsciiString name) {m_triggerName = name;};

void setLayerName(AsciiString name) {m_layerName = name;};
AsciiString getLayerName() const {return m_layerName;}

void setShouldRender(Bool toggle) {m_shouldRender = toggle;}
Bool getShouldRender() const {return m_shouldRender;}

void setSelected(Bool toggle) {m_selected = toggle;}
Bool getSelected() const {return m_selected;}

void getCenterPoint(Coord3D* pOutCoord) const;
Real getRadius() const;

Expand Down
19 changes: 18 additions & 1 deletion Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ m_numPoints(0),
m_sizePoints(0),
m_exportWithScripts(false),
m_isWaterArea(false),
m_shouldRender(true),
m_selected(false),
m_isRiver(FALSE),
m_riverStart(0)
{
Expand Down Expand Up @@ -140,6 +142,7 @@ Bool PolygonTrigger::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChu
Bool isRiver;
Int riverStart;
AsciiString triggerName;
AsciiString layerName;
// Remove any existing polygon triggers, if any.
PolygonTrigger::deleteTriggers(); // just in case.
PolygonTrigger *pPrevTrig = nullptr;
Expand All @@ -148,6 +151,9 @@ Bool PolygonTrigger::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChu
while (count>0) {
count--;
triggerName = file.readAsciiString();
if (info->version >= K_TRIGGERS_VERSION_4) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @stephanmeesters mentioned it already (but I don't see his comment appear here), this needs to be behind ZH guards or the Generals World Builder will no longer work properly.

#if RTS_ZEROHOUR
  if (info->version >= K_TRIGGERS_VERSION_4) {
    layerName = file.readAsciiString();
  }
#endif

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the reader version gated rather than Zero Hour-only. With the updated writer logic in #3024, Generals writes version 3 by default, but it can still write version 4 when RETAIL_COMPATIBLE_WORLDBUILDER is disabled. The Generals reader therefore still needs to understand version 4. Zero Hour continues writing and reading version 4.

layerName = file.readAsciiString();
}
triggerID = file.readInt();
isWater = false;
if (info->version >= K_TRIGGERS_VERSION_2) {
Expand All @@ -163,6 +169,9 @@ Bool PolygonTrigger::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChu
numPoints = file.readInt();
PolygonTrigger *pTrig = newInstance(PolygonTrigger)(numPoints+1);
pTrig->setTriggerName(triggerName);
if (info->version >= K_TRIGGERS_VERSION_4) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be behind RTS_ZEROHOUR guards

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same reasoning applies here. Generals can receive version 4 data when compatibility is disabled, so it needs to apply the serialized layer name. The version check ensures that version 1–3 files retain their existing layout and behavior.

pTrig->setLayerName(layerName);
}
pTrig->setWaterArea(isWater);
pTrig->setRiver(isRiver);
pTrig->setRiverStart(riverStart);
Expand Down Expand Up @@ -224,7 +233,12 @@ Bool PolygonTrigger::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChu
*/
void PolygonTrigger::WritePolygonTriggersDataChunk(DataChunkOutput &chunkWriter)
{
#if RETAIL_COMPATIBLE_WORLDBUILDER && RTS_GENERALS
// TheSuperHackers @info OmarAglan 30/07/2026 Retail Generals expects version 3 and does not read polygon trigger layer names.
chunkWriter.openDataChunk("PolygonTriggers", K_TRIGGERS_VERSION_3);
#else
chunkWriter.openDataChunk("PolygonTriggers", K_TRIGGERS_VERSION_4);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be behind RTS_ZEROHOUR guards

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in #3024. Both writer implementations now use RETAIL_COMPATIBLE_WORLDBUILDER && RTS_GENERALS: Generals writes version 3 by default, while Zero Hour and non-compatible Generals builds write version 4.

#endif

PolygonTrigger *pTrig;
Int count = 0;
Expand All @@ -234,6 +248,9 @@ void PolygonTrigger::WritePolygonTriggersDataChunk(DataChunkOutput &chunkWriter)
chunkWriter.writeInt(count);
for (pTrig=PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
chunkWriter.writeAsciiString(pTrig->getTriggerName());
#if !(RETAIL_COMPATIBLE_WORLDBUILDER && RTS_GENERALS)
chunkWriter.writeAsciiString(pTrig->getLayerName());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be behind RTS_ZEROHOUR guards

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated alongside the version selection. layerName is written only when the version 4 branch is selected, using !(RETAIL_COMPATIBLE_WORLDBUILDER && RTS_GENERALS) in both games.

#endif
chunkWriter.writeInt(pTrig->getID());
chunkWriter.writeByte(pTrig->isWaterArea());
chunkWriter.writeByte(pTrig->isRiver());
Expand Down Expand Up @@ -480,7 +497,7 @@ const WaterHandle* PolygonTrigger::getWaterHandle() const

Bool PolygonTrigger::isValid() const
{
if (m_numPoints == 0) {
if (m_numPoints < 2) {
return FALSE;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1640,7 +1640,8 @@ PolygonTrigger *TerrainLogic::getTriggerAreaByName( AsciiString name )
{
for (PolygonTrigger* pTrig = PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
const AsciiString& trigName = pTrig->getTriggerName();
if (name == trigName)
// TheSuperHackers @info OmarAglan 30/07/2026 Retain invalid triggers for snapshot compatibility, but do not expose them as usable areas.
if (name == trigName && pTrig->isValid())
return pTrig;
}
return nullptr;
Expand Down
7 changes: 7 additions & 0 deletions Generals/Code/Tools/WorldBuilder/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ set(WORLDBUILDER_SRC
"src/RampTool.cpp"
"src/RoadOptions.cpp"
"src/RoadTool.cpp"
"src/RulerOptions.cpp"
"src/RulerTool.cpp"
"src/SaveMap.cpp"
"src/ScorchOptions.cpp"
"src/ScorchTool.cpp"
Expand All @@ -75,6 +77,7 @@ set(WORLDBUILDER_SRC
"src/TeamBehavior.cpp"
"src/TeamGeneric.cpp"
"src/TeamIdentity.cpp"
"src/TeamObjectProperties.cpp"
"src/TeamReinforcement.cpp"
"src/teamsdialog.cpp"
"src/TerrainMaterial.cpp"
Expand Down Expand Up @@ -156,6 +159,8 @@ set(WORLDBUILDER_SRC
"include/RampTool.h"
"include/RoadOptions.h"
"include/RoadTool.h"
"include/RulerOptions.h"
"include/RulerTool.h"
"include/SaveMap.h"
"include/ScorchOptions.h"
"include/ScorchTool.h"
Expand All @@ -171,6 +176,7 @@ set(WORLDBUILDER_SRC
"include/TeamBehavior.h"
"include/TeamGeneric.h"
"include/TeamIdentity.h"
"include/TeamObjectProperties.h"
"include/TeamReinforcement.h"
"include/teamsdialog.h"
"include/TerrainMaterial.h"
Expand All @@ -184,6 +190,7 @@ set(WORLDBUILDER_SRC
"include/WaypointTool.h"
"include/WBFrameWnd.h"
"include/WBHeightMap.h"
"include/WBPopupSlider.h"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I missing something or is this file not actually being added in this PR?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WBPopupSlider.h already exists in both WorldBuilder trees on main, This PR only adds the existing header to the CMake source list, so there is no new file in this diff.

"include/wbview.h"
"include/wbview3d.h"
"include/WHeightMapEdit.h"
Expand Down
24 changes: 24 additions & 0 deletions Generals/Code/Tools/WorldBuilder/include/CUndoable.h
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,27 @@ class DeletePolygonUndoable : public Undoable
virtual void Do() override;
virtual void Undo() override;
};

/// MultipleUndoable
/**
* An undoable that doesn't do anything; it just consolidates a number of other
* Undoables in a single logical undo step.
*/
class MultipleUndoable : public Undoable
{
protected:
Undoable * m_undoableList; //< The head of the list of undoables, in the order they should be done. Reverse order for undoes
public:
MultipleUndoable();
// destructor.
virtual ~MultipleUndoable() override;

/** Add other undoables in the order you would want them UNdone; e.g. in the reverse order you want them done
* The MultipleUndoable object will then own the pointers.
*/
void addUndoable( Undoable * undoable );

virtual void Do() override;
virtual void Undo() override;
virtual void Redo() override;
};
2 changes: 2 additions & 0 deletions Generals/Code/Tools/WorldBuilder/include/CameraOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class CameraOptions : public CDialog, public PopupSliderOwner
// Generated message map functions
//{{AFX_MSG(CameraOptions)
afx_msg void OnCameraReset();
afx_msg void OnDropWaypointButton();
afx_msg void OnCenterOnSelectedButton();
afx_msg void OnMove(int x, int y);
virtual BOOL OnInitDialog() override;
afx_msg void OnChangePitchEdit();
Expand Down
22 changes: 20 additions & 2 deletions Generals/Code/Tools/WorldBuilder/include/DrawObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
class MeshClass;
class PolygonTrigger;
class WaterRenderObjClass;
class MapObject;
class Render2DClass;
//
// DrawObject: Draws 3d feedback for tools & objects.
//
Expand Down Expand Up @@ -77,12 +79,14 @@ class DrawObject : public RenderObjClass
// void Set_Flag(unsigned int flag, Bool onoff) { Flags &= (~flag); if (onoff) Flags |= flag; }

Int freeMapResources();
int initData();

void setDrawObjects(Bool val, Bool waypoints, Bool poly) { m_drawObjects = val; m_drawWaypoints=waypoints; m_drawPolygonAreas = poly;}
void setDrawObjects(Bool val, Bool waypoints, Bool poly, Bool bounding, Bool sight, Bool weapon, Bool sound, Bool testart, Bool letterbox) { m_drawObjects = val; m_drawWaypoints=waypoints; m_drawPolygonAreas = poly; m_drawBoundingBoxes = bounding; m_drawSightRanges = sight; m_drawWeaponRanges = weapon; m_drawSoundRanges = sound; m_drawTestArtHighlight = testart, m_drawLetterbox = letterbox;}
static void setDoBrushFeedback(Bool val) { m_toolWantsFeedback = val; m_meshFeedback=false;}
static void setDoMeshFeedback(Bool val) { m_meshFeedback = val; }
static void setDoRampFeedback(Bool val) { m_rampFeedback = val; }
static void setDoBoundaryFeedback(Bool val) { m_boundaryFeedback = val; }

static void setDoAmbientSoundFeedback(Bool val) { m_ambientSoundFeedback = val; }

static void setBrushFeedbackParms(Bool square, Int width, Int featherWidth)
Expand Down Expand Up @@ -122,6 +126,12 @@ class DrawObject : public RenderObjClass
Bool m_drawObjects;
Bool m_drawWaypoints;
Bool m_drawPolygonAreas;
Bool m_drawBoundingBoxes;
Bool m_drawSightRanges;
Bool m_drawWeaponRanges;
Bool m_drawSoundRanges;
Bool m_drawTestArtHighlight;
Bool m_drawLetterbox;

DX8VertexBufferClass *m_vertexFeedback; ///< Vertex buffer for brush feedback.
DX8IndexBufferClass *m_indexFeedback; ///< indices defining a triangle strip for the feedback on terrain
Expand All @@ -132,6 +142,8 @@ class DrawObject : public RenderObjClass

MeshClass *m_moldMesh; ///< W3D mesh model for the mold.
SphereClass m_moldMeshBounds; ///< Bounding sphere for mold mesh.
Render2DClass *m_lineRenderer; //< Used to render 2D lines for bounding boxes.
CPoint m_winSize; //< Holds the size of the window.

protected: // static state vars.
static Bool m_squareFeedback; ///< True for square brush feedback, false for round.
Expand All @@ -155,7 +167,7 @@ class DrawObject : public RenderObjClass
static Real m_rampWidth;

protected:
int initData();
void addCircleToLineRenderer( const Coord3D & center, Real radius, Real width, unsigned long color, CameraClass* camera );
Int updateVB(DX8VertexBufferClass *vertexBufferTile, Int color, Bool doArrow, Bool doDiamond);
void updatePolygonVB(PolygonTrigger *pTrig, Bool selected, Bool isOpen);
void updateFeedbackVB();
Expand All @@ -165,6 +177,12 @@ class DrawObject : public RenderObjClass
void updateForWater();
void updateBoundaryVB();
void updateAmbientSoundVB();
void updateVBWithBoundingBox(MapObject *pMapObj, CameraClass* camera);
void updateVBWithSightRange(MapObject *pMapObj, CameraClass* camera);
void updateVBWithWeaponRange(MapObject *pMapObj, CameraClass* camera);
void updateVBWithTestArtHighlight(MapObject *pMapObj, CameraClass* camera);
void updateVBWithSoundRanges(MapObject *pMapObj, CameraClass* camera);
bool worldToScreen(const Coord3D *w, ICoord2D *s, CameraClass* camera);

};

Expand Down
1 change: 1 addition & 0 deletions Generals/Code/Tools/WorldBuilder/include/EditAction.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class EditAction : public CDialog
CRichEditCtrl m_myEditCtrl;
CHARRANGE m_curLinkChrg;
Int m_curEditParameter;
CTreeCtrl m_actionTreeView;

protected:

Expand Down
Loading