#include "d/a/obj/d_a_obj_tubo.h"

#include "c/c_lib.h"
#include "c/c_math.h"
#include "common.h"
#include "d/a/d_a_base.h"
#include "d/a/d_a_item.h"
#include "d/a/d_a_player.h"
#include "d/a/npc/d_a_npc_ce_friend.h"
#include "d/a/npc/d_a_npc_ce_lady.h"
#include "d/a/obj/d_a_obj_base.h"
#include "d/a/obj/d_a_obj_log.h"
#include "d/col/bg/d_bg_pc.h"
#include "d/col/bg/d_bg_s.h"
#include "d/col/bg/d_bg_s_gnd_chk.h"
#include "d/col/bg/d_bg_s_lin_chk.h"
#include "d/col/c/c_cc_d.h"
#include "d/col/c/c_m3d_g_pla.h"
#include "d/col/cc/d_cc_s.h"
#include "d/d_light_env.h"
#include "d/d_linkage.h"
#include "d/flag/dungeonflag_manager.h"
#include "d/flag/sceneflag_manager.h"
#include "d/snd/d_snd_wzsound.h"
#include "egg/math/eggMath.h"
#include "f/f_manager.h"
#include "f/f_profile_name.h"
#include "m/m_angle.h"
#include "m/m_mtx.h"
#include "m/m_quat.h"
#include "m/m_vec.h"
#include "nw4r/math/math_arithmetic.h"
#include "rvl/MTX/mtxvec.h"
#include "s/s_Math.h"
#include "toBeSorted/attention.h"
#include "toBeSorted/d_emitter.h"
#include "toBeSorted/event_manager.h"
#include "toBeSorted/special_item_drop_mgr.h"

void float_ordering() {
    f32 f[] = {15.f, .4f, .4f, 5.f};
}

SPECIAL_ACTOR_PROFILE(OBJ_TUBO, dAcOtubo_c, fProfile::OBJ_TUBO, 0x194, 0, 130);

STATE_DEFINE(dAcOtubo_c, Wait);
STATE_DEFINE(dAcOtubo_c, Grab);
STATE_DEFINE(dAcOtubo_c, Put);
STATE_DEFINE(dAcOtubo_c, Slope);
STATE_DEFINE(dAcOtubo_c, Rebirth);

dCcD_SrcSph dAcOtubo_c::sSphSrc = {
    /* mObjInf */
    {/* mObjAt */ {AT_TYPE_PHYSICS, 0x12, {0, 0, 0}, 2, 0, 0, 0, 0, 0},
     /* mObjTg */ {~(AT_TYPE_BUGNET | AT_TYPE_GLITTERING_SPORES | AT_TYPE_0x8000), 0x801111, {0, 0xA, 0x40F}, 8, 0},
     /* mObjCo */ {0x1E9}},
    /* mSphInf */
    {30.f},
};

bool dAcOtubo_c::createHeap() {
    mRes = nw4r::g3d::ResFile(getOarcResFile("Tubo"));
    const char *subtype = getSubtype() == 0 ? "Tubo00" : "Tubo01";
    TRY_CREATE(mMdl.create(mRes.GetResMdl(subtype), &mAllocator, 0x120, 1, nullptr));
    return true;
}

int dAcOtubo_c::actorCreate() {
    CREATE_ALLOCATOR(dAcOtubo_c);
    mObjAcch.Set(this, 1, &mAcchCir);
    mAcchCir.SetWall(20.f, 30.f);
    mEff_0x91C.init(this, 60.f, mScale.x, 0.f);
    mEff_0x91C.setIsSmall(true);
    int unkParam = getParams_0x3000();
    if (unkParam != 1 && unkParam != 2) {
        fn_8002EA30(-60.f, -20.f, 2.f, 1.f, 4.f, &mObjAcch);
    }
    mStts.SetRank(5);
    mSph.Set(sSphSrc);
    mSph.SetStts(mStts);
    mSph.ClrAtSet();

    mAcceleration = -4.f;
    mMaxSpeed = -40.f;
    mbMovingForward = 0;
    field_0x9D8 = 2.f;

    mQuat_0x98C.set(1.f, 0.f, 0.f, 0.f);
    mQuat_0x97C.set(1.f, 0.f, 0.f, 0.f);
    mQuat_0x99C.set(1.f, 0.f, 0.f, 0.f);

    mSceneflag = getSceneflag();
    mSubtype = getSubtype();
    mbField_0x9ED = getParams_0x3000() == 0;
    mbField_0x9EF = getParams_0x3000() == 2;
    if (getParams_0x3000() == 1) {
        mTimer_0x9F4 = 0xF;
        mSph.SetTgInfo_0x2(0);
    }
    mbField_0x9F0 = getParams_0xC000() == 0;

    if (mSceneflag < 0xFF && checkSceneflag()) {
        fillUpperParams2Byte();
    }

    mStateMgr.changeState(StateID_Wait);
    mBoundingBox.Set(mVec3_c(-100.f, -40.f, -100.f), mVec3_c(100.f, 80.f, 100.f));
    getLinkage().set(0xC8A0, 28.f, 50.f, 28.f, nullptr); // TODO (ActorCarry Flags)
    getLinkage().field_0x8C = 26.f;
    getLinkage().field_0x90 = 46.f;
    getLinkage().field_0x94 = 28.f;
    getLinkage().field_0x98 = 0.f;

    return SUCCEEDED;
}

