#include "Runtime/World/CScriptTrigger.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Camera/CGameCamera.hpp" #include "Runtime/Weapon/CGameProjectile.hpp" #include "Runtime/World/CActorParameters.hpp" #include "Runtime/World/CPlayer.hpp" #include #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { namespace { logvisor::Module Log("CScriptTrigger"); } CScriptTrigger::CScriptTrigger(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& pos, const zeus::CAABox& bounds, const CDamageInfo& dInfo, const zeus::CVector3f& forceField, ETriggerFlags triggerFlags, bool active, bool deactivateOnEntered, bool deactivateOnExited) : CActor(uid, active, name, info, zeus::CTransform::Translate(pos), CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::Trigger), CActorParameters::None(), kInvalidUniqueId) , x100_damageInfo(dInfo) , x11c_forceField(forceField) , x128_forceMagnitude(forceField.magnitude()) , x12c_flags(triggerFlags) , x130_bounds(bounds) , x148_26_deactivateOnEntered(deactivateOnEntered) , x148_27_deactivateOnExited(deactivateOnExited) { SetCallTouch(false); #ifdef NDEBUG // FIXME: HACK This fixes the HotE softlock, definitely need to look into the morphball's collision codepath and // FIXME: determine the proper fix if (GetEditorId() == 0x0034004B) { Log.report(logvisor::Warning, FMT_STRING("BUG THIS!: Overriding forceField.x() for trigger {} in area {}"), GetEditorId(), GetAreaIdAlways()); x11c_forceField.x() = 0.f; } #else // HACK: For some reason MetroidPrime's lair doesn't enable this trigger until after the cutscene, activate it in debug build if (GetEditorId() == 0x000B01DB && !GetActive()) { Log.report(logvisor::Warning, FMT_STRING("BUG THIS!: Overriding active for trigger {} in area {}"), GetEditorId(), GetAreaIdAlways()); SetActive(true); } #endif } void CScriptTrigger::Accept(IVisitor& visitor) { visitor.Visit(this); } void CScriptTrigger::Think(float dt, CStateManager& mgr) { if (GetActive()) { UpdateInhabitants(dt, mgr); } } void CScriptTrigger::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { if (GetActive() && (msg == EScriptObjectMessage::Deactivate || msg == EScriptObjectMessage::Deleted)) { if (msg == EScriptObjectMessage::Deactivate) { xe8_inhabitants.clear(); x148_25_camSubmerged = false; } if (x148_28_playerTriggerProc) { x148_28_playerTriggerProc = false; if (x148_29_didPhazonDamage) { mgr.Player()->DecrementEnvironmentDamage(); x148_29_didPhazonDamage = false; } if (x8_uid == mgr.GetLastTriggerId()) { mgr.SetLastTriggerId(kInvalidUniqueId); } } } CActor::AcceptScriptMsg(msg, uid, mgr); } CScriptTrigger::CObjectTracker* CScriptTrigger::FindObject(TUniqueId id) { auto& inhabitants = GetInhabitants(); const auto iter = std::find(inhabitants.begin(), inhabitants.end(), CObjectTracker{id}); if (iter == inhabitants.end()) { return nullptr; } return &*iter; } void CScriptTrigger::UpdateInhabitants(float dt, CStateManager& mgr) { bool sendInside = false; bool sendExited = false; std::list::iterator nextIt; for (auto it = xe8_inhabitants.begin(); it != xe8_inhabitants.end(); it = nextIt) { nextIt = it; ++nextIt; if (const TCastToPtr act = mgr.ObjectById(it->GetObjectId())) { bool playerValid = true; if (it->GetObjectId() == mgr.GetPlayer().GetUniqueId()) { if (False(x12c_flags & ETriggerFlags::DetectPlayer) && ((mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed && True(x12c_flags & ETriggerFlags::DetectUnmorphedPlayer)) || (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed && True(x12c_flags & ETriggerFlags::DetectMorphedPlayer)))) { playerValid = false; } if (!playerValid) { xe8_inhabitants.erase(it); sendExited = true; if (x148_28_playerTriggerProc) { x148_28_playerTriggerProc = false; if (x148_29_didPhazonDamage) { mgr.GetPlayer().DecrementEnvironmentDamage(); x148_29_didPhazonDamage = false; } if (mgr.GetLastTriggerId() == GetUniqueId()) { mgr.SetLastTriggerId(kInvalidUniqueId); } } InhabitantExited(*act, mgr); continue; } } const auto touchBounds = GetTouchBounds(); const auto actTouchBounds = act->GetTouchBounds(); if (touchBounds && actTouchBounds && touchBounds->intersects(*actTouchBounds)) { sendInside = true; InhabitantIdle(*act, mgr); if (act->HealthInfo(mgr) && x100_damageInfo.GetDamage() > 0.f) { mgr.ApplyDamage(GetUniqueId(), act->GetUniqueId(), GetUniqueId(), {x100_damageInfo, dt}, CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f); } if (x128_forceMagnitude > 0.f) { if (TCastToPtr pact = act.GetPtr()) { float forceMult = 1.f; if (True(x12c_flags & ETriggerFlags::UseBooleanIntersection)) { forceMult = touchBounds->booleanIntersection(*actTouchBounds).volume() / actTouchBounds->volume(); } const zeus::CVector3f force = forceMult * x11c_forceField; if (True(x12c_flags & ETriggerFlags::UseCollisionImpulses)) { pact->ApplyImpulseWR(force, zeus::CAxisAngle()); pact->UseCollisionImpulses(); } else { pact->ApplyForceWR(force, zeus::CAxisAngle()); } } } } else { const TUniqueId tmpId = it->GetObjectId(); xe8_inhabitants.erase(it); sendExited = true; if (mgr.GetPlayer().GetUniqueId() == tmpId && x148_28_playerTriggerProc) { x148_28_playerTriggerProc = false; if (x148_29_didPhazonDamage) { mgr.Player()->DecrementEnvironmentDamage(); x148_29_didPhazonDamage = false; } if (mgr.GetLastTriggerId() == GetUniqueId()) mgr.SetLastTriggerId(kInvalidUniqueId); } InhabitantExited(*act, mgr); } } else { const TUniqueId tmpId = it->GetObjectId(); xe8_inhabitants.erase(it); if (mgr.GetPlayer().GetUniqueId() == tmpId && x148_28_playerTriggerProc) { x148_28_playerTriggerProc = false; if (x148_29_didPhazonDamage) { mgr.Player()->DecrementEnvironmentDamage(); x148_29_didPhazonDamage = false; } if (mgr.GetLastTriggerId() == GetUniqueId()) { mgr.SetLastTriggerId(kInvalidUniqueId); } } } } if (True(x12c_flags & ETriggerFlags::DetectCamera) || x148_24_detectCamera) { CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr); const bool camInTrigger = GetTriggerBoundsWR().pointInside(cam->GetTranslation()); if (x148_25_camSubmerged) { if (!camInTrigger) { x148_25_camSubmerged = false; if (True(x12c_flags & ETriggerFlags::DetectCamera)) { sendExited = true; InhabitantExited(*cam, mgr); } } else { if (True(x12c_flags & ETriggerFlags::DetectCamera)) { InhabitantIdle(*cam, mgr); sendInside = true; } } } else { if (camInTrigger) { x148_25_camSubmerged = true; if (True(x12c_flags & ETriggerFlags::DetectCamera)) { InhabitantAdded(*cam, mgr); SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None); } } } } if (sendInside) { SendScriptMsgs(EScriptObjectState::Inside, mgr, EScriptObjectMessage::None); } if (sendExited) { SendScriptMsgs(EScriptObjectState::Exited, mgr, EScriptObjectMessage::None); if (x148_27_deactivateOnExited) { mgr.SendScriptMsg(GetUniqueId(), mgr.GetEditorIdForUniqueId(GetUniqueId()), EScriptObjectMessage::Deactivate, EScriptObjectState::Exited); } } } std::list& CScriptTrigger::GetInhabitants() { return xe8_inhabitants; } std::optional CScriptTrigger::GetTouchBounds() const { if (x30_24_active) { return GetTriggerBoundsWR(); } return std::nullopt; } constexpr auto sktonOHurtWeaponMode = CWeaponMode(EWeaponType::Power, false, false, true); void CScriptTrigger::Touch(CActor& act, CStateManager& mgr) { if (!act.GetActive() || act.GetMaterialList().HasMaterial(EMaterialTypes::Trigger)) { return; } if (FindObject(act.GetUniqueId()) == nullptr) { auto testFlags = ETriggerFlags::None; const TCastToPtr pl(act); if (pl) { if (x128_forceMagnitude > 0.f && True(x12c_flags & ETriggerFlags::DetectPlayer) && mgr.GetLastTriggerId() == kInvalidUniqueId) { mgr.SetLastTriggerId(x8_uid); } testFlags |= ETriggerFlags::DetectPlayer; if (pl->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) { testFlags |= ETriggerFlags::DetectUnmorphedPlayer; } else if (pl->GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Morphed) { testFlags |= ETriggerFlags::DetectMorphedPlayer; } } else if (TCastToPtr(act)) { testFlags |= ETriggerFlags::DetectAI; } else if (TCastToPtr(act)) { testFlags |= ETriggerFlags::DetectProjectiles1 | ETriggerFlags::DetectProjectiles2 | ETriggerFlags::DetectProjectiles3 | ETriggerFlags::DetectProjectiles4 | ETriggerFlags::DetectProjectiles5 | ETriggerFlags::DetectProjectiles6 | ETriggerFlags::DetectProjectiles7; } else if (const TCastToConstPtr weap = act) { if ((weap->GetAttribField() & EProjectileAttrib::Bombs) == EProjectileAttrib::Bombs) { testFlags |= ETriggerFlags::DetectBombs; } else if ((weap->GetAttribField() & EProjectileAttrib::PowerBombs) == EProjectileAttrib::PowerBombs) { testFlags |= ETriggerFlags::DetectPowerBombs; } } if (True(testFlags & x12c_flags)) { xe8_inhabitants.emplace_back(act.GetUniqueId()); InhabitantAdded(act, mgr); if (pl) { if (!x148_28_playerTriggerProc) { x148_28_playerTriggerProc = true; if (x148_29_didPhazonDamage) { mgr.Player()->DecrementEnvironmentDamage(); x148_29_didPhazonDamage = false; } else if (x100_damageInfo.GetDamage() > 0.f) { const CDamageVulnerability* dVuln = mgr.Player()->GetDamageVulnerability(); if (dVuln->WeaponHurts(x100_damageInfo.GetWeaponMode(), false) && x100_damageInfo.GetWeaponMode().GetType() == EWeaponType::Phazon && !mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::PhazonSuit)) { pl->IncrementEnvironmentDamage(); x148_29_didPhazonDamage = true; } } } } SendScriptMsgs(EScriptObjectState::Entered, mgr, EScriptObjectMessage::None); if (x148_26_deactivateOnEntered) { mgr.SendScriptMsg(x8_uid, mgr.GetEditorIdForUniqueId(x8_uid), EScriptObjectMessage::Deactivate, EScriptObjectState::Entered); if (act.HealthInfo(mgr) && x100_damageInfo.GetDamage() > 0.f) { mgr.ApplyDamage(x8_uid, act.GetUniqueId(), x8_uid, x100_damageInfo, CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {0ull}), zeus::skZero3f); } } if (True(x12c_flags & ETriggerFlags::KillOnEnter) && act.HealthInfo(mgr)) { CHealthInfo* hInfo = act.HealthInfo(mgr); mgr.ApplyDamage(x8_uid, act.GetUniqueId(), x8_uid, {sktonOHurtWeaponMode, 10.f * hInfo->GetHP(), 0.f, 0.f}, CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {0ull}), zeus::skZero3f); } } else { InhabitantRejected(act, mgr); } } } zeus::CAABox CScriptTrigger::GetTriggerBoundsWR() const { return {x130_bounds.min + x34_transform.origin, x130_bounds.max + x34_transform.origin}; } void CScriptTrigger::DebugDraw() { if (m_debugSelected || m_debugHovered) { m_debugBox.setAABB(GetTriggerBoundsWR()); m_debugBox.draw(m_debugAddColor); } } } // namespace metaforce