#include "CPlasmaProjectile.hpp" #include "TCastTo.hpp" #include "GameGlobalObjects.hpp" #include "CSimplePool.hpp" #include "World/CGameLight.hpp" #include "CStateManager.hpp" #include "Graphics/CBooRenderer.hpp" #include "World/CPlayer.hpp" #include "World/CHUDBillboardEffect.hpp" namespace urde { CPlasmaProjectile::RenderObjects::RenderObjects(boo::IGraphicsDataFactory::Context& ctx, boo::ObjToken tex, boo::ObjToken glowTex) : m_beamStrip1(ctx, 8, CColoredStripShader::Mode::Additive, {}) , m_beamStrip2(ctx, 10, CColoredStripShader::Mode::FullAdditive, tex) , m_beamStrip3(ctx, 18, CColoredStripShader::Mode::FullAdditive, tex) , m_beamStrip4(ctx, 14, CColoredStripShader::Mode::Additive, glowTex) , m_beamStrip1Sub(ctx, 8, CColoredStripShader::Mode::Subtractive, {}) , m_beamStrip2Sub(ctx, 10, CColoredStripShader::Mode::Subtractive, tex) , m_beamStrip3Sub(ctx, 18, CColoredStripShader::Mode::Subtractive, tex) , m_beamStrip4Sub(ctx, 14, CColoredStripShader::Mode::Subtractive, glowTex) , m_motionBlurStrip(ctx, 16, CColoredStripShader::Mode::Alpha, {}) {} CPlasmaProjectile::CPlasmaProjectile(const TToken& wDesc, std::string_view name, EWeaponType wType, const CBeamInfo& bInfo, const zeus::CTransform& xf, EMaterialTypes matType, const CDamageInfo& dInfo, TUniqueId uid, TAreaId aid, TUniqueId owner, const PlayerEffectResoures& res, bool growingBeam, EProjectileAttrib attribs) : CBeamProjectile(wDesc, name, wType, xf, bInfo.GetLength(), bInfo.GetRadius(), bInfo.GetTravelSpeed(), matType, dInfo, uid, aid, owner, attribs, growingBeam) , x478_beamAttributes(bInfo.GetBeamAttributes()) , x47c_lifeTime(bInfo.GetLifeTime()) , x480_pulseSpeed(bInfo.GetPulseSpeed()) , x484_shutdownTime(bInfo.GetShutdownTime()) , x488_expansionSpeed(bInfo.GetExpansionSpeed()) , x48c_(bInfo.GetLength() / 32.f) , x490_innerColor(bInfo.GetInnerColor()) , x494_outerColor(bInfo.GetOuterColor()) { x4e8_texture = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), bInfo.GetTextureId()}); x4f4_glowTexture = g_SimplePool->GetObj(SObjectTag{FOURCC('TXTR'), bInfo.GetGlowTextureId()}); x500_contactFxDesc = g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), bInfo.GetContactFxId()}); x50c_pulseFxDesc = g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), bInfo.GetPulseFxId()}); x518_contactGen = std::make_unique(x500_contactFxDesc, CElementGen::EModelOrientationType::One); x51c_pulseGen = std::make_unique(x50c_pulseFxDesc, CElementGen::EModelOrientationType::Normal); x524_freezeSteamTxtr = res[0]; x528_freezeIceTxtr = res[1]; if (res[2] != UINT64_MAX) x52c_visorElectric = g_SimplePool->GetObj(SObjectTag{FOURCC('ELSC'), res[2]}); if (res[3] != UINT64_MAX) x538_visorParticle = g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), res[3]}); x544_freezeSfx = CSfxManager::TranslateSFXID(res[4]); x546_electricSfx = CSfxManager::TranslateSFXID(res[5]); x548_25_enableEnergyPulse = true; x548_28_drawOwnerFirst = growingBeam; x518_contactGen->SetGlobalScale(zeus::CVector3f(bInfo.GetContactFxScale())); x51c_pulseGen->SetGlobalScale(zeus::CVector3f(bInfo.GetPulseFxScale())); x518_contactGen->SetParticleEmission(false); x51c_pulseGen->SetParticleEmission(false); CGraphics::CommitResources([this](boo::IGraphicsDataFactory::Context& ctx) { m_renderObjs.emplace(ctx, x4e8_texture->GetBooTexture(), x4f4_glowTexture->GetBooTexture()); return true; } BooTrace); } void CPlasmaProjectile::Accept(IVisitor& visitor) { visitor.Visit(this); } void CPlasmaProjectile::SetLightsActive(bool active, CStateManager& mgr) { for (TUniqueId lid : x468_lights) { if (lid != kInvalidUniqueId) { if (TCastToPtr light = mgr.ObjectById(lid)) { light->SetActive(active); } } } } void CPlasmaProjectile::CreatePlasmaLights(u32 sourceId, const CLight& l, CStateManager& mgr) { DeletePlasmaLights(mgr); x468_lights.reserve(3); for (int i = 0; i < 3; ++i) { TUniqueId lid = mgr.AllocateUniqueId(); auto* light = new CGameLight(lid, GetAreaId(), GetActive(), ""sv, GetTransform(), GetUniqueId(), l, sourceId, 0, 0.f); mgr.AddObject(light); x468_lights.push_back(lid); } } void CPlasmaProjectile::DeletePlasmaLights(CStateManager& mgr) { for (TUniqueId lid : x468_lights) mgr.FreeScriptObject(lid); x468_lights.clear(); } void CPlasmaProjectile::UpdateLights(float expansion, float dt, CStateManager& mgr) { if (x520_weaponGen) { x520_weaponGen->Update(dt); CLight l = x520_weaponGen->GetLight(); l.SetColor(zeus::CColor::lerp(zeus::skClear, l.GetColor(), expansion)); float halfLen = 0.5f * GetCurrentLength(); float y = 0.f; for (TUniqueId lid : x468_lights) { if (TCastToPtr light = mgr.ObjectById(lid)) { light->SetTransform({}); light->SetTranslation(GetBeamTransform() * zeus::CVector3f(0.f, y, 0.f)); light->SetLight(l); } y += halfLen; } } } void CPlasmaProjectile::UpdateEnergyPulse(float dt) { if (GetDamageType() != EDamageType::None && x548_25_enableEnergyPulse) { x4d8_energyPulseTimer -= dt; if (x4d8_energyPulseTimer <= 0.f) { x4d8_energyPulseTimer = 2.f * dt; x51c_pulseGen->SetParticleEmission(true); float t = GetCurrentLength() / GetMaxLength(); for (float i = 0.f; i <= t; i += 0.1f) { float y = i * GetMaxLength() + x4cc_energyPulseStartY; if (y > GetCurrentLength()) continue; x51c_pulseGen->SetTranslation({0.f, y, 0.f}); x51c_pulseGen->ForceParticleCreation(1); } x51c_pulseGen->SetGlobalOrientAndTrans(GetBeamTransform()); x51c_pulseGen->SetParticleEmission(false); } } x51c_pulseGen->Update(dt); } void CPlasmaProjectile::RenderMotionBlur() const { CGraphics::SetModelMatrix({}); zeus::CColor color1 = x494_outerColor; zeus::CColor color2 = x494_outerColor; color1.a() = 63.f / 255.f; color2.a() = 0.f; CColoredStripShader::Vert verts[16]; for (int i = 0; i < 8; ++i) { auto& v1 = verts[i * 2]; auto& v2 = verts[i * 2 + 1]; v1.m_pos = GetBeamTransform().origin; v1.m_color = zeus::CColor::lerp(color1, color2, i / 8.f); v2.m_pos = GetPointCache()[i]; v2.m_color = v1.m_color; } m_renderObjs->m_motionBlurStrip.draw(zeus::skWhite, 16, verts); } void CPlasmaProjectile::RenderBeam(s32 subdivs, float width, const zeus::CColor& color, s32 flags, CColoredStripShader& shader) const { // Flags: 0x1: textured, 0x2: length controlled UVY 0x4: alpha controlled additive blend, // 0x8: glow texture, 0x10: subtractive blend if ((flags & 0x1) == 0 || (flags & 0x8) ? x4f4_glowTexture.IsLoaded() : x4e8_texture.IsLoaded()) { float angIncrement = 2.f * M_PIF / float(subdivs); float uvY1 = -(x4cc_energyPulseStartY / 16.f); float uvY2 = (uvY1 + float((flags & 0x3) == 0x3) != 0.f) ? 2.f : 0.5f * GetCurrentLength(); CColoredStripShader::Vert verts[18]; s32 numNodes = subdivs + 1; float angle = 0.f; bool flip = false; for (s32 i = 0; i < numNodes; ++i) { CColoredStripShader::Vert& v0 = verts[i * 2]; CColoredStripShader::Vert& v1 = verts[i * 2 + 1]; float x = std::cos(angle); float y = std::sin(angle); float uvX; if (flags & 0x8) uvX = 0.5f * y; else if (flip) uvX = width; else uvX = 0.f; flip ^= true; v0.m_pos = zeus::CVector3f(width * x, 0.f, width * y); v0.m_color = color; v0.m_uv = zeus::CVector2f(uvX, uvY1); v1.m_pos = zeus::CVector3f(width * x, GetCurrentLength(), width * y); v1.m_color = color; v1.m_uv = zeus::CVector2f(uvX, uvY2); angle += angIncrement; } shader.draw(zeus::skWhite, numNodes * 2, verts); } } void CPlasmaProjectile::ResetBeam(CStateManager& mgr, bool fullReset) { if (fullReset) { SetActive(false); SetLightsActive(false, mgr); x4bc_lifeTimer = 0.f; x4c0_expansionT = 0.f; x4c8_beamAngle = 0.f; x4d0_shutdownTimer = 0.f; x4d4_contactPulseTimer = 0.f; x4d8_energyPulseTimer = 0.f; x4dc_playerEffectPulseTimer = 0.f; x4b4_expansionState = EExpansionState::Inactive; x548_26_firing = false; x518_contactGen->SetParticleEmission(false); x51c_pulseGen->SetParticleEmission(false); } else { x548_26_firing = false; x4b4_expansionState = EExpansionState::Release; x518_contactGen->SetParticleEmission(false); x51c_pulseGen->SetParticleEmission(false); } } float CPlasmaProjectile::UpdateBeamState(float dt, CStateManager& mgr) { switch (x4b4_expansionState) { case EExpansionState::Attack: if (x4c0_expansionT > 0.5f) x4b4_expansionState = EExpansionState::Sustain; else x4c0_expansionT += dt * x488_expansionSpeed; break; case EExpansionState::Sustain: if (x478_beamAttributes & 0x4) { if (x4bc_lifeTimer > x47c_lifeTime) x4b4_expansionState = EExpansionState::Release; else x4bc_lifeTimer += dt; } break; case EExpansionState::Release: x4c0_expansionT += dt * x488_expansionSpeed; if (x4c0_expansionT > 1.f) { x4c0_expansionT = 1.f; x4b4_expansionState = EExpansionState::Done; x548_25_enableEnergyPulse = false; } break; case EExpansionState::Done: x4d0_shutdownTimer += dt; if (x4d0_shutdownTimer > x484_shutdownTime && (!x518_contactGen || x518_contactGen->GetParticleCountAll() == 0)) { x4b4_expansionState = EExpansionState::Inactive; ResetBeam(mgr, true); } break; default: break; } return -4.f * x4c0_expansionT * (x4c0_expansionT - 1.f); } void CPlasmaProjectile::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) { switch (msg) { case EScriptObjectMessage::Registered: { xe6_27_thermalVisorFlags = 2; const SChildGeneratorDesc& apsm = x170_projectile.GetWeaponDescription()->x34_APSM; if (apsm) x520_weaponGen = std::make_unique(apsm.m_token); if (x520_weaponGen && x520_weaponGen->SystemHasLight()) CreatePlasmaLights(x170_projectile.GetWeaponDescription().GetObjectTag()->id.Value(), x520_weaponGen->GetLight(), mgr); else x520_weaponGen.reset(); if (x548_28_drawOwnerFirst) xc6_nextDrawNode = xec_ownerId; mgr.AddWeaponId(xec_ownerId, xf0_weaponType); break; } case EScriptObjectMessage::Deleted: mgr.RemoveWeaponId(xec_ownerId, xf0_weaponType); DeletePlasmaLights(mgr); if (x548_29_activePlayerPhazon) { mgr.GetPlayer().DecrementPhazon(); x548_29_activePlayerPhazon = false; } break; default: break; } CGameProjectile::AcceptScriptMsg(msg, sender, mgr); } void CPlasmaProjectile::MakeBillboardEffect(const std::optional>& particle, const std::optional>& electric, std::string_view name, CStateManager& mgr) { auto* effect = new CHUDBillboardEffect(particle, electric, mgr.AllocateUniqueId(), true, name, CHUDBillboardEffect::GetNearClipDistance(mgr), CHUDBillboardEffect::GetScaleForPOV(mgr), zeus::skWhite, zeus::skOne3f, zeus::skZero3f); mgr.AddObject(effect); } void CPlasmaProjectile::UpdatePlayerEffects(float dt, CStateManager& mgr) { x4dc_playerEffectPulseTimer -= dt; if ((x4b4_expansionState == EExpansionState::Attack || x4b4_expansionState == EExpansionState::Sustain) && x4dc_playerEffectPulseTimer <= 0.f && GetDamageType() == EDamageType::Actor && GetCollisionActorId() == mgr.GetPlayer().GetUniqueId()) { if ((x478_beamAttributes & 0x8) && !x548_29_activePlayerPhazon) { x548_29_activePlayerPhazon = true; x4e4_playerDamageTimer = 0.f; mgr.GetPlayer().IncrementPhazon(); } switch (xf0_weaponType) { case EWeaponType::Ice: mgr.GetPlayer().Freeze(mgr, x524_freezeSteamTxtr, x544_freezeSfx, x528_freezeIceTxtr); break; case EWeaponType::Wave: if (x52c_visorElectric) { MakeBillboardEffect({}, {x52c_visorElectric}, "PlasmaElectricFx"sv, mgr); CSfxManager::SfxStart(x546_electricSfx, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); mgr.GetPlayer().SetHudDisable(3.f, 0.5f, 2.5f); mgr.GetPlayer().SetOrbitRequestForTarget(mgr.GetPlayer().GetOrbitTargetId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr); mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), 0.2f, 3.f); } break; case EWeaponType::Plasma: if (x538_visorParticle) MakeBillboardEffect({x538_visorParticle}, {}, "PlasmaVisorFx"sv, mgr); break; default: break; } x4dc_playerEffectPulseTimer = 0.75f; } if (x548_29_activePlayerPhazon) { CDamageInfo scaledDamage(x498_phazonDamage, dt); mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), xec_ownerId, scaledDamage, xf8_filter, zeus::skZero3f); x4e4_playerDamageTimer += dt; if (x4e4_playerDamageTimer >= x4e0_playerDamageDuration) { mgr.GetPlayer().DecrementPhazon(); x4e4_playerDamageTimer = 0.f; x548_29_activePlayerPhazon = false; } } } void CPlasmaProjectile::UpdateFx(const zeus::CTransform& xf, float dt, CStateManager& mgr) { if (!GetActive()) return; x548_27_texturesLoaded = x4e8_texture.IsLoaded() && x4f4_glowTexture.IsLoaded(); CauseDamage(x4b4_expansionState == EExpansionState::Attack || x4b4_expansionState == EExpansionState::Sustain); CBeamProjectile::UpdateFx(xf, dt, mgr); UpdatePlayerEffects(dt, mgr); if (x478_beamAttributes & 0x1) { for (int i = 7; i > 0; --i) PointCache()[i] = PointCache()[i - 1]; PointCache()[0] = GetCurrentPos(); } if (x518_contactGen) { x4d4_contactPulseTimer -= dt; if ((GetDamageType() != EDamageType::None ? x548_25_enableEnergyPulse : false) && x4d4_contactPulseTimer <= 0.f) { x518_contactGen->SetOrientation(zeus::lookAt(zeus::skZero3f, GetSurfaceNormal())); x518_contactGen->SetTranslation(GetSurfaceNormal() * 0.001f + GetCurrentPos()); x518_contactGen->SetParticleEmission(true); x4d4_contactPulseTimer = 1.f / 16.f; } else { x518_contactGen->SetParticleEmission(false); } x518_contactGen->Update(dt); } float modulation = UpdateBeamState(dt, mgr); UpdateEnergyPulse(dt); x4c8_beamAngle += 720.f * dt; if (x4c8_beamAngle > 360.f) x4c8_beamAngle = 0.f; x4b8_beamWidth = modulation * GetMaxRadius(); x4c4_expansion = modulation; x4cc_energyPulseStartY += dt * x480_pulseSpeed; if (x4cc_energyPulseStartY > 5.f) x4cc_energyPulseStartY = 0.f; UpdateLights(modulation, dt, mgr); } void CPlasmaProjectile::Fire(const zeus::CTransform& xf, CStateManager& mgr, bool b) { SetActive(true); SetLightsActive(true, mgr); x548_25_enableEnergyPulse = true; x548_26_firing = true; x548_24_ = b; x4b4_expansionState = EExpansionState::Attack; if (x478_beamAttributes & 0x1) std::fill(PointCache().begin(), PointCache().end(), xf.origin); } void CPlasmaProjectile::Touch(CActor& other, CStateManager& mgr) { // Empty } bool CPlasmaProjectile::CanRenderUnsorted(const CStateManager& mgr) const { return false; } void CPlasmaProjectile::AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr) const { if (GetActive()) { g_Renderer->AddParticleGen(*x518_contactGen); if (x478_beamAttributes & 0x2) { g_Renderer->AddParticleGen(*x51c_pulseGen); } } EnsureRendered(mgr, GetBeamTransform().origin, GetSortingBounds(mgr)); } void CPlasmaProjectile::Render(const CStateManager& mgr) const { if (!GetActive()) return; SCOPED_GRAPHICS_DEBUG_GROUP("CPlasmaProjectile::Render", zeus::skOrange); zeus::CTransform xf = GetBeamTransform(); // Subtractive blending for xray s32 flags = mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay ? 0x10 : 0x0; if ((x478_beamAttributes & 0x1) == 0) xf.origin += mgr.GetCameraManager()->GetGlobalCameraTranslation(mgr); // Z test, no write if ((x478_beamAttributes & 0x1) && x548_25_enableEnergyPulse && x4b4_expansionState != EExpansionState::Attack) RenderMotionBlur(); // Pass1: alpha-controlled additive CGraphics::SetModelMatrix(xf); RenderBeam(3, 0.25f * x4b8_beamWidth, zeus::CColor(1.f, 0.3f), flags | 0x4, (flags & 0x10) ? m_renderObjs->m_beamStrip1Sub : m_renderObjs->m_beamStrip1); // Pass2: textured CGraphics::SetModelMatrix(xf * zeus::CTransform::RotateY(zeus::degToRad(x4c8_beamAngle))); RenderBeam(4, 0.5f * x4b8_beamWidth, x490_innerColor, flags | 0x1, (flags & 0x10) ? m_renderObjs->m_beamStrip2Sub : m_renderObjs->m_beamStrip2); // Pass3: textured | length-controlled UVY CGraphics::SetModelMatrix(xf * zeus::CTransform::RotateY(zeus::degToRad(-x4c8_beamAngle))); RenderBeam(8, x4b8_beamWidth, x494_outerColor, flags | 0x3, (flags & 0x10) ? m_renderObjs->m_beamStrip3Sub : m_renderObjs->m_beamStrip3); // Pass4: textured | alpha-controlled additive | glow texture CGraphics::SetModelMatrix(xf); RenderBeam(6, 1.25f * x4b8_beamWidth, x494_outerColor, flags | 0xd, (flags & 0x10) ? m_renderObjs->m_beamStrip4Sub : m_renderObjs->m_beamStrip4); } } // namespace urde