#include "Runtime/MP1/World/CChozoGhost.hpp"

#include "Runtime/GameGlobalObjects.hpp"
#include "Runtime/CStateManager.hpp"
#include "Runtime/CSimplePool.hpp"
#include "Runtime/CRandom16.hpp"
#include "Runtime/Character/CPASAnimParmData.hpp"
#include "Runtime/Weapon/CGameProjectile.hpp"
#include "Runtime/World/CPlayer.hpp"
#include "Runtime/World/CScriptCoverPoint.hpp"
#include "Runtime/World/CScriptWaypoint.hpp"
#include "Runtime/World/CTeamAiMgr.hpp"

#include "TCastTo.hpp" // Generated file, do not modify include path

namespace metaforce::MP1 {
CChozoGhost::CBehaveChance::CBehaveChance(CInputStream& in)
: x0_propertyCount(in.readUint32Big())
, x4_lurk(in.readFloatBig())
, x8_taunt(in.readFloatBig())
, xc_attack(in.readFloatBig())
, x10_move(in.readFloatBig())
, x14_lurkTime(in.readFloatBig())
, x18_chargeAttack(x0_propertyCount < 6 ? 0.5f : in.readFloatBig() * .01f)
, x1c_numBolts(x0_propertyCount < 7 ? 2 : in.readUint32Big()) {
  float f2 = 1.f / (x10_move + xc_attack + x4_lurk + x8_taunt);
  x4_lurk *= f2;
  x8_taunt *= f2;
  xc_attack *= f2;
  x10_move *= f2;
}

EBehaveType CChozoGhost::CBehaveChance::GetBehave(EBehaveType type, CStateManager& mgr) const {
  float lurkChance = x4_lurk;
  float tauntChance = x8_taunt;
  float attackChance = xc_attack;
  if (type == EBehaveType::Lurk) {
    float delta = lurkChance / 3.f;
    lurkChance = 0.f;
    tauntChance += delta;
    attackChance += delta;
  } else if (type == EBehaveType::Taunt) {
    float delta = tauntChance / 3.f;
    tauntChance = 0.f;
    lurkChance += delta;
    attackChance += delta;
  } else if (type == EBehaveType::Attack) {
    float delta = attackChance / 3.f;
    attackChance = 0.f;
    lurkChance += delta;
    tauntChance += delta;
  } else if (type == EBehaveType::Move) {
    float delta = x10_move / 3.f;
    lurkChance += delta;
    tauntChance += delta;
    attackChance += delta;
  }

  float rnd = mgr.GetActiveRandom()->Float();
  if (lurkChance > rnd)
    return EBehaveType::Lurk;
  else if (tauntChance > rnd - lurkChance)
    return EBehaveType::Taunt;
  else if (attackChance > rnd - lurkChance - tauntChance)
    return EBehaveType::Attack;
  return EBehaveType::Move;
}

CChozoGhost::CChozoGhost(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf,
                         CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo,
                         float hearingRadius, float fadeOutDelay, float attackDelay, float freezeTime, CAssetId wpsc1,
                         const CDamageInfo& dInfo1, CAssetId wpsc2, const CDamageInfo& dInfo2,
                         const CBehaveChance& chance1, const CBehaveChance& chance2, const CBehaveChance& chance3,
                         u16 soundImpact, float f5, u16 sId2, u16 sId3, u32 w1, float f6, u32 w2, float hurlRecoverTime,
                         CAssetId projectileVisorEffect, s16 soundProjectileVisor, float f8, float f9, u32 nearChance,
                         u32 midChance)
: CPatterned(ECharacter::ChozoGhost, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo,
             EMovementType::Flyer, EColliderType::Zero, EBodyType::BiPedal, actParms, EKnockBackVariant::Medium)
, x568_hearingRadius(hearingRadius)
, x56c_fadeOutDelay(fadeOutDelay)
, x570_attackDelay(attackDelay)
, x574_freezeTime(freezeTime)
, x578_(wpsc1, dInfo1)
, x5a0_(wpsc2, dInfo2)
, x5c8_(chance1)
, x5e8_(chance2)
, x608_(chance3)
, x628_soundImpact(soundImpact)
, x62c_(f5)
, x630_sfxFadeIn(sId2)
, x632_sfxFadeOut(sId3)
, x634_(f6)
, x638_hurlRecoverTime(hurlRecoverTime)
, x63c_(w2)
, x650_sound_ProjectileVisor(soundProjectileVisor)
, x654_(f8)
, x658_(f9)
, x65c_nearChance(nearChance)
, x660_midChance(midChance)
, x664_24_behaviorEnabled(w1)
, x664_25_flinch(!w1)
, x680_behaveType(x664_24_behaviorEnabled ? EBehaveType::Attack : EBehaveType::None)
, x68c_boneTracking(*GetModelData()->GetAnimationData(), "Head_1"sv, zeus::degToRad(80.f), zeus::degToRad(180.f),
                    EBoneTrackingFlags::None) {
  x578_.Token().Lock();
  x5a0_.Token().Lock();
  x668_ = GetModelData()->GetScale().z() *
          GetAnimationDistance(CPASAnimParmData(pas::EAnimationState::Jump, CPASAnimParm::FromEnum(3), CPASAnimParm::FromEnum(0)));
  x66c_ = GetModelData()->GetScale().z() *
          GetAnimationDistance(CPASAnimParmData(pas::EAnimationState::Slide, CPASAnimParm::FromEnum(1), CPASAnimParm::FromReal32(90.f)));
  x670_ = GetModelData()->GetScale().z() *
          GetAnimationDistance(CPASAnimParmData(pas::EAnimationState::MeleeAttack, CPASAnimParm::FromEnum(2), CPASAnimParm::FromEnum(1)));

  if (projectileVisorEffect.IsValid())
    x640_projectileVisor = g_SimplePool->GetObj({SBIG('PART'), projectileVisorEffect});
  x460_knockBackController.SetEnableBurn(false);
  x460_knockBackController.SetEnableLaggedBurnDeath(false);
  x460_knockBackController.SetEnableShock(false);
  x460_knockBackController.SetEnableFreeze(false);
  CreateShadow(false);
  MakeThermalColdAndHot();
}

void CChozoGhost::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) {
  CPatterned::AcceptScriptMsg(msg, uid, mgr);

  switch (msg) {
  case EScriptObjectMessage::Activate:
    AddToTeam(mgr);
    break;
  case EScriptObjectMessage::Deactivate:
  case EScriptObjectMessage::Deleted:
    RemoveFromTeam(mgr);
    break;
  case EScriptObjectMessage::Action:
    if (x664_25_flinch)
      x665_29_aggressive = true;
    break;
  case EScriptObjectMessage::Alert:
    if (x664_26_alert)
      break;
    x664_26_alert = true;
    x400_24_hitByPlayerProjectile = true;
    break;
  case EScriptObjectMessage::Falling:
  case EScriptObjectMessage::Jumped: {
    if (!x328_25_verticalMovement)
      x150_momentum = {0.f, 0.f, -(GetGravityConstant() * GetMass())};
    break;
  }
  case EScriptObjectMessage::InitializedInArea:
    if (GetActive())
      AddToTeam(mgr);
    break;
  default:
    break;
  }
}

