/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

// actor_turret.cpp

#include "actor.h"

void Actor::InitTurret(GlobalFuncs_t *func)
{
    func->ThinkState                 = &Actor::Think_Turret;
    func->BeginState                 = &Actor::Begin_Turret;
    func->EndState                   = &Actor::End_Turret;
    func->SuspendState               = &Actor::Suspend_Turret;
    func->RestartState               = NULL;
    func->FinishedAnimation          = &Actor::FinishedAnimation_Turret;
    func->PassesTransitionConditions = &Actor::PassesTransitionConditions_Attack;
    func->PostShoot                  = &Actor::InterruptPoint_Turret;
    func->ReceiveAIEvent             = &Actor::ReceiveAIEvent_Turret;
    func->IsState                    = &Actor::IsAttackState;
    func->PathnodeClaimRevoked       = &Actor::PathnodeClaimRevoked_Turret;
}

bool Actor::Turret_IsRetargeting(void) const
{
    return m_State > ACTOR_STATE_TURRET_SHOOT && m_State < ACTOR_STATE_TURRET_NUM_STATES;
}

bool Actor::Turret_DecideToSelectState(void)
{
    switch (m_State) {
    case ACTOR_STATE_TURRET_COMBAT:
        if (level.inttime > m_iStateTime + 5000) {
            InterruptPoint_Turret();
        }
        return false;
    case ACTOR_STATE_TURRET_REACQUIRE:
    case ACTOR_STATE_TURRET_GRENADE:
    case ACTOR_STATE_TURRET_TAKE_SNIPER_NODE:
    case ACTOR_STATE_TURRET_FAKE_ENEMY:
    case ACTOR_STATE_TURRET_SHOOT:
        return false;
    }

    return !Turret_IsRetargeting();
}

void Actor::Turret_SelectState(void)
{
    float  fDistSquared;
    vec2_t vDelta;

    if (!m_Enemy) {
        TransitionState(ACTOR_STATE_TURRET_FAKE_ENEMY, (rand() & 0x7FF) + 250);
        return;
    }

    VectorSub2D(origin, m_vHome, vDelta);
    fDistSquared = VectorLength2DSquared(vDelta);

    if (m_State == ACTOR_STATE_TURRET_RUN_HOME && fDistSquared > (m_fLeashSquared * 0.64 + 64.0)) {
        if (PathExists() && !PathComplete()) {
            return;
        }
    } else if (fDistSquared <= m_fLeashSquared * 1.21 + 256.0) {
        m_iRunHomeTime = 0;
    } else if (!m_iRunHomeTime) {
        m_iRunHomeTime = level.inttime + (rand() & 0xFFF) + 1000;
    } else if (level.inttime >= m_iRunHomeTime) {
        m_iRunHomeTime = 0;

        ClearPath();
        SetPath(m_vHome, NULL, 0, NULL, 0.0);
        ShortenPathToAvoidSquadMates();

        if (!PathExists()) {
            Com_Printf(
                "^~^~^ (entnum %i, radnum %d, targetname '%s') cannot reach his leash home\n",
                entnum,
                radnum,
                targetname.c_str()
            );
        } else if (!PathComplete()) {
            TransitionState(ACTOR_STATE_TURRET_RUN_HOME, 0);
            return;
        }
    }

    VectorSub2D(origin, m_Enemy->origin, vDelta);
    fDistSquared = VectorLength2DSquared(vDelta);

    if (m_State == ACTOR_STATE_TURRET_RUN_AWAY && fDistSquared < m_fMinDistanceSquared * 2.25) {
        return;
    }

    if (fDistSquared < m_fMinDistanceSquared) {
        ClearPath();
        TransitionState(ACTOR_STATE_TURRET_RUN_AWAY, 0);
        return;
    }

    if (fDistSquared > m_fMaxDistanceSquared) {
        if (m_Team == TEAM_GERMAN && (m_Enemy->origin - m_vHome).lengthSquared() >= Square(m_fLeash + m_fMaxDistance)
            && !CanSeeEnemy(200)) {
            ClearPath();
            TransitionState(ACTOR_STATE_TURRET_WAIT, 0);
        } else if (m_State != ACTOR_STATE_TURRET_CHARGE) {
            ClearPath();
            TransitionState(ACTOR_STATE_TURRET_CHARGE, 0);
        }
    } else {
        if (DecideToThrowGrenade(m_vLastEnemyPos + m_Enemy->velocity, &m_vGrenadeVel, &m_eGrenadeMode, false)) {
            SetDesiredYawDir(m_vGrenadeVel);

            DesiredAnimation(
                ANIM_MODE_NORMAL,
                m_eGrenadeMode == AI_GREN_TOSS_ROLL ? STRING_ANIM_GRENADETOSS_SCR : STRING_ANIM_GRENADETHROW_SCR
            );
            TransitionState(ACTOR_STATE_TURRET_GRENADE, 0);
            return;
        }

        if (m_State != ACTOR_STATE_TURRET_COMBAT && m_State != ACTOR_STATE_TURRET_SNIPER_NODE
            && m_State != ACTOR_STATE_TURRET_WAIT && m_State != ACTOR_STATE_TURRET_SHOOT) {
            ClearPath();
            TransitionState(ACTOR_STATE_TURRET_COMBAT);
        }
    }
}

