diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index 7a6f4be4d11..165287ba38d 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -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) diff --git a/Generals/Code/GameEngine/Include/Common/MapReaderWriterInfo.h b/Generals/Code/GameEngine/Include/Common/MapReaderWriterInfo.h index c370ac5b3c0..6496fb891bb 100644 --- a/Generals/Code/GameEngine/Include/Common/MapReaderWriterInfo.h +++ b/Generals/Code/GameEngine/Include/Common/MapReaderWriterInfo.h @@ -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. diff --git a/Generals/Code/GameEngine/Include/Common/ThingTemplate.h b/Generals/Code/GameEngine/Include/Common/ThingTemplate.h index a2a5847e6d2..4a27a19838f 100644 --- a/Generals/Code/GameEngine/Include/Common/ThingTemplate.h +++ b/Generals/Code/GameEngine/Include/Common/ThingTemplate.h @@ -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. @@ -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; @@ -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: // diff --git a/Generals/Code/GameEngine/Include/Common/WellKnownKeys.h b/Generals/Code/GameEngine/Include/Common/WellKnownKeys.h index 67cacd62e22..ff1277b478b 100644 --- a/Generals/Code/GameEngine/Include/Common/WellKnownKeys.h +++ b/Generals/Code/GameEngine/Include/Common/WellKnownKeys.h @@ -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. diff --git a/Generals/Code/GameEngine/Include/GameLogic/PolygonTrigger.h b/Generals/Code/GameEngine/Include/GameLogic/PolygonTrigger.h index d3e133834fc..c2abfe51688 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/PolygonTrigger.h +++ b/Generals/Code/GameEngine/Include/GameLogic/PolygonTrigger.h @@ -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. @@ -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; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp index 6cea7c6227a..e666eab1825 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp @@ -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) { @@ -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; @@ -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) { @@ -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); @@ -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; @@ -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()); +#endif chunkWriter.writeInt(pTrig->getID()); chunkWriter.writeByte(pTrig->isWaterArea()); chunkWriter.writeByte(pTrig->isRiver()); @@ -480,7 +497,7 @@ const WaterHandle* PolygonTrigger::getWaterHandle() const Bool PolygonTrigger::isValid() const { - if (m_numPoints == 0) { + if (m_numPoints < 2) { return FALSE; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 00386047267..9f09848ba60 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -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; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/PolygonTrigger.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/PolygonTrigger.h index d618de16a06..680766111db 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/PolygonTrigger.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/PolygonTrigger.h @@ -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; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp index 4f19ee9cfc2..8f74d44f7eb 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp @@ -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); } else { @@ -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; @@ -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()); @@ -496,7 +497,7 @@ const WaterHandle* PolygonTrigger::getWaterHandle() const Bool PolygonTrigger::isValid() const { - if (m_numPoints == 0) { + if (m_numPoints < 2) { return FALSE; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 5a3d16f86ae..9e9fd789ac3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -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;