void CChozoGhost::Think(float dt, CStateManager& mgr) {
  if (!GetActive())
    return;

  CPatterned::Think(dt, mgr);
  UpdateThermalFrozenState(false);
  x68c_boneTracking.Update(dt);
  x6c8_spaceWarpTime = std::max(0.f, x6c8_spaceWarpTime - dt);
  xe7_31_targetable = IsVisibleEnough(mgr);
}

void CChozoGhost::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {
  x402_29_drawParticles = mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay;
  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Thermal) {
    SetCalculateLighting(false);
    x90_actorLights->BuildConstantAmbientLighting(zeus::skWhite);
  } else {
    SetCalculateLighting(true);
  }

  u8 alpha = GetModelAlphau8(mgr);
  u8 r, g, b, a;
  x42c_color.toRGBA8(r, g, b, a);
  if (alpha < 255 || r == 0) {
    zeus::CColor col;
    if (r == 0) {
      col = zeus::skWhite;
    } else {
      float v = std::max<s8>(0, r - 255) * (1.f / 255.f);
      col.r() = 1.f;
      col.g() = v;
      col.b() = v;
    }
    col.a() = alpha * (1.f / 255.f);
    xb4_drawFlags = CModelFlags(5, 0, 3, col);
  } else {
    xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite);
  }
  CPatterned::PreRender(mgr, frustum);
  x68c_boneTracking.PreRender(mgr, *GetModelData()->GetAnimationData(), GetTransform(), GetModelData()->GetScale(),
                              *GetBodyController());
}

