diff --git a/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h b/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h index a66480e02e8..ccb3a664713 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ b/Generals/Code/GameEngine/Include/GameLogic/AIPathfind.h @@ -809,7 +809,7 @@ class Pathfinder : PathfindServicesInterface, public Snapshot const Object *obj, Int attackDistance); Path *buildActualPath( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, - const Coord3D *fromPos, PathfindCell *goalCell, Bool center, Bool blocked ); ///< Work backwards from goal cell to construct final path + const Coord3D *fromPos, PathfindCell *goalCell, Bool center, Bool blocked, Int requireWaterLevel ); ///< Work backwards from goal cell to construct final path Path *buildGroundPath( Bool isCrusher,const Coord3D *fromPos, PathfindCell *goalCell, Bool center, Int pathDiameter ); ///< Work backwards from goal cell to construct final path Path *buildHierachicalPath( const Coord3D *fromPos, PathfindCell *goalCell); ///< Work backwards from goal cell to construct final path diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index cd439448f12..585d4849ed7 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -335,6 +335,7 @@ class GlobalData : public SubsystemInterface Real m_MinDistFromEdgeOfMapForBuild; Real m_SupplyBuildBorder; Real m_allowedHeightVariationForBuilding; ///< how "flat" is still flat enough to build on + Real m_allowedHeightVariationForBuildingShipyard; ///< how "flat" is still flat enough to build on Real m_MinLowEnergyProductionSpeed; Real m_MaxLowEnergyProductionSpeed; Real m_LowEnergyPenaltyModifier; @@ -586,7 +587,7 @@ class GlobalData : public SubsystemInterface //AudioEventRTS m_chronoDisableSoundLoop; DeathTypeFlags m_defaultExcludedDeathTypes; - + Bool m_heightAboveTerrainIncludesWater; // the trailing '\' is included! const AsciiString &getPath_UserData() const { return m_userDataDir; } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h index c816f66faf6..08e5508ad0c 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h @@ -191,6 +191,7 @@ enum KindOfType CPP_11(: Int) KINDOF_SUPERHEAVY_VEHICLE, KINDOF_TELEPORTER, + KINDOF_SHIPYARD, KINDOF_EXTRA1, KINDOF_EXTRA2, diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h index a76f26cc521..75f26f31903 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIPathfind.h @@ -155,7 +155,7 @@ class Path : public MemoryPoolObject, public Snapshot void appendNode( const Coord3D *pos, PathfindLayerEnum layer ); ///< Create a new node at the end of the path void setBlockedByAlly(Bool blocked) {m_blockedByAlly = blocked;} Bool getBlockedByAlly(void) {return m_blockedByAlly;} - void optimize( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, Bool blocked ); ///< Optimize the path to discard redundant nodes + void optimize( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, Bool blocked, Int requiredWaterLevel); ///< Optimize the path to discard redundant nodes void optimizeGroundPath( Bool crusher, Int diameter ); ///< Optimize the ground path to discard redundant nodes @@ -343,6 +343,9 @@ class PathfindCell Bool getPinched(void) const {return m_pinched;} void setPinched(Bool pinch) {m_pinched = pinch; } + Short getWaterLevel(void) const { return m_waterLevel; } + void setWaterLevel(Short level) { m_waterLevel = level; } + Bool allocateInfo(const ICoord2D &pos); void releaseInfo(void); Bool hasInfo(void) const {return m_info!=NULL;} @@ -373,6 +376,8 @@ class PathfindCell UnsignedByte m_flags:4; ///< what type of units are in or moving through this cell. UnsignedByte m_connectsToLayer:4; ///< This cell can pathfind onto this layer, if > LAYER_TOP. UnsignedByte m_layer:4; ///< Layer of this cell. + //This is added for ship pathing + Short m_waterLevel:8; ///< how far away is this cell from land (distance transform), capped at 15 }; typedef PathfindCell *PathfindCellP; @@ -673,7 +678,7 @@ class Pathfinder : PathfindServicesInterface, public Snapshot void setIgnoreObstacleID( ObjectID objID ); ///< if non-zero, the pathfinder will ignore the given obstacle - Bool validMovementPosition( Bool isCrusher, LocomotorSurfaceTypeMask acceptableSurfaces, PathfindCell *toCell, PathfindCell *fromCell = NULL ); ///< Return true if given position is a valid movement location + Bool validMovementPosition( Bool isCrusher, LocomotorSurfaceTypeMask acceptableSurfaces, Int requiredWaterLevel, PathfindCell *toCell, PathfindCell *fromCell = NULL ); ///< Return true if given position is a valid movement location Bool validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, Int x, Int y ); ///< Return true if given position is a valid movement location Bool validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, const Coord3D *pos ); ///< Return true if given position is a valid movement location Bool validMovementTerrain( PathfindLayerEnum layer, const Locomotor* locomotor, const Coord3D *pos ); ///< Return true if given position is a valid movement location @@ -686,7 +691,7 @@ class Pathfinder : PathfindServicesInterface, public Snapshot Bool isLinePassable( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, PathfindLayerEnum layer, const Coord3D& startWorld, const Coord3D& endWorld, - Bool blocked, Bool allowPinched ); ///< Return true if the straight line between the given points is passable + Bool blocked, Bool allowPinched, Int requiredWaterDepth); ///< Return true if the straight line between the given points is passable void moveAlliesAwayFromDestination( Object *obj,const Coord3D& destination); @@ -817,7 +822,7 @@ class Pathfinder : PathfindServicesInterface, public Snapshot const Object *obj, Int attackDistance); Path *buildActualPath( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, - const Coord3D *fromPos, PathfindCell *goalCell, Bool center, Bool blocked ); ///< Work backwards from goal cell to construct final path + const Coord3D *fromPos, PathfindCell *goalCell, Bool center, Bool blocked, Int requiredWaterLevel ); ///< Work backwards from goal cell to construct final path Path *buildGroundPath( Bool isCrusher,const Coord3D *fromPos, PathfindCell *goalCell, Bool center, Int pathDiameter ); ///< Work backwards from goal cell to construct final path Path *buildHierachicalPath( const Coord3D *fromPos, PathfindCell *goalCell); ///< Work backwards from goal cell to construct final path @@ -888,7 +893,7 @@ inline void Pathfinder::worldToGrid( const Coord3D *pos, ICoord2D *cellIndex ) inline Bool Pathfinder::validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, Int x, Int y ) { - return validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), getCell( layer, x, y ) ); + return validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), getCell(layer, x, y)); } inline Bool Pathfinder::validMovementPosition( Bool isCrusher, PathfindLayerEnum layer, const LocomotorSet& locomotorSet, const Coord3D *pos ) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index c8bc06ffade..0fb97be20a6 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -59,7 +59,7 @@ enum LocomotorAppearance CPP_11(: Int) LOCO_CLIMBER, // human climber - backs down cliffs. LOCO_OTHER, LOCO_MOTORCYCLE, - + LOCO_SHIP, LOCOMOTOR_APPEARANCE_COUNT }; @@ -84,7 +84,7 @@ static const char *const TheLocomotorAppearanceNames[] = "CLIMBER", "OTHER", "MOTORCYCLE", - + "SHIP", NULL }; static_assert(ARRAY_SIZE(TheLocomotorAppearanceNames) == LOCOMOTOR_APPEARANCE_COUNT + 1, "Array size"); @@ -218,6 +218,8 @@ class LocomotorTemplate : public Overridable Real m_rudderCorrectionRate; Real m_elevatorCorrectionDegree; Real m_elevatorCorrectionRate; + + Int m_requiredWaterLevel; ///< for LOCO_SHIP, how deep the water must be }; typedef OVERRIDE LocomotorTemplateOverride; @@ -321,6 +323,9 @@ class Locomotor : public MemoryPoolObject, public Snapshot void setPreferredHeight( Real height ) { m_preferredHeight = height; } + //Returns 0 for non SHIP locomotors + inline Int getRequireWaterLevel() const { return m_template->m_appearance == LOCO_SHIP ? m_template->m_requiredWaterLevel : 0; }; + #ifdef CIRCLE_FOR_LANDING /** if we are climbing/diving more than this, circle as needed rather diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/LocomotorSet.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/LocomotorSet.h index c4002d35628..80213ebb2d5 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/LocomotorSet.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/LocomotorSet.h @@ -80,6 +80,7 @@ class LocomotorSet : public Snapshot LocomotorVector m_locomotors; LocomotorSurfaceTypeMask m_validLocomotorSurfaces; Bool m_downhillOnly; + Int m_requiredWaterLevel; // for ships, if there are multiple water locomotors the lowest allowed value is used LocomotorSet(const LocomotorSet& that); LocomotorSet& operator=(const LocomotorSet& that); @@ -105,5 +106,6 @@ class LocomotorSet : public Snapshot LocomotorSurfaceTypeMask getValidSurfaces() const { return m_validLocomotorSurfaces; } Bool isDownhillOnly( void ) const { return m_downhillOnly; }; + Int getRequiredWaterLevel() const { return m_requiredWaterLevel; }; }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DumbProjectileBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DumbProjectileBehavior.h index 26204d3b652..baaf1196784 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DumbProjectileBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DumbProjectileBehavior.h @@ -96,6 +96,7 @@ class DumbProjectileBehavior : public UpdateModule, public ProjectileUpdateInter virtual void projectileNowJammed() {} virtual Object* getTargetObject(); virtual const Coord3D* getTargetPosition(); + virtual Bool projectileShouldCollideWithWater() const override; protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/FreeFallProjectileBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/FreeFallProjectileBehavior.h index 82c15a77c73..8c9637c431b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/FreeFallProjectileBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/FreeFallProjectileBehavior.h @@ -102,6 +102,7 @@ class FreeFallProjectileBehavior : public UpdateModule, public ProjectileUpdateI virtual void projectileNowJammed() {} virtual Object* getTargetObject(); virtual const Coord3D* getTargetPosition(); + virtual bool projectileShouldCollideWithWater() const override; protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h index b00d6ca5e02..fa13c1d1587 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h @@ -109,6 +109,7 @@ class MissileAIUpdate : public AIUpdateInterface, public ProjectileUpdateInterfa virtual void projectileNowJammed();///< We lose our Object target and scatter to the ground virtual Object* getTargetObject(); virtual const Coord3D* getTargetPosition(); + virtual bool projectileShouldCollideWithWater() const override; virtual Bool processCollision(PhysicsBehavior *physics, Object *other); ///< Returns true if the physics collide should apply the force. Normally not. jba. diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpdateModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpdateModule.h index c5d4a9ea7df..a22c407437e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpdateModule.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpdateModule.h @@ -266,6 +266,7 @@ class ProjectileUpdateInterface virtual Bool projectileHandleCollision(Object *other) = 0; virtual void setFramesTillCountermeasureDiversionOccurs( UnsignedInt frames ) = 0; ///< Number of frames till missile diverts to countermeasures. virtual void projectileNowJammed() = 0; + virtual Bool projectileShouldCollideWithWater() const { return false; }; virtual Object* getTargetObject() = 0; virtual const Coord3D* getTargetPosition() = 0; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h index 7a135f7e54f..62269eb165e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h @@ -146,7 +146,8 @@ enum WeaponCollideMaskType CPP_11(: Int) WEAPON_COLLIDE_WALLS = 0x0020, WEAPON_COLLIDE_SMALL_MISSILES = 0x0040, //All missiles are also projectiles! WEAPON_COLLIDE_BALLISTIC_MISSILES = 0x0080, //All missiles are also projectiles! - WEAPON_COLLIDE_CONTROLLED_STRUCTURES = 0x0100 //this is "ONLY structures belonging to the projectile's controller". + WEAPON_COLLIDE_CONTROLLED_STRUCTURES = 0x0100, //this is "ONLY structures belonging to the projectile's controller". + WEAPON_COLLIDE_WATER = 0x0200 }; #ifdef DEFINE_WEAPONCOLLIDEMASK_NAMES @@ -161,6 +162,7 @@ static const char *const TheWeaponCollideMaskNames[] = "SMALL_MISSILES", //All missiles are also projectiles! "BALLISTIC_MISSILES", //All missiles are also projectiles! "CONTROLLED_STRUCTURES", + "WATER", NULL }; #endif @@ -399,21 +401,21 @@ class WeaponTemplate : public MemoryPoolObject { friend class WeaponStore; - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( WeaponTemplate, "WeaponTemplate" ) + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(WeaponTemplate, "WeaponTemplate") public: WeaponTemplate(); // virtual destructor declared by memory pool - void reset( void ); + void reset(void); - void friend_setNextTemplate(WeaponTemplate *nextTemplate) { m_nextTemplate = nextTemplate; } - WeaponTemplate *friend_clearNextTemplate( void ) { WeaponTemplate *ret = m_nextTemplate; m_nextTemplate = NULL; return ret; } - Bool isOverride( void ) { return m_nextTemplate != NULL; } + void friend_setNextTemplate(WeaponTemplate* nextTemplate) { m_nextTemplate = nextTemplate; } + WeaponTemplate* friend_clearNextTemplate(void) { WeaponTemplate* ret = m_nextTemplate; m_nextTemplate = NULL; return ret; } + Bool isOverride(void) { return m_nextTemplate != NULL; } /// field table for loading the values from an INI - const FieldParse *getFieldParse() const { return TheWeaponTemplateFieldParseTable; } + const FieldParse* getFieldParse() const { return TheWeaponTemplateFieldParseTable; } /** fire the weapon. return the logic-frame in which the damage will be dealt. @@ -425,15 +427,15 @@ class WeaponTemplate : public MemoryPoolObject */ UnsignedInt fireWeaponTemplate ( - const Object *sourceObj, + const Object* sourceObj, WeaponSlotType wslot, Int specificBarrelToUse, - Object *victimObj, + Object* victimObj, const Coord3D* victimPos, const WeaponBonus& bonus, Bool isProjectileDetonation, Bool ignoreRanges, - Weapon *firingWeapon, + Weapon* firingWeapon, ObjectID* projectileID, Bool inflictDamage ) const; @@ -445,8 +447,8 @@ class WeaponTemplate : public MemoryPoolObject take weapon range into account -- it ASSUMES that the victim is within range! */ Real estimateWeaponTemplateDamage( - const Object *sourceObj, - const Object *victimObj, + const Object* sourceObj, + const Object* victimObj, const Coord3D* victimPos, const WeaponBonus& bonus ) const; @@ -467,7 +469,7 @@ class WeaponTemplate : public MemoryPoolObject Real getShockWaveRadius() const { return m_shockWaveRadius; } Real getShockWaveTaperOff() const { return m_shockWaveTaperOff; } - Real getRequestAssistRange() const {return m_requestAssistRange;} + Real getRequestAssistRange() const { return m_requestAssistRange; } AsciiString getName() const { return m_name; } AsciiString getProjectileStreamName() const { return m_projectileStreamName; } AsciiString getLaserName() const { return m_laserName; } @@ -499,7 +501,7 @@ class WeaponTemplate : public MemoryPoolObject Int getContinuousFireOneShotsNeeded() const { return m_continuousFireOneShotsNeeded; } Int getContinuousFireTwoShotsNeeded() const { return m_continuousFireTwoShotsNeeded; } UnsignedInt getContinuousFireCoastFrames() const { return m_continuousFireCoastFrames; } - UnsignedInt getAutoReloadWhenIdleFrames() const { return m_autoReloadWhenIdleFrames; } + UnsignedInt getAutoReloadWhenIdleFrames() const { return m_autoReloadWhenIdleFrames; } UnsignedInt getSuspendFXDelay() const { return m_suspendFXDelay; } const FXList* getFireFX(VeterancyLevel v) const { return m_fireFXs[v]; } @@ -530,8 +532,9 @@ class WeaponTemplate : public MemoryPoolObject Bool isScatterTargetAligned() const { return m_scatterTargetAligned; } Bool isScatterTargetRandom() const { return m_scatterTargetRandom; } Bool isScatterTargetRandomAngle() const { return m_scatterTargetRandomAngle; } - Real getScatterTargetMinScalar () const { return m_scatterTargetMinScalar; } + Real getScatterTargetMinScalar() const { return m_scatterTargetMinScalar; } Bool isScatterTargetCenteredAtShooter() const { return m_scatterTargetCenteredAtShooter; } + Bool isScatterOnWaterSurface() const { return m_scatterOnWaterSurface; } Bool shouldProjectileCollideWith( const Object* projectileLauncher, @@ -659,6 +662,8 @@ class WeaponTemplate : public MemoryPoolObject UnsignedInt m_scatterTargetResetTime; ///< if this much time between shots has passed, we reset the scatter targets Bool m_scatterTargetResetRecenter; ///< when resetting scatter targets, use indices in the "middle" of the list, to keep the target centered for Line based attacks + Bool m_scatterOnWaterSurface; ///< Scatter radius and targets include the water surface instead of just the terrain height + mutable HistoricWeaponDamageList m_historicDamage; mutable UnsignedInt m_historicDamageTriggerId; }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index ba232252e68..c38ed69fd7f 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -398,6 +398,7 @@ GlobalData* GlobalData::m_theOriginal = NULL; { "MinDistFromEdgeOfMapForBuild", INI::parseReal, NULL, offsetof( GlobalData, m_MinDistFromEdgeOfMapForBuild ) }, { "SupplyBuildBorder", INI::parseReal, NULL, offsetof( GlobalData, m_SupplyBuildBorder ) }, { "AllowedHeightVariationForBuilding", INI::parseReal,NULL, offsetof( GlobalData, m_allowedHeightVariationForBuilding ) }, + { "AllowedHeightVariationForBuildingShipyard", INI::parseReal,NULL, offsetof(GlobalData, m_allowedHeightVariationForBuildingShipyard) }, { "MinLowEnergyProductionSpeed",INI::parseReal, NULL, offsetof( GlobalData, m_MinLowEnergyProductionSpeed ) }, { "MaxLowEnergyProductionSpeed",INI::parseReal, NULL, offsetof( GlobalData, m_MaxLowEnergyProductionSpeed ) }, { "LowEnergyPenaltyModifier", INI::parseReal, NULL, offsetof( GlobalData, m_LowEnergyPenaltyModifier ) }, @@ -572,7 +573,7 @@ GlobalData* GlobalData::m_theOriginal = NULL; {"ChronoDamageParticleSystemSmall", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemSmall) }, {"DefaultExcludedDeathTypes", INI::parseDeathTypeFlagsList, NULL, offsetof(GlobalData, m_defaultExcludedDeathTypes) }, - + {"HeightAboveTerrainIncludesWater", INI::parseBool, NULL, offsetof(GlobalData, m_heightAboveTerrainIncludesWater) }, { NULL, NULL, NULL, 0 } // keep this last }; @@ -915,6 +916,7 @@ GlobalData::GlobalData() m_MinDistFromEdgeOfMapForBuild = 0.0f; m_SupplyBuildBorder = 0.0f; m_allowedHeightVariationForBuilding = 0.0f; + m_allowedHeightVariationForBuildingShipyard = 0.0f; m_MinLowEnergyProductionSpeed = 0.0f; m_MaxLowEnergyProductionSpeed = 0.0f; m_LowEnergyPenaltyModifier = 0.0f; @@ -1158,6 +1160,8 @@ GlobalData::GlobalData() m_chronoDisableParticleSystemSmall.clear(); // m_chronoTintStatusType = TINT_STATUS_INVALID; + m_heightAboveTerrainIncludesWater = false; + } // end GlobalData diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp index b3e11d5f10e..635c0811fa2 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/BuildAssistant.cpp @@ -478,6 +478,12 @@ struct SampleBuildData Real loZ; ///< lowest sample point used }; +struct SampleBuildDataShipyard : public SampleBuildData +{ + Short waterSamples; ///< how many samples hit water tiles + Short landSamples; ///< how many samples hit land tiles +}; + //------------------------------------------------------------------------------------------------- /** This will check the build conditions at the specified sample location point */ //------------------------------------------------------------------------------------------------- @@ -534,6 +540,73 @@ static void checkSampleBuildLocation( const Coord3D *samplePoint, void *userData } +//------------------------------------------------------------------------------------------------- +/** This will check the build conditions at the specified sample location point */ +//------------------------------------------------------------------------------------------------- +static void checkSampleBuildLocationShipyard(const Coord3D* samplePoint, void* userData) +{ + TerrainType* terrain; + SampleBuildDataShipyard* sampleData = (SampleBuildDataShipyard*)userData; + + // get the terrain tile here + terrain = TheTerrainVisual->getTerrainTile(samplePoint->x, samplePoint->y); + if (terrain) + { + + // check for the restricts building flag + if (terrain->getRestrictConstruction()) + sampleData->terrainRestricted = TRUE; + + } // end if + + Int cellX = REAL_TO_INT_FLOOR(samplePoint->x / PATHFIND_CELL_SIZE); + Int cellY = REAL_TO_INT_FLOOR(samplePoint->y / PATHFIND_CELL_SIZE); + + PathfindCell* cell = TheAI->pathfinder()->getCell(LAYER_GROUND, cellX, cellY); + if (!cell) { + sampleData->terrainRestricted = TRUE; + } + else { + enum PathfindCell::CellType type = cell->getType(); + if ((type == PathfindCell::CELL_CLIFF) || + (type == PathfindCell::CELL_IMPASSABLE)) { + sampleData->terrainRestricted = true; + } + } + + // + // record the highest and lowest Z points from all the samples and do not allow + // building when the difference between them is too great + // + if (samplePoint->z < sampleData->loZ) + sampleData->loZ = samplePoint->z; + if (samplePoint->z > sampleData->hiZ) + sampleData->hiZ = samplePoint->z; + + // too close to edge of map? + if (TheGlobalData->m_MinDistFromEdgeOfMapForBuild > 0.0f) + { + if (samplePoint->x < sampleData->mapRegion.lo.x + TheGlobalData->m_MinDistFromEdgeOfMapForBuild + || samplePoint->x > sampleData->mapRegion.hi.x - TheGlobalData->m_MinDistFromEdgeOfMapForBuild + || samplePoint->y < sampleData->mapRegion.lo.y + TheGlobalData->m_MinDistFromEdgeOfMapForBuild + || samplePoint->y > sampleData->mapRegion.hi.y - TheGlobalData->m_MinDistFromEdgeOfMapForBuild) + { + sampleData->terrainRestricted = TRUE; + } + } + + //Sample is valid + if (cell != nullptr && !sampleData->terrainRestricted) { + if (cell->getType() == PathfindCell::CELL_WATER) { + sampleData->waterSamples++; + } + else { + sampleData->landSamples++; + } + } + +} // end checkSampleBuildLocation + //------------------------------------------------------------------------------------------------- /** This function will call the user callback at each "sample point" across the footprint * of where this object would be in the world. The smaller the sample resolution param is @@ -1012,8 +1085,15 @@ LegalBuildCode BuildAssistant::isLocationLegalToBuild( const Coord3D *worldPos, if( ai == NULL ) return LBC_NO_CLEAR_PATH; - if( ai->isQuickPathAvailable( worldPos ) == FALSE ) + if (build->isKindOf(KINDOF_SHIPYARD)) { + // bypass Path check for now + // TODO checking bounding box? + } + else { + + if (ai->isQuickPathAvailable(worldPos) == FALSE) return LBC_NO_CLEAR_PATH; + } } @@ -1036,33 +1116,91 @@ LegalBuildCode BuildAssistant::isLocationLegalToBuild( const Coord3D *worldPos, return LBC_RESTRICTED_TERRAIN; } - // - // check the footprint of where the structure would go to be clear of any non-buildable - // tiles and to make sure there isn't a restricted tile and to make sure it's "flat" enough - // - SampleBuildData sampleData; - TheTerrainLogic->getExtent( &sampleData.mapRegion ); - sampleData.terrainRestricted = FALSE; - sampleData.hiZ = terrainExtent.lo.z; // note we set hi point to lowest point - sampleData.loZ = terrainExtent.hi.z; // note we set lo point to highest point - - // quick check at triple res. - iterateFootprint( build, angle, worldPos, 3*sampleResolution, - checkSampleBuildLocation, &sampleData ); - if( sampleData.terrainRestricted == TRUE ) - return LBC_RESTRICTED_TERRAIN; - // check if the height across the whole footprint area is too varied (not flat enough) - if( sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuilding ) - return LBC_NOT_FLAT_ENOUGH; - - // careful check at full res. - iterateFootprint( build, angle, worldPos, sampleResolution, - checkSampleBuildLocation, &sampleData ); - if( sampleData.terrainRestricted == TRUE ) - return LBC_RESTRICTED_TERRAIN; - // check if the height across the whole footprint area is too varied (not flat enough) - if( sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuilding ) - return LBC_NOT_FLAT_ENOUGH; + + if (!build->isKindOf(KINDOF_SHIPYARD)) { + // + // check the footprint of where the structure would go to be clear of any non-buildable + // tiles and to make sure there isn't a restricted tile and to make sure it's "flat" enough + // + SampleBuildData sampleData; + TheTerrainLogic->getExtent(&sampleData.mapRegion); + sampleData.terrainRestricted = FALSE; + sampleData.hiZ = terrainExtent.lo.z; // note we set hi point to lowest point + sampleData.loZ = terrainExtent.hi.z; // note we set lo point to highest point + + // quick check at triple res. + iterateFootprint(build, angle, worldPos, 3 * sampleResolution, + checkSampleBuildLocation, &sampleData); + if (sampleData.terrainRestricted == TRUE) + return LBC_RESTRICTED_TERRAIN; + // check if the height across the whole footprint area is too varied (not flat enough) + if (sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuilding) + return LBC_NOT_FLAT_ENOUGH; + + // careful check at full res. + iterateFootprint(build, angle, worldPos, sampleResolution, + checkSampleBuildLocation, &sampleData); + if (sampleData.terrainRestricted == TRUE) + return LBC_RESTRICTED_TERRAIN; + // check if the height across the whole footprint area is too varied (not flat enough) + + if (sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuilding) + return LBC_NOT_FLAT_ENOUGH; + } + else { + // IF Shipyard need some special code + + // + // check the footprint of where the structure would go to be clear of any non-buildable + // tiles and to make sure there isn't a restricted tile and to make sure it's "flat" enough + // Also check how many samples are on water or land for shipyards + // + SampleBuildDataShipyard sampleData; + TheTerrainLogic->getExtent(&sampleData.mapRegion); + sampleData.terrainRestricted = FALSE; + sampleData.hiZ = terrainExtent.lo.z; // note we set hi point to lowest point + sampleData.loZ = terrainExtent.hi.z; // note we set lo point to highest point + sampleData.waterSamples = 0; + sampleData.landSamples = 0; + + // quick check at triple res. + iterateFootprint(build, angle, worldPos, 3 * sampleResolution, + checkSampleBuildLocationShipyard, &sampleData); + if (sampleData.terrainRestricted == TRUE) + return LBC_RESTRICTED_TERRAIN; + // check if the height across the whole footprint area is too varied (not flat enough) + if (sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuildingShipyard) + return LBC_NOT_FLAT_ENOUGH; + + if (sampleData.waterSamples == 0 || sampleData.landSamples == 0) { + return LBC_RESTRICTED_TERRAIN; + } + + // Reset sample count before detailed check + sampleData.waterSamples = 0; + sampleData.landSamples = 0; + + // careful check at full res. + iterateFootprint(build, angle, worldPos, sampleResolution, + checkSampleBuildLocationShipyard, &sampleData); + if (sampleData.terrainRestricted == TRUE) + return LBC_RESTRICTED_TERRAIN; + // check if the height across the whole footprint area is too varied (not flat enough) + + if (sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuildingShipyard) + return LBC_NOT_FLAT_ENOUGH; + + + //Check if enough parts in water and land + Short totalSamples = sampleData.waterSamples + sampleData.landSamples; + Real threshold_water = totalSamples * 0.6f; + Real threshold_land = 1.0f; + + if (static_cast(sampleData.waterSamples) < threshold_water || static_cast(sampleData.landSamples) < threshold_land) { + return LBC_RESTRICTED_TERRAIN; + } + + } } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp index d8c85ea97e7..1573fcfc451 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp @@ -180,6 +180,7 @@ const char* const KindOfMaskType::s_bitNameList[] = "SUPERHEAVY_VEHICLE", "TELEPORTER", + "SHIPYARD", "EXTRA1", "EXTRA2", diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 9acfb20ef69..8616791fae8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -1483,6 +1483,7 @@ Bool Drawable::calcPhysicsXform(PhysicsXformInfo& info) calcPhysicsXformTreads(locomotor, info); hasPhysicsXform = true; break; + case LOCO_SHIP: case LOCO_HOVER: case LOCO_WINGS: calcPhysicsXformHoverOrWings(locomotor, info); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index c72b4cd3b6e..ee6b325d4b9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -90,6 +90,7 @@ #include "GameNetwork/NetworkInterface.h" #include "Common/UnitTimings.h" //Contains the DO_UNIT_TIMINGS define jba. +#include "Common/Geometry.h" @@ -1506,7 +1507,6 @@ void InGameUI::evaluateSoloNexus( Drawable *newlyAddedDrawable ) } - void InGameUI::handleBuildPlacements( void ) { @@ -1524,28 +1524,29 @@ void InGameUI::handleBuildPlacements( void ) // location the icon will be at (anchored is the start, otherwise it's the mouse) if( isPlacementAnchored() ) { - ICoord2D start, end; + if (!m_pendingPlaceType->isKindOf(KINDOF_SHIPYARD)) { + ICoord2D start, end; - // get the placement arrow points - getPlacementPoints( &start, &end ); + // get the placement arrow points + getPlacementPoints(&start, &end); - // set icon to anchor point - loc = start; + // set icon to anchor point + loc = start; - // only adjust angle if we've actually moved the mouse - if( start.x != end.x || start.y != end.y ) - { - Coord3D worldStart, worldEnd; + // only adjust angle if we've actually moved the mouse + if (start.x != end.x || start.y != end.y) + { + Coord3D worldStart, worldEnd; - // project the start and the end points of the line anchor into the 3D world - TheTacticalView->screenToTerrain( &start, &worldStart ); - TheTacticalView->screenToTerrain( &end, &worldEnd ); - - Coord2D v; - v.x = worldEnd.x - worldStart.x; - v.y = worldEnd.y - worldStart.y; - angle = v.toAngle(); + // project the start and the end points of the line anchor into the 3D world + TheTacticalView->screenToTerrain(&start, &worldStart); + TheTacticalView->screenToTerrain(&end, &worldEnd); + Coord2D v; + v.x = worldEnd.x - worldStart.x; + v.y = worldEnd.y - worldStart.y; + angle = v.toAngle(); + } // TheSuperHackers @tweak Stubbjax 04/08/2025 Snap angle to nearest 45 degrees // while using force attack mode for convenience. if (isInForceAttackMode()) @@ -1554,8 +1555,18 @@ void InGameUI::handleBuildPlacements( void ) angle = WWMath::Round(angle / snapRadians) * snapRadians; } } + else { + // In case of Shipyard, do not update rotation + ICoord2D start, end; - } + // get the placement arrow points + getPlacementPoints(&start, &end); + + // set icon to anchor point + loc = start; + } + + } // end if else { const MouseIO *mouseIO = TheMouse->getMouseStatus(); @@ -1563,7 +1574,53 @@ void InGameUI::handleBuildPlacements( void ) // location is the mouse position loc = mouseIO->pos; - } + if (m_pendingPlaceType->isKindOf(KINDOF_SHIPYARD)) { + // For shipyard sample the terrain an rotate according to descending terrain (water is lower) + const GeometryInfo& geom = m_pendingPlaceType->getTemplateGeometryInfo(); + + Coord3D worldPos; + TheTacticalView->screenToTerrain(&loc, &worldPos); + + Real check_radius = 0.0f; + if (geom.getGeomType() == GEOMETRY_BOX) + { + check_radius = std::max(geom.getMinorRadius(), geom.getMajorRadius()); + } // end if + else if (geom.getGeomType() == GEOMETRY_SPHERE || + geom.getGeomType() == GEOMETRY_CYLINDER) + { + check_radius = geom.getBoundingCircleRadius(); + } // end else if + else + { + DEBUG_ASSERTCRASH(0, ("InGameUI::handleBuildPlacements (Shipyard placement): Undefined geometry '%d' for '%s'", geom.getGeomType(), m_pendingPlaceType->getName().str())); + return; + } // end else + + //Check 4 sample points + Real hx1 = TheTerrainLogic->getGroundHeight(worldPos.x + check_radius, worldPos.y); + Real hx2 = TheTerrainLogic->getGroundHeight(worldPos.x - check_radius, worldPos.y); + Real hy1 = TheTerrainLogic->getGroundHeight(worldPos.x, worldPos.y + check_radius); + Real hy2 = TheTerrainLogic->getGroundHeight(worldPos.x, worldPos.y - check_radius); + + Real dx = hx1 - hx2; + Real dy = hy1 - hy2; + + Coord2D v; + v.x = dx; + v.y = dy; + constexpr Real pi2 = PI * 2.0f; + angle = v.toAngle() + m_pendingPlaceType->getPlacementViewAngle(); + if (angle < 0.0f) { + angle += pi2; + } + else if (angle > pi2) { + angle -= pi2; + } + } + + + } // end else // set the location and angle of the place icon /**@todo this whole orientation vector thing is LAME! Must replace, all I want to diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp index c956f0820be..0ad64da8959 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp @@ -669,7 +669,7 @@ Bool AIGroup::friend_computeGroundPath( const Coord3D *pos, CommandSourceType cm { if (!TheAI->pathfinder()->isLinePassable(obj, ai->getLocomotorSet().getValidSurfaces(), obj->getLayer(), *obj->getPosition(), - center, false, true)) { + center, false, true, ai->getLocomotorSet().getRequiredWaterLevel())) { isPassable = false; } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index bc2eb2aed11..6040e59d77c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -67,7 +67,8 @@ #include "Common/PerfMetrics.h" //------------------------------------------------------------------------------------------------- - +#include +#include static inline Bool IS_IMPASSABLE(PathfindCell::CellType type) { // Return true if cell is impassable to ground units. jba. [8/18/2003] @@ -470,7 +471,7 @@ void Path::updateLastNode( const Coord3D *pos ) /** * Optimize the path by checking line of sight */ -void Path::optimize( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, Bool blocked ) +void Path::optimize( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, Bool blocked, Int requiredWaterLevel) { PathNode *node, *anchor; @@ -522,7 +523,7 @@ void Path::optimize( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfa Bool isPassable = false; //CRCDEBUG_LOG(("Path::optimize() calling isLinePassable()")); if (TheAI->pathfinder()->isLinePassable( obj, acceptableSurfaces, layer, *anchor->getPosition(), - *node->getPosition(), blocked, false)) + *node->getPosition(), blocked, false, requiredWaterLevel)) { isPassable = true; } @@ -930,8 +931,9 @@ void Path::computePointOnPath( Bool gotPos = false; CRCDEBUG_LOG(("Path::computePointOnPath() calling isLinePassable() 1")); + if (TheAI->pathfinder()->isLinePassable( obj, locomotorSet.getValidSurfaces(), out.layer, pos, *nextNodePos, - false, true )) + false, true, locomotorSet.getRequiredWaterLevel() )) { out.posOnPath = *nextNodePos; gotPos = true; @@ -964,7 +966,7 @@ void Path::computePointOnPath( tryPos.y = (nextNodePos->y + next->getPosition()->y) * 0.5; tryPos.z = nextNodePos->z; CRCDEBUG_LOG(("Path::computePointOnPath() calling isLinePassable() 2")); - if (veryClose || TheAI->pathfinder()->isLinePassable( obj, locomotorSet.getValidSurfaces(), closeNext->getLayer(), pos, tryPos, false, true )) + if (veryClose || TheAI->pathfinder()->isLinePassable( obj, locomotorSet.getValidSurfaces(), closeNext->getLayer(), pos, tryPos, false, true, locomotorSet.getRequiredWaterLevel())) { gotPos = true; out.posOnPath = tryPos; @@ -982,7 +984,7 @@ void Path::computePointOnPath( out.posOnPath.z = closeNodePos->z; CRCDEBUG_LOG(("Path::computePointOnPath() calling isLinePassable() 3")); - if (TheAI->pathfinder()->isLinePassable( obj, locomotorSet.getValidSurfaces(), out.layer, pos, out.posOnPath, false, true )) + if (TheAI->pathfinder()->isLinePassable( obj, locomotorSet.getValidSurfaces(), out.layer, pos, out.posOnPath, false, true, locomotorSet.getRequiredWaterLevel())) { k = 0.5f; gotPos = true; @@ -1255,7 +1257,7 @@ void PathfindCell::reset( ) } m_connectsToLayer = LAYER_INVALID; m_layer = LAYER_GROUND; - + m_waterLevel = 0; } /** @@ -4251,7 +4253,6 @@ void Pathfinder::classifyMapCell( Int i, Int j , PathfindCell *cell) { Coord3D topLeftCorner, bottomRightCorner; - Bool hasObstacle = (cell->getType() == PathfindCell::CELL_OBSTACLE) ; topLeftCorner.y = (Real)j * PATHFIND_CELL_SIZE_F; @@ -4261,6 +4262,7 @@ void Pathfinder::classifyMapCell( Int i, Int j , PathfindCell *cell) bottomRightCorner.x = topLeftCorner.x + PATHFIND_CELL_SIZE_F; cell->setPinched(false); + cell->setWaterLevel(0); PathfindCell::CellType type = PathfindCell::CELL_CLEAR; if (TheTerrainLogic->isCliffCell(topLeftCorner.x, topLeftCorner.y)) @@ -4283,6 +4285,84 @@ void Pathfinder::classifyMapCell( Int i, Int j , PathfindCell *cell) cell->releaseInfo(); } +struct Point { + int x, y; +}; + +static void calculateWaterLevels(IRegion2D bounds, PathfindCell** map) +{ + std::queue q; + constexpr int MAX_LEVEL = 15; + + // Directions: Up, Down, Left, Right + const int dirs[4][2] = { {0, 1}, {0, -1}, {1, 0}, {-1, 0} }; + + // 1. Initialization Pass + // Iterate only within the defined region + for (int i = bounds.lo.x; i < bounds.hi.x; ++i) { + for (int j = bounds.lo.y; j < bounds.hi.y; ++j) { + auto& cell = map[i][j]; + + if (cell.getType() != PathfindCell::CELL_WATER) { + // Found LAND/OBSTACLE inside the region + cell.setWaterLevel(0); + q.push({ i, j }); + } + else { + // Mark as unvisited + cell.setWaterLevel(-1); + } + } + } + + // 2. BFS Propagation + while (!q.empty()) { + Point curr = q.front(); + q.pop(); + + int currentDist = map[curr.x][curr.y].getWaterLevel(); + + // Optimization: Stop expanding if we hit the max clamp limit + if (currentDist >= MAX_LEVEL) { + continue; + } + + // Check neighbors + for (const auto& dir : dirs) { + int nx = curr.x + dir[0]; + int ny = curr.y + dir[1]; + + // Bounds Check: Ensure neighbor is strictly within IRegion2D + if (nx >= bounds.lo.x && nx < bounds.hi.x && + ny >= bounds.lo.y && ny < bounds.hi.y) { + + auto& neighbor = map[nx][ny]; + + // Only propagate into unvisited (-1) WATER cells + if (neighbor.getWaterLevel() == -1 && + neighbor.getType() == PathfindCell::CELL_WATER) { + + int newDist = currentDist + 1; + neighbor.setWaterLevel(newDist); + q.push({ nx, ny }); + } + } + } + } + + // 3. Final Cleanup + // Handle "Deep Ocean" or isolated water cells that BFS didn't reach + // (either because they are > 15 away, or isolated by bounds/obstacles) + for (int i = bounds.lo.x; i < bounds.hi.x; ++i) { + for (int j = bounds.lo.y; j < bounds.hi.y; ++j) { + if (map[i][j].getWaterLevel() == -1) { + map[i][j].setWaterLevel(MAX_LEVEL); + } + //DEBUG_LOG(("Cell %d, %d has waterlevel: %d", i, j, map[i][j].getWaterLevel())); + } + } +} + /** * Set up for a new map. */ @@ -4407,6 +4487,10 @@ void Pathfinder::classifyMap(void) } } #endif + DEBUG_LOG(("WATER LEVEL: Checking map")); + // set water depth values for ship navigation + calculateWaterLevels(m_extent, m_map); + for (i=0; igetType()); if ((locomotor->getLegalSurfaces() & acceptableSurfaces) == 0) return false; + + if ((locomotor->getAppearance() == LOCO_SHIP)) { + if (toCell->getWaterLevel() < locomotor->getRequireWaterLevel()) { + return false; + } + } return true; } @@ -4586,8 +4676,8 @@ void Pathfinder::cleanOpenAndClosedLists(void) { // // Return true if we can move onto this position // -Bool Pathfinder::validMovementPosition( Bool isCrusher, LocomotorSurfaceTypeMask acceptableSurfaces, - PathfindCell *toCell, PathfindCell *fromCell ) +Bool Pathfinder::validMovementPosition( Bool isCrusher, LocomotorSurfaceTypeMask acceptableSurfaces, Int requiredWaterLevel, + PathfindCell *toCell, PathfindCell *fromCell) { if (toCell == NULL) return false; @@ -4606,6 +4696,14 @@ Bool Pathfinder::validMovementPosition( Bool isCrusher, LocomotorSurfaceTypeMask if ((cellSurfaces & acceptableSurfaces) == 0) return false; + // Check for water depth validity of ships + if (requiredWaterLevel >0 && toCell->getType() == PathfindCell::CELL_WATER) { + if (toCell->getWaterLevel() < requiredWaterLevel) { + return false; + } + } + + return true; } @@ -5718,7 +5816,7 @@ struct ExamineCellsStruct Bool isCrusher = d->obj ? d->obj->getCrusherLevel() > 0 : false; if (d->thePathfinder->m_isTunneling) return 1; // abort. if (from && to) { - if (!d->thePathfinder->validMovementPosition( isCrusher, d->theLoco->getValidSurfaces(), to, from )) { + if (!d->thePathfinder->validMovementPosition( isCrusher, d->theLoco->getValidSurfaces(), d->theLoco->getRequiredWaterLevel(), to, from )) { return 1; } if ( (to->getLayer() == LAYER_GROUND) && !d->thePathfinder->m_zoneManager.isPassable(to_x, to_y) ) { @@ -5897,7 +5995,7 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * continue; } - Bool movementValid = validMovementPosition(isCrusher, locomotorSet.getValidSurfaces(), newCell, parentCell); + Bool movementValid = validMovementPosition(isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), newCell, parentCell); Bool dozerHack = false; if (!movementValid && obj->isKindOf(KINDOF_DOZER) && newCell->getType() == PathfindCell::CELL_OBSTACLE) { Object* obstacle = TheGameLogic->findObjectByID(newCell->getObstacleID()); @@ -6008,7 +6106,7 @@ Int Pathfinder::examineNeighboringCells(PathfindCell *parentCell, PathfindCell * } if (m_isTunneling) { - if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), newCell, parentCell )) { + if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), newCell, parentCell )) { newCostSoFar += 10*COST_ORTHOGONAL; } } @@ -6249,7 +6347,7 @@ Path *Pathfinder::internalFindPath( Object *obj, const LocomotorSet& locomotorSe m_isTunneling = false; // construct and return path - Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), from, goalCell, centerInCell, false ); + Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), from, goalCell, centerInCell, false, locomotorSet.getRequiredWaterLevel() ); #if RETAIL_COMPATIBLE_PATHFINDING if (!s_useFixedPathfinding) { @@ -7978,7 +8076,7 @@ Bool Pathfinder::pathDestination( Object *obj, const LocomotorSet& locomotorSet Real closestDistanceSqr = FLT_MAX; Coord3D closestPos; - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell ) == false) { + if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell ) == false) { parentCell->releaseInfo(); goalCell->releaseInfo(); return FALSE; @@ -8074,7 +8172,7 @@ Bool Pathfinder::pathDestination( Object *obj, const LocomotorSet& locomotorSet } } - if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), newCell, parentCell )) { + if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), newCell, parentCell )) { continue; } @@ -8254,7 +8352,7 @@ Int Pathfinder::checkPathCost(Object *obj, const LocomotorSet& locomotorSet, con } } - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell ) == false) { + if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell ) == false) { parentCell->releaseInfo(); goalCell->releaseInfo(); return MAX_COST; @@ -8358,7 +8456,7 @@ Int Pathfinder::checkPathCost(Object *obj, const LocomotorSet& locomotorSet, con } } - if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), newCell, parentCell )) { + if (!validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), newCell, parentCell )) { continue; } @@ -8539,7 +8637,7 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet if (parentCell == NULL) return NULL; - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell ) == false) { + if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell ) == false) { m_isTunneling = true; // We can't move from our current location. So relax the constraints. } TCheckMovementInfo info; @@ -8646,7 +8744,7 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet debugShowSearch(true); m_isTunneling = false; // construct and return path - Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), from, goalCell, centerInCell, blocked); + Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), from, goalCell, centerInCell, blocked, locomotorSet.getRequiredWaterLevel()); #if RETAIL_COMPATIBLE_PATHFINDING if (!s_useFixedPathfinding) { parentCell->releaseInfo(); @@ -8666,7 +8764,7 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet // put parent cell onto closed list - its evaluation is finished m_closedList = parentCell->putOnClosedList( m_closedList ); if (!m_isTunneling && checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), parentCell->getLayer(), radius, centerInCell)) { - if (!startedStuck || validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell )) { + if (!startedStuck || validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell )) { dx = IABS(goalCell->getXIndex()-parentCell->getXIndex()); dy = IABS(goalCell->getYIndex()-parentCell->getYIndex()); distSqr = dx*dx+dy*dy; @@ -8738,7 +8836,7 @@ Path *Pathfinder::findClosestPath( Object *obj, const LocomotorSet& locomotorSet rawTo->x = closesetCell->getXIndex()*PATHFIND_CELL_SIZE_F + PATHFIND_CELL_SIZE_F/2.0f; rawTo->y = closesetCell->getYIndex()*PATHFIND_CELL_SIZE_F + PATHFIND_CELL_SIZE_F/2.0f; // construct and return path - Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), from, closesetCell, centerInCell, blocked ); + Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), from, closesetCell, centerInCell, blocked, locomotorSet.getRequiredWaterLevel()); #if RETAIL_COMPATIBLE_PATHFINDING if (!s_useFixedPathfinding) { goalCell->releaseInfo(); @@ -8803,7 +8901,7 @@ void Pathfinder::adjustCoordToCell(Int cellX, Int cellY, Bool centerInCell, Coor * Work backwards from goal cell to construct final path. */ Path *Pathfinder::buildActualPath( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, const Coord3D *fromPos, - PathfindCell *goalCell, Bool center, Bool blocked ) + PathfindCell *goalCell, Bool center, Bool blocked, Int requiredWaterLevel) { DEBUG_ASSERTCRASH( goalCell, ("Pathfinder::buildActualPath: goalCell == NULL") ); @@ -8816,7 +8914,7 @@ Path *Pathfinder::buildActualPath( const Object *obj, LocomotorSurfaceTypeMask a prependCells(path, fromPos, goalCell, center); // cleanup the path by checking line of sight - path->optimize(obj, acceptableSurfaces, blocked); + path->optimize(obj, acceptableSurfaces, blocked, requiredWaterLevel); #if defined(RTS_DEBUG) if (TheGlobalData->m_debugAI==AI_DEBUG_PATHS) @@ -9425,6 +9523,7 @@ struct LinePassableStruct Bool centerInCell; Bool blocked; Bool allowPinched; + Int requiredWaterDepth; //Added for ships to not be able to get too close to coast }; /*static*/ Int Pathfinder::linePassableCallback(Pathfinder* pathfinder, PathfindCell* from, PathfindCell* to, Int to_x, Int to_y, void* userData) @@ -9460,11 +9559,15 @@ struct LinePassableStruct } } - if (pathfinder->validMovementPosition( isCrusher, d->acceptableSurfaces, to, from ) == false) + if (pathfinder->validMovementPosition( isCrusher, d->acceptableSurfaces, d->requiredWaterDepth, to, from ) == false) { return 1; // bail out } + if (to->getType() == PathfindCell::CELL_WATER && d->requiredWaterDepth > 0 && to->getWaterLevel() < d->requiredWaterDepth) { + return 1; // bail out, ship needs deeper water + } + return 0; // keep going } @@ -9498,7 +9601,7 @@ struct GroundPathPassableStruct Bool Pathfinder::isLinePassable( const Object *obj, LocomotorSurfaceTypeMask acceptableSurfaces, PathfindLayerEnum layer, const Coord3D& startWorld, const Coord3D& endWorld, Bool blocked, - Bool allowPinched) + Bool allowPinched, Int requiredWaterDepth) { LinePassableStruct info; //CRCDEBUG_LOG(("Pathfinder::isLinePassable(): %d %d %d ", m_ignoreObstacleID, m_isMapReady, m_isTunneling)); @@ -9508,6 +9611,7 @@ Bool Pathfinder::isLinePassable( const Object *obj, LocomotorSurfaceTypeMask acc getRadiusAndCenter(obj, info.radius, info.centerInCell); info.blocked = blocked; info.allowPinched = allowPinched; + info.requiredWaterDepth = requiredWaterDepth; Int ret = iterateCellsAlongLine(startWorld, endWorld, layer, linePassableCallback, (void*)&info); return ret == 0; @@ -10103,7 +10207,7 @@ Path *Pathfinder::getMoveAwayFromPath(Object* obj, Object *otherObj, const LocomotorSet& locomotorSet = obj->getAIUpdateInterface()->getLocomotorSet(); m_isTunneling = false; - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell ) == false) { + if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell ) == false) { m_isTunneling = true; // We can't move from our current location. So relax the constraints. } @@ -10194,7 +10298,7 @@ Path *Pathfinder::getMoveAwayFromPath(Object* obj, Object *otherObj, debugShowSearch(true); m_isTunneling = false; // construct and return path - Path *newPath = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false); + Path *newPath = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false, locomotorSet.getRequiredWaterLevel()); #if RETAIL_COMPATIBLE_PATHFINDING if (!s_useFixedPathfinding) { parentCell->releaseInfo(); @@ -10405,7 +10509,7 @@ Path *Pathfinder::patchPath( const Object *obj, const LocomotorSet& locomotorSet prependCells(path, obj->getPosition(), parentCell, centerInCell); // cleanup the path by checking line of sight - path->optimize(obj, locomotorSet.getValidSurfaces(), blocked); + path->optimize(obj, locomotorSet.getValidSurfaces(), blocked, locomotorSet.getRequiredWaterLevel()); #if RETAIL_COMPATIBLE_PATHFINDING if (!s_useFixedPathfinding) { parentCell->releaseInfo(); @@ -10491,10 +10595,9 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot ICoord2D cellNdx; worldToCell(&testPos, &cellNdx); PathfindCell *aCell = getCell(obj->getLayer(), cellNdx.x, cellNdx.y); - if (!aCell) - break; + if (!aCell) break; - if (!validMovementPosition(isCrusher, locomotorSet.getValidSurfaces(), aCell)) + if (!validMovementPosition(isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), aCell)) break; if (!checkDestination(obj, cellNdx.x, cellNdx.y, obj->getLayer(), radius, centerInCell)) @@ -10718,7 +10821,7 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot } } } - Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false); + Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false, locomotorSet.getRequiredWaterLevel()); #if RETAIL_COMPATIBLE_PATHFINDING if (!s_useFixedPathfinding) { if (goalCell->hasInfo() && !goalCell->getClosed() && !goalCell->getOpen()) { @@ -10737,7 +10840,7 @@ Path *Pathfinder::findAttackPath( const Object *obj, const LocomotorSet& locomot } } if (checkDestination(obj, parentCell->getXIndex(), parentCell->getYIndex(), parentCell->getLayer(), radius, centerInCell)) { - if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), parentCell )) { + if (validMovementPosition( isCrusher, locomotorSet.getValidSurfaces(), locomotorSet.getRequiredWaterLevel(), parentCell )) { Real dx = IABS(victimCellNdx.x-parentCell->getXIndex()); Real dy = IABS(victimCellNdx.y-parentCell->getYIndex()); Real distSqr = dx*dx+dy*dy; @@ -10921,7 +11024,7 @@ Path *Pathfinder::findSafePath( const Object *obj, const LocomotorSet& locomotor //DEBUG_LOG(("Attack path took %d cells, %f sec", cellCount, (::GetTickCount()-startTimeMS)/1000.0f)); #endif // construct and return path - Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false); + Path *path = buildActualPath( obj, locomotorSet.getValidSurfaces(), obj->getPosition(), parentCell, centerInCell, false, locomotorSet.getRequiredWaterLevel()); #if RETAIL_COMPATIBLE_PATHFINDING if (!s_useFixedPathfinding) { parentCell->releaseInfo(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp index 6af3e05b1b3..01fa0431369 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp @@ -2637,7 +2637,7 @@ void TerrainLogic::setActiveBoundary(Int newActiveBoundary) // ------------------------------------------------------------------------------------------------ void TerrainLogic::flattenTerrain(Object *obj) { - if (obj->getGeometryInfo().getIsSmall()) { + if (obj->getGeometryInfo().getIsSmall() || obj->isKindOf(KINDOF_SHIPYARD)) { return; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp index a65e20e7396..be90acd23b0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp @@ -711,6 +711,14 @@ Object* DumbProjectileBehavior::getTargetObject() return TheGameLogic->findObjectByID(m_victimID); } +bool DumbProjectileBehavior::projectileShouldCollideWithWater() const +{ + if (m_detonationWeaponTmpl != nullptr) { + return m_detonationWeaponTmpl->getProjectileCollideMask() & WeaponCollideMaskType::WEAPON_COLLIDE_WATER; + } + return false; +} + // ------------------------------------------------------------------------------------------------ /** displayFlightPath for debugging */ // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp index b55ef3999f0..47401e6bc9a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp @@ -399,6 +399,15 @@ Object* FreeFallProjectileBehavior::getTargetObject() return TheGameLogic->findObjectByID(m_victimID); } +bool FreeFallProjectileBehavior::projectileShouldCollideWithWater() const +{ + if (m_detonationWeaponTmpl != nullptr) { + return m_detonationWeaponTmpl->getProjectileCollideMask() & WeaponCollideMaskType::WEAPON_COLLIDE_WATER; + } + return false; +} + + // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index dac09904f4b..4d9f5cefcaf 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -45,8 +45,8 @@ #include "GameLogic/Module/PhysicsUpdate.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/Module/AIUpdate.h" - - +#include "vector3.h" +#include static const Real DONUT_TIME_DELAY_SECONDS=2.5f; static const Real DONUT_DISTANCE=4.0*PATHFIND_CELL_SIZE_F; @@ -350,6 +350,7 @@ LocomotorTemplate::LocomotorTemplate() m_elevatorCorrectionDegree = 0.0f; m_elevatorCorrectionRate = 0.0f; + m_requiredWaterLevel = 0; } //------------------------------------------------------------------------------------------------- @@ -422,6 +423,13 @@ void LocomotorTemplate::validate() m_minSpeed = 0.01f; } } + + if (m_appearance != LOCO_SHIP) { + if (m_requiredWaterLevel > 0) { + DEBUG_CRASH(("Non SHIP locomotors should not have a 'RequiredWaterLevel' of greater than 0!")); + } + } + } //------------------------------------------------------------------------------------------------- @@ -500,7 +508,8 @@ const FieldParse* LocomotorTemplate::getFieldParse() const { "RudderCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionRate) }, { "ElevatorCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionDegree) }, { "ElevatorCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionRate) }, - { NULL, NULL, NULL, 0 } + { "RequiredWaterLevel", INI::parseUnsignedInt, NULL, offsetof(LocomotorTemplate, m_requiredWaterLevel)}, + { NULL, NULL, NULL, 0 } // keep this last }; return TheFieldParse; @@ -1097,6 +1106,7 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP case LOCO_TREADS: moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); break; + case LOCO_SHIP: case LOCO_HOVER: moveTowardsPositionHover(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); break; @@ -2504,6 +2514,7 @@ Bool Locomotor::locoUpdate_maintainCurrentPosition(Object* obj) maintainCurrentPositionTreads(obj, physics); requiresConstantCalling = FALSE; break; + case LOCO_SHIP: case LOCO_HOVER: maintainCurrentPositionHover(obj, physics); requiresConstantCalling = TRUE; @@ -2648,7 +2659,7 @@ LocomotorSet::LocomotorSet() m_locomotors.clear(); m_validLocomotorSurfaces = 0; m_downhillOnly = FALSE; - + m_requiredWaterLevel = 0; } //------------------------------------------------------------------------------------------------- @@ -2737,7 +2748,7 @@ void LocomotorSet::xfer( Xfer *xfer ) xfer->xferInt(&m_validLocomotorSurfaces); xfer->xferBool(&m_downhillOnly); - + xfer->xferInt(&m_requiredWaterLevel); } // ------------------------------------------------------------------------------------------------ @@ -2805,6 +2816,11 @@ void LocomotorSet::addLocomotor(const LocomotorTemplate* lt) if (loco) { m_locomotors.push_back(loco); + + if (loco->getAppearance() == LOCO_SHIP) { + m_requiredWaterLevel = std::max(m_requiredWaterLevel, loco->getRequireWaterLevel()); + } + m_validLocomotorSurfaces |= loco->getLegalSurfaces(); if (loco->getIsDownhillOnly()) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index b70559767f3..70112eb1f26 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -1773,7 +1773,7 @@ Bool AIUpdateInterface::computePath( PathfindServicesInterface *pathServices, Co LocomotorSurfaceTypeMask surfaces = m_locomotorSet.getValidSurfaces(); if (!m_isFinalGoal && TheAI->pathfinder()->isLinePassable( getObject(), surfaces, - getObject()->getLayer(), *getObject()->getPosition(), originalDestination, false, true)) { + getObject()->getLayer(), *getObject()->getPosition(), originalDestination, false, true, m_locomotorSet.getRequiredWaterLevel())) { return computeQuickPath(destination); } @@ -2355,8 +2355,10 @@ UpdateSleepTime AIUpdateInterface::doLocomotor( void ) } // After our movement for the frame, update our AirborneTarget flag. - if(getObject()->getHeightAboveTerrain() > m_curLocomotor->getAirborneTargetingHeight() ) + if(TheGlobalData->m_heightAboveTerrainIncludesWater && getObject()->getHeightAboveTerrainOrWater() > m_curLocomotor->getAirborneTargetingHeight() ) getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_AIRBORNE_TARGET ) ); + else if (!TheGlobalData->m_heightAboveTerrainIncludesWater && getObject()->getHeightAboveTerrain() > m_curLocomotor->getAirborneTargetingHeight()) + getObject()->setStatus(MAKE_OBJECT_STATUS_MASK(OBJECT_STATUS_AIRBORNE_TARGET)); else getObject()->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_AIRBORNE_TARGET ) ); @@ -2486,6 +2488,10 @@ 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/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index cc654ea0702..59a6207279a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -999,6 +999,14 @@ Object* MissileAIUpdate::getTargetObject() return getGoalObject(); } +bool MissileAIUpdate::projectileShouldCollideWithWater() const +{ + if (m_detonationWeaponTmpl != nullptr) { + return m_detonationWeaponTmpl->getProjectileCollideMask() & WeaponCollideMaskType::WEAPON_COLLIDE_WATER; + } + return false; +} + // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index b14864d242c..c5f836a7737 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -836,7 +836,19 @@ UpdateSleepTime PhysicsBehavior::update() applyForce(&bounceForce); } - Bool airborneAtEnd = obj->isAboveTerrain(); + Bool isWaterCollision{ false }; + Bool airborneAtEnd{ false }; + if (m_pui != nullptr && m_pui->projectileShouldCollideWithWater()) { + airborneAtEnd = obj->isAboveTerrainOrWater(); + + if (!airborneAtEnd) { + const Coord3D* pos = obj->getPosition(); + isWaterCollision = TheTerrainLogic->isUnderwater(pos->x, pos->y); + } + } + else { + airborneAtEnd = obj->isAboveTerrain(); + } // it's not good enough to check for airborne being different between // the start and end of this func... we have to compare since last frame, diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index d007264ad28..080d63f18a7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -252,6 +252,7 @@ const FieldParse WeaponTemplate::TheWeaponTemplateFieldParseTable[] = { "ContinuousLaserLoopTime", INI::parseDurationUnsignedInt, NULL, offsetof(WeaponTemplate, m_continuousLaserLoopTime) }, { "LaserGroundTargetHeight", INI::parseReal, NULL, offsetof(WeaponTemplate, m_laserGroundTargetHeight) }, { "LaserGroundUnitTargetHeight", INI::parseReal, NULL, offsetof(WeaponTemplate, m_laserGroundUnitTargetHeight) }, + { "ScatterOnWaterSurface", INI::parseBool, NULL, offsetof(WeaponTemplate, m_scatterOnWaterSurface) }, { NULL, NULL, NULL, 0 } // keep this last }; @@ -345,7 +346,7 @@ WeaponTemplate::WeaponTemplate() : m_nextTemplate(NULL) m_scatterTargetResetTime = 0; m_preAttackFXDelay = 6; // Non-Zero default! 6 frames = 200ms. This should be a good base value to avoid spamming m_laserGroundUnitTargetHeight = 10; // Default Height offset - + m_scatterOnWaterSurface = false; m_historicDamageTriggerId = 0; } @@ -1025,7 +1026,16 @@ UnsignedInt WeaponTemplate::fireWeaponTemplate //If we aim for the center point of our target and miss, the shot will go much farther than //we expect! // srj sez: we should actually fire at the layer the victim is on, if possible, in case it is on a bridge... - projectileDestination.z = TheTerrainLogic->getLayerHeight( projectileDestination.x, projectileDestination.y, targetLayer ); + + if (targetLayer == LAYER_GROUND && firingWeapon->getTemplate()->isScatterOnWaterSurface()) { + Real waterZ; + Real terrainZ; + TheTerrainLogic->isUnderwater(projectileDestination.x, projectileDestination.y, &waterZ, &terrainZ); + projectileDestination.z = std::max(waterZ, terrainZ); + } + else { + projectileDestination.z = TheTerrainLogic->getLayerHeight(projectileDestination.x, projectileDestination.y, targetLayer); + } } if (getProjectileTemplate() == NULL || isProjectileDetonation) @@ -2979,7 +2989,15 @@ Bool Weapon::privateFireWeapon( targetPos.x += scatterOffset.x; targetPos.y += scatterOffset.y; - targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); + if (m_template->isScatterOnWaterSurface()) { + Real waterZ; + Real terrainZ; + TheTerrainLogic->isUnderwater(targetPos.x, targetPos.y, &waterZ, &terrainZ); + targetPos.z = std::max(waterZ, terrainZ); + } + else { + targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); + } // Note AW: We have to ignore Ranges when using ScatterTargets, or else the weapon can fail in the next stage ignoreRanges = TRUE; diff --git a/GeneralsMD/Code/Libraries/Include/Lib/BaseType.h b/GeneralsMD/Code/Libraries/Include/Lib/BaseType.h index 33131c51ef2..293ff830797 100644 --- a/GeneralsMD/Code/Libraries/Include/Lib/BaseType.h +++ b/GeneralsMD/Code/Libraries/Include/Lib/BaseType.h @@ -197,6 +197,7 @@ struct Coord2D Real toAngle( void ) const; ///< turn 2D vector into angle (where angle 0 is down the +x axis) + void rotateByAngle(Real angle); ///< rotate the vector by the given angle (radians) }; inline Real Coord2D::toAngle( void ) const @@ -215,6 +216,18 @@ inline Real Coord2D::toAngle( void ) const return y < 0.0f ? -ACos(c) : ACos(c); } +inline void Coord2D::rotateByAngle(Real angle) +{ + Real cs = Cos(angle); + Real sn = Sin(angle); + + Real px = x * cs - y * sn; + Real py = x * sn + y * cs; + + x = px; + y = py; +} + struct ICoord2D { Int x, y;