int dAcOtubo_c::actorPostCreate() {
    mCeLady.link((dAcNpcCeLady_c *)fManager_c::searchBaseByProfName(fProfile::NPC_CE_LADY, nullptr));

    if (mbField_0x9EF) {
        if (DungeonflagManager::sInstance->getCounterOrFlag(3, 8) != 0) {
            return FAILED;
        }

        setActorProperty(AC_PROP_0x4);

        if (dBgS_ObjGndChk ::CheckPos(mPosition + mVec3_c::Ey * 50.f)) {
            mPosition.y = dBgS_ObjGndChk::GetGroundHeight();
        }
    }

    return SUCCEEDED;
}

int dAcOtubo_c::doDelete() {
    return SUCCEEDED;
}

int dAcOtubo_c::actorExecute() {
    mStateMgr.executeState();
    bool bOffGround = false;
    if (mTimer_0x9F4 != 0) {
        field_0x9DC = 0.f;

        if (dBgS_ObjGndChk::CheckPos(mPosition + mVec3_c::Ey * 500.f)) {
            if (dBgS_ObjGndChk::GetGroundHeight() - mPosition.y > 5.f) {
                bOffGround = true;
                mSph.ClrTgSet();
            } else if (sLib::calcTimer(&mTimer_0x9F4) == 0) {
                mSph.SetTgInfo_0x2(8);
                mSph.OnTgSet();
                bOffGround = false;
            }
        }
    }
    if (mSph.ChkCoHit()) {
        mObjAcch.OnLineCheck();
    } else {
        mObjAcch.OffLineCheck();
    }

    if (!bOffGround) {
        if (!checkCarryType()) {
            calcVelocity();
            mPosition += mVelocity;
            mPosition += mStts.GetCcMove();
            field_0x9DC += mPosition.y - mOldPosition.y;
            adjustRoll();
        }
        getLinkage().bushTpFunc(mObjAcch);
    }

    if (mStateMgr.isState(StateID_Wait) || mStateMgr.isState(StateID_Slope)) {
        attemptDestroy();
    }
    mSph.SetC(getCenter());
    dCcS::GetInstance()->Set(&mSph);

    getLinkage().fn_800511E0(this);
    field_0x8F0.modifyMtx();
    calcRoll();

    mMdl.setLocalMtx(mWorldMtx);
    mMdl.calc(false);
    mPositionCopy2 = mPosition + mVec3_c(0.f, 28.f, 0.f);
    mPositionCopy3 = mPositionCopy2;
    mPositionCopy3.y += 48.f;
    mbField_0x9EB = 0;

    return SUCCEEDED;
}

extern "C" void fn_800A0550(dAcBase_c *);

void dAcOtubo_c::registerInEvent() {
    if (EventManager::isCurrentEvent("HoleIn") || EventManager::isCurrentEvent("HoleOut")) {
        fn_800A0550(this);
    }
}

int dAcOtubo_c::draw() {
    drawModelType1(&mMdl);

    static mQuat_c quat(0.f, 30.f, 0.f, 40.f);

    if (mYOffset >= 0.f) {
        drawShadow(mShdw, nullptr, mWorldMtx, &quat, -1, -1, -1, -1, -1, mPosition.y - mObjAcch.GetGroundH());
    }
    return SUCCEEDED;
}