bool Actor::Turret_CheckRetarget(void)
{
    if (level.inttime < m_iStateTime + 5000 || level.inttime < m_iLastHitTime + 5000) {
        return false;
    }

    Turret_BeginRetarget();

    return true;
}

void Actor::Begin_Turret(void)
{
    DoForceActivate();
    m_csMood = STRING_ALERT;

    ClearPath();

    if (m_Enemy) {
        TransitionState(ACTOR_STATE_TURRET_COVER_INSTEAD, 0);
    } else {
        TransitionState(ACTOR_STATE_TURRET_FAKE_ENEMY, (rand() & 0x7FF) + 250);
    }
}

void Actor::End_Turret(void)
{
    if (m_pCoverNode && m_State != ACTOR_STATE_TURRET_BECOME_COVER) {
        m_pCoverNode->Relinquish();
        m_pCoverNode = NULL;
    }
    TransitionState(-1, 0);
}

void Actor::Suspend_Turret(void)
{
    if (!m_Enemy) {
        TransitionState(ACTOR_STATE_TURRET_COVER_INSTEAD, 0);
    } else if (m_State == ACTOR_STATE_TURRET_GRENADE || m_State == ACTOR_STATE_TURRET_INTRO_AIM) {
        Turret_BeginRetarget();
    }
}

void Actor::State_Turret_Combat(void)
{
    if (CanSeeEnemy(200)) {
        ClearPath();
        Anim_Attack();
        AimAtTargetPos();
        Turret_CheckRetarget();
        return;
    }

    if (!PathExists() || PathComplete() || !PathAvoidsSquadMates()) {
        SetPathWithLeash(m_vLastEnemyPos, NULL, 0);
        ShortenPathToAvoidSquadMates();
    }

    if (!PathExists() || PathComplete() || !PathAvoidsSquadMates()) {
        FindPathNearWithLeash(m_vLastEnemyPos, m_fMinDistanceSquared * 4);
        if (!ShortenPathToAttack(0.0)) {
            ClearPath();
        }
        ShortenPathToAvoidSquadMates();
    }

    if (!PathExists() || PathComplete() || !PathAvoidsSquadMates()) {
        m_pszDebugState = "combat->chill";
        Turret_BeginRetarget();
        return;
    }

    m_pszDebugState = "combat->move";
    if (!MovePathWithLeash()) {
        m_pszDebugState = "combat->move->aim";
        Turret_BeginRetarget();
        return;
    }

    Turret_CheckRetarget();
}

void Actor::Turret_BeginRetarget(void)
{
    SetEnemyPos(m_Enemy->origin);
    AimAtEnemyBehavior();

    // Replaced in 2.0
    //  Use the Retarget_Suppress state instead of the Retarget_Sniper_Node state
    if (g_target_game >= target_game_e::TG_MOHTA) {
        TransitionState(ACTOR_STATE_TURRET_RETARGET_SUPPRESS, 0);
    } else {
        TransitionState(ACTOR_STATE_TURRET_RETARGET_SNIPER_NODE, 0);
    }
}