void CChozoGhost::Render(CStateManager& mgr) {
  if (x6c8_spaceWarpTime > 0.f)
    mgr.DrawSpaceWarp(x6cc_spaceWarpPosition, std::sin((M_PIF * x6c8_spaceWarpTime) / x56c_fadeOutDelay));

  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {
    CElementGen::SetSubtractBlend(true);
    CElementGen::SetMoveRedToAlphaBuffer(true);
    CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack);
    GetModelData()->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnFirst();
    mgr.SetupFogForArea3XRange(GetAreaIdAlways());
  }

  CPatterned::Render(mgr);

  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) {
    CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack);
    GetModelData()->GetAnimationData()->GetParticleDB().RenderSystemsToBeDrawnLast();
    mgr.SetupFogForArea(GetAreaIdAlways());
    CElementGen::SetSubtractBlend(false);
    CElementGen::SetMoveRedToAlphaBuffer(false);
  }
}

void CChozoGhost::Touch(CActor& act, CStateManager& mgr) {
  if (IsVisibleEnough(mgr)) {
    if (TCastToPtr<CPlayer> pl = act) {
      if (x420_curDamageRemTime <= 0.f) {
        mgr.ApplyDamage(GetUniqueId(), pl->GetUniqueId(), GetUniqueId(), GetContactDamage(),
                        CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), {});
        x420_curDamageRemTime = x424_damageWaitTime;
      }
    }
  }
  CPatterned::Touch(act, mgr);
}

EWeaponCollisionResponseTypes CChozoGhost::GetCollisionResponseType(const zeus::CVector3f& pos,
                                                                    const zeus::CVector3f& dir, const CWeaponMode& mode,
                                                                    EProjectileAttrib attrib) const {
  return EWeaponCollisionResponseTypes::ChozoGhost;
}

void CChozoGhost::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) {

  if (type == EUserEventType::FadeIn) {
    if (x664_30_fadedOut) {
      x3e8_alphaDelta = 2.f;
      CSfxManager::AddEmitter(x630_sfxFadeIn, GetTranslation(), {}, false, false, 127, -1);
    }
    AddMaterial(EMaterialTypes::Target, mgr);
    x664_30_fadedOut = false;
    x664_29_fadedIn = true;
    return;
  } else if (type == EUserEventType::Projectile) {
    const zeus::CTransform& xf =
        zeus::lookAt(GetLctrTransform(node.GetLocatorName()).origin, mgr.GetPlayer().GetAimPosition(mgr, 0.f));
    if (x67c_attackType == 2) {
      CGameProjectile* proj =
          LaunchProjectile(xf, mgr, 2, EProjectileAttrib::BigStrike | EProjectileAttrib::StaticInterference, true,
                           x640_projectileVisor, x650_sound_ProjectileVisor, false, zeus::skOne3f);
      if (proj) {
        proj->AddAttrib(EProjectileAttrib::BigStrike);
        proj->SetDamageDuration(x62c_);
        proj->AddAttrib(EProjectileAttrib::StaticInterference);
        proj->SetInterferenceDuration(x62c_);
        proj->SetMinHomingDistance(x634_);
      }
    } else {
      CGameProjectile* proj =
          LaunchProjectile(xf, mgr, 5, EProjectileAttrib::DamageFalloff | EProjectileAttrib::StaticInterference, true,
                           {x640_projectileVisor}, x650_sound_ProjectileVisor, false, zeus::skOne3f);
      if (proj) {
        proj->AddAttrib(EProjectileAttrib::BigStrike);
        proj->SetDamageDuration(x62c_);
        proj->AddAttrib(EProjectileAttrib::StaticInterference);
        proj->SetInterferenceDuration(x62c_);
        proj->SetMinHomingDistance(x634_);
      }
    }
    return;
  } else if (type == EUserEventType::FadeOut) {
    if (x664_29_fadedIn) {
      x3e8_alphaDelta = -2.f;
      CSfxManager::AddEmitter(x632_sfxFadeOut, GetTranslation(), {}, false, false, 127, -1);
    }
    RemoveMaterial(EMaterialTypes::Target, mgr);
    x664_29_fadedIn = false;
    x664_30_fadedOut = true;
    x665_26_shouldSwoosh = true;
    return;
  }

  CPatterned::DoUserAnimEvent(mgr, node, type, dt);

  if (type == EUserEventType::Delete)
    x3e8_alphaDelta = -1.f;
}

