From 7a71cf882b17dc70c45c8305cb2f8b2552ac2dfa Mon Sep 17 00:00:00 2001 From: pWn3d Date: Sat, 3 Jan 2026 14:22:36 +0100 Subject: [PATCH 1/4] Ship stuff --- .../W3DDevice/GameClient/BaseHeightMap.h | 3 +- .../W3DDevice/GameClient/WorldHeightMap.h | 12 +++--- .../W3DDevice/GameClient/BaseHeightMap.cpp | 6 +-- .../Source/W3DDevice/GameClient/HeightMap.cpp | 2 +- .../W3DDevice/GameClient/W3DTerrainVisual.cpp | 4 +- .../W3DDevice/GameClient/WorldHeightMap.cpp | 22 ++++++++-- .../Tools/WorldBuilder/src/FeatherTool.cpp | 8 ++-- GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../Code/GameEngine/Include/Common/INI.h | 3 +- .../Code/GameEngine/Include/Common/MapData.h | 37 ++++++++++++++++ .../GameEngine/Include/Common/ThingTemplate.h | 4 +- .../GameEngine/Include/GameLogic/Locomotor.h | 2 + .../GameEngine/Source/Common/GameEngine.cpp | 2 + .../Code/GameEngine/Source/Common/INI/INI.cpp | 6 ++- .../Source/Common/INI/INIMapData.cpp | 2 + .../Code/GameEngine/Source/Common/MapData.cpp | 43 +++++++++++++++++++ .../Source/Common/System/BuildAssistant.cpp | 3 +- .../Source/Common/Thing/ThingTemplate.cpp | 2 + .../GameClient/GUI/ControlBar/ControlBar.cpp | 14 +++++- .../GameEngine/Source/GameClient/InGameUI.cpp | 14 ++++++ .../Source/GameLogic/AI/AIPathfind.cpp | 7 +-- .../Source/GameLogic/Object/Locomotor.cpp | 43 +++++++++++++++++++ .../GameLogic/Object/Update/AIUpdate.cpp | 4 -- .../Source/GameLogic/System/GameLogic.cpp | 2 + .../GameLogic/System/GameLogicDispatch.cpp | 9 +++- .../Tools/WorldBuilder/src/FeatherTool.cpp | 8 ++-- .../Tools/WorldBuilder/src/WHeightMapEdit.cpp | 17 ++++++-- .../Tools/WorldBuilder/src/WorldBuilder.cpp | 2 + .../WorldBuilder/src/WorldBuilderDoc.cpp | 14 +++++- 29 files changed, 252 insertions(+), 45 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/Common/MapData.h create mode 100644 GeneralsMD/Code/GameEngine/Source/Common/MapData.cpp diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h index 9d3469a36b1..48d3be58b53 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/BaseHeightMap.h @@ -37,7 +37,6 @@ #include "W3DDevice/GameClient/WorldHeightMap.h" #define MAX_ENABLED_DYNAMIC_LIGHTS 20 -typedef UnsignedByte HeightSampleType; //type of data to store in heightmap class W3DTreeBuffer; class W3DBibBuffer; class W3DRoadBuffer; @@ -133,7 +132,7 @@ class BaseHeightMapRenderObjClass : public RenderObjClass, public DX8_CleanupHoo } - UnsignedByte getClipHeight(Int x, Int y) const + HeightSampleType getClipHeight(Int x, Int y) const { Int xextent = m_map->getXExtent() - 1; Int yextent = m_map->getYExtent() - 1; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/WorldHeightMap.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/WorldHeightMap.h index 932edf2a7a5..bba3bac0d1c 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/WorldHeightMap.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/WorldHeightMap.h @@ -37,7 +37,7 @@ #include "Common/STLTypedefs.h" typedef std::vector VecICoord2D; - +typedef UnsignedShort HeightSampleType; //type of data to store in heightmap, changed to short (2bytes) to allow more height difference /** MapObject class Not ref counted. Do not store pointers to this class. */ @@ -117,7 +117,7 @@ class WorldHeightMap : public RefCountClass, Int m_borderSize; ///< Non-playable border area. VecICoord2D m_boundaries; ///< the in-game boundaries Int m_dataSize; ///< size of m_data. - UnsignedByte *m_data; ///< array of z(height) values in the height map. + HeightSampleType *m_data; ///< array of z(height) values in the height map. UnsignedByte *m_seismicUpdateFlag; ///< array of bits to prevent ovelapping physics-update regions from doubling effects on shared cells UnsignedInt m_seismicUpdateWidth; ///< width of the array holding SeismicUpdateFlags @@ -223,7 +223,7 @@ class WorldHeightMap : public RefCountClass, static Int getMinHeightValue(void) {return K_MIN_HEIGHT;} static Int getMaxHeightValue(void) {return K_MAX_HEIGHT;} - UnsignedByte *getDataPtr(void) {return m_data;} + HeightSampleType *getDataPtr(void) {return m_data;} Int getXExtent(void) {return m_width;} ///=0) && (ndx=0) && (ndxgetDataPtr(); + const HeightSampleType* data = logicHeightMap->getDataPtr(); int idx = ix + iy*xExtent; float p0 = data[idx]; float p2 = data[idx + xExtent + 1]; @@ -1074,7 +1074,7 @@ Bool BaseHeightMapRenderObjClass::isClearLineOfSight(const Coord3D& pos, const C Real zinc = dz * nsInv; Bool result = true; - const UnsignedByte* data = logicHeightMap->getDataPtr(); + const HeightSampleType* data = logicHeightMap->getDataPtr(); Int xExtent = logicHeightMap->getXExtent(); Int yExtent = logicHeightMap->getYExtent(); for (Int curpixel = 0; curpixel < numpixels; curpixel++) @@ -1221,7 +1221,7 @@ Real BaseHeightMapRenderObjClass::getMaxCellHeight(Real x, Real y) const if (iY >= (logicHeightMap->getYExtent()-1)) { iY = logicHeightMap->getYExtent()-2; } - UnsignedByte *data = logicHeightMap->getDataPtr(); + HeightSampleType *data = logicHeightMap->getDataPtr(); p0=data[iX+iY*logicHeightMap->getXExtent()]*MAP_HEIGHT_SCALE; p1=data[(iX+offset)+iY*logicHeightMap->getXExtent()]*MAP_HEIGHT_SCALE; p2=data[(iX+offset)+(iY+offset)*logicHeightMap->getXExtent()]*MAP_HEIGHT_SCALE; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp index 55f228a81d2..62cb4accf1c 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp @@ -2264,7 +2264,7 @@ void HeightMapRenderObjClass::renderExtraBlendTiles(void) if (!vb || !ib) return; - const UnsignedByte* data = m_map->getDataPtr(); + const HeightSampleType* data = m_map->getDataPtr(); //Loop over visible terrain and extract all the tiles that need extra blend Int drawEdgeY=m_map->getDrawOrgY()+m_map->getDrawHeight()-1; diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp index 77935049c58..770315f0f40 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp @@ -1210,8 +1210,8 @@ void W3DTerrainVisual::xfer( Xfer *xfer ) // Write out the terrain height data. if (version >= 2) { - UnsignedByte *data = m_logicHeightMap->getDataPtr(); - Int len = m_logicHeightMap->getXExtent()*m_logicHeightMap->getYExtent(); + HeightSampleType *data = m_logicHeightMap->getDataPtr(); + Int len = m_logicHeightMap->getXExtent()*m_logicHeightMap->getYExtent() * sizeof(HeightSampleType); Int xferLen = len; xfer->xferInt(&xferLen); if (len!=xferLen) { diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp index 52384dfed58..225c8da6126 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp @@ -41,6 +41,7 @@ #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" #include "Common/WellKnownKeys.h" +#include "Common/MapData.h" #include "GameLogic/PolygonTrigger.h" #include "GameLogic/SidesList.h" @@ -878,7 +879,7 @@ Bool WorldHeightMap::ParseHeightMapData(DataChunkInput &file, DataChunkInfo *inf } m_dataSize = file.readInt(); - m_data = MSGNEW("WorldHeightMap_ParseHeightMapData") UnsignedByte[m_dataSize]; + m_data = MSGNEW("WorldHeightMap_ParseHeightMapData") HeightSampleType[m_dataSize]; if (m_dataSize <= 0 || (m_dataSize != (m_width*m_height))) { throw ERROR_CORRUPT_FILE_FORMAT ; } @@ -891,8 +892,14 @@ Bool WorldHeightMap::ParseHeightMapData(DataChunkInput &file, DataChunkInfo *inf m_seismicZVelocities = MSGNEW("WorldHeightMap_ParseHeightMapData _ zvelocities allocated") Real[m_dataSize]; fillSeismicZVelocities( 0 ); + //load as bytes first + std::vector loaded_data(m_dataSize); + + file.readArrayOfBytes((char *)&loaded_data.at(0), m_dataSize); + for (size_t i = 0; i < loaded_data.size(); i++) { + m_data[i] = static_cast(std::round(loaded_data[i]* TheMapData->m_HeightmapScale)); + } - file.readArrayOfBytes((char *)m_data, m_dataSize); // Resize me. if (info->version == K_HEIGHT_MAP_VERSION_1) { Int newWidth = (m_width+1)/2; @@ -952,11 +959,18 @@ Bool WorldHeightMap::ParseSizeOnly(DataChunkInput &file, DataChunkInfo *info, vo } m_dataSize = file.readInt(); - m_data = MSGNEW("WorldHeightMap_ParseSizeOnly") UnsignedByte[m_dataSize]; + m_data = MSGNEW("WorldHeightMap_ParseSizeOnly") HeightSampleType[m_dataSize]; if (m_dataSize <= 0 || (m_dataSize != (m_width*m_height))) { throw ERROR_CORRUPT_FILE_FORMAT ; } - file.readArrayOfBytes((char *)m_data, m_dataSize); + + //load as bytes first + std::vector loaded_data(m_dataSize); + file.readArrayOfBytes((char*)&loaded_data.at(0), m_dataSize); + for (size_t i = 0; i < loaded_data.size(); i++) { + m_data[i] = static_cast(std::round(loaded_data[i] * TheMapData->m_HeightmapScale)); + } + // Resize me. if (info->version == K_HEIGHT_MAP_VERSION_1) { Int newWidth = (m_width+1)/2; diff --git a/Generals/Code/Tools/WorldBuilder/src/FeatherTool.cpp b/Generals/Code/Tools/WorldBuilder/src/FeatherTool.cpp index 117004025ff..3b73e70f48b 100644 --- a/Generals/Code/Tools/WorldBuilder/src/FeatherTool.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/FeatherTool.cpp @@ -110,7 +110,7 @@ void FeatherTool::mouseDown(TTrackingMode m, CPoint viewPt, WbView* pView, CWorl m_htMapFeatherCopy = pDoc->GetHeightMap()->duplicate(); m_htMapRateCopy = pDoc->GetHeightMap()->duplicate(); Int size = m_htMapRateCopy->getXExtent() * m_htMapRateCopy->getYExtent(); - UnsignedByte *pData = m_htMapRateCopy->getDataPtr(); + HeightSampleType *pData = m_htMapRateCopy->getDataPtr(); Int i; for (i=0; iupdateHeightMap(m_htMapEditCopy, true, partialRange); if (redoRate) { Int size = m_htMapRateCopy->getXExtent() * m_htMapRateCopy->getYExtent(); - UnsignedByte *pData = m_htMapRateCopy->getDataPtr(); - UnsignedByte *pFeather = m_htMapFeatherCopy->getDataPtr(); - UnsignedByte *pEdit = m_htMapEditCopy->getDataPtr(); + HeightSampleType *pData = m_htMapRateCopy->getDataPtr(); + HeightSampleType *pFeather = m_htMapFeatherCopy->getDataPtr(); + HeightSampleType *pEdit = m_htMapEditCopy->getDataPtr(); Int i; for (i=0; i(m_maxPathfindingCellRadius); }; + protected: // @@ -823,7 +825,7 @@ class ThingTemplate : public Overridable UnsignedByte m_crusherLevel; ///< crusher > crushable level to actually crush UnsignedByte m_crushableLevel; ///< Specifies the level of crushability (must be hit by a crusher greater than this to crush me). Byte m_ammoPipsStyle; ///< How ammo pips are displayed for this thing - + UnsignedByte m_maxPathfindingCellRadius; ///< Limit cells radius for pathfinding, defaults to 2, can be increased for large units }; //----------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index 0fb97be20a6..4bb6ca882cf 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -101,6 +101,7 @@ enum LocomotorBehaviorZ CPP_11(: Int) Z_FIXED_ABSOLUTE_HEIGHT, // stays fixed at absolute height, regardless of physics Z_RELATIVE_TO_GROUND_AND_BUILDINGS, // stays fixed at surface-rel height including buildings, regardless of physics Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER, // try to follow a height relative to the highest layer. + Z_SEA_SURFACE_RELATIVE_HEIGHT, //try to follow a specific height relative to terrain / water height, optimized for submarines LOCOMOTOR_BEHAVIOR_Z_COUNT }; @@ -116,6 +117,7 @@ static const char *const TheLocomotorBehaviorZNames[] = "FIXED_ABSOLUTE_HEIGHT", "FIXED_RELATIVE_TO_GROUND_AND_BUILDINGS", "RELATIVE_TO_HIGHEST_LAYER", + "SEA_SURFACE_RELATIVE_HEIGHT", NULL }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 3ce34e9ecb2..fc1516a7147 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -71,6 +71,7 @@ #include "Common/GameLOD.h" #include "Common/Registry.h" #include "Common/GameCommon.h" // FOR THE ALLOW_DEBUG_CHEATS_IN_RELEASE #define +#include "Common/MapData.h" #include "GameLogic/Armor.h" #include "GameLogic/AI.h" @@ -451,6 +452,7 @@ void GameEngine::init() initSubsystem(TheWritableGlobalData, "TheWritableGlobalData", TheWritableGlobalData, &xferCRC, "Data\\INI\\Default\\GameData", "Data\\INI\\GameData"); TheWritableGlobalData->parseCustomDefinition(); + initSubsystem(TheWriteableMapData, "TheWriteableMapData", MapData::createMapDataSystem(), &xferCRC); #ifdef DUMP_PERF_STATS/////////////////////////////////////////////////////////////////////////// GetPrecisionTimer(&endTime64);////////////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index 96bd2eb0846..1e144647c03 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -420,7 +420,11 @@ UnsignedInt INI::load( AsciiString filename, INILoadType loadType, Xfer *pXfer, // the first word is the type of data we're processing const char *token = strtok( m_buffer, m_seps ); - if( token ) + + // skip non MapData blocks if loading + bool skip = (loadType == INI_LOAD_MAPDATA_ONLY) && (strcmp(token, "MapData") != 0); + + if( token && !skip) { INIBlockParse parse = findBlockParse(token); if (parse) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMapData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMapData.cpp index 16632e27920..46b208e700e 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMapData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMapData.cpp @@ -31,6 +31,7 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "Common/INI.h" +#include "Common/MapData.h" /////////////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// @@ -45,6 +46,7 @@ //------------------------------------------------------------------------------------------------- void INI::parseMapDataDefinition( INI* ini ) { + MapData::parseMapDataDefinition(ini); } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/MapData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/MapData.cpp new file mode 100644 index 00000000000..01114cd4643 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/Common/MapData.cpp @@ -0,0 +1,43 @@ +////////////////////////////////////////// +// FILE: MapData.cpp +// Store some extra metadata about the current map +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#include "Common/MapData.h" + +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +MapData* TheWriteableMapData = NULL; ///< The current map data singleton + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/*static*/ const FieldParse MapData::s_MapDataFieldParseTable[] = +{ + { "HeightMapScale", INI::parseReal, NULL, offsetof( MapData, m_HeightmapScale) }, + { "EnableShips", INI::parseBool, NULL, offsetof( MapData, m_enableShips) }, + { NULL, NULL, NULL, 0 } // keep this last + +}; + +MapData::MapData() : SubsystemInterface() +{ + m_HeightmapScale = 1.0f; + m_enableShips = false; +} + +void MapData::init() { + m_HeightmapScale = 1.0f; + m_enableShips = false; +} + +void MapData::reset() { + m_HeightmapScale = 1.0f; + m_enableShips = false; +} + +void MapData::parseMapDataDefinition(INI* ini) { + ini->initFromINI(TheWriteableMapData, s_MapDataFieldParseTable); +} diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp index 635c0811fa2..b26dca14b1e 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp @@ -1195,8 +1195,9 @@ LegalBuildCode BuildAssistant::isLocationLegalToBuild( const Coord3D *worldPos, Short totalSamples = sampleData.waterSamples + sampleData.landSamples; Real threshold_water = totalSamples * 0.6f; Real threshold_land = 1.0f; + Real max_land = totalSamples * 0.2f; - if (static_cast(sampleData.waterSamples) < threshold_water || static_cast(sampleData.landSamples) < threshold_land) { + if (static_cast(sampleData.waterSamples) < threshold_water || static_cast(sampleData.landSamples) < threshold_land || static_cast(sampleData.landSamples) > max_land) { return LBC_RESTRICTED_TERRAIN; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ThingTemplate.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ThingTemplate.cpp index 355c00723f0..5b8f72d8e13 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ThingTemplate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ThingTemplate.cpp @@ -250,6 +250,7 @@ const FieldParse ThingTemplate::s_objectFieldParseTable[] = { "CrusherLevel", INI::parseUnsignedByte, NULL, offsetof( ThingTemplate, m_crusherLevel ) }, { "CrushableLevel", INI::parseUnsignedByte, NULL, offsetof( ThingTemplate, m_crushableLevel ) }, { "AmmoPipsStyle", INI::parseByteSizedIndexList, AmmoPipsStyleNames, offsetof(ThingTemplate, m_ammoPipsStyle) }, + { "MaxPathfindingCellRadius", INI::parseUnsignedByte, NULL, offsetof(ThingTemplate, m_maxPathfindingCellRadius) }, { 0, 0, 0, 0 } }; @@ -1048,6 +1049,7 @@ ThingTemplate::ThingTemplate() : m_crushableLevel = 255; //Unspecified, this object is unable to be crushed by anything! m_ammoPipsStyle = AMMO_PIPS_DEFAULT; + m_maxPathfindingCellRadius = 2U; } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index e097cba0d25..a65717ba145 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp @@ -59,6 +59,7 @@ #include "GameLogic/Module/StealthUpdate.h" #include "GameLogic/Module/RebuildHoleBehavior.h" #include "GameLogic/ScriptEngine.h" +#include "GameLogic/TerrainLogic.h" #include "GameClient/AnimateWindowManager.h" #include "GameClient/ControlBar.h" @@ -2764,8 +2765,17 @@ void ControlBar::showRallyPoint(const Coord3D* loc) // sanity DEBUG_ASSERTCRASH(marker, ("showRallyPoint: No rally point marker found")); - // set the position of the rally point drawble to the position passed in - marker->setPosition(loc); + // Adapt position to water height if under water + Real waterZ{ 0 }; + if (TheTerrainLogic->isUnderwater(loc->x, loc->y, &waterZ)) { + Coord3D waterLoc = *loc; + waterLoc.z = waterZ; + marker->setPosition(&waterLoc); + } + else { + // set the position of the rally point drawble to the position passed in + marker->setPosition(loc); + } marker->setOrientation(TheGlobalData->m_downwindAngle); // To blow down wind -- ML // set the marker colors to that of the local player diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index ee6b325d4b9..bcd799a13bd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -1581,6 +1581,11 @@ void InGameUI::handleBuildPlacements( void ) Coord3D worldPos; TheTacticalView->screenToTerrain(&loc, &worldPos); + Real terrainZ{ 0 }; + Real waterZ{ 0 }; + TheTerrainLogic->isUnderwater(worldPos.x, worldPos.y, &waterZ, &terrainZ); + worldPos.z = std::max(terrainZ, waterZ); + Real check_radius = 0.0f; if (geom.getGeomType() == GEOMETRY_BOX) { @@ -1626,6 +1631,15 @@ void InGameUI::handleBuildPlacements( void ) /**@todo this whole orientation vector thing is LAME! Must replace, all I want to to do is set a simple angle and have it automatically change, ug! */ TheTacticalView->screenToTerrain( &loc, &world ); + + // If shipyard move up building to at least waterheight if lower + if (m_pendingPlaceType->isKindOf(KINDOF_SHIPYARD)) { + Real terrainZ{ 0 }; + Real waterZ{ 0 }; + TheTerrainLogic->isUnderwater(world.x, world.y, &waterZ, &terrainZ); + world.z = std::max(terrainZ, waterZ); + } + m_placeIcon[ 0 ]->setPosition( &world ); m_placeIcon[ 0 ]->setOrientation( angle ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 6040e59d77c..e50c5cf5fc2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -9650,13 +9650,14 @@ void Pathfinder::changeBridgeState( PathfindLayerEnum layer, Bool repaired) void Pathfinder::getRadiusAndCenter(const Object *obj, Int &iRadius, Bool ¢er) { - enum {MAX_RADIUS = 2}; if (!obj) { center = true; iRadius = 0; return; } + const Int max_radius = obj->getTemplate() != nullptr ? obj->getTemplate()->getMaxPathFindingCellRadius() : 2; + Real diameter = 2*obj->getGeometryInfo().getBoundingCircleRadius(); if (diameter>PATHFIND_CELL_SIZE_F && diameter<2.0f*PATHFIND_CELL_SIZE_F) { diameter = 2.0f*PATHFIND_CELL_SIZE_F; @@ -9669,9 +9670,9 @@ void Pathfinder::getRadiusAndCenter(const Object *obj, Int &iRadius, Bool ¢e center = true; } iRadius /= 2; - if (iRadius > MAX_RADIUS) + if (iRadius > max_radius) { - iRadius = MAX_RADIUS; + iRadius = max_radius; center = true; } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index 4d9f5cefcaf..34b22f012ca 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -1107,6 +1107,8 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); break; case LOCO_SHIP: + moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; case LOCO_HOVER: moveTowardsPositionHover(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); break; @@ -2377,6 +2379,47 @@ Bool Locomotor::handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coo } } break; + + case Z_SEA_SURFACE_RELATIVE_HEIGHT: + requiresConstantCalling = TRUE; + { + if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) + { + Coord3D pos = *obj->getPosition(); + + Real waterZ; + Real terrainZ; + Real preferredHeight; + if (TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ, &terrainZ)) { + // for submarines with negative preferred height, if water is too shallow always stay above terrain height + preferredHeight = std::max(waterZ + m_preferredHeight, terrainZ); + } + else { + preferredHeight = TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()) + m_preferredHeight; + } + + if (getFlag(PRECISE_Z_POS)) + preferredHeight = goalPos.z; + + Real delta = preferredHeight - pos.z; + delta *= getPreferredHeightDamping(); + preferredHeight = pos.z + delta; + + // surfaceHt is an unused parameter + Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, 0.0f, preferredHeight); + + //DEBUG_LOG(("HandleBZ %d LiftToUse %f",TheGameLogic->getFrame(),liftToUse)); + if (liftToUse != 0.0f) + { + Coord3D force; + force.x = 0.0f; + force.y = 0.0f; + force.z = liftToUse * physics->getMass(); + physics->applyMotiveForce(&force); + } + } + } + break; } return requiresConstantCalling; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index 70112eb1f26..e10d5e84631 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -2488,10 +2488,6 @@ Bool AIUpdateInterface::isAircraftThatAdjustsDestination(void) const { return FALSE; // thrust doesn't adjust. } - if (m_curLocomotor->getAppearance() == LOCO_SHIP) - { - return TRUE; // behave like hover - } return FALSE; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index a6001e2206b..6d2f72021cc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -62,6 +62,7 @@ #include "Common/XferCRC.h" #include "Common/XferDeepCRC.h" #include "Common/GameSpyMiscPreferences.h" +#include "Common/MapData.h" #include "GameClient/ControlBar.h" #include "GameClient/Drawable.h" @@ -2451,6 +2452,7 @@ static void findAndSelectCommandCenter(Object *obj, void* alreadyFound) // ------------------------------------------------------------------------------------------------ void GameLogic::loadMapINI( AsciiString mapName ) { + TheWriteableMapData->reset(); if (!TheMapCache) { // Need the map cache to get the map and user map directories. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 699c9bba7a3..317c64895c1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -147,7 +147,14 @@ static void doSetRallyPoint( Object *obj, const Coord3D& pos ) // for now, just use the basic human locomotor ... and enable the above code when Steven // tells me how to get the locomotor sets based on a thing template (CBD) // - NameKeyType key = NAMEKEY( "BasicHumanLocomotor" ); + NameKeyType key; + if (obj->isKindOf(KINDOF_SHIPYARD)) { + key = NAMEKEY("BasicBoatLocomotor"); + } + else { + key = NAMEKEY("BasicHumanLocomotor"); + } + LocomotorSet locomotorSet; locomotorSet.addLocomotor( TheLocomotorStore->findLocomotorTemplate( key ) ); if( TheAI->pathfinder()->clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &pos ) == FALSE ) diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp index b50fbd0d40e..5035177d962 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/FeatherTool.cpp @@ -110,7 +110,7 @@ void FeatherTool::mouseDown(TTrackingMode m, CPoint viewPt, WbView* pView, CWorl m_htMapFeatherCopy = pDoc->GetHeightMap()->duplicate(); m_htMapRateCopy = pDoc->GetHeightMap()->duplicate(); Int size = m_htMapRateCopy->getXExtent() * m_htMapRateCopy->getYExtent(); - UnsignedByte *pData = m_htMapRateCopy->getDataPtr(); + HeightSampleType *pData = m_htMapRateCopy->getDataPtr(); Int i; for (i=0; iupdateHeightMap(m_htMapEditCopy, true, partialRange); if (redoRate) { Int size = m_htMapRateCopy->getXExtent() * m_htMapRateCopy->getYExtent(); - UnsignedByte *pData = m_htMapRateCopy->getDataPtr(); - UnsignedByte *pFeather = m_htMapFeatherCopy->getDataPtr(); - UnsignedByte *pEdit = m_htMapEditCopy->getDataPtr(); + HeightSampleType *pData = m_htMapRateCopy->getDataPtr(); + HeightSampleType *pFeather = m_htMapFeatherCopy->getDataPtr(); + HeightSampleType *pEdit = m_htMapEditCopy->getDataPtr(); Int i; for (i=0; i binary_data(m_dataSize); + const Real f = 1.0f / TheMapData->m_HeightmapScale; + for (size_t i = 0; i < m_dataSize; i++) { + binary_data[i] = std::clamp(static_cast(std::round(m_data[i] * f)), static_cast(0U), static_cast(255U)); + } + + chunkWriter.writeArrayOfBytes((char *)&binary_data[0], m_dataSize); /* chunkWriter.writeInt(m_width); @@ -1799,7 +1808,7 @@ Bool WorldHeightMapEdit::resize(Int newXSize, Int newYSize, Int newHeight, Int n Short *tileNdxes = new Short[newDataSize]; Short *blendTileNdxes = new Short[newDataSize]; Short *extraBlendTileNdxes = new Short[newDataSize]; - UnsignedByte *data = new UnsignedByte[newDataSize]; + HeightSampleType *data = new HeightSampleType[newDataSize]; Short *cliffInfoNdxes = new Short[newDataSize]; Int i, j; diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp index ba2930f30a0..03b2f00f25b 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp @@ -35,6 +35,7 @@ #include "W3DDevice/GameClient/W3DFileSystem.h" #include "Common/FramePacer.h" #include "Common/GlobalData.h" +#include "Common/MapData.h" #include "WHeightMapEdit.h" //#include "Common/GameFileSystem.h" #include "Common/FileSystem.h" @@ -345,6 +346,7 @@ BOOL CWorldBuilderApp::InitInstance() INI ini; initSubsystem(TheWritableGlobalData, new GlobalData(), "Data\\INI\\Default\\GameData", "Data\\INI\\GameData"); + initSubsystem(TheWriteableMapData, new MapData()); TheFramePacer = new FramePacer(); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp index dae135ea3e4..78ca7541911 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp @@ -33,6 +33,7 @@ #include "Common/ThingTemplate.h" #include "Common/ThingFactory.h" #include "Common/WellKnownKeys.h" +#include "Common/MapData.h" #include "GameClient/Line2D.h" #include "GameClient/View.h" @@ -41,7 +42,7 @@ #include "GameLogic/PolygonTrigger.h" #include "GameLogic/SidesList.h" #include "GameLogic/ScriptEngine.h" - +#include "GameLogic/FPUControl.h" #include "Compression.h" #include "CUndoable.h" @@ -1238,6 +1239,7 @@ BOOL CWorldBuilderDoc::OnNewDocument() // clear out map-specific text TheGameText->reset(); + TheWriteableMapData->reset(); TNewHeightInfo hi; hi.initialHeight = AfxGetApp()->GetProfileInt("GameOptions", "Default Map Height", 16); @@ -1404,16 +1406,26 @@ BOOL CWorldBuilderDoc::OnOpenDocument(LPCTSTR lpszPathName) // clear out map-specific text TheGameText->reset(); + TheWriteableMapData->reset(); AsciiString s = lpszPathName; + AsciiString s_mapini; const char* lastSep = s.reverseFind('\\'); if (lastSep != NULL) { s.truncateTo(lastSep - s.str() + 1); } + s_mapini = s; s.concat("map.str"); DEBUG_LOG(("Looking for map-specific text in [%s]", s.str())); TheGameText->initMapStringFile(s); + s_mapini.concat("map.ini"); + if (TheFileSystem->doesFileExist(s_mapini.str())) + { + INI ini; + ini.load(s_mapini, INI_LOAD_MAPDATA_ONLY, nullptr, true); + } + WbApp()->setCurrentDirectory(AsciiString(buf)); ::GetModuleFileName(NULL, buf, sizeof(buf)); if (char *pEnd = strrchr(buf, '\\')) { From 13a95e51ea4756233591d8ce68b345a8316dedea Mon Sep 17 00:00:00 2001 From: pWn3d Date: Sat, 3 Jan 2026 14:34:34 +0100 Subject: [PATCH 2/4] remove unnecessary include --- GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp index 78ca7541911..fbb0e1a2df8 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp @@ -42,7 +42,6 @@ #include "GameLogic/PolygonTrigger.h" #include "GameLogic/SidesList.h" #include "GameLogic/ScriptEngine.h" -#include "GameLogic/FPUControl.h" #include "Compression.h" #include "CUndoable.h" From d689e9e7761ec3e07250663831c68db5bff026cd Mon Sep 17 00:00:00 2001 From: pWn3d Date: Tue, 6 Jan 2026 20:02:47 +0100 Subject: [PATCH 3/4] Ship improvements --- .../Source/W3DDevice/GameClient/W3DView.cpp | 10 +++++++ .../GameEngine/Include/GameLogic/Damage.h | 3 ++ .../GameLogic/Module/MissileAIUpdate.h | 2 ++ .../Include/GameLogic/TerrainLogic.h | 3 ++ .../Source/GameLogic/AI/AIGroup.cpp | 6 ++++ .../Source/GameLogic/AI/AIPathfind.cpp | 5 ++++ .../Source/GameLogic/Map/TerrainLogic.cpp | 28 +++++++++++++++++++ .../Update/AIUpdate/MissileAIUpdate.cpp | 10 ++++++- .../DefaultProductionExitUpdate.cpp | 6 ++++ .../Source/GameLogic/Object/WeaponSet.cpp | 5 ++++ .../Source/GameLogic/System/Damage.cpp | 3 ++ .../GameLogic/System/GameLogicDispatch.cpp | 16 ++++++++--- .../GameClient/Shadow/W3DProjectedShadow.cpp | 14 ++++++++++ 13 files changed, 106 insertions(+), 5 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index 704f000e259..62a650b5934 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -2319,6 +2319,16 @@ void W3DView::screenToTerrain( const ICoord2D *screen, Coord3D *world ) intersection = bridgePt; } + //Check for water height in this area, create a dummy plane around the point + if (TheGlobalData->m_heightAboveTerrainIncludesWater) { + Vector3 outPos{ 0,0,0 }; + if (TheTerrainLogic->pickWaterPlane(rayStart, rayEnd, intersection, outPos)) { + if (outPos.Z > intersection.Z) { + intersection = outPos; + } + } + } + world->x = intersection.X; world->y = intersection.Y; world->z = intersection.Z; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h index 7cdd5a458f8..2a967b7efa0 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h @@ -97,8 +97,11 @@ enum DamageType CPP_11(: Int) DAMAGE_SEISMIC, DAMAGE_RAD_BEAM, DAMAGE_TESLA, + DAMAGE_JET_TORPEDO, + DAMAGE_ANTI_SHIP, // Specific damage types with special logic attached + DAMAGE_TORPEDO, ///< can only attack units over water DAMAGE_CHRONO_GUN, ///< Disable target and remove them once health threshold is reached DAMAGE_CHRONO_UNRESISTABLE, ///< Used for recovery from CHRONO_GUN // DAMAGE_ZOMBIE_VIRUS, // TODO diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h index fa13c1d1587..6f6375fd7a1 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h @@ -71,6 +71,8 @@ class MissileAIUpdateModuleData : public AIUpdateModuleData Bool m_applyLauncherBonus; ///< Apply the launcher's weapon bonus flags (for any non-detonate triggered weapon) + Bool m_isTorpedo; ///< die outside of water, strike objects from below. + MissileAIUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h index f56c268ae65..7d75e15896c 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/TerrainLogic.h @@ -288,6 +288,9 @@ class TerrainLogic : public Snapshot, virtual Drawable *pickBridge(const Vector3 &from, const Vector3 &to, Vector3 *pos); + // get water around a world point + virtual bool pickWaterPlane(const Vector3 &from, const Vector3 &to, const Vector3 &aroundPos, Vector3 &outPos); + virtual void addBridgeToLogic(BridgeInfo *pInfo, Dict *props, AsciiString bridgeTemplateName); ///< Adds a bridge's logical info. virtual void addLandmarkBridgeToLogic(Object *bridgeObj); ///< Adds a bridge's logical info. virtual void deleteBridge( Bridge *bridge ); ///< remove a bridge diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp index 0ad64da8959..2a2d88f8076 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp @@ -542,6 +542,12 @@ void AIGroup::computeIndividualDestination( Coord3D *dest, const Coord3D *groupD dest->x = groupDest->x + v.x; dest->y = groupDest->y + v.y; dest->z = TheTerrainLogic->getLayerHeight( dest->x, dest->y, layer ); + + // Put waypoints on water surface instead of ground + if (Real waterZ = 0; TheTerrainLogic->isUnderwater(dest->x, dest->y, &waterZ)) { + if (waterZ > dest->z) dest->z = waterZ; + } + AIUpdateInterface *ai = obj->getAIUpdateInterface(); if (ai && ai->isDoingGroundMovement()) { if (isFormation) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index e50c5cf5fc2..b545e8330d9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -8894,6 +8894,11 @@ void Pathfinder::adjustCoordToCell(Int cellX, Int cellY, Bool centerInCell, Coor pos.y = ((Real)cellY+0.05) * PATHFIND_CELL_SIZE_F; } pos.z = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layer ); + + //Adjust to water surface + if (Real waterZ = 0; TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { + if (waterZ > pos.z) pos.z = waterZ; + } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 01fa0431369..7730fff1d2d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -1975,6 +1975,34 @@ Drawable *TerrainLogic::pickBridge(const Vector3 &from, const Vector3 &to, Vecto return(curDraw); } +bool TerrainLogic::pickWaterPlane(const Vector3& from, const Vector3& to, const Vector3& aroundPos, Vector3 &outPos) { + Real waterZ{ 0 }; + if (isUnderwater(aroundPos.X, aroundPos.Y, &waterZ)) { + Vector3 normal(0, 0, 1.0f); + Vector3 point(aroundPos.X, aroundPos.Y, waterZ); + + PlaneClass plane(normal, point); + + Real t; + plane.Compute_Intersection(from, to, &t); + Vector3 intersectPos; + intersectPos = from + (to - from) * t; + + outPos.X = intersectPos.X; + outPos.Y = intersectPos.Y; + outPos.Z = intersectPos.Z; + + return true; + } + else { + outPos.X = 0; + outPos.Y = 0; + outPos.Z = 0; + return false; + } +} + + //------------------------------------------------------------------------------------------------- /** Deletes the bridges list. */ //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 59a6207279a..c502e25a57e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -131,6 +131,7 @@ void MissileAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { "KillSelfDelay", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_killSelfDelay ) }, { "ZCorrectionFactor", INI::parseReal, NULL, offsetof(MissileAIUpdateModuleData, m_zDirFactor) }, { "ApplyLauncherBonus", INI::parseBool, NULL, offsetof(MissileAIUpdateModuleData, m_applyLauncherBonus) }, + { "IsTorpedo", INI::parseBool, NULL, offsetof(MissileAIUpdateModuleData, m_isTorpedo) }, { 0, 0, 0, 0 } }; @@ -633,7 +634,7 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) } } - if(curLoco && curLoco->getPreferredHeight() > 0) + if(curLoco && (curLoco->getPreferredHeight() > 0 || curLoco->getPreferredHeight() < 0) ) { // Am I close enough to the target to ignore my preferred height setting? Real distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalPosition(), FROM_CENTER_2D ); @@ -840,6 +841,13 @@ UpdateSleepTime MissileAIUpdate::update() TheGameLogic->destroyObject(getObject()); return UPDATE_SLEEP_FOREVER; } + + // If treated as torpedo, explode when not over water + const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData(); + if (d->m_isTorpedo && !getObject()->isOverWater()) { + detonate(); + } + switch( m_state ) { case PRELAUNCH: diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionExitUpdate/DefaultProductionExitUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionExitUpdate/DefaultProductionExitUpdate.cpp index b6b360bae8e..7879b5e78a4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionExitUpdate/DefaultProductionExitUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProductionExitUpdate/DefaultProductionExitUpdate.cpp @@ -81,6 +81,12 @@ void DefaultProductionExitUpdate::exitObjectViaDoor( Object *newObj, ExitDoorTyp // make sure the point is on the terrain loc.Z = TheTerrainLogic ? TheTerrainLogic->getLayerHeight( loc.X, loc.Y, creationObject->getLayer() ) : 0.0f; + // If underwater use water height, fixes shipyards spawning ships under water + if (Real waterZ = 0; TheTerrainLogic && TheTerrainLogic->isUnderwater(loc.X, loc.Y, &waterZ)) { + if (waterZ > loc.Z) loc.Z = waterZ; + } + + // we need it in Coord3D form createPoint.x = loc.X; createPoint.y = loc.Y; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp index 9319729398d..aef91b91bd0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp @@ -838,6 +838,11 @@ CanAttackResult WeaponSet::getAbleToUseWeaponAgainstTarget( AbleToAttackType att continue; } + // Torpedoes cannot attack units not above water + if (weapon->getDamageType() == DAMAGE_TORPEDO && !victim->isOverWater()) { + continue; + } + return okResult; } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp index 76e2568678b..a4144e500dc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp @@ -92,7 +92,10 @@ const char* const DamageTypeFlags::s_bitNameList[] = "SEISMIC", "RAD_BEAM", "TESLA", + "JET_TORPEDO", + "ANTI_SHIP", // Specific damage types with special logic attached + "TORPEDO", "CHRONO_GUN", "CHRONO_UNRESISTABLE", //"ZOMBIE_VIRUS", // TODO diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 317c64895c1..18d98355f41 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -148,8 +148,16 @@ static void doSetRallyPoint( Object *obj, const Coord3D& pos ) // tells me how to get the locomotor sets based on a thing template (CBD) // NameKeyType key; + Coord3D rallyPointPos = pos; if (obj->isKindOf(KINDOF_SHIPYARD)) { key = NAMEKEY("BasicBoatLocomotor"); + + //Shift pos up to water height + Real waterZ; + if (TheTerrainLogic->isUnderwater(rallyPointPos.x, rallyPointPos.y, &waterZ)) { + rallyPointPos.z = waterZ; + } + } else { key = NAMEKEY("BasicHumanLocomotor"); @@ -157,7 +165,7 @@ static void doSetRallyPoint( Object *obj, const Coord3D& pos ) LocomotorSet locomotorSet; locomotorSet.addLocomotor( TheLocomotorStore->findLocomotorTemplate( key ) ); - if( TheAI->pathfinder()->clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &pos ) == FALSE ) + if( TheAI->pathfinder()->clientSafeQuickDoesPathExist( locomotorSet, obj->getPosition(), &rallyPointPos) == FALSE ) { // user feedback @@ -169,7 +177,7 @@ static void doSetRallyPoint( Object *obj, const Coord3D& pos ) // play the no can do sound static AudioEventRTS rallyNotSet("UnableToSetRallyPoint"); - rallyNotSet.setPosition(&pos); + rallyNotSet.setPosition(&rallyPointPos); rallyNotSet.setPlayerIndex(obj->getControllingPlayer()->getPlayerIndex()); TheAudio->addAudioEvent(&rallyNotSet); @@ -191,7 +199,7 @@ static void doSetRallyPoint( Object *obj, const Coord3D& pos ) // play a sound for setting the rally point static AudioEventRTS rallyPointSet("RallyPointSet"); - rallyPointSet.setPosition(&pos); + rallyPointSet.setPosition(&rallyPointPos); rallyPointSet.setPlayerIndex(obj->getControllingPlayer()->getPlayerIndex()); TheAudio->addAudioEvent(&rallyPointSet); @@ -207,7 +215,7 @@ static void doSetRallyPoint( Object *obj, const Coord3D& pos ) if( exitInterface ) { // set the rally point - exitInterface->setRallyPoint( &pos ); + exitInterface->setRallyPoint( &rallyPointPos); } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp index 350082ebac2..5007e7e8fa4 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp @@ -1039,6 +1039,13 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) { hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; hmapVertex.Z=__max((float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE,layerHeight); + + if (TheGlobalData->m_heightAboveTerrainIncludesWater) { + if (Real waterZ = 0; TheTerrainLogic->isUnderwater(hmapVertex.X, hmapVertex.Y, &waterZ)) { + if (waterZ > hmapVertex.Z) hmapVertex.Z = waterZ; + } + } + pvVertices->x=hmapVertex.X; pvVertices->y=hmapVertex.Y; pvVertices->z=hmapVertex.Z; @@ -1058,6 +1065,13 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) { hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; hmapVertex.Z=(float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE+0.01f * MAP_XY_FACTOR; + + if (TheGlobalData->m_heightAboveTerrainIncludesWater) { + if (Real waterZ = 0; TheTerrainLogic->isUnderwater(hmapVertex.X, hmapVertex.Y, &waterZ)) { + if (waterZ > hmapVertex.Z) hmapVertex.Z = waterZ; + } + } + pvVertices->x=hmapVertex.X; pvVertices->y=hmapVertex.Y; pvVertices->z=hmapVertex.Z; From 1c8e473066955e98d639dd7edc5e7236c829c9e4 Mon Sep 17 00:00:00 2001 From: pWn3d Date: Tue, 6 Jan 2026 20:07:24 +0100 Subject: [PATCH 4/4] add extra ifs --- .../Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp | 8 +++++--- .../Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp index 2a2d88f8076..4e8519f8f9b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp @@ -543,9 +543,11 @@ void AIGroup::computeIndividualDestination( Coord3D *dest, const Coord3D *groupD dest->y = groupDest->y + v.y; dest->z = TheTerrainLogic->getLayerHeight( dest->x, dest->y, layer ); - // Put waypoints on water surface instead of ground - if (Real waterZ = 0; TheTerrainLogic->isUnderwater(dest->x, dest->y, &waterZ)) { - if (waterZ > dest->z) dest->z = waterZ; + if (TheGlobalData->m_heightAboveTerrainIncludesWater) { + // Put waypoints on water surface instead of ground + if (Real waterZ = 0; TheTerrainLogic->isUnderwater(dest->x, dest->y, &waterZ)) { + if (waterZ > dest->z) dest->z = waterZ; + } } AIUpdateInterface *ai = obj->getAIUpdateInterface(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index b545e8330d9..24725c72c0b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -8895,9 +8895,11 @@ void Pathfinder::adjustCoordToCell(Int cellX, Int cellY, Bool centerInCell, Coor } pos.z = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layer ); - //Adjust to water surface - if (Real waterZ = 0; TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { - if (waterZ > pos.z) pos.z = waterZ; + if (TheGlobalData->m_heightAboveTerrainIncludesWater) { + //Adjust to water surface + if (Real waterZ = 0; TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { + if (waterZ > pos.z) pos.z = waterZ; + } } }