void dAcOtubo_c::initializeState_Wait() {}
void dAcOtubo_c::executeState_Wait() {
    if (mObjAcch.ChkGroundLanding()) {
        if (!mbField_0x9EF || !EventManager::isInEvent()) {
            if (field_0x9F6 == 2) {
                dJEffManager_c::spawnGroundEffect(mPosition, mPolyAttr0, mPolyAttr1, field_0x1B4, 0, 1.0f, field_0x1B0);
            }
            if (mbField_0x9F3) {
                startSound(SE_Tubo_PUT);
                mbField_0x9F3 = false;
            }
            if (checkOnLava()) {
                if (field_0x9F6 != 2) {
                    dJEffManager_c::spawnGroundEffect(
                        mPosition, mPolyAttr0, mPolyAttr1, field_0x1B4, 0, 1.0f, field_0x1B0
                    );
                }
                startSound(SE_O_FALL_LAVA_S);
            }
        }
    } else if (mObjAcch.ChkGndHit()) {
        mObjAcch.SetRoofNone();
        if (mbField_0x9EE) {
            mbField_0x9EE = false;
        }

        addPickupTarget();
        if (checkSlope()) {
            mStateMgr.changeState(StateID_Slope);
            return;
        }

        adjustSpeed();
        if (mSpeed < 1.f) {
            mbMovingForward = 0;
            sLib::chase(&mSpeed, 0.f, 0.05f);
            field_0x9D8 = 2.f;
        }

        if (mSph.ChkAtSet()) {
            mSph.ClrAtSet();
        }

        playRollSound();
        if (!mbField_0x9EF) {
            if (dBgS::GetInstance()->ChkMoveBG(mObjAcch.GetGnd(), true)) {
                unsetActorProperty(AC_PROP_0x1);
            } else {
                setActorProperty(AC_PROP_0x1);
            }
        }
        field_0x9DC = 0.f;
    }
    fn_272_2670();
}
void dAcOtubo_c::finalizeState_Wait() {}

void dAcOtubo_c::initializeState_Grab() {
    mVelocity = mVec3_c::Zero;
    mAngle = mAng3_c::Zero;
    mbMovingForward = 0;
    mSph.SetCo_0x400();
    unsetActorProperty(AC_PROP_0x1);
    if (mbField_0x9ED) {
        mTimer_0x9F7 = 5;
        mbField_0x9EE = true;
    }
    mTimer_0x9F5 = 15;
    field_0x9DC = 0.f;
}
void dAcOtubo_c::executeState_Grab() {
    if (mbField_0x9ED && sLib::calcTimer(&mTimer_0x9F7) == 0) {
        dAcNpcCeLady_c *lady = mCeLady.get();
        dAcNpcCeFriend_c *ceFriend = mCeFriend.get();
        if (ceFriend && ceFriend->fn_11_17C0(this)) {
            getLinkage().forceRemove(this);
        } else if (lady && lady->fn_12_1C20(this)) {
            getLinkage().forceRemove(this);
        }
    }

    if (getLinkage().checkConnection(dLinkage_c::CONNECTION_5) && sLib::calcTimer(&mTimer_0x9F5) == 0 &&
        (mObjAcch.ChkGndHit() || mObjAcch.ChkWallHit(nullptr) || mObjAcch.ChkRoofHit())) {
        destroy();
    } else if (dAcPy_c::GetLink()->getCurrentAction() == 66 /* Put Down Medium */) { // TODO (Link Action ID)
        mStateMgr.changeState(StateID_Put);
    } else {
        if (!getLinkage().checkState(dLinkage_c::STATE_DELETE)) {
            if (getLinkage().checkConnection(dLinkage_c::CONNECTION_5)) {
                field_0x9F6 = dLinkage_c::STATE_0;
            } else {
                field_0x9F6 = getLinkage().getState();
            }
        }

        if (!checkCarryType()) {
            mStateMgr.changeState(StateID_Wait);
        }
    }
}
void dAcOtubo_c::finalizeState_Grab() {
    field_0x9DC = 0.f;
    if (mSpeed > 0.f) {
        mbMovingForward = 1;
    }
    field_0x9D4 = cM::rndF(40.f);
    mSph.ClrCo_0x400();
    if ((u8)getLinkage().mState == 2) {
        mSph.OnAtSet();
    }
    mObjAcch.ClrRoofNone();
    mObjAcch.field_0x0D4 = 50.f;
}