void CChozoGhost::KnockBack(const zeus::CVector3f& dir, CStateManager& mgr, const CDamageInfo& info,
                            EKnockBackType type, bool inDeferred, float magnitude) {
  if (!IsAlive()) {
    x460_knockBackController.SetAvailableState(EKnockBackAnimationState::Hurled, false);
  } else if (!x460_knockBackController.TestAvailableState(EKnockBackAnimationState::KnockBack) &&
             info.GetWeaponMode().IsCharged()) {
    x460_knockBackController.SetAnimationStateRange(EKnockBackAnimationState::Hurled, EKnockBackAnimationState::Fall);
  }

  CPatterned::KnockBack(dir, mgr, info, type, inDeferred, magnitude);
  x460_knockBackController.SetAnimationStateRange(EKnockBackAnimationState::Flinch, EKnockBackAnimationState::Fall);
  if (!IsAlive()) {
    Stop();
    x150_momentum.zeroOut();
  } else if (x460_knockBackController.GetActiveParms().x0_animState == EKnockBackAnimationState::Hurled) {
    x330_stateMachineState.SetState(mgr, *this, GetStateMachine(), "Hurled"sv);
  }
}

bool CChozoGhost::CanBeShot(const CStateManager& mgr, int) { return IsVisibleEnough(mgr); }

void CChozoGhost::Dead(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    ReleaseCoverPoint(mgr, x674_coverPoint);
    x3e8_alphaDelta = 4.f;
    x664_30_fadedOut = false;
    x664_29_fadedIn = false;
    x68c_boneTracking.SetActive(false);
    Stop();
  }
}

void CChozoGhost::SelectTarget(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate)
    FindBestAnchor(mgr);
}

void CChozoGhost::Run(CStateManager& mgr, EStateMsg msg, float dt) {
  if (msg == EStateMsg::Activate) {
    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk);
    x400_24_hitByPlayerProjectile = false;
    x460_knockBackController.SetAvailableState(EKnockBackAnimationState::KnockBack, false);
    x665_28_inRange = false;
  } else if (msg == EStateMsg::Update) {
    GetBodyController()->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(x688_.Seek(*this, x2e0_destPos), {}, 1.f));
    if (x665_26_shouldSwoosh) {
      x678_floorLevel = x2e0_destPos.z();
      FloatToLevel(x678_floorLevel, dt);
      GetModelData()->GetAnimationData()->SetParticleEffectState("SpeedSwoosh", true, mgr);
      x665_24_ = false;
      if (!x665_28_inRange) {
        const float range = 2.5f * (dt * x138_velocity.magnitude()) + x66c_;
        x665_28_inRange = (GetTranslation() - x2e0_destPos).magSquared() < range * range;
      }
    }
  } else if (msg == EStateMsg::Deactivate) {
    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Crouch);
    SetDestPos(mgr.GetPlayer().GetTranslation());
    GetModelData()->GetAnimationData()->SetParticleEffectState("SpeedSwoosh", false, mgr);
    x460_knockBackController.SetAvailableState(EKnockBackAnimationState::KnockBack, true);
    x665_28_inRange = false;
  }
}

void CChozoGhost::Generate(CStateManager& mgr, EStateMsg msg, float dt) {
  if (msg == EStateMsg::Activate) {
    x330_stateMachineState.SetDelay(x56c_fadeOutDelay);
    x32c_animState = EAnimState::Ready;
    x664_27_onGround = false;
    const CRayCastResult& res = mgr.RayStaticIntersection(GetTranslation(), zeus::skDown, 100.f,
                                                          CMaterialFilter::MakeInclude({EMaterialTypes::Floor}));
    if (res.IsInvalid()) {
      x678_floorLevel = mgr.GetPlayer().GetTranslation().z();
    } else {
      x678_floorLevel = res.GetPoint().z();
    }
    x3e8_alphaDelta = 1.f;
    x664_29_fadedIn = true;
    if (x56c_fadeOutDelay > 0.f) {
      x6c8_spaceWarpTime = x56c_fadeOutDelay;
      FindSpaceWarpPosition(mgr, zeus::skDown);
    }
  } else if (msg == EStateMsg::Update) {
    TryCommand(mgr, pas::EAnimationState::Jump, &CPatterned::TryJump, 0);
    if (x32c_animState == EAnimState::Over) {
      x68c_boneTracking.SetActive(true);
      x68c_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());
      FloatToLevel(x678_floorLevel, dt);
    } else if (x32c_animState == EAnimState::Repeat) {
      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);
      if (!x664_27_onGround) {
        const zeus::CVector3f& pos = GetTranslation();
        SetTranslation({pos.x(), pos.y(), x678_floorLevel + x668_});
        x664_27_onGround = true;
      }
    }
  } else if (msg == EStateMsg::Deactivate) {
    x32c_animState = EAnimState::NotReady;
    x665_24_ = false;
    x664_27_onGround = false;
  }
}