void Actor::Turret_NextRetarget(void)
{
    vec2_t vDelta;

    m_State++;
    if (m_State < ACTOR_STATE_TURRET_NUM_STATES) {
        TransitionState(m_State);
        return;
    }

    VectorSub2D(origin, m_vHome, vDelta);

    if (VectorLength2DSquared(vDelta) >= m_fLeashSquared) {
        SetPath(m_vHome, NULL, 0, NULL, 0.0);
        ShortenPathToAvoidSquadMates();

        if (PathExists() && !PathComplete()) {
            TransitionState(ACTOR_STATE_TURRET_RUN_HOME, 0);
            State_Turret_RunHome(true);
            return;
        }
    }

    if (m_Team == TEAM_AMERICAN) {
        if (!CanSeeEnemy(200)) {
            m_PotentialEnemies.FlagBadEnemy(m_Enemy);
            UpdateEnemy(-1);
        }

        if (!m_Enemy) {
            Anim_Stand();
            return;
        }

        TransitionState(ACTOR_STATE_TURRET_COMBAT, 0);
        State_Turret_Combat();
    } else if (CanSeeEnemy(200)) {
        m_pszDebugState = "Retarget->Combat";
        TransitionState(ACTOR_STATE_TURRET_COMBAT, 0);
        State_Turret_Combat();
    } else {
        TransitionState(ACTOR_STATE_TURRET_WAIT, 0);
        State_Turret_Wait();
    }
}

void Actor::Turret_SideStep(int iStepSize, vec3_t vDir)
{
    AimAtEnemyBehavior();
    StrafeToAttack(iStepSize, vDir);

    if (PathExists() && !PathComplete() && PathAvoidsSquadMates()) {
        TransitionState(ACTOR_STATE_TURRET_REACQUIRE);
        return;
    }

    StrafeToAttack(-iStepSize, vDir);

    if (PathExists() && !PathComplete() && PathAvoidsSquadMates()) {
        TransitionState(ACTOR_STATE_TURRET_REACQUIRE);
        return;
    }

    Turret_NextRetarget();
}

void Actor::State_Turret_Shoot(void)
{
    assert(g_target_game > target_game_e::TG_MOH);

    if (CanSeeEnemy(200) || FriendlyInLineOfFire(m_Enemy)) {
        TransitionState(ACTOR_STATE_TURRET_COMBAT);
        State_Turret_Combat();
        return;
    }

    if (level.inttime >= m_iStateTime + 15000) {
        Turret_SelectState();
        if (m_State == ACTOR_STATE_TURRET_SHOOT) {
            Turret_BeginRetarget();
        }
    }
}

void Actor::State_Turret_Retarget_Suppress(void)
{
    trace_t trace;

    assert(g_target_game > target_game_e::TG_MOH);

    if (rand() % 100 >= m_iSuppressChance) {
        AimAtEnemyBehavior();
        Turret_NextRetarget();
        return;
    }

    if (level.inttime >= m_iLastEnemyVisibleTime + 15000) {
        AimAtEnemyBehavior();
        Turret_NextRetarget();
        return;
    }

    if (FriendlyInLineOfFire(m_Enemy)) {
        AimAtEnemyBehavior();
        Turret_NextRetarget();
        return;
    }

    trace = G_Trace(
        EyePosition(),
        vec_zero,
        vec_zero,
        m_Enemy->EyePosition(),
        this,
        MASK_CANSEE,
        qfalse,
        "Actor::State_Turret_Retarget_Suppress"
    );
    if (trace.fraction <= 0.5f) {
        AimAtEnemyBehavior();
        Turret_NextRetarget();
        return;
    }

    if (trace.fraction != 1.f && trace.plane.normal[2] >= 0.7f) {
        AimAtEnemyBehavior();
        Turret_NextRetarget();
        return;
    }

    TransitionState(ACTOR_STATE_TURRET_SHOOT);
    State_Turret_Shoot();
}