void dAcOtubo_c::initializeState_Put() {}
void dAcOtubo_c::executeState_Put() {
    if (dAcPy_c::LINK->getCurrentAction() == 66 /* Put Down Medium */) { // TODO (Link Action ID)
        mQuat_0x98C.slerpTo(mQuat_0x97C, 0.5f, mQuat_0x98C);
        mQuat_0x98C.normalise();
    }
    if (!checkCarryType()) {
        mStateMgr.changeState(StateID_Wait);
    }
}
void dAcOtubo_c::finalizeState_Put() {
    if (dBgS_ObjGndChk::CheckPos(mPosition + mVec3_c::Ey * 10.f)) {
        mbField_0x9F3 = nw4r::math::FAbs(mPosition.y - dBgS_ObjGndChk::GetGroundHeight()) < 100.f;
    }
    if (!(dBgS_ObjGndChk::CheckPos(mPosition + mVec3_c::Ey * 10.f) &&
          nw4r::math::FAbs(mPosition.y - dBgS_ObjGndChk::GetGroundHeight()) < 100.f)) {
        mSph.OnAtSet();
    }
    mSpeed = 0.f;
}

void dAcOtubo_c::initializeState_Slope() {
    cM3dGPla pla;
    dBgS::GetInstance()->GetTriPla(mObjAcch.GetGnd(), &pla);
    if (cM::isZero(mSpeed)) {
        mAngle.y = pla.GetAngleY();
    }
    mAng plaAng = pla.GetAngleY();
    field_0x9C4 = (plaAng - mAngle.y);
    mAng other = labs((s16)(mAngle.y - field_0x9C4));
    field_0x9C6 = nw4r::math::FSqrt(other.degree_c() / 180.f) * 910.f;
}
void dAcOtubo_c::executeState_Slope() {
    if (mObjAcch.ChkGroundLanding()) {
        dJEffManager_c::spawnGroundEffect(mPosition, mPolyAttr0, mPolyAttr1, field_0x1B4, 0, 1.0f, field_0x1B0);
    } else if (mObjAcch.ChkGndHit()) {
        field_0x9DC = 0.f;
        addPickupTarget();
        if (checkRollHitMaybe()) {
            adjustAngle();
        } else {
            mbMovingForward = 0;
            mSpeed = 0.f;
            mVelocity = mVec3_c::Zero;
            cM3dGPla pla;
            dBgS::GetInstance()->GetTriPla(mObjAcch.GetGnd(), &pla);
            mAngle.y = cM::atan2s(pla.GetN().x, pla.GetN().z);
        }
        adjustSpeed();
        if (mSph.ChkAtSet()) {
            mSph.ClrAtSet();
        }
        playRollSound();
        if (!checkSlope()) {
            mStateMgr.changeState(StateID_Wait);
            return;
        }
    }

    fn_272_2670();
}
void dAcOtubo_c::finalizeState_Slope() {}

void dAcOtubo_c::initializeState_Rebirth() {
    SpecialItemDropMgr *mgr = SpecialItemDropMgr::GetInstance();
    mgr->giveSpecialDropItem(getParams2UpperByte(), mRoomID, &mPosition, 0, mRotation.y, -1);
    field_0x9AC = mPosition;
    mpPosition = &field_0x9AC;
    field_0x9F6 = 0;
    mRotation = mRotationCopy;

    mQuat_0x98C.set(1.f, 0.f, 0.f, 0.f);

    mSpeed = 0.f;
    mVelocity.y = 0.f;
    field_0x9DC = 0.f;

    mSph.ClrCoSet();
    mSph.ClrTgSet();
    setObjectProperty(OBJ_PROP_0x200);
    getLinkage().forceRemove(this);

    int item_drop_table = getParams2UpperByte();
    switch (item_drop_table) {
        default:                   break;
        case SPECIAL_ITEM_10BOMBS: field_0x9FC = 2; break;
        case SPECIAL_ITEM_ARROWS:  field_0x9FC = 0; break;
        case SPECIAL_ITEM_SEEDS:   field_0x9FC = 1; break;
    }
    mTimer_0x9E8 = 150;
    unsetActorProperty(AC_PROP_0x1);
}
void dAcOtubo_c::executeState_Rebirth() {
    setPosition(mPositionCopy);
    mOldPosition = mPositionCopy;
    u8 count = -1;
    switch (field_0x9FC) {
        case 2: count = dAcItem_c::getTotalBombCount(); break;
        case 0: count = dAcItem_c::getTotalArrowCount(); break;
        case 1: count = dAcItem_c::getTotalSeedCount(); break;
    }
    if (checkObjectProperty(OBJ_PROP_0x2) && count <= 3) {
        if (sLib::calcTimer(&mTimer_0x9E8) == 0) {
            mStateMgr.changeState(StateID_Wait);
        }
    } else {
        mTimer_0x9E8 = 150;
    }
}
void dAcOtubo_c::finalizeState_Rebirth() {
    mSph.OnCoSet();
    mSph.OnTgSet();
    mpPosition = &mPosition;
    unsetObjectProperty(OBJ_PROP_0x200);
    setActorProperty(AC_PROP_0x1);
}