void CChozoGhost::Deactivate(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x68c_boneTracking.SetActive(false);
    ReleaseCoverPoint(mgr, x674_coverPoint);
    x32c_animState = EAnimState::Ready;
    x665_24_ = true;
  } else if (msg == EStateMsg::Update) {
    TryCommand(mgr, pas::EAnimationState::Generate, &CPatterned::TryGenerateNoXf, 1);
    if (x32c_animState == EAnimState::Repeat)
      GetBodyController()->SetLocomotionType(pas::ELocomotionType::Relaxed);
  } else if (msg == EStateMsg::Deactivate) {
    x32c_animState = EAnimState::NotReady;
  }
}

void CChozoGhost::Attack(CStateManager& mgr, EStateMsg msg, float dt) {
  if (msg == EStateMsg::Activate) {
    CTeamAiMgr::AddAttacker(CTeamAiMgr::EAttackType::Melee, mgr, x6c4_teamMgr, GetUniqueId());
    x32c_animState = EAnimState::Ready;
    if (x6d8_ == 1)
      x67c_attackType = 3;
    else if (x6d8_ == 2)
      x67c_attackType = 4;
    else if (x6d8_ == 3)
      x67c_attackType = 5;

    const CRayCastResult& res = mgr.RayStaticIntersection(GetTranslation() + (zeus::skUp * 0.5f), zeus::skUp, x670_,
                                                          CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));
    if (x665_26_shouldSwoosh && res.IsValid()) {
      x67c_attackType = 2;
      x460_knockBackController.SetAvailableState(EKnockBackAnimationState::KnockBack, false);
    }
    x150_momentum.zeroOut();
    xfc_constantForce.zeroOut();
  } else if (msg == EStateMsg::Update) {
    TryCommand(mgr, pas::EAnimationState::MeleeAttack, &CPatterned::TryMeleeAttack, x67c_attackType);
    GetBodyController()->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation());
    if (x67c_attackType != 2)
      FloatToLevel(x678_floorLevel, dt);
  } else if (msg == EStateMsg::Deactivate) {
    x32c_animState = EAnimState::NotReady;
    x665_26_shouldSwoosh = false;
    x460_knockBackController.SetAvailableState(EKnockBackAnimationState::KnockBack, true);
    CTeamAiMgr::ResetTeamAiRole(CTeamAiMgr::EAttackType::Ranged, mgr, x6c4_teamMgr, GetUniqueId(), true);
  }
}

void CChozoGhost::Shuffle(CStateManager& mgr, EStateMsg msg, float) {
  if (msg != EStateMsg::Activate)
    return;

  const CBehaveChance& chance1 = ChooseBehaveChanceRange(mgr);
  CTeamAiRole* role = CTeamAiMgr::GetTeamAiRole(mgr, x6c4_teamMgr, GetUniqueId());
  if (role && role->GetTeamAiRole() == CTeamAiRole::ETeamAiRole::Ranged &&
      !CTeamAiMgr::CanAcceptAttacker(CTeamAiMgr::EAttackType::Ranged, mgr, x6c4_teamMgr, GetUniqueId())) {
    x680_behaveType = EBehaveType::Attack;
  }

  const CBehaveChance& chance2 = ChooseBehaveChanceRange(mgr);
  x680_behaveType = chance2.GetBehave(x680_behaveType, mgr);
  if (x680_behaveType == EBehaveType::Lurk)
    x684_lurkDelay = chance1.GetLurkTime();
  else if (x680_behaveType == EBehaveType::Attack) {
    x665_25_ = mgr.GetActiveRandom()->Float() < chance1.GetChargeAttack();
    const int rnd = mgr.GetActiveRandom()->Next();
    x6d8_ = (rnd - (rnd / chance1.GetNumBolts()) * chance1.GetNumBolts()) + 1;
  }
  x664_31_ = false;
  x665_27_playerInLeashRange = false;
}

