#include "CScriptWater.hpp" #include "CStateManager.hpp" #include "TCastTo.hpp" #include "GameGlobalObjects.hpp" #include "CSimplePool.hpp" #include "World/CWorld.hpp" #include "Graphics/CBooRenderer.hpp" #include "Camera/CGameCamera.hpp" namespace urde { const float CScriptWater::kSplashScales[6] = { 1.0f, 3.0f, 0.709f, 1.19f, 0.709f, 1.f }; CScriptWater::CScriptWater(CStateManager& mgr, TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& pos, const zeus::CAABox& box, const urde::CDamageInfo& dInfo, zeus::CVector3f& orientedForce, ETriggerFlags triggerFlags, bool thermalCold, bool allowRender, CAssetId patternMap1, CAssetId patternMap2, CAssetId colorMap, CAssetId bumpMap, CAssetId envMap, CAssetId envBumpMap, CAssetId unusedMap, const zeus::CVector3f& bumpLightDir, float bumpScale, float morphInTime, float morphOutTime, bool active, CFluidPlane::EFluidType fluidType, bool b4, float alpha, const CFluidUVMotion& uvMot, float turbSpeed, float turbDistance, float turbFreqMax, float turbFreqMin, float turbPhaseMax, float turbPhaseMin, float turbAmplitudeMax, float turbAmplitudeMin, const zeus::CColor& splashColor, const zeus::CColor& unkColor, CAssetId splashParticle1, CAssetId splashParticle2, CAssetId splashParticle3, CAssetId visorRunoffParticle, CAssetId unmorphVisorRunoffparticle, s32 visorRunoffSfx, s32 unmorphVisorRunoffSfx, s32 splashSfx1, s32 splashSfx2, s32 splashSfx3, float tileSize, u32 tileSubdivisions, float specularMin, float specularMax, float reflectionSize, float rippleIntensity, float reflectionBlend, float fogBias, float fogMagnitude, float fogSpeed, const zeus::CColor& fogColor, CAssetId lightmapId, float unitsPerLightmapTexel, float alphaInTime, float alphaOutTime, u32, u32, bool, s32, s32, std::unique_ptr&& u32Arr) : CScriptTrigger(uid, name, info, pos, box, dInfo, orientedForce, triggerFlags, active, false, false), x1b8_positionMorphed(pos), x1c4_extentMorphed(box.max - box.min), x1d0_morphInTime(morphInTime), x1d4_positionOrig(pos), x1e0_extentOrig(box.max - box.min), x1ec_damageOrig(dInfo.GetDamage()), x1f0_damageMorphed(dInfo.GetDamage()), x1f4_morphOutTime(morphOutTime), x214_fogBias(fogBias), x218_fogMagnitude(fogMagnitude), x21c_origFogBias(fogBias), x220_origFogMagnitude(fogMagnitude), x224_fogSpeed(fogSpeed), x228_fogColor(fogColor), x22c_splashParticle1Id(splashParticle1), x230_splashParticle2Id(splashParticle2), x234_splashParticle3Id(splashParticle3), x238_visorRunoffParticleId(visorRunoffParticle), x24c_unmorphVisorRunoffParticleId(unmorphVisorRunoffparticle), x260_visorRunoffSfx(CSfxManager::TranslateSFXID(visorRunoffSfx)), x262_unmorphVisorRunoffSfx(CSfxManager::TranslateSFXID(unmorphVisorRunoffSfx)), x2a4_splashColor(splashColor), x2a8_fogColor(unkColor), x2ac_alphaInTime(alphaInTime), x2b0_alphaOutTime(alphaOutTime), x2b4_alphaInRecip((alphaInTime != 0.f) ? 1.f / alphaInTime : 0.f), x2b8_alphaOutRecip((alphaOutTime != 0.f) ? 1.f / alphaOutTime : 0.f), x2bc_alpha(alpha), x2c0_tileSize(tileSize) { zeus::CAABox triggerAABB = GetTriggerBoundsWR(); x2c4_gridDimX = u32((x2c0_tileSize + triggerAABB.max.x - triggerAABB.min.x - 0.01f) / x2c0_tileSize); x2c8_gridDimY = u32((x2c0_tileSize + triggerAABB.max.y - triggerAABB.min.y - 0.01f) / x2c0_tileSize); x2cc_gridCellCount = (x2c4_gridDimX + 1) * (x2c8_gridDimY + 1); x2e8_24_b4 = b4; x2e8_27_allowRender = allowRender; x2e8_28_recomputeClipping = true; x1b4_fluidPlane = std::make_unique(patternMap1, patternMap2, colorMap, bumpMap, envMap, envBumpMap, lightmapId, unitsPerLightmapTexel, tileSize, tileSubdivisions, fluidType, x2bc_alpha, bumpLightDir, bumpScale, uvMot, turbSpeed, turbDistance, turbFreqMax, turbFreqMin, turbPhaseMax, turbPhaseMin, turbAmplitudeMax, turbAmplitudeMin, specularMin, specularMax, reflectionBlend, reflectionSize, rippleIntensity, x2cc_gridCellCount * ((std::max(u32(2), tileSubdivisions) * 4 + 2) * 4)); u32Arr.reset(); x264_splashEffects.resize(3); if (x22c_splashParticle1Id.IsValid()) x264_splashEffects[0].emplace(g_SimplePool->GetObj({FOURCC('PART'), x22c_splashParticle1Id})); if (x230_splashParticle2Id.IsValid()) x264_splashEffects[1].emplace(g_SimplePool->GetObj({FOURCC('PART'), x230_splashParticle2Id})); if (x234_splashParticle3Id.IsValid()) x264_splashEffects[2].emplace(g_SimplePool->GetObj({FOURCC('PART'), x234_splashParticle3Id})); if (x238_visorRunoffParticleId.IsValid()) x23c_visorRunoffEffect.emplace(g_SimplePool->GetObj({FOURCC('PART'), x238_visorRunoffParticleId})); if (x24c_unmorphVisorRunoffParticleId.IsValid()) x250_unmorphVisorRunoffEffect.emplace(g_SimplePool->GetObj({FOURCC('PART'), x24c_unmorphVisorRunoffParticleId})); x298_splashSounds.push_back(CSfxManager::TranslateSFXID(splashSfx1)); x298_splashSounds.push_back(CSfxManager::TranslateSFXID(splashSfx2)); x298_splashSounds.push_back(CSfxManager::TranslateSFXID(splashSfx3)); SetCalculateLighting(true); if (lightmapId.IsValid()) x90_actorLights->DisableAreaLights(); x90_actorLights->SetMaxDynamicLights(4); x90_actorLights->SetCastShadows(false); x90_actorLights->SetAmbientChannelOverflow(false); x90_actorLights->SetFindNearestDynamicLights(true); x148_24_detectCamera = true; CalculateRenderBounds(); xe6_27_renderVisorFlags = u8(thermalCold ? 2 : 1); if (!x30_24_active) { x2bc_alpha = 0.f; x214_fogBias = 0.f; x218_fogMagnitude = 0.f; } SetupGrid(true); } void CScriptWater::SetupGrid(bool recomputeClipping) { zeus::CAABox triggerAABB = GetTriggerBoundsWR(); auto dimX = u32((triggerAABB.max.x - triggerAABB.min.x + x2c0_tileSize) / x2c0_tileSize); auto dimY = u32((triggerAABB.max.y - triggerAABB.min.y + x2c0_tileSize) / x2c0_tileSize); x2e4_gridCellCount2 = x2cc_gridCellCount = (dimX + 1) * (dimY + 1); x2dc_vertIntersects.reset(); if (!x2d8_tileIntersects || dimX != x2c4_gridDimX || dimY != x2c8_gridDimY) x2d8_tileIntersects.reset(new bool[dimX * dimY]); x2c4_gridDimX = dimX; x2c8_gridDimY = dimY; for (int i=0 ; i= x2cc_gridCellCount) return; if (!x2dc_vertIntersects) x2dc_vertIntersects.reset(new bool[(x2c4_gridDimX + 1) * (x2c8_gridDimY + 1)]); zeus::CAABox triggerBounds = GetTriggerBoundsWR(); zeus::CVector3f basePos = triggerBounds.min; basePos.z = triggerBounds.max.z + 0.8f; auto gridDiv = std::div(int(x2e4_gridCellCount2), int(x2c4_gridDimX + 1)); float yOffset = x2c0_tileSize * gridDiv.quot; float xOffset = x2c0_tileSize * gridDiv.rem; float mag = std::min(120.f, 2.f * (x130_bounds.max.z - x130_bounds.min.z) + 0.8f); for (int i = x2e4_gridCellCount2; i < std::min(x2e4_gridCellCount2 + computeVerts, x2cc_gridCellCount); ++i) { zeus::CVector3f pos = basePos; pos.x += xOffset; pos.y += yOffset; x2dc_vertIntersects[i] = mgr.RayStaticIntersection(pos, zeus::CVector3f::skDown, mag, SolidFilter).IsValid(); gridDiv.rem += 1; xOffset += x2c0_tileSize; if (gridDiv.rem > x2c4_gridDimX) { yOffset += x2c0_tileSize; xOffset = 0.f; gridDiv.rem = 0; } } x2e4_gridCellCount2 += computeVerts; if (x2e4_gridCellCount2 < x2cc_gridCellCount) return; x2e4_gridCellCount2 = x2cc_gridCellCount; x2d8_tileIntersects.reset(new bool[x2c4_gridDimX * x2c8_gridDimY]); for (int i = 0; i < x2c8_gridDimY; ++i) { int rowBase = x2c4_gridDimX * i; int nextRowBase = (x2c4_gridDimX + 1) * i; for (int j = 0; j < x2c4_gridDimX; ++j) { x2d8_tileIntersects[rowBase + j] = x2dc_vertIntersects[nextRowBase + j] || x2dc_vertIntersects[nextRowBase + j + 1] || x2dc_vertIntersects[nextRowBase + j + x2c4_gridDimX + 1] || x2dc_vertIntersects[nextRowBase + j + x2c4_gridDimX + 2]; } } int tilesPerPatch = std::min(42 / x1b4_fluidPlane->GetTileSubdivisions(), 7); x2d0_patchDimX = (tilesPerPatch + x2c4_gridDimX - 1) / tilesPerPatch; x2d4_patchDimY = (tilesPerPatch + x2c8_gridDimY - 1) / tilesPerPatch; x2e0_patchIntersects.reset(new u8[x2d0_patchDimX * x2d4_patchDimY]); int curTileY = 0; int nextTileY; for (int i=0 ; i act = mgr.ObjectById(inhab.first); bool intersects = false; if (act) { if (auto tb = act->GetTouchBounds()) { zeus::CAABox thisTb = GetTriggerBoundsWR(); if (tb->min.z <= thisTb.max.z && tb->max.z >= thisTb.max.z) intersects = true; } } if (act && inhab.second) { if (intersects) act->FluidFXThink(EFluidState::InFluid, *this, mgr); mgr.SendScriptMsg(act.GetPtr(), GetUniqueId(), EScriptObjectMessage::UpdateSplashInhabitant); inhab.second = false; } else { it = x1fc_waterInhabitants.erase(it); if (act) { if (intersects) act->FluidFXThink(EFluidState::LeftFluid, *this, mgr); mgr.SendScriptMsg(act.GetPtr(), GetUniqueId(), EScriptObjectMessage::RemoveSplashInhabitant); } continue; } ++it; } } void CScriptWater::Think(float dt, CStateManager& mgr) { if (!x30_24_active) return; bool oldCamSubmerged = x148_25_camSubmerged; CScriptTrigger::Think(dt, mgr); CGameCamera* curCam = mgr.GetCameraManager()->GetCurrentCamera(mgr); if (x148_25_camSubmerged && !oldCamSubmerged) mgr.SendScriptMsg(curCam, x8_uid, EScriptObjectMessage::AddSplashInhabitant); else if (!x148_25_camSubmerged && oldCamSubmerged) mgr.SendScriptMsg(curCam, x8_uid, EScriptObjectMessage::RemoveSplashInhabitant); UpdateSplashInhabitants(mgr); if (x2e8_30_alphaOut) { x2bc_alpha -= x2b8_alphaOutRecip * dt * x1b4_fluidPlane->GetAlpha(); x214_fogBias -= x2b8_alphaOutRecip * dt * x21c_origFogBias; x218_fogMagnitude -= x2b8_alphaOutRecip * dt * x220_origFogMagnitude; if (x2bc_alpha <= 0.f) { x218_fogMagnitude = 0.f; x214_fogBias = 0.f; x2bc_alpha = 0.f; x2e8_30_alphaOut = false; } } else if (x2e8_29_alphaIn) { x2bc_alpha += x2b4_alphaInRecip * dt * x1b4_fluidPlane->GetAlpha(); x214_fogBias -= x2b4_alphaInRecip * dt * x21c_origFogBias; x218_fogMagnitude -= x2b4_alphaInRecip * dt * x220_origFogMagnitude; if (x2bc_alpha > x1b4_fluidPlane->GetAlpha()) { x2bc_alpha = x1b4_fluidPlane->GetAlpha(); x214_fogBias = x21c_origFogBias; x218_fogMagnitude = x220_origFogMagnitude; x2e8_29_alphaIn = false; } } if (x2e8_26_morphing) { bool stillMorphing = true; if (x2e8_25_morphIn) { x1f8_morphFactor += dt / x1d0_morphInTime; if (x1f8_morphFactor > 1.f) { x1f8_morphFactor = 1.f; stillMorphing = false; } } else { x1f8_morphFactor -= dt / x1f4_morphOutTime; if (x1f8_morphFactor < 0.f) { x1f8_morphFactor = 0.f; stillMorphing = false; } } SetTranslation(zeus::CVector3f::lerp(x1d4_positionOrig, x1b8_positionMorphed, x1f8_morphFactor)); zeus::CVector3f lerpExtent = zeus::CVector3f::lerp(x1e0_extentOrig, x1c4_extentMorphed, x1f8_morphFactor); x130_bounds = zeus::CAABox(lerpExtent * -0.5f, lerpExtent * 0.5f); CalculateRenderBounds(); if (!stillMorphing) SetMorphing(false); else SetupGrid(false); } SetupGridClipping(mgr, 4); } void CScriptWater::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId other, CStateManager& mgr) { switch (msg) { case EScriptObjectMessage::Next: if (!x30_24_active) break; x2e8_25_morphIn = !x2e8_25_morphIn; if (x2e8_25_morphIn) { for (const SConnection& conn : x20_conns) { if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) continue; auto list = mgr.GetIdListForScript(conn.x8_objId); if (list.first == mgr.GetIdListEnd()) continue; if (TCastToConstPtr trig = mgr.GetObjectById(list.first->second)) { x1b8_positionMorphed = trig->GetTranslation(); x1c4_extentMorphed = trig->GetTriggerBoundsOR().max - trig->GetTriggerBoundsOR().min; x1f0_damageMorphed = trig->GetDamageInfo().GetDamage(); x1d4_positionOrig = GetTranslation(); x1e0_extentOrig = x130_bounds.max - x130_bounds.min; x1ec_damageOrig = x100_damageInfo.GetDamage(); break; } } } SetMorphing(true); break; case EScriptObjectMessage::Activate: x2e8_30_alphaOut = false; if (std::fabs(x2ac_alphaInTime) < 0.00001f) { x2bc_alpha = x1b4_fluidPlane->GetAlpha(); x214_fogBias = x21c_origFogBias; x218_fogMagnitude = x220_origFogMagnitude; } else { x2e8_29_alphaIn = true; } break; case EScriptObjectMessage::Action: x2e8_29_alphaIn = false; if (std::fabs(x2b0_alphaOutTime) < 0.00001f) { x2bc_alpha = 0.f; x214_fogBias = 0.f; x218_fogMagnitude = 0.f; } else { x2e8_30_alphaOut = true; } break; default: break; } CScriptTrigger::AcceptScriptMsg(msg, other, mgr); } void CScriptWater::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) { if (x2e8_27_allowRender) { zeus::CAABox aabb = GetSortingBounds(mgr); xe4_30_outOfFrustum = !frustum.aabbFrustumTest(aabb); if (!xe4_30_outOfFrustum) { if (x4_areaId != kInvalidAreaId) { if (x90_actorLights->GetMaxAreaLights() && (xe4_29_actorLightsDirty || x90_actorLights->GetIsDirty())) { const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x4_areaId); if (area->IsPostConstructed()) { x90_actorLights->BuildAreaLightList(mgr, *area, GetTriggerBoundsWR()); xe4_29_actorLightsDirty = false; } } x90_actorLights->BuildDynamicLightList(mgr, GetTriggerBoundsWR()); } x150_frustum = frustum; } } else { xe4_30_outOfFrustum = true; } } void CScriptWater::AddToRenderer(const zeus::CFrustum& /*frustum*/, const CStateManager& mgr) const { if (!xe4_30_outOfFrustum) { zeus::CPlane plane; plane.vec = x34_transform.origin.normalized(); plane.d = x34_transform.origin.z + x130_bounds.max.z; zeus::CAABox renderBounds = GetSortingBounds(mgr); mgr.AddDrawableActorPlane(*this, plane, renderBounds); } } void CScriptWater::Render(const CStateManager& mgr) const { if (x30_24_active && !xe4_30_outOfFrustum) { float zOffset = 0.5f * (x9c_renderBounds.max.z + x9c_renderBounds.min.z) - x34_transform.origin.z; zeus::CAABox aabb = x9c_renderBounds.getTransformedAABox( zeus::CTransform::Translate(-x34_transform.origin.x, -x34_transform.origin.y, -x34_transform.origin.z - zOffset)); zeus::CTransform xf = x34_transform; xf.origin.z += zOffset; zeus::CVector3f areaCenter = mgr.GetWorld()->GetAreaAlways(mgr.GetNextAreaId())->GetAABB().center(); std::experimental::optional rippleMan(mgr.GetFluidPlaneManager()->GetRippleManager()); x1b4_fluidPlane->Render(mgr, x2bc_alpha, aabb, xf, mgr.GetWorld()->GetAreaAlways(x4_areaId)->GetTransform(), false, x150_frustum, rippleMan, x8_uid, x2d8_tileIntersects.get(), x2c4_gridDimX, x2c8_gridDimY, areaCenter); if (x214_fogBias != 0.f) { if (mgr.GetPlayerState()->CanVisorSeeFog(mgr)) { float fogLevel = mgr.IntegrateVisorFog(x218_fogMagnitude * std::sin(x224_fogSpeed * CGraphics::GetSecondsMod900()) + x214_fogBias); if (fogLevel > 0.f) { zeus::CAABox fogBox = GetTriggerBoundsWR(); fogBox.min.z = fogBox.max.z; fogBox.max.z += fogLevel; zeus::CTransform modelXf = zeus::CTransform::Translate(fogBox.center()) * zeus::CTransform::Scale((fogBox.max - fogBox.min) * 0.5f); zeus::CAABox renderAABB(zeus::CVector3f::skNegOne, zeus::CVector3f::skOne); CGraphics::SetModelMatrix(modelXf); g_Renderer->SetAmbientColor(zeus::CColor::skWhite); g_Renderer->RenderFogVolume(x228_fogColor, renderAABB, nullptr, nullptr); } } } CGraphics::DisableAllLights(); } CActor::Render(mgr); } void CScriptWater::Touch(CActor& otherAct, CStateManager& mgr) { if (!x30_24_active) return; CScriptTrigger::Touch(otherAct, mgr); if (otherAct.GetMaterialList().HasMaterial(EMaterialTypes::Trigger)) return; for (auto& inhab : x1fc_waterInhabitants) if (inhab.first == otherAct.GetUniqueId()) { inhab.second = true; return; } auto touchBounds = otherAct.GetTouchBounds(); if (!touchBounds) return; x1fc_waterInhabitants.emplace_back(otherAct.GetUniqueId(), true); float triggerMaxZ = GetTriggerBoundsWR().max.z; if (touchBounds->min.z <= triggerMaxZ && touchBounds->max.z >= triggerMaxZ) otherAct.FluidFXThink(EFluidState::EnteredFluid, *this, mgr); mgr.SendScriptMsg(&otherAct, x8_uid, EScriptObjectMessage::AddSplashInhabitant); } void CScriptWater::CalculateRenderBounds() { zeus::CVector3f aabbMin = x130_bounds.min; aabbMin.z = x130_bounds.max.z - 1.f; zeus::CVector3f aabbMax = x130_bounds.max; aabbMax.z += 1.f; zeus::CVector3f transAABBMin = aabbMin + GetTranslation(); zeus::CVector3f transAABBMax = aabbMax + GetTranslation(); x9c_renderBounds = zeus::CAABox(transAABBMin, transAABBMax); } zeus::CAABox CScriptWater::GetSortingBounds(const CStateManager& mgr) const { zeus::CVector3f max = x9c_renderBounds.max; max.z = std::max(max.z, x9c_renderBounds.max.z - 1.f + x214_fogBias + x218_fogMagnitude); return {x9c_renderBounds.min, max}; } EWeaponCollisionResponseTypes CScriptWater::GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&, const CWeaponMode&, int) const { return EWeaponCollisionResponseTypes::Water; } s16 CScriptWater::GetSplashSound(float dt) const { return x298_splashSounds[GetSplashIndex(dt)]; } const std::experimental::optional>& CScriptWater::GetSplashEffect(float dt) const { return x264_splashEffects[GetSplashIndex(dt)]; } float CScriptWater::GetSplashEffectScale(float dt) const { if (std::fabs(dt - 1.f) < 0.00001f) return kSplashScales[5]; u32 idx = GetSplashIndex(dt); float s = dt - zeus::floorF(dt * 3.f); return ((1.f - s) * (s * kSplashScales[idx * 2])) + kSplashScales[idx]; } u32 CScriptWater::GetSplashIndex(float dt) const { auto idx = u32(dt * 3.f); return (idx < 3 ? idx : idx - 1); } void CScriptWater::SetMorphing(bool m) { if (m == x2e8_26_morphing) return; x2e8_26_morphing = m; SetupGrid(!m); } const CScriptWater* CScriptWater::GetNextConnectedWater(const CStateManager& mgr) const { for (const SConnection& conn : x20_conns) { if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) continue; auto its = mgr.GetIdListForScript(conn.x8_objId); if (its.first != mgr.GetIdListEnd()) if (TCastToConstPtr water = mgr.GetObjectById(its.first->second)) return water.GetPtr(); } return nullptr; } bool CScriptWater::CanRippleAtPoint(const zeus::CVector3f& point) const { if (!x2d8_tileIntersects) return true; auto xTile = int((point.x - GetTriggerBoundsOR().min.x) / x2c0_tileSize); if (xTile < 0 || xTile >= x2c4_gridDimX) return false; auto yTile = int((point.y - GetTriggerBoundsOR().min.y) / x2c0_tileSize); if (xTile < 0 || xTile >= x2c8_gridDimY) return false; return x2d8_tileIntersects[yTile * x2c4_gridDimX + xTile]; } }