void Actor::State_Turret_Retarget_Sniper_Node(void)
{
    PathNode *pSniperNode;
    bool      bTryAgain;

    AimAtEnemyBehavior();
    if (m_pCoverNode) {
        m_pCoverNode->Relinquish();
        m_pCoverNode = NULL;
    }

    pSniperNode = FindSniperNodeAndSetPath(&bTryAgain);
    if (pSniperNode) {
        m_pCoverNode = pSniperNode;
        pSniperNode->Claim(this);
        TransitionState(ACTOR_STATE_TURRET_TAKE_SNIPER_NODE);
        State_Turret_TakeSniperNode();
    } else if (bTryAgain) {
        ContinueAnimation();
    } else {
        Turret_NextRetarget();
    }
}

void Actor::State_Turret_Retarget_Step_Side_Small(void)
{
    Turret_SideStep((rand() & 64) - 32, orientation[1]);
}

void Actor::State_Turret_Retarget_Path_Exact(void)
{
    AimAtEnemyBehavior();
    SetPathWithLeash(m_vLastEnemyPos, NULL, 0);

    if (!ShortenPathToAttack(128)) {
        Turret_NextRetarget();
        return;
    }

    ShortenPathToAvoidSquadMates();

    if (!PathExists()) {
        Turret_NextRetarget();
        return;
    }

    TransitionState(ACTOR_STATE_TURRET_REACQUIRE, 0);
}

void Actor::State_Turret_Retarget_Path_Near(void)
{
    AimAtEnemyBehavior();
    FindPathNearWithLeash(m_vLastEnemyPos, m_fMinDistanceSquared);

    if (ShortenPathToAttack(128)) {
        TransitionState(ACTOR_STATE_TURRET_REACQUIRE, 0);
    } else {
        Turret_NextRetarget();
    }
}

void Actor::State_Turret_Retarget_Step_Side_Medium(void)
{
    Turret_SideStep((rand() & 256) - 128, orientation[1]);
}

void Actor::State_Turret_Retarget_Step_Side_Large(void)
{
    Turret_SideStep((rand() & 512) - 256, orientation[1]);
}

void Actor::State_Turret_Retarget_Step_Face_Medium(void)
{
    Turret_SideStep((rand() & 256) - 128, orientation[0]);
}

void Actor::State_Turret_Retarget_Step_Face_Large(void)
{
    Turret_SideStep((rand() & 512) - 256, orientation[0]);
}

void Actor::State_Turret_Reacquire(void)
{
    if (!PathExists() || PathComplete()) {
        m_pszDebugState = "Retarget->Cheat";
        SetEnemyPos(m_Enemy->origin);
        TransitionState(ACTOR_STATE_TURRET_COMBAT, 0);
        State_Turret_Combat();
        return;
    }

    if (CanMovePathWithLeash()) {
        Anim_RunToInOpen(ANIM_MODE_PATH_GOAL);
        FaceEnemyOrMotion(level.inttime - m_iStateTime);
    } else {
        Turret_BeginRetarget();
    }
}

void Actor::State_Turret_TakeSniperNode(void)
{
    if (!PathExists() || PathComplete()) {
        AimAtEnemyBehavior();
        TransitionState(ACTOR_STATE_TURRET_SNIPER_NODE, 0);
        return;
    }

    FaceMotion();
    Anim_RunToDanger(ANIM_MODE_PATH_GOAL);
}

void Actor::State_Turret_SniperNode(void)
{
    AimAtTargetPos();
    Anim_Sniper();

    if (Turret_CheckRetarget()) {
        m_pCoverNode->Relinquish();
        m_pCoverNode->MarkTemporarilyBad();
        m_pCoverNode = NULL;
    }
}

bool Actor::State_Turret_RunHome(bool bAttackOnFail)
{
    SetPath(m_vHome, NULL, 0, NULL, 0.0);
    ShortenPathToAvoidSquadMates();

    if (!PathExists() || PathComplete()) {
        Com_Printf(
            "^~^~^ (entnum %i, radnum %i, targetname '%s') cannot reach his leash home\n",
            entnum,
            radnum,
            targetname.c_str()
        );
        if (bAttackOnFail) {
            m_pszDebugState = "home->combat";
            State_Turret_Combat();
        }
        return false;
    }

    FaceMotion();
    Anim_RunToInOpen(ANIM_MODE_PATH);
    return true;
}

