#include "Runtime/MP1/World/CBouncyGrenade.hpp" #include "Runtime/CPlayerState.hpp" #include "Runtime/Factory/CSimplePool.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/Collision/CCollisionActor.hpp" namespace metaforce::MP1 { CBouncyGrenade::CBouncyGrenade(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, CModelData&& mData, const CActorParameters& actParams, TUniqueId parentId, const SBouncyGrenadeData& data, float velocity, float explodePlayerDistance) : CPhysicsActor(uid, true, name, info, xf, std::move(mData), {EMaterialTypes::Projectile, EMaterialTypes::Solid}, mData.GetBounds(), SMoverData{data.GetUnkStruct().GetMass()}, actParams, 0.3f, 0.1f) , x258_data(data) , x294_numBounces(data.GetNumBounces()) , x298_parentId(parentId) , x2a0_elementGenCombat(std::make_unique(g_SimplePool->GetObj({'PART', data.GetElementGenId1()}))) , x2a4_elementGenXRay(std::make_unique(g_SimplePool->GetObj({'PART', data.GetElementGenId2()}))) , x2a8_elementGenThermal(std::make_unique(g_SimplePool->GetObj({'PART', data.GetElementGenId3()}))) , x2ac_elementGen4(std::make_unique(g_SimplePool->GetObj({'PART', data.GetElementGenId4()}))) , x2b0_explodePlayerDistance(explodePlayerDistance) { SetMomentumWR({0.f, 0.f, -GravityConstant() * GetMass()}); SetVelocityWR(velocity * xf.frontVector()); x2a0_elementGenCombat->SetParticleEmission(false); x2a4_elementGenXRay->SetParticleEmission(false); x2a8_elementGenThermal->SetParticleEmission(false); x2ac_elementGen4->SetParticleEmission(true); CMaterialFilter filter = GetMaterialFilter(); filter.ExcludeList().Add(EMaterialTypes::Character); SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(filter.IncludeList(), filter.ExcludeList())); } void CBouncyGrenade::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) { CActor::AddToRenderer(frustum, mgr); if (!x2b4_24_exploded) { g_Renderer->AddParticleGen(*x2ac_elementGen4); return; } const auto visor = mgr.GetPlayerState()->GetActiveVisor(mgr); if (visor == CPlayerState::EPlayerVisor::Combat || visor == CPlayerState::EPlayerVisor::Scan) { g_Renderer->AddParticleGen(*x2a0_elementGenCombat); } else if (visor == CPlayerState::EPlayerVisor::XRay || visor == CPlayerState::EPlayerVisor::Thermal) { g_Renderer->AddParticleGen(*x2a8_elementGenThermal); } } void CBouncyGrenade::CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr) { constexpr auto matList = CMaterialList{ EMaterialTypes::Solid, EMaterialTypes::Ceiling, EMaterialTypes::Floor, EMaterialTypes::Character, }; bool shouldExplode = false; if (x298_parentId != id) { const CEntity* const entity = mgr.GetObjectById(id); if (entity != nullptr) { if (TCastToConstPtr actor = entity) { shouldExplode = actor->GetOwnerId() != x298_parentId; } else { shouldExplode = true; } } } if (shouldExplode) { Explode(mgr, id); } else { for (const auto& info : list) { if (info.GetMaterialLeft().SharesMaterials(matList)) { if (x294_numBounces == 0) { Explode(mgr, kInvalidUniqueId); } else { const zeus::CVector3f* normal = &info.GetNormalLeft(); if (GetVelocity().dot(info.GetNormalLeft()) > 0.f) { normal = &info.GetNormalRight(); } const zeus::CVector3f impulse = (x258_data.GetUnkStruct().GetSpeed() * GetConstantForce().magnitude()) * *normal; const zeus::CVector3f angle = -x258_data.GetUnkStruct().GetSpeed() * GetAngularMomentum(); ApplyImpulseWR(impulse, angle); CSfxManager::AddEmitter(x258_data.GetBounceSfx(), GetTranslation(), zeus::skUp, false, false, 0x7f, GetAreaIdAlways()); --x294_numBounces; } break; } } } CPhysicsActor::CollidedWith(id, list, mgr); } std::optional CBouncyGrenade::GetTouchBounds() const { return GetModelData()->GetBounds(GetTransform()); } void CBouncyGrenade::Render(CStateManager& mgr) { if (!x2b4_24_exploded) { GetModelData()->Render(mgr, GetTransform(), nullptr, {0, 0, 3, zeus::skWhite}); } else if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) { CElementGen::SetSubtractBlend(true); CElementGen::SetMoveRedToAlphaBuffer(true); CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack); x2a4_elementGenXRay->Render(); mgr.SetupFogForArea(GetAreaIdAlways()); CElementGen::SetSubtractBlend(false); CElementGen::SetMoveRedToAlphaBuffer(false); } } void CBouncyGrenade::Think(float dt, CStateManager& mgr) { if (GetActive()) { const zeus::CTransform& orientation = GetTransform().getRotation(); const zeus::CVector3f& translation = GetTranslation(); const zeus::CVector3f& scale = GetModelData()->GetScale(); auto UpdateElementGen = [orientation, translation, scale, dt](CElementGen& gen) { gen.SetOrientation(orientation); gen.SetGlobalTranslation(translation); gen.SetGlobalScale(scale); gen.Update(dt); }; if (x2b4_24_exploded) { Stop(); UpdateElementGen(*x2a0_elementGenCombat); UpdateElementGen(*x2a4_elementGenXRay); UpdateElementGen(*x2a8_elementGenThermal); } else { UpdateElementGen(*x2ac_elementGen4); } x29c_ += dt; if (x29c_ > 0.3f) { x2b4_25_ = true; } const zeus::CVector3f& playerDistance = mgr.GetPlayer().GetTranslation() + zeus::CVector3f{0.f, 0.f, 0.5f * mgr.GetPlayer().GetEyeHeight()} - translation; if (playerDistance.magSquared() < x2b0_explodePlayerDistance * x2b0_explodePlayerDistance) { Explode(mgr, kInvalidUniqueId); } } if (x2a0_elementGenCombat->IsSystemDeletable() && x2a4_elementGenXRay->IsSystemDeletable() && x2a8_elementGenThermal->IsSystemDeletable()) { mgr.FreeScriptObject(GetUniqueId()); } } void CBouncyGrenade::Touch(CActor& act, CStateManager& mgr) { CActor::Touch(act, mgr); } void CBouncyGrenade::Explode(CStateManager& mgr, TUniqueId uid) { if (x2b4_24_exploded) { return; } x2b4_24_exploded = true; CSfxManager::AddEmitter(x258_data.GetExplodeSfx(), GetTranslation(), zeus::skUp, false, false, 0x7f, GetAreaIdAlways()); x2a0_elementGenCombat->SetParticleEmission(true); x2a4_elementGenXRay->SetParticleEmission(true); x2a8_elementGenThermal->SetParticleEmission(true); x2ac_elementGen4->SetParticleEmission(false); const CDamageInfo& dInfo = x258_data.GetDamageInfo(); { bool isParent = x298_parentId == uid; if (TCastToConstPtr actor = mgr.GetObjectById(uid)) { isParent = x298_parentId == actor->GetOwnerId(); } if (uid != kInvalidUniqueId && !isParent) { mgr.ApplyDamage(GetUniqueId(), uid, GetUniqueId(), dInfo, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f); } } const float radius = dInfo.GetRadius(); if (radius > 1.f) { const zeus::CVector3f& pos = GetTranslation(); const CMaterialFilter filter = CMaterialFilter::MakeInclude({EMaterialTypes::Player, EMaterialTypes::Character}); EntityList nearList; mgr.BuildNearList(nearList, {pos - radius, pos + radius}, filter, nullptr); for (const auto& id : nearList) { bool isParent = x298_parentId == id; if (TCastToConstPtr cActor = mgr.GetObjectById(id)) { isParent = x298_parentId == cActor->GetOwnerId(); } if (isParent) { continue; } const auto* actor = static_cast(mgr.GetObjectById(id)); if (actor == nullptr) { continue; } const float magnitude = (actor->GetTranslation() - GetTranslation()).magnitude(); if (radius <= magnitude) { continue; } float scale = (radius - magnitude) / radius; const CDamageInfo info{dInfo.GetWeaponMode(), scale * dInfo.GetDamage(), radius, scale * dInfo.GetKnockBackPower()}; mgr.ApplyDamage(GetUniqueId(), id, GetUniqueId(), info, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f); } } } } // namespace metaforce::MP1