void CChozoGhost::InActive(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    if (!x450_bodyController->GetActive())
      x450_bodyController->Activate(mgr);

    if (x63c_ == 3) {
      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch);
      x42c_color.a() = 1.f;
    } else {
      x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed);
      x42c_color.a() = 0.f;
    }

    RemoveMaterial(EMaterialTypes::Solid, mgr);
    x150_momentum.zeroOut();
    x665_24_ = true;
  }
}

void CChozoGhost::Taunt(CStateManager& mgr, EStateMsg msg, float dt) {
  if (msg == EStateMsg::Activate) {
    x32c_animState = EAnimState::Ready;
  } else if (msg == EStateMsg::Update) {
    TryCommand(mgr, pas::EAnimationState::Taunt, &CPatterned::TryTaunt, 0);
    FloatToLevel(x678_floorLevel, dt);
  } else {
    x32c_animState = EAnimState::NotReady;
    x665_26_shouldSwoosh = false;
  }
}

void CChozoGhost::Hurled(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x328_25_verticalMovement = false;
    x664_27_onGround = false;
    x665_24_ = true;
  } else if (msg == EStateMsg::Update) {
    x3e8_alphaDelta = 2.f;
    if (x664_27_onGround)
      return;

    if (x138_velocity.z() < 0.f) {
      const CRayCastResult& res = mgr.RayStaticIntersection(GetTranslation() + zeus::skUp, zeus::skDown, 2.f,
                                                            CMaterialFilter::MakeInclude({EMaterialTypes::Floor}));
      if (res.IsValid()) {
        x664_27_onGround = true;
        x150_momentum.zeroOut();
        SetVelocityWR({x138_velocity.x(), x138_velocity.y(), 0.f});
        x678_floorLevel = res.GetPoint().z();
        x330_stateMachineState.SetCodeTrigger();
      }
    }
    if (!x664_27_onGround && x638_hurlRecoverTime < x330_stateMachineState.GetTime()) {
      GetBodyController()->GetCommandMgr().DeliverCmd(CBodyStateCmd(EBodyStateCmd::ExitState));
      GetBodyController()->SetLocomotionType(pas::ELocomotionType::Lurk);
      x330_stateMachineState.SetCodeTrigger();
    }
  } else if (msg == EStateMsg::Deactivate) {
    x328_25_verticalMovement = true;
    x150_momentum.zeroOut();
  }
}

void CChozoGhost::WallDetach(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x330_stateMachineState.SetDelay(x56c_fadeOutDelay);
    x3e8_alphaDelta = 0.f;
    x664_29_fadedIn = false;
    if (x56c_fadeOutDelay > 0.f) {
      x6c8_spaceWarpTime = x56c_fadeOutDelay;
      FindSpaceWarpPosition(mgr, GetTransform().basis[1]);
    }
    TUniqueId wpId = GetWaypointForState(mgr, EScriptObjectState::Attack, EScriptObjectMessage::Follow);
    TCastToConstPtr<CScriptWaypoint> wp;
    if (wpId != kInvalidUniqueId) {
      wp = TCastToConstPtr<CScriptWaypoint>(mgr.GetObjectById(wpId));
    }
    if (wp)
      SetDestPos(wp->GetTranslation());
    else
      SetDestPos(GetTranslation() + (2.f * x66c_) * GetTranslation());

    SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::Follow);
  } else if (msg == EStateMsg::Deactivate) {
    x68c_boneTracking.SetActive(true);
    x68c_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());
    x665_24_ = false;
    x680_behaveType = EBehaveType::Move;
  }
}