void Actor::State_Turret_RunAway(void)
{
    if (!PathExists() || PathComplete()) {
        FindPathAwayWithLeash(m_vLastEnemyPos, origin - m_Enemy->origin, 1.5 * m_fMinDistance);
    }

    if (!PathExists() || PathComplete()) {
        m_pszDebugState = "runaway->combat";
        State_Turret_Combat();
        return;
    }

    if (!CanMovePathWithLeash()) {
        m_pszDebugState = "runaway->leash->combat";
        State_Turret_Combat();
        return;
    }

    Anim_RunAwayFiring(ANIM_MODE_PATH);
    FaceEnemyOrMotion(level.inttime - m_iStateTime);
}

void Actor::State_Turret_Charge(void)
{
    SetPathWithLeash(m_vLastEnemyPos, NULL, 0);
    ShortenPathToAvoidSquadMates();

    if (!PathExists()) {
        m_pszDebugState = "charge->near";
        FindPathNearWithLeash(m_vLastEnemyPos, m_fMaxDistanceSquared);

        if (!ShortenPathToAttack(0)) {
            ClearPath();
        }
    }

    if (!PathExists() || PathComplete() || !PathAvoidsSquadMates()) {
        ClearPath();

        if (CanSeeEnemy(500)) {
            m_pszDebugState = "charge->combat";
            State_Turret_Combat();
            return;
        }

        m_pszDebugState = "charge->chill";

        ForwardLook();
        Anim_Idle();

        if (m_Team == TEAM_AMERICAN || m_PotentialEnemies.HasAlternateEnemy()) {
            m_PotentialEnemies.FlagBadEnemy(m_Enemy);
            UpdateEnemy(-1);
        }

        if (m_Enemy) {
            Turret_CheckRetarget();
        }
    } else if (!MovePathWithLeash()) {
        m_pszDebugState = "charge->leash->combat";
        TransitionState(ACTOR_STATE_TURRET_COMBAT, 0);
        State_Turret_Combat();
        return;
    }
}

void Actor::State_Turret_Grenade(void)
{
    GenericGrenadeTossThink();
}

void Actor::State_Turret_FakeEnemy(void)
{
    AimAtTargetPos();
    Anim_Aim();

    if (level.inttime >= m_iStateTime) {
        SetThinkState(THINKSTATE_IDLE, THINKLEVEL_IDLE);
    }
}

void Actor::State_Turret_Wait(void)
{
    PathNode *pNode;

    if (CanSeeEnemy(500) || CanShootEnemy(500)) {
        if (Turret_TryToBecomeCoverGuy()) {
            m_pszDebugState = "Wait->CoverInstead";
            ContinueAnimation();
        } else {
            m_pszDebugState = "Wait->Combat";
            TransitionState(ACTOR_STATE_TURRET_COMBAT);
            State_Turret_Combat();
        }
        return;
    }

    if (level.inttime >= m_iLastEnemyVisibleTime + 25000) {
        m_iLastEnemyVisibleTime = level.inttime;
        m_vLastEnemyPos         = m_Enemy->origin;
        Turret_BeginRetarget();
    }

    if (level.inttime >= m_iLastFaceDecideTime + 1500) {
        m_iLastFaceDecideTime = level.inttime + (rand() & 0x1FF);

        pNode = PathManager.FindCornerNodeForExactPath(this, m_Enemy, 0);

        if (pNode) {
            SetDesiredYawDest(pNode->m_PathPos);
            m_eDontFaceWallMode = 6;
        } else {
            AimAtTargetPos();
            DontFaceWall();
        }
    }

    if (m_eDontFaceWallMode == 7 || m_eDontFaceWallMode == 8) {
        Anim_Stand();
    } else {
        Anim_Aim();
    }
}

