diff --git a/Core/GameEngine/Source/GameClient/FXList.cpp b/Core/GameEngine/Source/GameClient/FXList.cpp index e711c0ed60c..5251fc4e244 100644 --- a/Core/GameEngine/Source/GameClient/FXList.cpp +++ b/Core/GameEngine/Source/GameClient/FXList.cpp @@ -945,7 +945,7 @@ class ParticleSystemFXNugget : public FXNugget if (!isValidSurface(primary, &info)) continue; - refHeight = surfaceInfo->m_isWater ? info.m_waterHeight : info.m_groundHeight; + refHeight = info.m_isWater ? info.m_waterHeight : info.m_groundHeight; } if (m_createAtGroundHeight) { diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h index 4dc5ad10018..9e22ca724f1 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DeliverPayloadAIUpdate.h @@ -271,6 +271,7 @@ class DeliverPayloadData const WeaponTemplate *m_visiblePayloadWeaponTemplate; RadiusDecalTemplate m_deliveryDecalTemplate; Real m_deliveryDecalRadius; + Bool m_strafingWeaponTargetsWater; DeliverPayloadData() { @@ -298,6 +299,7 @@ class DeliverPayloadData m_visibleDropBoneName.clear(); m_visiblePayloadTemplateName.clear(); m_visibleSubObjectName.clear(); + m_strafingWeaponTargetsWater = false; } static const FieldParse* getFieldParse(); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParticleUplinkCannonUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParticleUplinkCannonUpdate.h index 28d42e1f1a9..49940f163b4 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParticleUplinkCannonUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParticleUplinkCannonUpdate.h @@ -103,6 +103,7 @@ class ParticleUplinkCannonUpdateModuleData : public ModuleData Real m_manualDrivingSpeed; Real m_manualFastDrivingSpeed; UnsignedInt m_doubleClickToFastDriveDelay; + Bool m_hitWaterSurface; ParticleUplinkCannonUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpectreGunshipUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpectreGunshipUpdate.h index 6660616ace3..75e325bacbc 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpectreGunshipUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/SpectreGunshipUpdate.h @@ -66,8 +66,10 @@ class SpectreGunshipUpdateModuleData : public ModuleData Real m_strafingIncrement; Real m_orbitInsertionSlope; Real m_randomOffsetForHowitzer; + Bool m_hitWaterSurface; const ParticleSystemTemplate * m_gattlingStrafeFXParticleSystem; + const ParticleSystemTemplate * m_gattlingStrafeFXParticleSystemWater; SpectreGunshipUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp index fa6fbd12cef..b2d0f2c1302 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ObjectCreationList.cpp @@ -1146,6 +1146,13 @@ class GenericObjectCreationNugget : public ObjectCreationNugget chunkPos.z = getGroundHeight(pos, layer); obj->setLayer(layer); obj->setPosition(&chunkPos); + + //Set water model condition if demotrap on water + if (obj->isKindOf(KINDOF_DEMOTRAP)) { + if (TheTerrainLogic->isUnderwater(chunkPos.x, chunkPos.y)) { + obj->setModelConditionState(MODELCONDITION_OVER_WATER); + } + } } if( BitIsSet( m_disposition, SEND_IT_OUT ) ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp index ffb362a2ffb..d321498e39a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/DeliverPayloadAIUpdate.cpp @@ -106,6 +106,8 @@ const FieldParse* DeliverPayloadData::getFieldParse() { "DeliveryDecal", RadiusDecalTemplate::parseRadiusDecalTemplate, nullptr, offsetof( DeliverPayloadData, m_deliveryDecalTemplate ) }, { "DeliveryDecalRadius", INI::parseReal, nullptr, offsetof(DeliverPayloadData, m_deliveryDecalRadius) }, + { "StrafingWeaponTargetsWater", INI::parseBool, nullptr, offsetof(DeliverPayloadData, m_strafingWeaponTargetsWater) }, + { nullptr, nullptr, nullptr, 0 } }; return dataFieldParse; @@ -240,6 +242,12 @@ UpdateSleepTime DeliverPayloadAIUpdate::update( void ) strafePoint.add( &velocity ); strafePoint.z = TheTerrainLogic->getGroundHeight( strafePoint.x, strafePoint.y ); + if (getData()->m_strafingWeaponTargetsWater) { + Real waterz{ 0 }; + if (TheTerrainLogic->isUnderwater(strafePoint.x, strafePoint.y, &waterz)) { + strafePoint.z = waterz; + } + } // lock it just till the weapon is empty or the attack is "done" getObject()->setWeaponLock( m_data.m_strafingWeaponSlot, LOCKED_TEMPORARILY ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp index e360f8842ff..b8de4d0ae4d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp @@ -90,6 +90,7 @@ ParticleUplinkCannonUpdateModuleData::ParticleUplinkCannonUpdateModuleData() m_doubleClickToFastDriveDelay = 500; m_swathOfDeathAmplitude = 0.0f; m_swathOfDeathDistance = 0.0f; + m_hitWaterSurface = false; } //------------------------------------------------------------------------------------------------- @@ -150,6 +151,7 @@ ParticleUplinkCannonUpdateModuleData::ParticleUplinkCannonUpdateModuleData() { "ManualDrivingSpeed", INI::parseReal, nullptr, offsetof( ParticleUplinkCannonUpdateModuleData, m_manualDrivingSpeed ) }, { "ManualFastDrivingSpeed", INI::parseReal, nullptr, offsetof( ParticleUplinkCannonUpdateModuleData, m_manualFastDrivingSpeed ) }, { "DoubleClickToFastDriveDelay", INI::parseDurationUnsignedInt, nullptr, offsetof( ParticleUplinkCannonUpdateModuleData, m_doubleClickToFastDriveDelay ) }, + { "HitWaterSurface", INI::parseBool, nullptr, offsetof( ParticleUplinkCannonUpdateModuleData, m_hitWaterSurface ) }, { nullptr, nullptr, nullptr, 0 } }; @@ -622,8 +624,18 @@ UpdateSleepTime ParticleUplinkCannonUpdate::update() m_currentTargetPosition.y += vector.y; } - //Regardless of which method we used to set the target position, make sure the z position is at the terrain. - m_currentTargetPosition.z = TheTerrainLogic->getGroundHeight( m_currentTargetPosition.x, m_currentTargetPosition.y ); + //Regardless of which method we used to set the target position, make sure the z position is at the terrain + //(or, when configured, at the water surface over water). + Real waterZ; + if( data->m_hitWaterSurface && + TheTerrainLogic->isUnderwater( m_currentTargetPosition.x, m_currentTargetPosition.y, &waterZ ) ) + { + m_currentTargetPosition.z = waterZ; + } + else + { + m_currentTargetPosition.z = TheTerrainLogic->getGroundHeight( m_currentTargetPosition.x, m_currentTargetPosition.y ); + } Coord3D orbitPosition; orbitPosition.set( &m_currentTargetPosition ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpectreGunshipUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpectreGunshipUpdate.cpp index 9d117b02808..70a58192324 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpectreGunshipUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/SpectreGunshipUpdate.cpp @@ -83,6 +83,7 @@ SpectreGunshipUpdateModuleData::SpectreGunshipUpdateModuleData() m_specialPowerTemplate = nullptr; /******BOTH*******//*BOTH*//******BOTH*******//******BOTH*******/ m_attackAreaRadius = 200.0f; /*************/ m_gattlingStrafeFXParticleSystem = nullptr; +/*************/ m_gattlingStrafeFXParticleSystemWater = nullptr; /*************/ m_howitzerWeaponTemplate = nullptr; /*************/ m_orbitFrames = 0; /*************/ m_targetingReticleRadius = 25.0f; @@ -92,6 +93,7 @@ SpectreGunshipUpdateModuleData::SpectreGunshipUpdateModuleData() m_howitzerFiringRate = 10; m_howitzerFollowLag = 0; m_randomOffsetForHowitzer = 20.0f; + m_hitWaterSurface = false; } static Real zero = 0.0f; @@ -115,8 +117,10 @@ static Real zero = 0.0f; { "GunshipOrbitRadius", INI::parseReal, nullptr, offsetof( SpectreGunshipUpdateModuleData, m_gunshipOrbitRadius ) }, { "HowitzerWeaponTemplate", INI::parseWeaponTemplate, nullptr, offsetof( SpectreGunshipUpdateModuleData, m_howitzerWeaponTemplate ) }, { "GattlingStrafeFXParticleSystem", INI::parseParticleSystemTemplate, nullptr, offsetof( SpectreGunshipUpdateModuleData, m_gattlingStrafeFXParticleSystem ) }, + { "GattlingStrafeFXParticleSystemWater", INI::parseParticleSystemTemplate, nullptr, offsetof( SpectreGunshipUpdateModuleData, m_gattlingStrafeFXParticleSystemWater ) }, { "AttackAreaDecal", RadiusDecalTemplate::parseRadiusDecalTemplate, nullptr, offsetof( SpectreGunshipUpdateModuleData, m_attackAreaDecalTemplate ) }, { "TargetingReticleDecal", RadiusDecalTemplate::parseRadiusDecalTemplate, nullptr, offsetof( SpectreGunshipUpdateModuleData, m_targetingReticleDecalTemplate ) }, + { "HitWaterSurface", INI::parseBool, nullptr, offsetof( SpectreGunshipUpdateModuleData, m_hitWaterSurface ) }, @@ -576,13 +580,23 @@ UpdateSleepTime SpectreGunshipUpdate::update() + //Aim at the water surface over water when configured. Use a copy so the strafing/winding + //math on m_gattlingTargetPosition (below) keeps its original 3D behavior. + Coord3D gattlingAimPos = m_gattlingTargetPosition; + if( data->m_hitWaterSurface ) + { + Real waterZ; + if( TheTerrainLogic->isUnderwater( gattlingAimPos.x, gattlingAimPos.y, &waterZ ) ) + gattlingAimPos.z = waterZ; + } + //lets keep a constant barrage of gattling bullets on the current aim location if( gattlingAI ) { if ( validTargetObject )// either in the reticle or the targeting area gattlingAI->aiAttackObject( validTargetObject, LOTS_OF_SHOTS, CMD_FROM_AI ); else - gattlingAI->aiAttackPosition( &m_gattlingTargetPosition, LOTS_OF_SHOTS, CMD_FROM_AI ); + gattlingAI->aiAttackPosition( &gattlingAimPos, LOTS_OF_SHOTS, CMD_FROM_AI ); } @@ -596,9 +610,9 @@ UpdateSleepTime SpectreGunshipUpdate::update() { Coord3D attackPositionWithRandomOffset; Real offs = data->m_randomOffsetForHowitzer; - attackPositionWithRandomOffset.x = m_gattlingTargetPosition.x + GameLogicRandomValue( -offs, offs ); - attackPositionWithRandomOffset.y = m_gattlingTargetPosition.y + GameLogicRandomValue( -offs, offs ); - attackPositionWithRandomOffset.z = m_gattlingTargetPosition.z; + attackPositionWithRandomOffset.x = gattlingAimPos.x + GameLogicRandomValue( -offs, offs ); + attackPositionWithRandomOffset.y = gattlingAimPos.y + GameLogicRandomValue( -offs, offs ); + attackPositionWithRandomOffset.z = gattlingAimPos.z; TheWeaponStore->createAndFireTempWeapon( wt, gunship, &attackPositionWithRandomOffset ); m_howitzerFireSound.setObjectID(gunship->getID()); @@ -649,13 +663,23 @@ UpdateSleepTime SpectreGunshipUpdate::update() { // This makes the client smoke effects of the gattling cannon strafing the ground toward the attack position - ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(tmp); - if (sys) - { Coord3D impactPosition; impactPosition.x = m_gattlingTargetPosition.x + GameClientRandomValueReal( -5.0f, 5.0f ); impactPosition.y = m_gattlingTargetPosition.y + GameClientRandomValueReal( -5.0f, 5.0f ); - impactPosition.z = TheTerrainLogic->getGroundHeight( impactPosition.x, impactPosition.y ); + + Real waterZ, terrainZ; + const Bool overWater = data->m_hitWaterSurface && + TheTerrainLogic->isUnderwater( impactPosition.x, impactPosition.y, &waterZ, &terrainZ ); + + // Over water, prefer the water-specific FX when one is configured. + const ParticleSystemTemplate *useTmp = tmp; + if( overWater && data->m_gattlingStrafeFXParticleSystemWater ) + useTmp = data->m_gattlingStrafeFXParticleSystemWater; + + ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(useTmp); + if (sys) + { + impactPosition.z = overWater ? waterZ : TheTerrainLogic->getGroundHeight( impactPosition.x, impactPosition.y ); sys->setPosition( &impactPosition ); }