void CChozoGhost::Growth(CStateManager& mgr, EStateMsg msg, float) {
  if (msg == EStateMsg::Activate) {
    x330_stateMachineState.SetDelay(x56c_fadeOutDelay);
    GetBodyController()->SetLocomotionType(pas::ELocomotionType::Crouch);
    x3e8_alphaDelta = 1.f;
    x664_29_fadedIn = true;
    if (x56c_fadeOutDelay > 0.f) {
      x6c8_spaceWarpTime = x56c_fadeOutDelay;
      FindSpaceWarpPosition(mgr, zeus::skUp);
    }
  } else if (msg == EStateMsg::Deactivate) {
    x665_24_ = false;
    x68c_boneTracking.SetActive(false);
    x68c_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId());
  }
}

void CChozoGhost::Land(CStateManager& mgr, EStateMsg msg, float dt) {
  if (msg == EStateMsg::Update) {
    FloatToLevel(x678_floorLevel, dt);
    if (std::fabs(x678_floorLevel - GetTranslation().z()) < 0.05f) {
      x330_stateMachineState.SetCodeTrigger();
    }
  }
}

void CChozoGhost::Lurk(CStateManager& mgr, EStateMsg msg, float dt) {
  if (msg == EStateMsg::Activate) {
    x330_stateMachineState.SetDelay(x684_lurkDelay);
  } else if (msg == EStateMsg::Update) {
    FloatToLevel(x678_floorLevel, dt);
  }
}

bool CChozoGhost::Leash(CStateManager& mgr, float arg) {
  return x665_27_playerInLeashRange || CPatterned::Leash(mgr, arg);
}

bool CChozoGhost::InRange(CStateManager& mgr, float arg) { return x665_28_inRange; }

bool CChozoGhost::ShouldAttack(CStateManager& mgr, float arg) { return x680_behaveType == EBehaveType::Attack; }

bool CChozoGhost::AggressionCheck(CStateManager& mgr, float arg) { return x665_29_aggressive; }

bool CChozoGhost::ShouldTaunt(CStateManager& mgr, float arg) { return x680_behaveType == EBehaveType::Taunt; }

bool CChozoGhost::ShouldFlinch(CStateManager& mgr, float arg) { return x664_25_flinch; }

bool CChozoGhost::ShouldMove(CStateManager& mgr, float arg) { return x680_behaveType == EBehaveType::Move; }

bool CChozoGhost::AIStage(CStateManager& mgr, float arg) { return arg == x63c_; }

u8 CChozoGhost::GetModelAlphau8(const CStateManager& mgr) const {
  if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay && IsAlive())
    return 255;
  return u8(x42c_color.a() * 255);
}

bool CChozoGhost::IsOnGround() const { return x664_27_onGround; }

CProjectileInfo* CChozoGhost::GetProjectileInfo() { return x67c_attackType == 2 ? &x578_ : &x5a0_; }

void CChozoGhost::AddToTeam(CStateManager& mgr) {
  if (x6c4_teamMgr == kInvalidUniqueId)
    x6c4_teamMgr = CTeamAiMgr::GetTeamAiMgr(*this, mgr);

  if (x6c4_teamMgr == kInvalidUniqueId)
    return;

  if (TCastToPtr<CTeamAiMgr> teamMgr = mgr.ObjectById(x6c4_teamMgr))
    teamMgr->AssignTeamAiRole(*this, CTeamAiRole::ETeamAiRole::Ranged, CTeamAiRole::ETeamAiRole::Unknown,
                              CTeamAiRole::ETeamAiRole::Invalid);
}

void CChozoGhost::RemoveFromTeam(CStateManager& mgr) {
  if (x6c4_teamMgr == kInvalidUniqueId)
    return;

  if (TCastToPtr<CTeamAiMgr> teamMgr = mgr.ObjectById(x6c4_teamMgr)) {
    if (teamMgr->IsPartOfTeam(GetUniqueId())) {
      teamMgr->RemoveTeamAiRole(GetUniqueId());
      x6c4_teamMgr = kInvalidUniqueId;
    }
  }
}

void CChozoGhost::FloatToLevel(float level, float dt) {
  const zeus::CVector3f& pos = GetTranslation();
  SetTranslation({pos.x(), pos.y(), 4.f * (level - pos.z()) * dt + pos.z()});
}

const CChozoGhost::CBehaveChance& CChozoGhost::ChooseBehaveChanceRange(CStateManager& mgr) {
  const float dist = (GetTranslation() - mgr.GetPlayer().GetTranslation()).magnitude();
  if (x654_ <= dist && x658_ > dist)
    return x5e8_;
  else if (x658_ <= dist)
    return x608_;
  else
    return x5c8_;
}