void Actor::Think_Turret(void)
{
    if (!RequireThink()) {
        return;
    }

    UpdateEyeOrigin();
    NoPoint();
    UpdateEnemy(200);

    if (m_Enemy && m_State == ACTOR_STATE_TURRET_COVER_INSTEAD) {
        if (!m_bTurretNoInitialCover && Turret_TryToBecomeCoverGuy()) {
            m_pszDebugState = "CoverInstead";

            CheckUnregister();
            UpdateAngles();
            DoMove();
            UpdateBoneControllers();
            UpdateFootsteps();
            return;
        }

        m_bTurretNoInitialCover = false;
        Turret_SelectState();

        if (m_State == ACTOR_STATE_TURRET_COMBAT && !CanSeeEnemy(0)) {
            Turret_BeginRetarget();
        }

        SetLeashHome(origin);

        if (level.inttime < m_iEnemyChangeTime + 200 && AttackEntryAnimation()) {
            TransitionState(ACTOR_STATE_TURRET_INTRO_AIM);
            m_bLockThinkState = true;
        }
    }

    if (level.inttime > m_iStateTime + 3000) {
        Turret_SelectState();
    }

    if (m_State == ACTOR_STATE_TURRET_INTRO_AIM) {
        m_pszDebugState = "IntroAnim";
        AimAtTargetPos();
        ContinueAnimation();

        if (m_State == ACTOR_STATE_TURRET_WAIT) {
            PostThink(false);
        } else {
            PostThink(true);
        }
        return;
    }

    m_bLockThinkState = false;

    if (!m_Enemy && m_State != ACTOR_STATE_TURRET_FAKE_ENEMY && m_State != ACTOR_STATE_TURRET_RUN_HOME) {
        TransitionState(ACTOR_STATE_TURRET_FAKE_ENEMY, (rand() + 250) & 0x7FF);
    }

    if (!m_Enemy && m_State != ACTOR_STATE_TURRET_FAKE_ENEMY) {
        if (m_State != ACTOR_STATE_TURRET_RUN_HOME
            || (origin - m_vHome).lengthXYSquared() <= (m_fLeashSquared * 0.64f + 64.0f)
            || !State_Turret_RunHome(false)) {
            m_pszDebugState = "Idle";
            SetThinkState(THINKSTATE_IDLE, THINKLEVEL_IDLE);
            IdleThink();
        } else {
            m_pszDebugState = "Idle->RunHome";
            PostThink(true);
        }
        return;
    }

    if (m_Enemy && m_State == ACTOR_STATE_TURRET_FAKE_ENEMY) {
        Turret_BeginRetarget();
    }

    if (Turret_DecideToSelectState()) {
        Turret_SelectState();
    }

    switch (m_State) {
    case ACTOR_STATE_TURRET_COMBAT:
        m_pszDebugState = "Combat";
        State_Turret_Combat();
        break;
    case ACTOR_STATE_TURRET_REACQUIRE:
        m_pszDebugState = "Reacquire";
        State_Turret_Reacquire();
        break;
    case ACTOR_STATE_TURRET_TAKE_SNIPER_NODE:
        m_pszDebugState = "TakeSniperNode";
        State_Turret_TakeSniperNode();
        break;
    case ACTOR_STATE_TURRET_SNIPER_NODE:
        m_pszDebugState = "SniperNode";
        State_Turret_SniperNode();
        break;
    case ACTOR_STATE_TURRET_RUN_HOME:
        m_pszDebugState = "RunHome";
        State_Turret_RunHome(true);
        break;
    case ACTOR_STATE_TURRET_RUN_AWAY:
        m_pszDebugState = "RunAway";
        State_Turret_RunAway();
        break;
    case ACTOR_STATE_TURRET_CHARGE:
        m_pszDebugState = "Charge";
        State_Turret_Charge();
        break;
    case ACTOR_STATE_TURRET_GRENADE:
        m_pszDebugState = "Grenade";
        State_Turret_Grenade();
        break;
    case ACTOR_STATE_TURRET_FAKE_ENEMY:
        m_pszDebugState = "FakeEnemy";
        State_Turret_FakeEnemy();
        break;
    case ACTOR_STATE_TURRET_BECOME_COVER:
        m_pszDebugState = "BecomeCover";
        ContinueAnimation();
        break;
    case ACTOR_STATE_TURRET_WAIT:
        m_pszDebugState = "Wait";
        State_Turret_Wait();
        break;
    case ACTOR_STATE_TURRET_SHOOT:
        m_pszDebugState = "Shoot";
        State_Turret_Shoot();
        break;
    case ACTOR_STATE_TURRET_RETARGET_SUPPRESS:
        m_pszDebugState = "Retarget_Suppress";
        State_Turret_Retarget_Suppress();
        break;
    case ACTOR_STATE_TURRET_RETARGET_SNIPER_NODE:
        m_pszDebugState = "Retarget_Sniper_Node";
        State_Turret_Retarget_Sniper_Node();
        break;
    case ACTOR_STATE_TURRET_RETARGET_STEP_SIDE_SMALL:
        m_pszDebugState = "Retarget_Step_Side_Small";
        State_Turret_Retarget_Step_Side_Small();
        break;
    case ACTOR_STATE_TURRET_RETARGET_PATH_EXACT:
        m_pszDebugState = "Retarget_Path_Exact";
        State_Turret_Retarget_Path_Exact();
        break;
    case ACTOR_STATE_TURRET_RETARGET_PATH_NEAR:
        m_pszDebugState = "Retarget_Path_Near";
        State_Turret_Retarget_Path_Near();
        break;
    case ACTOR_STATE_TURRET_RETARGET_STEP_SIDE_MEDIUM:
        m_pszDebugState = "Retarget_Step_Side_Medium";
        State_Turret_Retarget_Step_Side_Medium();
        break;
    case ACTOR_STATE_TURRET_RETARGET_STEP_SIDE_LARGE:
        m_pszDebugState = "Retarget_Step_Side_Large";
        State_Turret_Retarget_Step_Side_Large();
        break;
    case ACTOR_STATE_TURRET_RETARGET_STEP_FACE_MEDIUM:
        m_pszDebugState = "Retarget_Step_Face_Medium";
        State_Turret_Retarget_Step_Face_Medium();
        break;
    case ACTOR_STATE_TURRET_RETARGET_STEP_FACE_LARGE:
        m_pszDebugState = "Retarget_Step_Face_Large";
        State_Turret_Retarget_Step_Face_Large();
        break;
    default:
        Com_Printf("Actor::Think_Turret: invalid think state %i\n", m_State);
        assert(!"invalid think state");
        break;
    }

    if (!CheckForTransition(THINKSTATE_GRENADE, THINKLEVEL_IDLE)) {
        CheckForTransition(THINKSTATE_BADPLACE, THINKLEVEL_IDLE);
    }

    if (m_State == ACTOR_STATE_TURRET_WAIT) {
        PostThink(false);
    } else {
        PostThink(true);
    }
}