extern "C" void fn_80022BE0(void *, const mVec3_c &);

void dAcOtubo_c::destroy() {
    dAcNpcCeLady_c *lady = mCeLady.get();
    bool boolParam = true;
    if (lady) {
        lady->fn_12_1E00(this, &boolParam);
    }
    if (!boolParam) {
        return;
    }
    fn_80022BE0(dLightEnv_c::GetPInstance(), mPosition);
    getLinkage().forceRemove(this);

    dEmitterBase_c *fx_thing = dJEffManager_c::spawnEffect(
        PARTICLE_RESOURCE_ID_MAPPING_211_, mPositionCopy2, nullptr, nullptr, nullptr, nullptr, 0, 0
    );
    if (fx_thing) {
        fx_thing->attachEmitterCallbackId(mSubtype != 0 ? dJEffManager_c::TsuboB : dJEffManager_c::TsuboA);
    }
    fx_thing = dJEffManager_c::spawnEffect(
        PARTICLE_RESOURCE_ID_MAPPING_116_, mPosition, nullptr, nullptr, nullptr, nullptr, 0, 0
    );
    if (fx_thing) {
        fx_thing->bindShpEmitter(mSubtype != 0 ? dJEffManager_c::TsuboB : dJEffManager_c::TsuboA, false);
    }

    startSound(SE_Tubo_BREAK);

    if (mSceneflag < 0xFF && !checkSceneflag()) {
        SceneflagManager::sInstance->setFlag(mRoomID, mSceneflag);
    }

    if (mbField_0x9F0) {
        mStateMgr.changeState(StateID_Rebirth);
    } else {
        deleteRequest();
    }
}

void dAcOtubo_c::calcRoll() {
    bool onLog = checkOnLog_0xE4E();
    if (mbMovingForward || onLog) {
        if (onLog) {
            mVelocity = mPosition - mOldPosition;
            mSpeed = EGG::Math<f32>::sqrt(mVelocity.x * mVelocity.x + mVelocity.z * mVelocity.z);
            mAngle.y = cM::atan2s(mVelocity.x, mVelocity.z);
        }
        f32 vel_mag = VEC3Len(mVelocity);
        f32 f1 = mAng(vel_mag * (field_0x9D4 + 200.f)).radian();
        f32 f2 = mAng(vel_mag * 182.0f * 0.2f).radian();
        f32 f0 = mAng(mAngle.y - mRotation.y).radian();

        mQuat_c q0, q1, q2, q3;
        q1.setAxisRotation(mVec3_c::Ey, f0);
        q0.setAxisRotation(mVec3_c::Ey, -f0);
        q3.setAxisRotation(mVec3_c::Ex, f1);
        q2.setAxisRotation(mVec3_c::Ey, f2);

        mQuat_0x98C = q1 * q3 * q2 * q0 * mQuat_0x98C;

        if (onLog) {
            mVelocity = mVec3_c::Zero;
            mSpeed = 0.f;
        }
    }

    mMtx_c mtx0, mtx1, mtx2;
    mtx0.fromQuat(mQuat_0x99C * mQuat_0x98C);
    mtx1.transS(0.f, 28.f, 0.f);
    mWorldMtx += mtx1;
    mWorldMtx += mtx0;
    mtx2.transS(0.f, -28.f, 0.f);
    mWorldMtx += mtx2;
}

void dAcOtubo_c::adjustRoll() {
    if (!mObjAcch.ChkGndHit()) {
        return;
    }
    if (cM::isZero(mSpeed)) {
        return;
    }

    s32 adj = mSpeed * 182;

    mVec3_c vel = mVelocity;
    vel.y = 0.f;

    vel.normalize();
    vel.rotY(0x4000);
    vel *= field_0x9D8 * field_0x9C8.sin();

    mAng old_9C8 = field_0x9C8;
    field_0x9C8 += adj;

    if (old_9C8.sin() * field_0x9C8.sin() < 0.f) {
        field_0x9D8 *= 0.75f;
    }

    mPosition += vel;
}

