metaforce/Runtime/Weapon/CPlasmaProjectile.cpp

448 lines
18 KiB
C++

#include "Runtime/Weapon/CPlasmaProjectile.hpp"
#include "Runtime/CSimplePool.hpp"
#include "Runtime/CStateManager.hpp"
#include "Runtime/GameGlobalObjects.hpp"
#include "Runtime/Graphics/CBooRenderer.hpp"
#include "Runtime/World/CGameLight.hpp"
#include "Runtime/World/CHUDBillboardEffect.hpp"
#include "Runtime/World/CPlayer.hpp"
#include "TCastTo.hpp" // Generated file, do not modify include path
namespace urde {
CPlasmaProjectile::RenderObjects::RenderObjects(boo::IGraphicsDataFactory::Context& ctx,
boo::ObjToken<boo::ITexture> tex,
boo::ObjToken<boo::ITexture> 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<CWeaponDescription>& 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 PlayerEffectResources& 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())
, x548_28_drawOwnerFirst(growingBeam) {
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<CElementGen>(x500_contactFxDesc, CElementGen::EModelOrientationType::One);
x51c_pulseGen = std::make_unique<CElementGen>(x50c_pulseFxDesc, CElementGen::EModelOrientationType::Normal);
x524_freezeSteamTxtr = res[0];
x528_freezeIceTxtr = res[1];
if (res[2].IsValid()) {
x52c_visorElectric = g_SimplePool->GetObj(SObjectTag{FOURCC('ELSC'), res[2]});
}
if (res[3].IsValid()) {
x538_visorParticle = g_SimplePool->GetObj(SObjectTag{FOURCC('PART'), res[3]});
}
x544_freezeSfx = CSfxManager::TranslateSFXID(u16(res[4].Value()));
x546_electricSfx = CSfxManager::TranslateSFXID(u16(res[5].Value()));
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<CGameLight> light = mgr.ObjectById(lid)) {
light->SetActive(active);
}
}
}
}
void CPlasmaProjectile::CreatePlasmaLights(u32 sourceId, const CLight& l, CStateManager& mgr) {
DeletePlasmaLights(mgr);
x468_lights.reserve(3);
for (size_t i = 0; i < x468_lights.capacity(); ++i) {
const TUniqueId lid = mgr.AllocateUniqueId();
auto* light = new CGameLight(lid, GetAreaId(), GetActive(), "", 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<CGameLight> 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() {
CGraphics::SetModelMatrix({});
zeus::CColor color1 = x494_outerColor;
zeus::CColor color2 = x494_outerColor;
color1.a() = 63.f / 255.f;
color2.a() = 0.f;
std::array<CColoredStripShader::Vert, 16> verts;
for (size_t i = 0; i < verts.size() / 2; ++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, float(i) / 8.f);
v2.m_pos = GetPointCache()[i];
v2.m_color = v1.m_color;
}
m_renderObjs->m_motionBlurStrip.draw(zeus::skWhite, verts.size(), verts.data());
}
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();
std::array<CColoredStripShader::Vert, 18> verts;
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.data());
}
}
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<CElementGen>(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().DecrementEnvironmentDamage();
x548_29_activePlayerPhazon = false;
}
break;
default:
break;
}
CGameProjectile::AcceptScriptMsg(msg, sender, mgr);
}
void CPlasmaProjectile::MakeBillboardEffect(const std::optional<TToken<CGenDescription>>& particle,
const std::optional<TToken<CElectricDescription>>& 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().IncrementEnvironmentDamage();
}
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().DecrementEnvironmentDamage();
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, CStateManager& mgr) {
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(CStateManager& mgr) {
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