#include "Runtime/MP1/World/CGrenadeLauncher.hpp" #include "Runtime/Character/CPASAnimParm.hpp" #include "Runtime/Character/CPASAnimParmData.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Weapon/CGameProjectile.hpp" #include "Runtime/World/CExplosion.hpp" #include "Runtime/World/CPatterned.hpp" #include "Runtime/World/CPlayer.hpp" namespace metaforce::MP1 { CGrenadeLauncher::CGrenadeLauncher(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, CModelData&& mData, const zeus::CAABox& bounds, const CHealthInfo& healthInfo, const CDamageVulnerability& vulnerability, const CActorParameters& actParams, TUniqueId parentId, const SGrenadeLauncherData& data, float f1) : CPhysicsActor(uid, true, name, info, xf, std::move(mData), {EMaterialTypes::Character, EMaterialTypes::Solid}, bounds, SMoverData{1000.f}, actParams, 0.3f, 0.1f) , x25c_healthInfo(healthInfo) , x264_vulnerability(vulnerability) , x2cc_parentId(parentId) , x2d0_data(data) , x328_cSphere({{}, mData.GetScale().z()}, {EMaterialTypes::Character, EMaterialTypes::Solid}) , x350_grenadeActorParams(actParams) , x3e8_thermalMag(actParams.GetThermalMag()) , x3f8_explodePlayerDistance(f1) { if (data.GetShootParticleGenDescId().IsValid()) { x3b8_particleGenDesc = g_SimplePool->GetObj({SBIG('PART'), data.GetShootParticleGenDescId()}); } GetModelData()->EnableLooping(true); const CPASDatabase& pasDatabase = GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase(); for (size_t i = 0; i < x3c8_animIds.size(); ++i) { const auto result = pasDatabase.FindBestAnimation( CPASAnimParmData{pas::EAnimationState::AdditiveAim, CPASAnimParm::FromEnum(int(i))}, -1); x3c8_animIds[i] = result.second; } } zeus::CVector3f CGrenadeLauncher::GrenadeTarget(const CStateManager& mgr) { const zeus::CVector3f aim = mgr.GetPlayer().GetAimPosition(mgr, 1.f); if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) { return aim - zeus::CVector3f{0.f, 0.f, 0.5f * mgr.GetPlayer().GetEyeHeight()}; } return aim; } void CGrenadeLauncher::CalculateGrenadeTrajectory(const zeus::CVector3f& target, const zeus::CVector3f& origin, const SGrenadeTrajectoryInfo& info, float& angleOut, float& velocityOut) { float angle = info.GetAngleMin(); float velocity = info.GetVelocityMin(); float delta = std::max(0.01f, 0.1f * (info.GetAngleMax() - info.GetAngleMin())); zeus::CVector3f dist = target - origin; float distXYMag = dist.toVec2f().magnitude(); float velocityMinSq = info.GetVelocityMin() * info.GetVelocityMin(); float velocityMaxSq = info.GetVelocityMax() * info.GetVelocityMax(); float gravAdj = distXYMag * ((0.5f * CPhysicsActor::GravityConstant()) * distXYMag); float currAngle = info.GetAngleMin(); float leastResult = FLT_MAX; while (info.GetAngleMax() >= currAngle) { float cos = std::cos(currAngle); float sin = std::sin(currAngle); float result = (distXYMag * (cos * sin) - (dist.z() * (cos * cos))); if (result > FLT_EPSILON) { float div = gravAdj / result; if (velocityMinSq <= result && result <= velocityMaxSq) { angle = currAngle; velocity = std::sqrt(div); break; } if (result <= velocityMaxSq) { result = velocityMinSq - result; } else { result = result - velocityMaxSq; } if (result < leastResult) { angle = currAngle; velocity = std::sqrt(div); leastResult = result; } } currAngle += delta; } angleOut = angle; velocityOut = velocity; } void CGrenadeLauncher::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { CActor::AcceptScriptMsg(msg, uid, mgr); switch (msg) { case EScriptObjectMessage::Start: if (x2cc_parentId == uid && x258_started != 1) { x258_started = 1; UpdateStartAnimation(); } break; case EScriptObjectMessage::Stop: if (x2cc_parentId == uid && x258_started != 0) { x258_started = 0; UpdateStartAnimation(); } break; case EScriptObjectMessage::Action: if (x2cc_parentId == uid && x258_started == 1) { x3fc_launchGrenade = true; } break; case EScriptObjectMessage::Registered: UpdateStartAnimation(); break; case EScriptObjectMessage::Damage: x3ec_damageTimer = 0.33f; break; default: break; } } void CGrenadeLauncher::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) { CActor::AddToRenderer(frustum, mgr); } std::optional CGrenadeLauncher::GetTouchBounds() const { return x328_cSphere.CalculateAABox(GetTransform()); } void CGrenadeLauncher::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) { if (x3f4_damageAddColor.a() == 1.f) { xb4_drawFlags = CModelFlags{2, 0, 3, zeus::skWhite}; // Original code redundantly sets a() = 1.f // TODO xb4_drawFlags.addColor = x3f4_damageAddColor; } else { xb4_drawFlags = CModelFlags{5, 0, 3, x3f4_damageAddColor}; } CActor::PreRender(mgr, frustum); } void CGrenadeLauncher::Render(CStateManager& mgr) { if (x3fd_visible) { CPhysicsActor::Render(mgr); } } void CGrenadeLauncher::Think(float dt, CStateManager& mgr) { if (GetActive()) { if (x3fc_launchGrenade) { LaunchGrenade(mgr); x3fc_launchGrenade = false; } UpdateCollision(); UpdateColor(dt); UpdateFollowPlayer(mgr, dt); UpdateDamageTime(dt); const SAdvancementDeltas& deltas = CActor::UpdateAnimation(dt, mgr, true); MoveToOR(deltas.x0_posDelta, dt); RotateToOR(deltas.xc_rotDelta, dt); TCastToPtr parent = mgr.ObjectById(x2cc_parentId); if (parent == nullptr || !parent->IsAlive() || parent->HealthInfo(mgr)->GetHP() <= 0.f) { mgr.SendScriptMsg(parent, GetUniqueId(), EScriptObjectMessage::Damage); CreateExplosion(mgr); mgr.FreeScriptObject(GetUniqueId()); } } } void CGrenadeLauncher::Touch(CActor& act, CStateManager& mgr) { if (TCastToPtr projectile = act) { if (projectile->GetOwnerId() == mgr.GetPlayer().GetUniqueId() && GetDamageVulnerability()->WeaponHurts(CWeaponMode{projectile->GetType()}, false)) { x348_shotTimer = 0.5f; CEntity* parent = mgr.ObjectById(x2cc_parentId); if (parent != nullptr) { mgr.SendScriptMsg(parent, GetUniqueId(), EScriptObjectMessage::Touched); } } } } void CGrenadeLauncher::UpdateCollision() { x328_cSphere.SetSphereCenter(GetLocatorTransform("lockon_target_LCTR"sv).origin); } void CGrenadeLauncher::UpdateColor(float arg) { if (x348_shotTimer > 0.f) { x348_shotTimer = std::max(0.f, x348_shotTimer - arg); x34c_color1 = zeus::CColor::lerp(zeus::skWhite, zeus::skRed, x348_shotTimer); } } void CGrenadeLauncher::UpdateDamageTime(float arg) { if (x3ec_damageTimer <= 0.f) { xd0_damageMag = x3e8_thermalMag; } else { x3ec_damageTimer = std::max(0.f, x3ec_damageTimer - arg); x3f4_damageAddColor = zeus::CColor::lerp(zeus::skBlack, x3f0_color2, std::clamp(x3ec_damageTimer / 0.33f, 0.f, 1.f)); xd0_damageMag = 5.f * x3ec_damageTimer + x3e8_thermalMag; } } void CGrenadeLauncher::CreateExplosion(CStateManager& mgr) { if (!x3b8_particleGenDesc) { return; } mgr.AddObject(new CExplosion(*x3b8_particleGenDesc, mgr.AllocateUniqueId(), true, {GetAreaIdAlways(), CEntity::NullConnectionList}, "Grenade Launcher Explode Fx"sv, GetTransform(), 0, GetModelData()->GetScale(), zeus::skWhite)); CSfxManager::SfxStart(x2d0_data.GetShootSfxId(), 1.f, 1.f, false, 0x7f, false, kInvalidAreaId); } void CGrenadeLauncher::UpdateFollowPlayer(CStateManager& mgr, float dt) { CModelData* modelData = GetModelData(); CAnimData* animData = nullptr; if (modelData != nullptr && (animData = modelData->GetAnimationData()) != nullptr && x258_started == 1 && x3fe_followPlayer) { const zeus::CVector3f target = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation(); const zeus::CVector3f rot = GetTransform().transposeRotate({target.x(), target.y(), 0.f}); if (rot.canBeNormalized()) { constexpr float p36d = zeus::degToRad(36.476f); constexpr float n45d = zeus::degToRad(-45.f); constexpr float p45d = zeus::degToRad(45.f); float l84 = p36d * std::clamp(std::atan2(rot.x(), rot.y()), n45d, p45d); float l88 = std::clamp((0.25f * (l84 - x3d8_)) / dt, -3.f, 3.f); float l8c = std::clamp((l88 - x3dc_) / dt, -10.f, 10.f); x3dc_ += dt * l8c; float l90 = p36d * std::clamp(std::atan2(rot.z(), rot.toVec2f().magnitude()), n45d, p45d); l88 = std::clamp((0.25f * (l90 - x3e0_)) / dt, -3.f, 3.f); l8c = std::clamp((l88 - x3e4_) / dt, -10.f, 10.f); x3e4_ += dt * l8c; float dVar7 = std::clamp(dt * x3dc_ + x3d8_, -0.5f, 0.5f); float dVar8 = std::clamp(dt * x3e4_ + x3e0_, -0.5f, 0.5f); if (dVar7 != x3d8_) { if (std::abs(x3d8_) > 0.f && x3d8_ * dVar7 <= 0.f) { animData->DelAdditiveAnimation(x3c8_animIds[x3d8_ >= 0.f ? 1 : 0]); } float weight = std::abs(dVar7); if (weight > 0.f) { animData->AddAdditiveAnimation(x3c8_animIds[dVar7 >= 0.f ? 1 : 0], weight, false, false); } } if (dVar8 != x3e0_) { if (std::abs(x3e0_) > 0.f && x3e0_ * dVar8 <= 0.f) { animData->DelAdditiveAnimation(x3c8_animIds[x3e0_ <= 0.f ? 3 : 2]); } float weight = std::abs(dVar8); if (weight > 0.f) { animData->AddAdditiveAnimation(x3c8_animIds[dVar8 <= 0.f ? 3 : 2], weight, false, false); } } x3d8_ = dVar7; x3e0_ = dVar8; } } else { if (x3d8_ != 0.f) { if (animData != nullptr) { // Original code does not check animData->DelAdditiveAnimation(x3c8_animIds[x3d8_ >= 0.f ? 1 : 0]); } x3d8_ = 0.f; } if (x3e0_ != 0.f) { if (animData != nullptr) { // Original code does not check animData->DelAdditiveAnimation(x3c8_animIds[x3e0_ <= 0.f ? 3 : 2]); } x3e0_ = 0.f; } } } void CGrenadeLauncher::UpdateStartAnimation() { CModelData* modelData = GetModelData(); CAnimData* animData; if (modelData == nullptr || (animData = modelData->GetAnimationData()) == nullptr || x258_started < 0 || x258_started > 1) { return; } constexpr std::array arr{0, 3}; const auto anim = animData->GetCharacterInfo().GetPASDatabase().FindBestAnimation( CPASAnimParmData{pas::EAnimationState::Locomotion, CPASAnimParm::FromEnum(0), CPASAnimParm::FromEnum(arr[x258_started])}, -1); if (anim.first > 0.f) { animData->SetAnimation({anim.second, -1, 1.f, true}, false); modelData->EnableLooping(true); } } void CGrenadeLauncher::LaunchGrenade(CStateManager& mgr) { CModelData* modelData = GetModelData(); CAnimData* animData; if (modelData == nullptr || (animData = modelData->GetAnimationData()) == nullptr) { return; } const auto& anim = animData->GetCharacterInfo().GetPASDatabase().FindBestAnimation( CPASAnimParmData{pas::EAnimationState::AdditiveFlinch}, -1); if (anim.first > 0.f) { animData->AddAdditiveAnimation(anim.second, 1.f, false, true); const zeus::CVector3f origin = GetTranslation() + GetTransform().rotate(GetLocatorTransform("grenade_LCTR"sv).origin); const zeus::CVector3f target = GrenadeTarget(mgr); float angleOut = x2d0_data.GetGrenadeTrajectoryInfo().GetAngleMin(); float velocityOut = x2d0_data.GetGrenadeTrajectoryInfo().GetVelocityMin(); CalculateGrenadeTrajectory(target, origin, x2d0_data.GetGrenadeTrajectoryInfo(), angleOut, velocityOut); zeus::CVector3f dist = target - origin; dist.z() = 0.f; const zeus::CVector3f front = GetTransform().frontVector(); if (dist.canBeNormalized()) { dist.normalize(); } else { dist = front; } constexpr float maxAngle = zeus::degToRad(45.f); if (zeus::CVector3f::getAngleDiff(front, dist) > maxAngle) { dist = zeus::CVector3f::slerp(front, dist, maxAngle); } const zeus::CVector3f look = zeus::CVector3f::slerp(dist, zeus::skUp, angleOut); const zeus::CTransform xf = zeus::lookAt(origin, origin + look, zeus::skUp); CModelData mData{CStaticRes{x2d0_data.GetGrenadeModelId(), GetModelData()->GetScale()}}; mgr.AddObject(new CBouncyGrenade(mgr.AllocateUniqueId(), "Bouncy Grenade"sv, {GetAreaIdAlways(), CEntity::NullConnectionList}, xf, std::move(mData), x350_grenadeActorParams, x2cc_parentId, x2d0_data.GetGrenadeData(), velocityOut, x3f8_explodePlayerDistance)); } } } // namespace metaforce::MP1