void dAcOtubo_c::fn_272_2670() {
    if (mbField_0x9EF && EventManager::isInEvent()) {
        field_0x9DC = 0.f;
        return;
    }

    if (mSph.ChkTgHit()) {
        if (mSph.ChkTgAtHitType(AT_TYPE_BELLOWS | AT_TYPE_WIND)) {
            field_0x9DC = 0.f;
            if (mTimer_0x9F4 == 0) {
                if (!mbField_0x9EB) {
                    field_0x9D0 = cM::rndFX(0.25f) + 1.f;
                }
                mbField_0x9EB = true;
                fn_272_2E60(mSph.GetTgAtHitDir());
                return;
            }
        }
    } else {
        if (mbField_0x9F1 && field_0x8F0.fn_80051780(mSph)) {
            mVelocity += field_0x9B8;
            mbField_0x9F1 = false;
        }
        static const s16 unk = {0}; // needed for rodata ordering
        if (!cM::isZero(field_0x9CA.mVal)) {
            mAngle.y = field_0x9CA.mVal; // HACK to force load again
            field_0x9CA.set(0);
        } else {
            mQuat_c q;
            q.set(1.f, 0.f, 0.f, 0.f);
            mQuat_0x99C.slerpTo(q, 0.2f, mQuat_0x99C);
        }
    }

    if (mObjAcch.ChkGndHit()) {
        if (mYOffset >= 0.f && !checkCarryType()) {
            field_0x9DC = 0.f;
        } else if (checkInvalidGround()) {
            mSpeed = 0.0f;
            mbMovingForward = 0;
        }
    }

    if (mSph.ChkCoHit()) {
        if (fn_272_38C0()) {
            fn_272_3020();
        }
    } else if (cM::isZero(field_0x9E0)) {
        field_0x9E0 = 0.1f;
    }

    // the ordering is weird here
    f32 groundH = mObjAcch.GetGroundH();
    f32 waterH = mObjAcch.GetWtrGroundH();
    bool noSound = mbSubmerged;
    mEff_0x91C.execute(waterH, groundH);

    if (checkSubmerged()) {
        mAcceleration = -0.8f;
        mMaxSpeed = -7.f;
        field_0x9DC = 0.f;
        cLib::addCalcPosXZ(&mVelocity, mVec3_c::Zero, 0.05f, 1.0f, 0.2f);
        mSpeed = EGG::Math<f32>::sqrt(mVelocity.x * mVelocity.x + mVelocity.z * mVelocity.z);
        if (!noSound) {
            startSound(SE_O_FALL_WATER_S);
        }
        mbSubmerged = true;
    } else {
        mAcceleration = -4.f;
        mMaxSpeed = -40.f;
        mbSubmerged = false;
        if (checkCarryType()) {
            mStateMgr.changeState(StateID_Grab);
        }
    }
}

void dAcOtubo_c::attemptDestroy() {
    if (mbField_0x9EF && mSph.ChkCoHit() && mSph.GetCoActor()->mProfileName == fProfile::B_MG) {
        destroy();
        return;
    }

    if (mObjAcch.ChkWallHit(nullptr) && mAng(mAcchCir.GetWallAngleY()).absDiff(mAngle.y) > mAng::d2s(70.f)) {
        attemptDestroyOnWall();
        mAngle.y = mAcchCir.GetWallAngleY();
        mSpeed *= 0.5f;
        return;
    }

    if (mSph.ChkTgHit() && ((mSph.ChkTgAtHitType(AT_TYPE_BEETLE) && mSph.ChkTgBit14()) ||
                            !mSph.ChkTgAtHitType(AT_TYPE_BEETLE | AT_TYPE_BELLOWS | AT_TYPE_WHIP | AT_TYPE_WIND))) {
        destroy();
        return;
    }

    if (mObjAcch.ChkGndHit() && mYOffset >= 0.f && !checkCarryType()) {
        if ((field_0x9DC < -100.f && !mbField_0x9EE) || fn_272_3660()) {
            destroy();
            return;
        }
        field_0x9DC = 0.f;
    }
    if (!mObjAcch.ChkGndHit() && mSph.ChkCoHit()) {
        if (getLinkage().mState != 1 && mSpeed > 0.f) {
            if (mSph.GetCoActor()->mTargetFiRelated == 4) {
                destroy();
                return;
            }
        }
    }
    if (mSph.ChkCoHit()) {
        dAcObjBase_c *obj = mSph.GetCoActor();
        if (obj->isPlayer() &&
            static_cast<dAcPy_c *>(obj)->getCurrentAction() == 0xC /* ROLL */) { // TODO (Player Action ID)
            destroy();
        }
    }

    if (mObjAcch.ChkRoofHit()) {
        destroy();
    } else if (!mbField_0x9EF && checkYOffsetField_0x100() && getParams_0x3000() != 1) {
        killNoItemDrop();
    } else if (fn_272_38A0()) {
        destroy();
    }
}