void CChozoGhost::FindSpaceWarpPosition(CStateManager& mgr, const zeus::CVector3f& dir) {
  const zeus::CVector3f& center = GetBoundingBox().center();
  const CRayCastResult& res =
      mgr.RayStaticIntersection(center + (dir * 8.f), -dir, 8.f, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}));
  if (res.IsInvalid()) {
    x6cc_spaceWarpPosition = center + dir;
  } else {
    x6cc_spaceWarpPosition = res.GetPoint();
  }
}

void CChozoGhost::FindBestAnchor(CStateManager& mgr) {
  x665_27_playerInLeashRange = false;
  u32 chance = mgr.GetActiveRandom()->Next() % 100;
  chance = chance < x65c_nearChance ? 0 : (chance < (x65c_nearChance + x660_midChance)) + 2;
  float dVar15 = 10.f * x658_;
  float dVar14 = dVar15;
  float dVar13 = dVar15;
  if (chance == 0) {
    dVar13 = dVar15 * 10.f;
    dVar14 = dVar15 * 5.f;
  } else if (chance == 1) {
    dVar13 = dVar15 * 5.f;
    dVar15 *= 10.f;
  } else if (chance == 2) {
    dVar14 = dVar15 * 5.f;
    dVar15 *= 10.f;
  }

  float prevDist = FLT_MAX;
  CScriptCoverPoint* target = nullptr;
  for (CEntity* ent : mgr.GetAiWaypointObjectList()) {
    if (TCastToPtr<CScriptCoverPoint> cover = ent) {
      if (cover->GetActive() && !cover->GetInUse(kInvalidUniqueId) && cover->GetAreaIdAlways() == GetAreaId()) {
        float fVar17 = (cover->GetTranslation() - GetTranslation()).magnitude();
        if (2.f * x66c_ <= fVar17) {
          float dist = std::max(0.f, x654_ - fVar17);
          zeus::CVector3f diff = cover->GetTranslation() - mgr.GetPlayer().GetTranslation();
          fVar17 = diff.magnitude();
          if (x2fc_minAttackRange <= fVar17) {
            if (std::fabs(diff.z()) / fVar17 < 0.2f) {
              dist = (20.f * x658_) * ((std::fabs(diff.z()) / fVar17) - 0.2f) + dist;
            }
            if (x654_ <= fVar17) {
              if (x658_ <= fVar17) {
                dist = (dist + dVar13);
              } else {
                dist = (dist + dVar14);
                if (dist < prevDist) {
                  fVar17 = 1.f / fVar17;
                  diff = diff * fVar17;
                  dist += (10.f * x658_) * (1.f - mgr.GetPlayer().GetTransform().basis[1].dot(diff));
                }
              }
            } else {
              dist += dVar15;
              if (dist < prevDist) {
                fVar17 = 1.f / fVar17;
                diff = diff * fVar17;
                dist += (10.f * x658_) * (1.f - mgr.GetPlayer().GetTransform().basis[1].dot(diff));
              }
            }
            if (dist < prevDist) {
              dist += x658_ * mgr.GetActiveRandom()->Float();
              if (dist < prevDist) {
                x665_27_playerInLeashRange = false;
                target = cover;
                prevDist = dist;
              }
            }
          }
        }
      }
    }
  }

  if (target) {
    x2dc_destObj = target->GetUniqueId();
    SetDestPos(target->GetTranslation());
    ReleaseCoverPoint(mgr, x674_coverPoint);
    SetCoverPoint(target, x674_coverPoint);
  } else if (mgr.GetPlayer().GetAreaIdAlways() == GetAreaIdAlways()) {
    x2dc_destObj = mgr.GetPlayer().GetUniqueId();
    zeus::CVector3f destPos =
        mgr.GetPlayer().GetTranslation() - x654_ * (mgr.GetPlayer().GetTranslation() - GetTranslation()).normalized();
    const CRayCastResult& res =
        mgr.RayStaticIntersection(destPos, zeus::skDown, 8.f, CMaterialFilter::MakeInclude(EMaterialTypes::Floor));
    if (res.IsValid())
      destPos = res.GetPoint();
    SetDestPos(destPos);
  } else {
    x2dc_destObj = kInvalidUniqueId;
    x2e0_destPos = GetTranslation();
  }
}
} // namespace metaforce::MP1
