diff --git a/Runtime/Character/CharacterCommon.hpp b/Runtime/Character/CharacterCommon.hpp index 51a3d710e..ca11ab243 100644 --- a/Runtime/Character/CharacterCommon.hpp +++ b/Runtime/Character/CharacterCommon.hpp @@ -94,7 +94,7 @@ enum class EGetupType { Invalid = -1, Zero = 0, One = 1, Two = 2 }; enum class ELoopState { Invalid = -1, Begin, Loop, End }; -enum class ELoopAttackType { Invalid = -1 }; +enum class ELoopAttackType { Invalid = -1, Zero, One, Two, Three }; enum class EGenerateType { Invalid = -1, Zero, One, Two, Three, Four, Five }; diff --git a/Runtime/MP1/World/CMetroid.cpp b/Runtime/MP1/World/CMetroid.cpp index 97500dfab..d858b2479 100644 --- a/Runtime/MP1/World/CMetroid.cpp +++ b/Runtime/MP1/World/CMetroid.cpp @@ -2,7 +2,9 @@ #include "Runtime/CStateManager.hpp" #include "Runtime/Character/CPASAnimParmData.hpp" +#include "Runtime/Collision/CGameCollision.hpp" #include "Runtime/Weapon/CGameProjectile.hpp" +#include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CTeamAiMgr.hpp" #include "Runtime/World/CWorld.hpp" #include "Runtime/World/ScriptLoader.hpp" @@ -43,17 +45,19 @@ constexpr CDamageVulnerability skNormalDamageVulnerability{ EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None, }; + +constexpr auto skPirateSuckJoint = "Head_1"sv; } // namespace CMetroidData::CMetroidData(CInputStream& in) : x0_frozenVulnerability(in) , x68_energyDrainVulnerability(in) , xd0_(in.readFloatBig()) -, xd4_(in.readFloatBig()) +, xd4_maxEnergyDrainAllowed(in.readFloatBig()) , xd8_(in.readFloatBig()) -, xdc_(in.readFloatBig()) -, xe0_(in.readFloatBig()) -, xe4_(in.readFloatBig()) { +, xdc_stage2GrowthScale(in.readFloatBig()) +, xe0_stage2GrowthEnergy(in.readFloatBig()) +, xe4_explosionGrowthEnergy(in.readFloatBig()) { xe8_animParms1 = ScriptLoader::LoadAnimationParameters(in); xf8_animParms2 = ScriptLoader::LoadAnimationParameters(in); x108_animParms3 = ScriptLoader::LoadAnimationParameters(in); @@ -186,4 +190,579 @@ const CDamageVulnerability* CMetroid::GetDamageVulnerability() const { return CAi::GetDamageVulnerability(); } +zeus::CVector3f CMetroid::GetOrigin(const CStateManager& mgr, const CTeamAiRole& role, + const zeus::CVector3f& aimPos) const { + CPlayer& player = mgr.GetPlayer(); + float range = 0.5f * (x2fc_minAttackRange + x300_maxAttackRange); + const zeus::CVector3f& pos = GetTranslation(); + TUniqueId target = x7b0_attackTarget; + if (target == player.GetUniqueId()) { + const zeus::CVector3f& playerPos = player.GetTranslation(); + const zeus::CVector3f& playerFace = player.GetTransform().frontVector(); + if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) { + zeus::CVector3f face = playerPos - pos; + face.z() = 0.f; + if (face.canBeNormalized()) { + face.normalize(); + } else { + face = playerFace; + } + return playerPos + zeus::CVector3f{range * face.x(), range * face.y(), 0.5f}; + } + return aimPos + zeus::CVector3f{range * playerFace.x(), range * playerFace.y(), 0.5f}; + } + if (TCastToConstPtr actor = mgr.GetObjectById(target)) { + const zeus::CVector3f& actorPos = actor->GetTranslation(); + zeus::CVector3f face = pos - actorPos; + face.z() = 0.f; + if (face.canBeNormalized()) { + face.normalize(); + } else { + face = actor->GetTransform().frontVector(); + } + return actorPos + zeus::CVector3f{range * face.x(), range * face.y(), 0.5f}; + } + return pos; +} + +void CMetroid::SelectTarget(CStateManager& mgr, EStateMsg msg, float arg) { + if (msg == EStateMsg::Activate) { + x568_state = x9bf_27_ ? EState::Over : EState::One; + if (x7b0_attackTarget == kInvalidUniqueId) { + CPlayer& player = mgr.GetPlayer(); + const zeus::CVector3f& pos = GetTranslation(); + float playerDistSq = (player.GetTranslation() - pos).magSquared(); + x7b0_attackTarget = player.GetUniqueId(); + if (!x450_bodyController->HasBeenFrozen()) { + float range = std::max(x3bc_detectionRange, std::sqrt(playerDistSq)); + rstl::reserved_vector nearList; + mgr.BuildNearList(nearList, zeus::CAABox{pos - range, pos + range}, + CMaterialFilter::MakeInclude({EMaterialTypes::Character}), nullptr); + CSpacePirate* closestPirate = nullptr; + for (const auto id : nearList) { + if (auto* pirate = CPatterned::CastTo(mgr.ObjectById(id))) { + if (IsPirateValidTarget(pirate, mgr)) { + float distSq = (pirate->GetTranslation() - pos).magSquared(); + if (distSq < playerDistSq) { + closestPirate = pirate; + playerDistSq = distSq; + } + } + } + } + if (closestPirate != nullptr) { + x7b0_attackTarget = closestPirate->GetUniqueId(); + closestPirate->SetAttackTarget(GetUniqueId()); + } + } + } + if (auto* pirate = CPatterned::CastTo(mgr.ObjectById(x7b0_attackTarget))) { + mgr.SendScriptMsg(pirate, GetUniqueId(), EScriptObjectMessage::Alert); + } + } else if (msg == EStateMsg::Update) { + if (x568_state == EState::One) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) { + x568_state = EState::Two; + } else { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Three, zeus::skZero3f)); + } + } else if (x568_state == EState::Two) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) { + if (x7b0_attackTarget != kInvalidUniqueId) { + if (TCastToConstPtr actor = mgr.GetObjectById(x7b0_attackTarget)) { + x450_bodyController->GetCommandMgr().DeliverTargetVector(actor->GetTranslation() - GetTranslation()); + } + } + } else { + x568_state = EState::Over; + } + } + } +} + +void CMetroid::Touch(CActor& act, CStateManager& mgr) { + if (IsAlive()) { + if (TCastToPtr proj = act) { + if (proj->GetOwnerId() == mgr.GetPlayer().GetUniqueId() && x3fc_flavor != CPatterned::EFlavorType::Two && + proj->HasAttrib(EProjectileAttrib::Ice)) { + if (GetDamageVulnerability()->WeaponHits(CWeaponMode{EWeaponType::Ice, false}, false)) { + float timeScale = proj->HasAttrib(EProjectileAttrib::Charged) ? 2.f : 1.f; + const zeus::CVector3f& projPos = proj->GetTranslation(); + Freeze(mgr, projPos - GetTranslation(), x34_transform.transposeRotate(projPos - proj->GetPreviousPos()), + timeScale * x4fc_freezeDur); + } + } + x9bf_26_shotAt = true; + } + CPatterned::Touch(act, mgr); + } +} + +bool CMetroid::IsPirateValidTarget(CSpacePirate* target, CStateManager& mgr) { + if (target->GetAttachedActor() == kInvalidUniqueId && target->IsTrooper()) { + const CHealthInfo* healthInfo = target->GetHealthInfo(mgr); + if (healthInfo != nullptr && healthInfo->GetHP() > 0.f) { + return true; + } + } + return false; +} + +void CMetroid::UpdateAttackChance(CStateManager& mgr, float dt) { + if (IsAttackInProgress(mgr)) { + x7b4_attackChance = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime; + } else if (x7b4_attackChance > 0.f) { + float delta = dt; + if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) { + delta = 2.f * dt; + } + x7b4_attackChance -= delta; + } +} + +bool CMetroid::IsAttackInProgress(CStateManager& mgr) { + if (TCastToConstPtr actor = mgr.GetObjectById(x7b0_attackTarget)) { + if (x7b0_attackTarget == mgr.GetPlayer().GetUniqueId()) { + const TUniqueId attachedActor = mgr.GetPlayer().GetAttachedActor(); + if (attachedActor != kInvalidUniqueId && attachedActor != GetUniqueId()) { + return true; + } + } else if (const auto* pirate = CPatterned::CastTo(actor.GetPtr())) { + const TUniqueId attachedActor = pirate->GetAttachedActor(); + if (attachedActor != kInvalidUniqueId && attachedActor != GetUniqueId()) { + return true; + } + } + } + return false; +} + +void CMetroid::SuckEnergyFromTarget(CStateManager& mgr, float dt) { + // TODO +} + +void CMetroid::RestoreSolidCollision(CStateManager& mgr) { + constexpr auto filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid, EMaterialTypes::AIBlock}); + const zeus::CVector3f& pos = GetTranslation(); + if (x9bf_30_ && !CGameCollision::DetectStaticCollisionBoolean(mgr, x6a0_collisionPrimitive, GetTransform(), filter)) { + bool add = true; + if (!x80c_.isZero()) { + const zeus::CVector3f dir = pos - x80c_; + float mag = dir.magnitude(); + if (mag > 0.f) { + // TODO double check bool + add = mgr.RayStaticIntersection(x80c_, (1.f / mag) * dir, mag, filter).IsInvalid(); + } + } + if (add) { + AddMaterial(EMaterialTypes::Solid, mgr); + x9bf_30_ = false; + } + } + if (x9bf_31_) { + constexpr auto nearFilter = + CMaterialFilter::MakeInclude({EMaterialTypes::Solid, EMaterialTypes::Player, EMaterialTypes::Character}); + float radius = x808_loopAttackDistance * GetModelData()->GetScale().y(); + const zeus::CAABox box{pos - radius, pos + radius}; + rstl::reserved_vector nearList; + mgr.BuildNearList(nearList, box, nearFilter, this); + if (!CGameCollision::DetectDynamicCollisionBoolean(x6a0_collisionPrimitive, GetTransform(), nearList, mgr)) { + x9bf_31_ = false; + CMaterialFilter matFilter = GetMaterialFilter(); + matFilter.ExcludeList().Remove({EMaterialTypes::Character, EMaterialTypes::Player}); + SetMaterialFilter(matFilter); + } + } +} + +void CMetroid::PreventWorldCollisions(CStateManager& mgr, float dt) { + float size = 2.f * x6a0_collisionPrimitive.GetSphere().radius; + if (IsSuckingEnergy()) { + if (x7b0_attackTarget == mgr.GetPlayer().GetUniqueId()) { + if (TCastToPtr actor = mgr.ObjectById(x7b0_attackTarget)) { + // why not use mgr.GetPlayer()? :thonking: + float mass = 300.f; + if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) { + float scale = std::min(1.f, 1.33f * x7c0_); + mass = 300.f * (1.f - scale) + 7500.f * scale; + } + CGameCollision::AvoidStaticCollisionWithinRadius(mgr, *actor, 8, dt, 0.25f, size, mass, 0.5f); + } + } + x7c4_ = 0.f; + } else if (!x9bf_30_ && !x9bf_31_) { + x7c4_ = 0.f; + } else { + x7c4_ += dt; + if (x7c4_ <= 6.f) { + if (x9bf_30_ && 0.25f < x7c4_) { + RemoveMaterial(EMaterialTypes::Solid, mgr); + } + } else { + MassiveDeath(mgr); + } + CGameCollision::AvoidStaticCollisionWithinRadius(mgr, *this, 8, dt, 0.25f, size, 15000.f, 0.5f); + } +} + +void CMetroid::SwarmRemove(CStateManager& mgr) { + if (x698_teamAiMgrId == kInvalidUniqueId) { + return; + } + if (TCastToPtr aiMgr = mgr.ObjectById(x698_teamAiMgrId)) { + if (aiMgr->IsPartOfTeam(GetUniqueId())) { + aiMgr->RemoveTeamAiRole(GetUniqueId()); + } + } +} + +void CMetroid::SwarmAdd(CStateManager& mgr) { + if (x698_teamAiMgrId == kInvalidUniqueId) { + return; + } + if (TCastToPtr aiMgr = mgr.ObjectById(x698_teamAiMgrId)) { + if (!aiMgr->IsPartOfTeam(GetUniqueId())) { + aiMgr->AssignTeamAiRole(*this, CTeamAiRole::ETeamAiRole::Melee, CTeamAiRole::ETeamAiRole::Invalid, + CTeamAiRole::ETeamAiRole::Invalid); + } + } +} + +void CMetroid::ApplyGrowth(float arg) { + x7f8_ += arg; + const float energy = std::clamp(x7f8_ / x56c_data.GetStage2GrowthEnergy(), 0.f, 1.f); + const float scale = x56c_data.GetStage2GrowthScale() - x7e8_scale3.y(); + x7d0_scale1 = zeus::CVector3f{energy * scale + x7e8_scale3.y()}; + TakeDamage(zeus::skZero3f, 0.f); +} + +bool CMetroid::IsSuckingEnergy() const { return x7c8_ == EUnknown::Two && !x450_bodyController->IsFrozen(); } + +void CMetroid::UpdateVolume() { + // TODO +} + +void CMetroid::UpdateTouchBounds() { + const zeus::CTransform& locXf = GetLocatorTransform("lockon_target_LCTR"sv); + x6a0_collisionPrimitive.SetSphereCenter(locXf * GetModelData()->GetScale()); +} + +void CMetroid::Attack(CStateManager& mgr, EStateMsg msg, float dt) { + if (msg == EStateMsg::Activate) { + if (AttachToTarget(mgr)) { + x568_state = EState::One; + x7bc_ = 0.f; + x7c8_ = EUnknown::One; + x9bf_29_isAttacking = true; + RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr); + mgr.GetPlayer().SetOrbitRequestForTarget(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr); + DisableSolidCollision(this); + AddMaterial(EMaterialTypes::Trigger, mgr); + } else { + x568_state = EState::Over; + } + } else if (msg == EStateMsg::Update) { + if (x568_state == EState::One) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::LoopAttack) { + x568_state = EState::Two; + } else { + GetBodyController()->GetCommandMgr().DeliverCmd(CBCLoopAttackCmd(pas::ELoopAttackType::Three)); + } + } else if (x568_state == EState::Two) { + if (GetModelData()->GetAnimationData()->GetIsLoop()) { + x7c8_ = EUnknown::Two; + } + const CPlayer& player = mgr.GetPlayer(); + if (x7b0_attackTarget == player.GetUniqueId() && player.GetAttachedActor() == GetUniqueId() && + player.GetAreaIdAlways() != GetAreaIdAlways()) { + DetachFromTarget(mgr); + x401_30_pendingDeath = true; + } else if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::LoopAttack) { + if (ShouldReleaseFromTarget(mgr)) { + GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState)); + DetachFromTarget(mgr); + x7c8_ = EUnknown::Three; + } + } else { + x568_state = EState::Over; + } + } + } else if (msg == EStateMsg::Deactivate) { + CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Melee, mgr, x698_teamAiMgrId, GetUniqueId(), false); + x7b4_attackChance = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime; + x7c8_ = EUnknown::Zero; + DetachFromTarget(mgr); + x9bf_29_isAttacking = false; + SetTransform({zeus::CQuaternion::fromAxisAngle({0.f, 0.f, 1.f}, GetYaw()), GetTranslation()}); + AddMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr); + RemoveMaterial(EMaterialTypes::Trigger, mgr); + } +} + +bool CMetroid::AttachToTarget(CStateManager& mgr) { + CPlayer& player = mgr.GetPlayer(); + if (x7b0_attackTarget == player.GetUniqueId()) { + if (player.AttachActorToPlayer(GetUniqueId(), false)) { + player.GetEnergyDrain().AddEnergyDrainSource(GetUniqueId(), 1.f); + return true; + } + return false; + } + return PreDamageSpacePirate(mgr); +} + +void CMetroid::DetachFromTarget(CStateManager& mgr) { + CPlayer& player = mgr.GetPlayer(); + CActor* target = nullptr; + zeus::CVector3f vec; + zeus::CTransform xf; + if (x7b0_attackTarget == player.GetUniqueId()) { + if (player.GetAttachedActor() == GetUniqueId()) { + player.DetachActorFromPlayer(); + if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) { + const auto q1 = zeus::CQuaternion::fromAxisAngle({0.f, 0.f, 1.f}, M_PI); + const auto q2 = zeus::CQuaternion::fromAxisAngle({0.f, 0.f, 1.f}, GetYaw()); + const auto mat = zeus::CMatrix3f{q2 * q1}; + vec = mat * zeus::skForward; + xf = zeus::CTransform{mat, player.GetTranslation()}; + } else { + vec = player.GetTransform().frontVector(); + xf = player.GetTransform(); + } + mgr.GetPlayer().GetEnergyDrain().RemoveEnergyDrainSource(GetUniqueId()); + x80c_ = player.GetAimPosition(mgr, 0.f); + target = &player; + } + } else if (x7b0_attackTarget != kInvalidUniqueId) { + if (auto* pirate = CPatterned::CastTo(mgr.ObjectById(x7b0_attackTarget))) { + if (pirate->GetAttachedActor() == GetUniqueId()) { + pirate->DetachActorFromPirate(); + vec = pirate->GetTransform().frontVector(); + xf = pirate->GetTransform(); + x80c_ = GetTranslation(); + target = pirate; + } + } + } + SetupExitFaceHugDirection(target, mgr, vec, xf); + x9bf_31_ = true; + x9bf_30_ = true; +} + +bool CMetroid::ShouldReleaseFromTarget(CStateManager& mgr) { + if (x450_bodyController->IsFrozen()) { + return true; + } + CPlayer& player = mgr.GetPlayer(); + if (x7b0_attackTarget == GetUniqueId()) { + if (x7bc_ >= x56c_data.GetMaxEnergyDrainAllowed() * GetDamageMultiplier() || IsPlayerUnderwater(mgr)) { + return true; + } + if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Unmorphed) { + return IsHunterAttacking(mgr); + } + } else if (x7b0_attackTarget != kInvalidUniqueId) { + if (const auto* pirate = CPatterned::CastTo(mgr.GetObjectById(x7b0_attackTarget))) { + if (!pirate->AllEnergyDrained() && !pirate->GetBodyController()->GetBodyStateInfo().GetCurrentState()->IsDead()) { + return false; + } + } + return true; + } + return false; +} + +void CMetroid::DisableSolidCollision(CMetroid* target) { + CMaterialFilter filter = target->GetMaterialFilter(); + filter.ExcludeList().Add({EMaterialTypes::Character, EMaterialTypes::Player}); + target->SetMaterialFilter(filter); +} + +void CMetroid::SetupExitFaceHugDirection(CActor* actor, CStateManager& mgr, const zeus::CVector3f& vec, + const zeus::CTransform& xf) { + // TODO +} + +bool CMetroid::PreDamageSpacePirate(CStateManager& mgr) { + // TODO + return false; +} + +bool CMetroid::IsPlayerUnderwater(CStateManager& mgr) { + // TODO + return false; +} + +bool CMetroid::IsHunterAttacking(CStateManager& mgr) { + // TODO + return false; +} + +float CMetroid::GetGrowthStage() { + const float energy = x7f8_; + const float stage2GrowthEnergy = x56c_data.GetStage2GrowthEnergy(); + if (energy < stage2GrowthEnergy) { + return 1.f + energy / stage2GrowthEnergy; + } + const float explosionGrowthEnergy = x56c_data.GetExplosionGrowthEnergy(); + if (energy < explosionGrowthEnergy) { + return 2.f + (energy - stage2GrowthEnergy) / (explosionGrowthEnergy - stage2GrowthEnergy); + } + return 3.f; +} + +bool CMetroid::ShouldAttack(CStateManager& mgr, float arg) { + if (!CanAttack(mgr)) { + return false; + } + if (TCastToPtr aiMgr = mgr.ObjectById(x698_teamAiMgrId)) { + return aiMgr->AddMeleeAttacker(GetUniqueId()); + } + return true; +} + +bool CMetroid::CanAttack(CStateManager& mgr) { + if (x7b4_attackChance <= 0.f) { + CPlayer& player = mgr.GetPlayer(); + if (x7b0_attackTarget == player.GetUniqueId()) { + if (IsPlayerUnderwater(mgr) || + (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed && + player.GetMorphBall()->GetSpiderBallState() == CMorphBall::ESpiderBallState::Active)) { + return false; + } + if (player.GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed && IsHunterAttacking(mgr)) { + return false; + } + } + const CEntity* target = mgr.GetObjectById(x7b0_attackTarget); + if (target != nullptr && target->GetAreaIdAlways() == GetAreaIdAlways()) { + return !IsAttackInProgress(mgr); + } + } + return false; +} + +void CMetroid::PathFind(CStateManager& mgr, EStateMsg msg, float arg) { + if (msg == EStateMsg::Activate) { + GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk); + GetBodyController()->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed); + GetBodyController()->GetCommandMgr().SetSteeringSpeedRange(1.f, 1.f); + SetUpPathFindBehavior(mgr); + } else if (msg == EStateMsg::Update) { + const auto* searchPath = GetSearchPath(); + if (searchPath == nullptr || PathShagged(mgr, 0.f) || PathOver(mgr, 0.f)) { + const auto* aiRole = CTeamAiMgr::GetTeamAiRole(mgr, x698_teamAiMgrId, GetUniqueId()); + if (aiRole == nullptr) { + x7a4_ = GetOrigin(mgr, CTeamAiRole{GetUniqueId()}, GetAttackTargetPos(mgr)); + } else { + x7a4_ = aiRole->GetTeamPosition(); + } + ApplyForwardSteering(mgr, x7a4_); + } else { + CPatterned::PathFind(mgr, msg, arg); + } + ApplySeparationBehavior(mgr, 9.f); + } else if (msg == EStateMsg::Deactivate) { + GetBodyController()->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::Normal); + } +} + +void CMetroid::ApplyForwardSteering(CStateManager& mgr, const zeus::CVector3f& vec) { + if ((vec - GetTranslation()).magSquared() <= 4.f) { + if (ShouldTurn(mgr, 0.f) && x7b0_attackTarget != kInvalidUniqueId) { + if (TCastToConstPtr actor = mgr.GetObjectById(x7b0_attackTarget)) { + const zeus::CVector3f dir = actor->GetTranslation() - GetTranslation(); + if (dir.canBeNormalized()) { + GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd{zeus::skZero3f, dir.normalized(), 1.f}); + } + } + } + } else { + const zeus::CVector3f arrival = x45c_steeringBehaviors.Arrival(*this, GetTranslation().toVec2f(), 0.5f); + if (arrival.magSquared() > 0.01f) { + GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(arrival, zeus::skZero3f, 3.f)); + } + if (TCastToConstPtr actor = mgr.GetObjectById(x7b0_attackTarget)) { + const auto move = x45c_steeringBehaviors.Pursuit(*this, vec, actor->GetVelocity()); + GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f)); + } else { + const auto move = x45c_steeringBehaviors.Seek(*this, vec); + GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f)); + } + } +} + +void CMetroid::ApplySeparationBehavior(CStateManager& mgr, float arg) { + // TODO +} + +void CMetroid::SetUpPathFindBehavior(CStateManager& mgr) { + x9bf_28_ = false; + if (GetSearchPath() == nullptr) { + return; + } + if (const auto* role = CTeamAiMgr::GetTeamAiRole(mgr, x698_teamAiMgrId, GetUniqueId())) { + SetDestPos(role->GetTeamPosition()); + } else { + SetDestPos(GetOrigin(mgr, CTeamAiRole{GetUniqueId()}, mgr.GetPlayer().GetTranslation())); + } + const zeus::CVector3f targetPos = GetAttackTargetPos(mgr); + const zeus::CVector3f dir = GetDestPos() - targetPos; + if (dir.canBeNormalized()) { + constexpr auto filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid, EMaterialTypes::AIBlock}); + float mag = dir.magnitude(); + const zeus::CVector3f dirScaled = (1.f / mag) * dir; + const auto result = mgr.RayStaticIntersection(targetPos, dirScaled, mag, filter); + if (result.IsValid()) { + SetDestPos(targetPos + 0.5f * result.GetT() * dirScaled); + x9bf_28_ = true; + } + } + x7a4_ = GetDestPos(); + CPatterned::PathFind(mgr, EStateMsg::Activate, 0.f); +} + +zeus::CVector3f CMetroid::GetAttackTargetPos(CStateManager& mgr) { + const TUniqueId targetId = x7b0_attackTarget; + if (targetId != kInvalidUniqueId) { + CPlayer& player = mgr.GetPlayer(); + if (targetId == player.GetUniqueId()) { + if (player.GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) { + return player.GetMorphBall()->GetBallToWorld().origin; + } + return player.GetTranslation() + zeus::CVector3f{0.f, 0.f, -0.6f + player.GetEyeHeight()}; + } + if (TCastToConstPtr actor = mgr.GetObjectById(targetId)) { + const auto locXf = actor->GetLocatorTransform(skPirateSuckJoint); + return actor->GetTranslation() + + zeus::CVector3f{0.f, 0.f, locXf.origin.z() * actor->GetModelData()->GetScale().z() + 0.4f}; + } + } + return mgr.GetPlayer().GetAimPosition(mgr, 0.f); +} + +bool CMetroid::AggressionCheck(CStateManager& mgr, float arg) { + if (x7b0_attackTarget != kInvalidUniqueId) { + CEntity* target = mgr.ObjectById(x7b0_attackTarget); + if (auto* pirate = CPatterned::CastTo(target)) { + if (!IsPirateValidTarget(pirate, mgr)) { + x7b0_attackTarget = kInvalidUniqueId; + return false; + } + } + if (TCastToPtr actor = target) { + const zeus::CVector3f dir = actor->GetTranslation() - GetTranslation(); + if (x3bc_detectionRange * x3bc_detectionRange > dir.magSquared()) { + if (x3c0_detectionHeightRange > 0.f) { + return dir.z() * dir.z() < x3c0_detectionHeightRange * x3c0_detectionHeightRange; + } + return true; + } + } else { + x7b0_attackTarget = kInvalidUniqueId; + } + } + return false; +} + } // namespace urde::MP1 diff --git a/Runtime/MP1/World/CMetroid.hpp b/Runtime/MP1/World/CMetroid.hpp index 486405da2..aa36bf3b4 100644 --- a/Runtime/MP1/World/CMetroid.hpp +++ b/Runtime/MP1/World/CMetroid.hpp @@ -19,11 +19,11 @@ private: CDamageVulnerability x0_frozenVulnerability; CDamageVulnerability x68_energyDrainVulnerability; float xd0_; - float xd4_; + float xd4_maxEnergyDrainAllowed; float xd8_; - float xdc_; - float xe0_; - float xe4_; + float xdc_stage2GrowthScale; + float xe0_stage2GrowthEnergy; + float xe4_explosionGrowthEnergy; std::optional xe8_animParms1; std::optional xf8_animParms2; std::optional x108_animParms3; @@ -35,7 +35,11 @@ public: static u32 GetNumProperties() { return skNumProperties; } const CDamageVulnerability& GetFrozenVulnerability() const { return x0_frozenVulnerability; } const CDamageVulnerability& GetEnergyDrainVulnerability() const { return x68_energyDrainVulnerability; } - bool GetStartsInWall() { return x128_24_startsInWall; } + float GetMaxEnergyDrainAllowed() const { return xd4_maxEnergyDrainAllowed; } + float GetStage2GrowthScale() const { return xdc_stage2GrowthScale; } + float GetStage2GrowthEnergy() const { return xe0_stage2GrowthEnergy; } + float GetExplosionGrowthEnergy() const { return xe4_explosionGrowthEnergy; } + bool GetStartsInWall() const { return x128_24_startsInWall; } }; class CMetroid : public CPatterned { @@ -128,36 +132,37 @@ public: void Touch(CActor& act, CStateManager& mgr) override; void Attack(CStateManager& mgr, EStateMsg msg, float dt) override; - void Dodge(CStateManager& mgr, EStateMsg msg, float dt) override; - void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override; - void Generate(CStateManager& mgr, EStateMsg msg, float arg) override; - void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo& info, EKnockBackType type, bool inDeferred, - float magnitude) override; + // void Dodge(CStateManager& mgr, EStateMsg msg, float dt) override; + // void Death(CStateManager& mgr, const zeus::CVector3f& direction, EScriptObjectState state) override; + // void Generate(CStateManager& mgr, EStateMsg msg, float arg) override; + // void KnockBack(const zeus::CVector3f&, CStateManager&, const CDamageInfo& info, EKnockBackType type, bool + // inDeferred, + // float magnitude) override; void PathFind(CStateManager& mgr, EStateMsg msg, float arg) override; - void Patrol(CStateManager& mgr, EStateMsg msg, float arg) override; - void TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) override; - void TelegraphAttack(CStateManager& mgr, EStateMsg msg, float dt) override; - void TurnAround(CStateManager& mgr, EStateMsg msg, float dt) override; - void WallHang(CStateManager& mgr, EStateMsg msg, float dt) override; + // void Patrol(CStateManager& mgr, EStateMsg msg, float arg) override; + // void TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) override; + // void TelegraphAttack(CStateManager& mgr, EStateMsg msg, float dt) override; + // void TurnAround(CStateManager& mgr, EStateMsg msg, float dt) override; + // void WallHang(CStateManager& mgr, EStateMsg msg, float dt) override; bool AnimOver(CStateManager&, float arg) override { return x568_state == EState::Over; } bool AggressionCheck(CStateManager& mgr, float arg) override; - bool Attacked(CStateManager& mgr, float arg) override; - bool AttackOver(CStateManager& mgr, float arg) override; - bool InAttackPosition(CStateManager& mgr, float arg) override; - bool InDetectionRange(CStateManager& mgr, float arg) override; - bool InPosition(CStateManager& mgr, float arg) override; - bool InRange(CStateManager& mgr, float arg) override; - bool Inside(CStateManager& mgr, float arg) override; - bool Leash(CStateManager& mgr, float arg) override; - bool LostInterest(CStateManager& mgr, float arg) override; - bool PatternShagged(CStateManager& mgr, float arg) override; + // bool Attacked(CStateManager& mgr, float arg) override; + // bool AttackOver(CStateManager& mgr, float arg) override; + // bool InAttackPosition(CStateManager& mgr, float arg) override; + // bool InDetectionRange(CStateManager& mgr, float arg) override; + // bool InPosition(CStateManager& mgr, float arg) override; + // bool InRange(CStateManager& mgr, float arg) override; + // bool Inside(CStateManager& mgr, float arg) override; + // bool Leash(CStateManager& mgr, float arg) override; + // bool LostInterest(CStateManager& mgr, float arg) override; + // bool PatternShagged(CStateManager& mgr, float arg) override; bool ShotAt(CStateManager& mgr, float arg) override { return x9bf_26_shotAt; } bool ShouldAttack(CStateManager& mgr, float arg) override; - bool ShouldDodge(CStateManager& mgr, float arg) override; - bool ShouldTurn(CStateManager& mgr, float arg) override; + // bool ShouldDodge(CStateManager& mgr, float arg) override; + // bool ShouldTurn(CStateManager& mgr, float arg) override; bool ShouldWallHang(CStateManager& mgr, float arg) override { return x56c_data.GetStartsInWall(); } - bool SpotPlayer(CStateManager& mgr, float arg) override; + // bool SpotPlayer(CStateManager& mgr, float arg) override; bool IsAttacking() const { return x9bf_29_isAttacking; } @@ -181,7 +186,7 @@ private: void DisableSolidCollision(CMetroid* target); void RestoreSolidCollision(CStateManager& mgr); void PreventWorldCollisions(CStateManager& mgr, float dt); - void SetupExitFaceHugDirection(CActor& actor, CStateManager& mgr, const zeus::CVector3f& vec, + void SetupExitFaceHugDirection(CActor* actor, CStateManager& mgr, const zeus::CVector3f& vec, const zeus::CTransform& xf); void DetachFromTarget(CStateManager& mgr); bool AttachToTarget(CStateManager& mgr); diff --git a/Runtime/MP1/World/CSpacePirate.hpp b/Runtime/MP1/World/CSpacePirate.hpp index e1e8be21f..d006780a9 100644 --- a/Runtime/MP1/World/CSpacePirate.hpp +++ b/Runtime/MP1/World/CSpacePirate.hpp @@ -330,5 +330,7 @@ public: CProjectileInfo* GetProjectileInfo() override; bool GetEnableAim() const { return x637_25_enableAim; } bool AllEnergyDrained() const { return x638_30_allEnergyDrained; } + TUniqueId GetAttachedActor() const { return x7b4_attachedActor; } + bool IsTrooper() const { return x636_24_trooper; } }; } // namespace urde::MP1 diff --git a/Runtime/World/CPatterned.cpp b/Runtime/World/CPatterned.cpp index 3f6c016f0..e1d4210f0 100644 --- a/Runtime/World/CPatterned.cpp +++ b/Runtime/World/CPatterned.cpp @@ -73,7 +73,7 @@ CPatterned::CPatterned(ECharacter character, TUniqueId uid, std::string_view nam x458_iceShatterSfx = pInfo.x134_iceShatterSfx; x4f4_intoFreezeDur = pInfo.x100_intoFreezeDur; x4f8_outofFreezeDur = pInfo.x104_outofFreezeDur; - x4fc_ = pInfo.x108_; + x4fc_freezeDur = pInfo.x108_freezeDur; x508_colliderType = colliderType; x50c_baseDamageMag = actorParms.GetThermalMag(); x514_deathExplosionOffset = pInfo.x110_particle1Scale; diff --git a/Runtime/World/CPatterned.hpp b/Runtime/World/CPatterned.hpp index 16a9362c4..a44b37634 100644 --- a/Runtime/World/CPatterned.hpp +++ b/Runtime/World/CPatterned.hpp @@ -214,7 +214,7 @@ protected: float x4f0_predictedLeashTime = 0.f; float x4f4_intoFreezeDur; float x4f8_outofFreezeDur; - float x4fc_; + float x4fc_freezeDur; float x500_preThinkDt = 0.f; float x504_damageDur = 0.f; EColliderType x508_colliderType; diff --git a/Runtime/World/CPatternedInfo.cpp b/Runtime/World/CPatternedInfo.cpp index 16a14de5f..bd10bd176 100644 --- a/Runtime/World/CPatternedInfo.cpp +++ b/Runtime/World/CPatternedInfo.cpp @@ -35,7 +35,7 @@ CPatternedInfo::CPatternedInfo(CInputStream& in, u32 pcount) , xfc_stateMachineId(in.readUint32Big()) , x100_intoFreezeDur(in.readFloatBig()) , x104_outofFreezeDur(in.readFloatBig()) -, x108_(in.readFloatBig()) +, x108_freezeDur(in.readFloatBig()) , x10c_pathfindingIndex(in.readUint32Big()) , x110_particle1Scale(zeus::CVector3f::ReadBig(in)) , x11c_particle1(in) diff --git a/Runtime/World/CPatternedInfo.hpp b/Runtime/World/CPatternedInfo.hpp index fa9f05252..2cb0a9b00 100644 --- a/Runtime/World/CPatternedInfo.hpp +++ b/Runtime/World/CPatternedInfo.hpp @@ -42,7 +42,7 @@ class CPatternedInfo { CAssetId xfc_stateMachineId; float x100_intoFreezeDur; float x104_outofFreezeDur; - float x108_; + float x108_freezeDur; u32 x10c_pathfindingIndex; diff --git a/Runtime/World/CPlayer.hpp b/Runtime/World/CPlayer.hpp index 819e09a27..c2f93e24f 100644 --- a/Runtime/World/CPlayer.hpp +++ b/Runtime/World/CPlayer.hpp @@ -584,6 +584,7 @@ public: float GetStaticTimer() const { return x740_staticTimer; } float GetDeathTime() const { return x9f4_deathTime; } const CPlayerEnergyDrain& GetEnergyDrain() const { return x274_energyDrain; } + CPlayerEnergyDrain& GetEnergyDrain() { return x274_energyDrain; } EPlayerZoneInfo GetOrbitZone() const { return x330_orbitZoneMode; } EPlayerZoneType GetOrbitType() const { return x334_orbitType; } const zeus::CTransform& GetFirstPersonCameraTransform(const CStateManager& mgr) const; diff --git a/Runtime/World/CTeamAiMgr.hpp b/Runtime/World/CTeamAiMgr.hpp index eddf3df22..7a60bef2e 100644 --- a/Runtime/World/CTeamAiMgr.hpp +++ b/Runtime/World/CTeamAiMgr.hpp @@ -26,7 +26,8 @@ private: zeus::CVector3f x1c_position; public: - CTeamAiRole(TUniqueId ownerId, ETeamAiRole a, ETeamAiRole b, ETeamAiRole c) + CTeamAiRole(TUniqueId ownerId, ETeamAiRole a = ETeamAiRole::Invalid, ETeamAiRole b = ETeamAiRole::Invalid, + ETeamAiRole c = ETeamAiRole::Invalid) : x0_ownerId(ownerId), x4_roleA(a), x8_roleB(b), xc_roleC(c) {} TUniqueId GetOwnerId() const { return x0_ownerId; } bool HasTeamAiRole() const { return false; } diff --git a/Runtime/World/ScriptLoader.cpp b/Runtime/World/ScriptLoader.cpp index 19588173e..6da251123 100644 --- a/Runtime/World/ScriptLoader.cpp +++ b/Runtime/World/ScriptLoader.cpp @@ -2010,7 +2010,7 @@ CEntity* ScriptLoader::LoadDrone(CStateManager& mgr, CInputStream& in, int propC CEntity* ScriptLoader::LoadMetroid(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) { if (!EnsurePropertyCount(propCount, MP1::CMetroidData::GetNumProperties(), "Metroid")) return nullptr; -#if 0 + std::string name = mgr.HashInstanceName(in); CPatterned::EFlavorType flavor = CPatterned::EFlavorType(in.readUint32Big()); zeus::CTransform xf = LoadEditorTransform(in); @@ -2028,12 +2028,8 @@ CEntity* ScriptLoader::LoadMetroid(CStateManager& mgr, CInputStream& in, int pro CModelData mData( CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), scale, animParms.GetInitialAnimation(), true)); - return new MP1::CMetroid(mgr.AllocateUniqueId(), name, flavor, info, xf, std::move(mData), pInfo, actParms, - metData, - kInvalidUniqueId); -#else - return nullptr; -#endif + return new MP1::CMetroid(mgr.AllocateUniqueId(), name, flavor, info, xf, std::move(mData), pInfo, actParms, metData, + kInvalidUniqueId); } CEntity* ScriptLoader::LoadDebrisExtended(CStateManager& mgr, CInputStream& in, int propCount,