void dAcOtubo_c::attemptDestroyOnWall(const u32 &param0, const bool &param1) {
    if (param1 && mAng(mAcchCir.GetWallAngleY()).absDiff(mAngle.y) > mAng::d2s(70.f) && 15.f < mSpeed) {
        destroy();
    }
}

mVec3_c dAcOtubo_c::getCenter() const {
    mMtx_c m;
    m.ZXYrotS(mRotation.x, mRotation.y, mRotation.z);
    mVec3_c dir;
    MTXMultVecSR(m, mVec3_c::Ey, dir);

    return mPosition + dir * 28.f;
}

void dAcOtubo_c::fn_272_2E60(const mVec3_c &vel) {
    if (field_0x8F0.fn_80051780(mSph)) {
        if (mSph.ChkCoHit()) {
            mPosition += mStts.GetCcMove();
            field_0x8F0.fn_800051630();
        }
        mSpeed = 0.f;
        mVelocity = mVec3_c::Zero;
        mbField_0x9F1 = 1;
        field_0x9B8 = vel * 2.f;
    } else if (mSph.ChkTgAtHitType(AT_TYPE_BELLOWS)) {
        mVelocity.x += vel.x * 0.06f;
        mVelocity.y += vel.y * 0.2f;
        mVelocity.z += vel.z * 0.06f;
        field_0x9CA = cM::atan2s(vel.x, vel.z);
        mSpeed = field_0x9D0 * EGG::Math<f32>::sqrt(mVelocity.x * mVelocity.x + mVelocity.z * mVelocity.z);
    }

    mbMovingForward = 1;
}

void dAcOtubo_c::fn_272_3020() {
    static const s16 ang_inc = {0x2000}; // needed for rodata ordering

    mbField_0x9EB = 1;
    static mVec3_c sRot = mVec3_c::Ey;

    mQuat_c quat;
    mVec3_c rot = mVec3_c::Ey + mVec3_c(0.f, 0.f, 1.f) * field_0x9E0;

    rot.normalize();
    rot.rotY(field_0x9CC);
    field_0x9CC += mAng(ang_inc);

    quat.makeVectorRotation(sRot, rot);
    sLib::chase(&field_0x9E0, 0.f, 0.005f);
    mQuat_0x99C.slerpTo(quat, 0.5f, mQuat_0x99C);
}

void dAcOtubo_c::addPickupTarget() {
    if (cM::isZero(mSpeed)) {
        AttentionManager *ins = AttentionManager::GetInstance();
        ins->addPickUpTarget(*this, 120.f);
        ins->addUnk3Target(*this, 1, 500.f, -200.f, 200.f);
    }
}

void dAcOtubo_c::adjustAngle() {
    cM3dGPla pla;
    dBgS::GetInstance()->GetTriPla(mObjAcch.GetGnd(), &pla);

    // ??
    f32 mult = 1.f;
    mVelocity.x += pla.mNormal.x * mult;
    mVelocity.z += pla.mNormal.z * mult;

    mVelocity.y = -(mVelocity.x * pla.GetN().x + mVelocity.z * pla.GetN().z) / pla.GetN().y;
    mSpeed = nw4r::math::FSqrt(mVelocity.x * mVelocity.x + mVelocity.z * mVelocity.z);
    mSpeed = cM::minMaxLimit(mSpeed, -30.f, 30.f);

    if (mAng::fromVec(pla.GetN()).absDiff(mAngle.y) < mAng::d2s(90.f)) {
        sLib::addCalcAngle(mAngle.y.ref(), pla.GetAngleY(), 5, 0x71C, 0x100);
    } else {
        mAngle.y = mAng::fromVec(mVelocity);
    }
}