void Actor::ReceiveAIEvent_Turret(
    vec3_t event_origin, int iType, Entity *originator, float fDistSquared, float fMaxDistSquared
)
{
    if (iType == AI_EVENT_WEAPON_IMPACT && m_Enemy && fDistSquared <= Square(128)) {
        Turret_TryToBecomeCoverGuy();
        return;
    }

    DefaultReceiveAIEvent(origin, iType, originator, fDistSquared, fMaxDistSquared);
}

bool Actor::Turret_TryToBecomeCoverGuy(void)
{
    PathNode *pOldCover = m_pCoverNode;

    Cover_FindCover(true);

    if (m_pCoverNode) {
        TransitionState(ACTOR_STATE_TURRET_BECOME_COVER, 0);
        SetThink(THINKSTATE_ATTACK, THINK_COVER);
        return true;
    }

    if (pOldCover) {
        m_pCoverNode = pOldCover;
        m_pCoverNode->Claim(this);
    }

    return false;
}

void Actor::FinishedAnimation_Turret(void)
{
    if (m_State == ACTOR_STATE_TURRET_GRENADE || m_State == ACTOR_STATE_TURRET_INTRO_AIM
        || m_State == ACTOR_STATE_TURRET_SHOOT) {
        Turret_SelectState();
    }
}

void Actor::InterruptPoint_Turret(void)
{
    if (m_Enemy && !Turret_TryToBecomeCoverGuy() && m_State == ACTOR_STATE_TURRET_COMBAT) {
        m_iStateTime = level.inttime;
        Turret_SelectState();
    }
}

void Actor::PathnodeClaimRevoked_Turret(void)
{
    if (!m_Enemy) {
        TransitionState(ACTOR_STATE_TURRET_COVER_INSTEAD, 0);
    } else {
        Turret_BeginRetarget();
    }
}
