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;}
Comment thread
OmarAglan marked this conversation as resolved.

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) {
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) {
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);
#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());
Comment thread
OmarAglan marked this conversation as resolved.
#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
6 changes: 3 additions & 3 deletions GeneralsMD/Code/GameEngine/Include/GameLogic/PolygonTrigger.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,13 @@ class PolygonTrigger : public MemoryPoolObject,
void setTriggerName(AsciiString name) {m_triggerName = name;};

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

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

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

void getCenterPoint(Coord3D* pOutCoord) const;
Real getRadius() const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,6 @@ Bool PolygonTrigger::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChu
loc.z = file.readInt();
pTrig->addPoint(loc);
}
if (numPoints<2) {
DEBUG_LOG(("Deleting polygon trigger '%s' with %d points.",
pTrig->getTriggerName().str(), numPoints));
deleteInstance(pTrig);
continue;
}
if (pPrevTrig) {
pPrevTrig->setNextPoly(pTrig);
Comment thread
greptile-apps[bot] marked this conversation as resolved.
} else {
Expand Down Expand Up @@ -239,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);
#endif

PolygonTrigger *pTrig;
Int count = 0;
Expand All @@ -249,7 +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());
#endif
chunkWriter.writeInt(pTrig->getID());
chunkWriter.writeByte(pTrig->isWaterArea());
chunkWriter.writeByte(pTrig->isRiver());
Expand Down Expand Up @@ -496,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
Loading