void dAcOtubo_c::adjustSpeed() {
    cM3dGPla pla;
    dBgS::GetInstance()->GetTriPla(mObjAcch.mGnd, &pla);
    f32 step = 0.4f;
    mAng gndAngle = mAng::fromRad(mVec3_c::Ey.angle(pla.GetN()));
    step *= gndAngle.cos();

    if (gndAngle < mAng::d2s(5) || mAng::fromVec(pla.GetN()).absDiff(mAngle.y) > mAng::d2s(90)) {
        f32 stepSize = mYOffset;
        sLib::chase(&mSpeed, 0.f, step + stepSize * -0.05f);
    } else {
        if (!mbMovingForward) {
            step *= -1.f;
        }
        mSpeed = cM::minMaxLimit(mSpeed + step, 0.f, 30.f);
    }

    mbMovingForward = mSpeed > 0.f;
}

bool dAcOtubo_c::checkSlope() {
    cM3dGPla pla;
    dBgS::GetInstance()->GetTriPla(mObjAcch.GetGnd(), &pla);
    mAng gndAngle = mAng::fromRad(mVec3_c::Ey.angle(pla.GetN()));

    return gndAngle.absDiff(0) > mAng::d2s(5);
}

bool dAcOtubo_c::fn_272_3660() {
    int poly_code = dBgS::GetInstance()->GetSpecialCode(mObjAcch.GetGnd());

    return field_0x9F6 == 2 && !mStateMgr.isState(StateID_Grab) && !checkSubmerged() && poly_code != POLY_ATTR_LAVA;
}

bool dAcOtubo_c::checkInvalidGround() {
    int poly_code = dBgS::GetInstance()->GetSpecialCode(mObjAcch.GetGnd());

    return poly_code == POLY_ATTR_LAVA || poly_code == POLY_ATTR_SAND_MED || poly_code == POLY_ATTR_SAND_DEEP_INSTANT ||
           poly_code == POLY_ATTR_SAND_DEEP_SLOW;
}

bool dAcOtubo_c::checkOnLava() {
    int poly_code = dBgS::GetInstance()->GetSpecialCode(mObjAcch.GetGnd());

    return poly_code == POLY_ATTR_LAVA;
}

bool dAcOtubo_c::checkCarryType() const {
    const dLinkage_c &linkage = getLinkage();
    return linkage.checkConnection(dLinkage_c::CONNECTION_1) || linkage.checkConnection(dLinkage_c::CONNECTION_7) ||
           linkage.checkConnection(dLinkage_c::CONNECTION_5);
}

bool dAcOtubo_c::checkSubmerged() {
    return mObjAcch.ChkWaterIn() && mPosition.y + 28.f < mObjAcch.mWtr.GetGroundH();
}

bool dAcOtubo_c::fn_272_38A0() {
    return field_0x9DC < -30000.f;
}

bool dAcOtubo_c::fn_272_38C0() {
    return mSph.ChkCoBit4();
}

bool dAcOtubo_c::checkOnLog_0xE4E() const {
    if (mObjAcch.ChkGndHit() && dBgS::GetInstance()->ChkMoveBG(mObjAcch.mGnd, true)) {
        const dAcObjBase_c *obj = dBgS::GetInstance()->GetActorPointer(mObjAcch.mGnd);
        if (obj && obj->mProfileName == fProfile::OBJ_LOG) {
            return static_cast<const dAcOlog_c *>(obj)->getField_0xE4E() >= 4;
        }
    }
    return false;
}

bool dAcOtubo_c::checkRollHitMaybe() {
    if (15.f < mSpeed) {
        return true;
    }
    if (mObjAcch.ChkWallHit(nullptr)) {
        mVec3_c a = mVec3_c::Ez * 60.f;
        mVec3_c start = getCenter();
        a.rotY(mAng::fromVec(mVelocity));
        mVec3_c end = start + a;
        if (dBgS_ObjLinChk::LineCross(&start, &end, this)) {
            return false;
        }
    }
    return true;
}

void dAcOtubo_c::playRollSound() {
    if (!(mSpeed > 0.f)) {
        return;
    }

    if (checkSubmerged()) {
        return;
    }

    holdSoundWithFloatParam(SE_Tubo_ROLL_LV, mSpeed);
}

void float_order() {
    const f32 arr[] = {-30000.f, -0.05f, 30.f};
}
