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..4e8519f8f9b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp @@ -542,6 +542,14 @@ 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 ); + + 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(); 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..24725c72c0b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -8894,6 +8894,13 @@ 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 ); + + 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; + } + } } 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;