#include "d/a/b/d_a_b_lastboss.h"

#include "c/c_lib.h"
#include "c/c_math.h"
#include "common.h"
#include "d/a/d_a_player.h"
#include "d/a/obj/d_a_obj_base.h"
#include "d/col/c/c_cc_d.h"
#include "d/col/cc/d_cc_s.h"
#include "d/d_base.h"
#include "d/d_camera.h"
#include "d/d_cc.h"
#include "d/d_light_env.h"
#include "d/d_player_act.h"
#include "d/d_rumble.h"
#include "d/d_sc_game.h"
#include "d/d_stage.h"
#include "d/d_stage_mgr.h"
#include "d/flag/sceneflag_manager.h"
#include "d/flag/storyflag_manager.h"
#include "d/lyt/d_lyt_boss_caption.h"
#include "d/snd/d_snd_bgm_mgr.h"
#include "d/snd/d_snd_wzsound.h"
#include "d/t/d_t_sword_battle_game.h"
#include "f/f_base.h"
#include "f/f_manager.h"
#include "f/f_profile_name.h"
#include "m/m3d/m_fanm.h"
#include "m/m_angle.h"
#include "m/m_mtx.h"
#include "m/m_vec.h"
#include "nw4r/g3d/res/g3d_resfile.h"
#include "nw4r/g3d/res/g3d_resmdl.h"
#include "nw4r/math/math_arithmetic.h"
#include "rvl/MTX/mtxvec.h"
#include "s/s_Math.h"
#include "toBeSorted/d_emitter.h"
#include "toBeSorted/event.h"
#include "toBeSorted/event_manager.h"
#include "toBeSorted/minigame_mgr.h"

SPECIAL_ACTOR_PROFILE(B_LASTBOSS, dAcBlastboss_c, fProfile::B_LASTBOSS, 0xBA, 0, 0);

STATE_DEFINE(dAcBlastboss_c, Fight);
STATE_DEFINE(dAcBlastboss_c, Guard);
STATE_DEFINE(dAcBlastboss_c, GuardBreak);
STATE_DEFINE(dAcBlastboss_c, Attack);
STATE_DEFINE(dAcBlastboss_c, CounterAttack);
STATE_DEFINE(dAcBlastboss_c, PunchAttack);
STATE_DEFINE(dAcBlastboss_c, DashAttack);
STATE_DEFINE(dAcBlastboss_c, SmallAttack);
STATE_DEFINE(dAcBlastboss_c, ThunderAttack);
STATE_DEFINE(dAcBlastboss_c, Damage);
STATE_DEFINE(dAcBlastboss_c, SitDamage);
STATE_DEFINE(dAcBlastboss_c, Down);
STATE_DEFINE(dAcBlastboss_c, Stun);
STATE_DEFINE(dAcBlastboss_c, ThunderWait);

static dCcD_SrcCyl sSrcCyl1 = {
    /* mObjInf */
    {/* mObjAt */ {0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0},
     /* mObjTg */
     {~(AT_TYPE_BUGNET | AT_TYPE_GLITTERING_SPORES | AT_TYPE_BELLOWS | AT_TYPE_BEETLE | AT_TYPE_WIND | AT_TYPE_0x8000),
      0x4103,
      {0, 0x4, 0x407},
      0x0,
      0x0},
     /* mObjCo */ {0x0}},
    /* mCylInf */
    {40.f, 300.f}
};

static dCcD_SrcCyl sSrcCyl2 = {
    /* mObjInf */
    {/* mObjAt */ {0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0},
     /* mObjTg */
     {AT_TYPE_BELLOWS, 0x103, {0, 0x4, 0x407}, 0x0, 0x0},
     /* mObjCo */ {0x0}},
    /* mCylInf */
    {70.f, 30.f}
};

static dCcD_SrcCyl sSrcCyl3 = {
    /* mObjInf */
    {/* mObjAt */ {0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0},
     /* mObjTg */
     {0x0, 0x0, {0, 0x0, 0x407}, 0x0, 0x0},
     /* mObjCo */ {0xE5}},
    /* mCylInf */
    {100.f, 250.f}
};

static dCcD_SrcSph sSrcSph1 = {
    /* mObjInf */
    {/* mObjAt */ {AT_TYPE_DAMAGE, 0x2008D, {0, 0, 0}, 4, 0, 0, 0, 0, 0},
     /* mObjTg */
     {0, 0, {0, 0x0, 0x407}, 0, 0},
     /* mObjCo */ {0}},
    /* mSphInf */
    {100.0f}
};

static dCcD_SrcSph sSrcSph2 = {
    /* mObjInf */
    {/* mObjAt */ {AT_TYPE_DAMAGE, 0x22008D, {0, 0, 4}, 8, 0, 0, 0, 0, 0},
     /* mObjTg */
     {AT_TYPE_0x800000 | AT_TYPE_BUGNET, 0x103, {0, 0x19, 0x407}, 0, 0},
     /* mObjCo */ {0}},
    /* mSphInf */
    {100.0f}
};

static dCcD_SrcSph sSrcSph = {
    /* mObjInf */
    {/* mObjAt */ {0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0},
     /* mObjTg */
     {AT_TYPE_BELLOWS, 0x4103, {0, 0x0, 0x40F}, 0, 0},
     /* mObjCo */ {0}},
    /* mSphInf */
    {30.0f}
};

s32 useUnused() {
    static const f32 a1[] = {
        0.95f,
        1.05f,
        0.0f,
    };
    return (s32)a1;
}

bool dAcBlastboss_c::createHeap() {
    bool result;
    void *pData = getOarcResFile("BLastBoss");

    TRY_CREATE2(mMdl.create3(*this, pData, "LastBoss", "Wait", 0x334));
    mScnCallback.attach(mMdl.getModel());

    nw4r::g3d::ResFile res(pData);

    TRY_CREATE2(mAnmTexSrt.create(mMdl.getModel().getResMdl(), res.GetResAnmTexSrt("Hair"), &mAllocator, nullptr, 1));
    mMdl.getModel().setAnm(mAnmTexSrt);

    TRY_CREATE2(mAnmMatClr1.create(mMdl.getModel().getResMdl(), res.GetResAnmClr("Wait"), &mAllocator, nullptr, 2));
    mAnmMatClr1.setAnm(mMdl.getModel(), res.GetResAnmClr("Hair"), 1, m3d::PLAY_MODE_0);
    mMdl.getModel().setAnm(mAnmMatClr1);

    mMdl.getModel().setCallback(&mMdlCallback);
    mMdlCallback.mpOwner = this;

    TRY_CREATE2(mSwordMdl.create(
        &mAllocator, pData, "LastBossSword", 0x130, mVec3_c(0.0f, 0.0f, 0.0f), mVec3_c(0.0f, -230.0f, 0.0f), mStts,
        nullptr, 1, nullptr
    ));
    // TODO I'm not going to try and make sense of this for now
    mSwordMdl.fn_8006B660(4, 1, 0, 0x10000, 0, 0, 0, 0x400, 0xc, 60.0f);
    mSwordMdl.mProc.setColor1(mColor(0xFF, 0xFF, 0xFF, 0x14));
    mSwordMdl.mProc.setColor2(mColor(0xFF, 0xFF, 0xFF, 0x14));

    TRY_CREATE2(mThunderMdl.create(res.GetResMdl("FX_LBThunder"), &mAllocator, 0x120));

    TRY_CREATE2(
        mThunderAnmMatClr.create(mThunderMdl.getResMdl(), res.GetResAnmClr("FX_LBThunder"), &mAllocator, nullptr, 1)
    );
    mThunderMdl.setAnm(mThunderAnmMatClr);

    dBase_c::createBase(fProfile::LYT_BOSS_CAPTION, this, 0, OTHER);

    return result;
}

int dAcBlastboss_c::create() {
    mThunderWaitSceneFlag = getFromParams(0, 0xFF);
    if (MinigameManager::GetInstance()->checkInBossRush()) {
        mInBossRush = true;
    }
    CREATE_ALLOCATOR(dAcBlastboss_c);

    mStts.SetRank(10);
    mCcList.addCc(mCc1, sSrcCyl1);
    mCcList.addCc(mCc2, sSrcCyl2);
    mCcList.addCc(mCc3, sSrcCyl3);

    for (int i = 0; i < (int)ARRAY_LENGTH(field_0x1268); i++) {
        field_0x1268[i].field_0x000 = i;
        field_0x1268[i].field_0x002 = cM::rndF(65536.0f);
        mCcList.addCc(field_0x1268[i].mCc, sSrcSph);
        for (int j = 0; j < (int)ARRAY_LENGTH(field_0x1268[j].field_0x014); j++) {
            field_0x1268[i].field_0x014[j] = mPosition;
        }
        field_0x1268[i].field_0x008 = i * 15000;
    }

    // TODO loop of length 1 might not exist
    for (int i = 0; i < (int)ARRAY_LENGTH(field_0x2948); i++) {
        mCcList.addCc(field_0x2948[i].mCc, sSrcSph);
        for (int j = 0; j < (int)ARRAY_LENGTH(field_0x2948[j].field_0x008); j++) {
            field_0x2948[i].field_0x008[j] = mPosition;
        }
    }

    mCcList.SetStts(mStts);

    mCc4.Set(sSrcSph1);
    mCc4.SetStts(mStts);

    mLightningCc.Set(sSrcSph2);
    mLightningCc.mTg.Set_0x4C(-1);
    mLightningCc.SetStts(mStts);

    mAcceleration = -5.0f;
    mMaxSpeed = -80.0f;

    mBoundingBox.Set(mVec3_c(-200.0f, -300.0f, -200.0f), mVec3_c(200.0f, 300.0f, 200.0f));
    mStateMgr.changeState(StateID_Fight);

    field_0x1132 = true;
    mCounter = cM::rndF(65536.0f);

    mHealth = 400;
    mPositionCopy3 = mPosition;

    mLastAttackDir = mSecondLastAttackDirection = -1;

    mWaterEmitter.init(this);
    mEmitter2.init(this);
    // mEmitter3.init(this); // TODO why not this one...
    mEmitter4.init(this);
    mEmitter5.init(this);
    mEmitter6.init(this);
    mEmitter7.init(this);

    mLightInfo.SetPosition(mPosition);
    mLightInfo.mClr.Set(100, 150, 255, 255);
    dLightEnv_c::GetPInstance()->plight_set(&mLightInfo);
    setLightningTimerMaybe(100);

    if (!mInBossRush) {
        mLightingStrikeState = LIGHTNING_STRIKE_TRIGGER_START;
        setActorProperty(AC_PROP_0x4);
    }

    return SUCCEEDED;
}

// why are these up here?
static const s16 moreAngs[] = {0xA000, 0xC000, 0xE000, 0xF000, 0x0000, 0x1000, 0x2000, 0x4000};

int dAcBlastboss_c::doDelete() {
    dLightEnv_c::GetPInstance()->plight_cut(&mLightInfo);
    return SUCCEEDED;
}

int dAcBlastboss_c::actorExecute() {
    if (mInBossRush && mpSwordBattleGame == nullptr) {
        mpSwordBattleGame = static_cast<dTgSwordBattleGame_c *>(
            fManager_c::searchBaseByProfName(fProfile::TAG_SWORD_BATTLE_GAME, mpSwordBattleGame)
        );
    }
    dAcPy_c *link = dAcPy_c::GetLinkM();

    mCounter++;

    field_0x1133 = 0;
    field_0x1131 = 0;
    mIsDown = false;
    field_0x114E = 0;

    field_0x11A4 = 0.0f;
    mLightInfo.mScale = 0.0f;
    field_0x11B0 = 0.7f;

    for (int i = 0; i < TIMER_MAX; i++) {
        if (mTimers[i] != 0) {
            mTimers[i]--;
        }
    }

    if (mMdlCallback.field_0x04.mVal != 0) {
        mMdlCallback.field_0x04.mVal--;
    }

    if (field_0x117C != 0) {
        field_0x117C--;
    }

    if (field_0x113D != 0) {
        field_0x113D--;
    }

    if (field_0x113F != 0) {
        field_0x113F--;
    }

    if (field_0x113E != 0) {
        field_0x113E--;
    }

    mVec3_c vEff;
    mVec3_c linkPos = link->getPosition();
    mYAngleToLink = cLib::targetAngleY(mPosition, linkPos);
    mXZDistanceToLink = link->getPosition().absXZTo(mPosition);

    mYRotationRelativeToLink = mYAngleToLink - mRotation.y;
    mSwordMdl.disable();

    mCc4.ClrAtSet();
    mLightningCc.ClrAtSet();

    checkDamage();
    if (mpSwordBattleGame == nullptr || mpSwordBattleGame->checkFightStarted() == true) {
        mStateMgr.executeState();
    }

    mRotation.x = mAngle.x;
    mRotation.z = mAngle.z;
    sLib::addCalcAngle(mRotation.y.ref(), mAngle.y, 2, 0x800);

    mMdl.play();
    if (!mIsDead) {
        mAnmMatClr1.play();
    }

    calcVelocity();
    mPosition += mVelocity;
    mPosition += mStts.GetCcMove();
    mPosition.x += mAttackKnockbackX;
    mPosition.z += mAttackKnockbackZ;

    mAttackKnockbackX *= 0.6f;
    mAttackKnockbackZ *= 0.6f;

    f32 f5 = 0.0f;
    if (mRotation.x != 0) {
        f5 += field_0x11C0 * mRotation.x.sin();
    }

    field_0x1140 = false;
    if (mPosition.y <= f5) {
        mPosition.y = f5;
        field_0x11BC = mVelocity.y;
        mVelocity.y = 0.0f;
        field_0x1140 = true;
    }

    updateMainNodeTransforms();

    mWorldMtx.transS(mPosition);
    mWorldMtx.ZXYrotM(mRotation);
    mScale.set(0.95f, 0.95f, 0.95f);
    mMdl.getModel().setScale(mScale);
    mMdl.getModel().setLocalMtx(mWorldMtx);
    if (!mIsDown) {
        mAnmTexSrt.play();
    }

    mMdl.getModel().calc(false);

    nw4r::g3d::ResMdl resMdl = mMdl.getModel().getResMdl();
    // TODO: These very deliberately placed matrices might work better with
    // inlines but I haven't found an inline that places rotMtx where it needs
    // to be.
    mMtx_c nodeMtx;
    mMtx_c rotMtx;
    mMtx_c scaleMtx;
    mMdl.getModel().getNodeWorldMtx(resMdl.GetResNode("loc_sword01").GetID(), nodeMtx);
    scaleMtx.scaleS(1.05f, 1.05f, 1.05f);
    MTXConcat(nodeMtx, scaleMtx, nodeMtx);

    mSwordMdl.calc(nodeMtx, mRotation.y, false);

    vEff.set(0.0f, -120.0f, 0.0f);
    if (mGuardDirection == GUARD_BT) {
        vEff.y = -80.0f;
        if (mXZDistanceToLink > 300.0f) {
            vEff.y -= (mXZDistanceToLink - 300.0f);
            if (vEff.y < -280.0f) {
                vEff.y = -280.0f;
            }
        }
    }
    MTXMultVec(nodeMtx, vEff, mSwordPosition);

    if (mIsSwordEmpowered) {
        mEmitter4.holdEffect(PARTICLE_RESOURCE_ID_MAPPING_619_, nodeMtx, nullptr, nullptr);
        mLightInfo.mPos = mSwordPosition;
        if (dScGame_c::getUpdateFrameCount() & 2) {
            mLightInfo.SetScale(300.0f);
        } else {
            mLightInfo.SetScale(200.0f);
        }
        holdSound(SE_BLasBos_SwordEnpowered);
    }

    if (mStunState != 0) {
        if (mStunState == 1) {
            mEmitter7.holdEffect(PARTICLE_RESOURCE_ID_MAPPING_852_, nodeMtx, nullptr, nullptr);
            mMdl.getModel().getNodeWorldMtx(resMdl.GetResNode("backbone2").GetID(), nodeMtx);
            mEmitter6.holdEffect(PARTICLE_RESOURCE_ID_MAPPING_853_, nodeMtx, nullptr, nullptr);
            holdSound(SE_BLasBos_UnderElecShock);
        } else if (mStunState == 2) {
            mMdl.getModel().getNodeWorldMtx(resMdl.GetResNode("backbone2").GetID(), nodeMtx);
            dJEffManager_c::spawnEffect(PARTICLE_RESOURCE_ID_MAPPING_851_, nodeMtx, nullptr, nullptr, 0, 0);
            mMtx_c transMtx;
            transMtx.transS(0.0f, -100000.0f, 0.0f);
            MTXConcat(nodeMtx, transMtx, nodeMtx);
            mEmitter7.holdEffect(PARTICLE_RESOURCE_ID_MAPPING_852_, nodeMtx, nullptr, nullptr);
            mEmitter6.holdEffect(PARTICLE_RESOURCE_ID_MAPPING_853_, nodeMtx, nullptr, nullptr);
            holdSound(SE_BLasBos_KillOffElecShock);
        }
        mLightInfo.mPos = mSwordPosition;
        if (dScGame_c::getUpdateFrameCount() & 2) {
            mLightInfo.SetScale(400.0f);
        } else {
            mLightInfo.SetScale(300.0f);
        }
        mStunState = 0;
    }

    mMdl.getModel().getNodeWorldMtx(B_LAST_BOSS_NODE_thumbL2, nodeMtx);
    nodeMtx.getTranslation(mThumbL2Translation);
    mMdl.getModel().getNodeWorldMtx(B_LAST_BOSS_NODE_backbone1, nodeMtx);
    nodeMtx.getTranslation(mBackbone1Translation);
    mMdl.getModel().getNodeWorldMtx(B_LAST_BOSS_NODE_chest, nodeMtx);
    nodeMtx.getTranslation(mChestTranslation);
    mMdl.getModel().getNodeWorldMtx(B_LAST_BOSS_NODE_toeL, nodeMtx);
    nodeMtx.getTranslation(mToeTranslation[0]);
    mMdl.getModel().getNodeWorldMtx(B_LAST_BOSS_NODE_toeR, nodeMtx);
    nodeMtx.getTranslation(mToeTranslation[1]);

    mMdl.getModel().getNodeWorldMtx(B_LAST_BOSS_NODE_head, nodeMtx);
    mVec3_c tmp1(0.0f, 0.0f, 0.0f);
    MTXMultVec(nodeMtx, tmp1, mPositionCopy2);
    mVec3_c v3;
    v3.x = mPositionCopy2.x;
    v3.y = mPositionCopy2.y + 50.0f;
    v3.z = mPositionCopy2.z;
    mPositionCopy3 = v3;

    if (field_0x1132) {
        fn_80030c20(0, 1500.0f, 50.0f, -200.0f, 200.0f);
    }

    nodeMtx.YrotS(mRotation.y);

    mVec3_c tmpCc;
    if (link->isAttacking() && mStateMgr.isState(StateID_Guard)) {
        if (mGuardDirection == GUARD_BT) {
            tmpCc.x = 0.0f;
            tmpCc.y = 0.0f;
            tmpCc.z = 100.0f;
            if (mXZDistanceToLink > 300.0f) {
                tmpCc.z = (mXZDistanceToLink - 300.0f) + 100.0f;
                if (tmpCc.z > 300) {
                    tmpCc.z = 300.0f;
                }
            }
        } else {
            tmpCc.x = 0.0f;
            tmpCc.y = 0.0f;
            tmpCc.z = 130.0f;
        }
        mCc1.SetR(20.0f);
    } else if (mStateMgr.isState(StateID_ThunderWait)) {
        tmpCc.set(0.0f, 0.0f, 0.0f);
        mCc1.SetR(150.0f);
    } else {
        tmpCc.set(0.0f, 0.0f, 80.0f);
        mCc1.SetR(55.0f);
    }

    MTXMultVec(nodeMtx, tmpCc, tmpCc);
    tmpCc += mPosition;

    if (link->isAttacking() && mXZDistanceToLink < 400.0f) {
        tmpCc.y += 100.0f;
        mCc1.SetH(100.0f);
    } else {
        mCc1.SetH(250.0f);
    }
    mCc1.SetC(tmpCc);

    tmpCc.x = mChestTranslation.x;
    tmpCc.y = mChestTranslation.y;
    tmpCc.z = mChestTranslation.z;
    tmpCc.y = mPosition.y;

    if (!mIsDown) {
        mCc3.SetR(100.0f);
    } else {
        mCc3.SetR(200.0f);
    }
    mCc3.SetC(tmpCc);

    tmpCc.x = mPosition.x;
    tmpCc.y = mPosition.y + 150.0f;
    tmpCc.z = mPosition.z;

    mCc2.SetC(tmpCc);

    field_0x1250[0] = mToeTranslation[0] + (mToeTranslation[1] - mToeTranslation[0]) * 0.5f;
    // UB: this should use string comparison functions for robustness, but
    // probably works fine in MWCC
    if ((mpCurrentAnm == "CatchThunderEnd" || mpCurrentAnm == "Stan") && mMdl.getAnm().getFrame() <= 3.0f) {
        field_0x11A4 = 25.0f;
    }

    updateSkirtHairTransforms();

    if (!link->isAttacking()) {
        mInvulnerabilityTimerMaybe = 1;
    }

    if (mInvulnerabilityTimerMaybe == 0) {
        mCc1.SetTg_0x4B(1);
        // TODO
        mCc1.mTg.field_0x6C = mSwordPosition;
    } else {
        mInvulnerabilityTimerMaybe--;
        tmpCc.set(0.0f, -10000.0f, 0.0);
        mCc1.SetTg_0x4B(1);
        // TODO
        mCc1.mTg.field_0x6C = tmpCc;
    }

    mCc1.ClrTg_0x10000();

    if (field_0x1142 != 0) {
        field_0x1142--;
        mCcList.ClrTg();
    } else {
        mCcList.TgSet();
    }
    mCcList.registerColliders();

    if (field_0x1139 != 0) {
        mCc4.OnAtSet();
        if (field_0x1139 == 1) {
            mCc4.setCenter(mThumbL2Translation);
            field_0x1139 = 2;
        } else {
            mCc4.moveCenter(mThumbL2Translation);
        }
        dCcS::GetInstance()->Set(&mCc4);
    }

    static mAng3_c sEffRot(0, 0, 0);
    static mVec3_c sEffScale(1.5f, 1.5f, 1.5f);
    mVec3_c effPos(mPosition.x, 10.0f, mPosition.z);
    // Basically the same code as in dWaterEffect_c::execute
    mWaterEmitter.holdEffect(PARTICLE_RESOURCE_ID_MAPPING_127_, effPos, &sEffRot, &sEffScale, nullptr, nullptr);
    f32 rate = nw4r::math::FAbs(mSpeed) * 0.02f;
    rate = rate > 0.95f ? 0.95f : rate;
    mWaterEmitter.setRate(rate + 0.05f);

    bool footSplash[2] = {false, false};
    // UB: this should use string comparison functions for robustness, but
    // probably works fine in MWCC
    if (mpCurrentAnm == "Walk" || mpCurrentAnm == "WalkBt") {
        if (mMdl.getAnm().checkFrame(22.0f)) {
            footSplash[0] = true;
        } else if (mMdl.getAnm().checkFrame(48.0f)) {
            footSplash[1] = true;
        }
    } else if (mpCurrentAnm == "TurnR") {
        if (mMdl.getAnm().checkFrame(31.0f)) {
            footSplash[0] = true;
        } else if (mMdl.getAnm().checkFrame(19.0f)) {
            footSplash[1] = true;
        }
    } else if (mpCurrentAnm == "TurnL") {
        if (mMdl.getAnm().checkFrame(31.0f)) {
            footSplash[1] = true;
        } else if (mMdl.getAnm().checkFrame(19.0f)) {
            footSplash[0] = true;
        }
    } else if (mpCurrentAnm == "AttackBreak") {
        if (mMdl.getAnm().checkFrame(16.0f)) {
            footSplash[0] = true;
        }
    } else if (mpCurrentAnm == "AttackL") {
        if (mMdl.getAnm().checkFrame(26.0f)) {
            footSplash[1] = true;
        }
    } else if (mpCurrentAnm == "AttackR") {
        if (mMdl.getAnm().checkFrame(24.0f)) {
            footSplash[1] = true;
        }
    } else if (mpCurrentAnm == "AttackU") {
        if (mMdl.getAnm().checkFrame(10.0f)) {
            footSplash[1] = true;
        } else if (mMdl.getAnm().checkFrame(24.0f)) {
            footSplash[0] = true;
        }
    } else if (mpCurrentAnm == "Counter") {
        if (mMdl.getAnm().checkFrame(21.0f)) {
            footSplash[0] = true;
        } else if (mMdl.getAnm().checkFrame(45.0f)) {
            footSplash[1] = true;
        }
    } else if (mpCurrentAnm == "AttackJumpEnd") {
        if (mMdl.getAnm().checkFrame(6.0f)) {
            footSplash[1] = true;
        }
    } else if (mpCurrentAnm == "DownGetUp") {
        if (mMdl.getAnm().checkFrame(137.0f)) {
            footSplash[1] = true;
        }
    } else if (mpCurrentAnm == "DownEscapeL") {
        if (mMdl.getAnm().checkFrame(15.0f)) {
            footSplash[0] = true;
        } else if (mMdl.getAnm().checkFrame(17.0f)) {
            footSplash[1] = true;
        }
    } else if (mpCurrentAnm == "DownEscapeR") {
        if (mMdl.getAnm().checkFrame(15.0f)) {
            footSplash[1] = true;
        } else if (mMdl.getAnm().checkFrame(17.0f)) {
            footSplash[0] = true;
        }
    } else if (mpCurrentAnm == "ThunderDemo") {
        if (mMdl.getAnm().checkFrame(18.0f)) {
            footSplash[1] = true;
        }
    } else if (mpCurrentAnm == "AttackThunder") {
        if (mMdl.getAnm().checkFrame(25.0f)) {
            footSplash[0] = true;
        }
    }

    // Spawn splash effects
    for (int i = 0; i < 2; i++) {
        if (footSplash[i]) {
            mToeTranslation[i].y = 10.0f;
            dJEffManager_c::spawnEffect(
                PARTICLE_RESOURCE_ID_MAPPING_855_, mToeTranslation[i], nullptr, nullptr, nullptr, nullptr, 0, 0
            );
        }
    }

    if (mIsPhaseTwo != 0) {
        if (!link->checkSwordAndMoreStates(0x400000) && (mIsPhaseTwo < 0 || !mIsSwordEmpowered)) {
            if (mLightningTimerMaybe == 0) {
                if (dLightEnv_c::GetPInstance()->getField_0x38DC() == 0) {
                    dLightEnv_c::GetPInstance()->setField_0x38DC(1);
                    setLightningTimerMaybe(100);
                }
            } else if (!mIsSwordEmpowered || (dScGame_c::getUpdateFrameCount() & 0b11) == 0) {
                mLightningTimerMaybe--;
            }
        } else {
            setLightningTimerMaybe(100);
        }
        sLib::addCalcScaledDiff(&field_0x11CC, 1.0f, 1.0f, 0.01f);
    }

    // TODO: inline doesn't work :(
    dLightEnv_c::GetInstance().GetOverrideSpf().mIdxStart = 1;
    dLightEnv_c::GetInstance().GetOverrideSpf().mIdxEnd = 2;
    dLightEnv_c::GetInstance().GetOverrideSpf().mRatio = field_0x11CC;

    if (mIsThunderBeamAttackActive && mThunderBeamTimer != 0) {
        mThunderBeamTimer--;
        mThunderBeamPosition += mThunderBeamVelocity;
        nodeMtx.transS(mThunderBeamPosition);
        nodeMtx.ZXYrotM(mThunderBeamRotation);

        if (!mThunderBeamReflected) {
            if (mLightningCc.ChkAtHit() || mLightningCc.ChkTgHit()) {
                dJEffManager_c::spawnEffect(PARTICLE_RESOURCE_ID_MAPPING_850_, nodeMtx, nullptr, nullptr, 0, 0);
                if (mLightningCc.ChkTgHit() && mLightningCc.ChkTgAtHitType(AT_TYPE_BUGNET)) {
                    // Reflect thunder beam attack with Bug Net
                    getSoundSource()->startSoundAtPosition2(SE_BLasBos_EmitThunderBeam, mThunderBeamPosition);
                    mThunderBeamTimer = 0x3C;
                    mThunderBeamVelocity *= -1.0f;
                    dRumble_c::start(dRumble_c::sRumblePreset2, dRumble_c::FLAG_SLOT0);
                    dStageMgr_c::GetInstance()->fn_80199B60(3);
                    mThunderBeamReflected = true;
                } else {
                    mIsThunderBeamAttackActive = false;
                    mMtx_c mtx;
                    mtx.transS(0.0f, -100000.0f, 0.0f);
                    MTXConcat(nodeMtx, mtx, nodeMtx);
                    getSoundSource()->startSoundAtPosition2(SE_BLasBos_BeamCrossCrush, mThunderBeamPosition);
                }
                mLightningCc.SetTgRpm(0);
                mLightningCc.ClrTgActorInfo();
                mLightningCc.SubtractTgEffCounter();
                mLightningCc.SetAtRpm(0);
                mLightningCc.ClrAtActorInfo();
                mLightningCc.SubtractAtEffCounter();
            }
        } else {
            if ((mThunderBeamPosition - mPosition).absXZ() < 150.0f) {
                // Boss hit by bug net reflected attack
                dJEffManager_c::spawnEffect(PARTICLE_RESOURCE_ID_MAPPING_850_, nodeMtx, nullptr, nullptr, 0, 0);
                mIsThunderBeamAttackActive = false;
                mMtx_c mtx;
                mtx.transS(0.0f, -100000.0f, 0.0f);
                MTXConcat(nodeMtx, mtx, nodeMtx);
                getSoundSource()->startSoundAtPosition2(SE_BLasBos_BeamCrossCrush, mThunderBeamPosition);
                v3.set(0.0f, 0.0f, 20.0f);
                rotMtx.YrotS(link->mRotation.y);
                MTXMultVec(rotMtx, v3, v3);
                mAttackKnockbackX = v3.x;
                mAttackKnockbackZ = v3.z;
                mStateMgr.changeState(StateID_Stun);
            }
        }
        mLightInfo.mPos = mThunderBeamPosition;
        if ((dScGame_c::getUpdateFrameCount() & 2) != 0) {
            mLightInfo.SetScale(300.0f);
        } else {
            mLightInfo.SetScale(200.0f);
        }

        if (mThunderBeamTimer <= 10) {
            // TODO: lightning timer?
            if (mThunderBeamTimer == 10) {
                mEmitter5.setFading(10);
            }
            if (mThunderBeamTimer == 0) {
                mIsThunderBeamAttackActive = false;
            }
            mLightningCc.ClrAtSet();
        } else {
            if (!mThunderBeamReflected) {
                mLightningCc.OnAtSet();
                mLightningCc.SetR(100.0f);
                mLightningCc.moveCenter(mThunderBeamPosition);
                dCcS::GetInstance()->Set(&mLightningCc);
            }
            getSoundSource()->holdSoundAtPosition(SE_BLasBos_ThunderBeamFly, mThunderBeamPosition);
        }
        mEmitter5.holdEffect(PARTICLE_RESOURCE_ID_MAPPING_618_, nodeMtx, nullptr, nullptr);
    }

    if (mLightingFallSwordTimer != 0) {
        mLightingFallSwordTimer--;
        mLightningCc.OnAtSet();
        mLightningCc.SetC(mPosition);
        mLightningCc.SetR(300.0f - mLightingFallSwordTimer * 30.0f);
        mLightningCc.SetAtVec(link->getPosition() - mPosition);
        dCcS::GetInstance()->Set(&mLightningCc);
        if (mLightingFallSwordTimer == 8) {
            vEff.x = mPosition.x;
            vEff.y = 10.0f;
            vEff.z = mPosition.z;
            dJEffManager_c::spawnEffect(
                PARTICLE_RESOURCE_ID_MAPPING_842_, vEff, nullptr, nullptr, nullptr, nullptr, 0, 0
            );
        }
    }
    executeLightningStrike();

    SceneflagManager *mgr = SceneflagManager::sInstance;
    if (mgr != nullptr) {
        if (mStateMgr.isState(StateID_ThunderWait)) {
            mgr->setFlag(getRoomId(), mThunderWaitSceneFlag);
        } else {
            mgr->unsetFlag(getRoomId(), mThunderWaitSceneFlag);
        }
    }

    if (mIsPhaseTwo != 0) {
        // "Target lock: Demise" (Phase 2)
        mTargetFiTextID = 0x38;
    } else {
        // "Target lock: Demise" (Phase 1)
        mTargetFiTextID = 0x37;
    }

    return SUCCEEDED;
}

struct dCcD_Check : public cCcD_Obj {
    bool ChkAtHit();
    bool ChkTgHit();
};

bool dCcD_Check::ChkAtHit() {
    if (cCcD_Obj::ChkAtHit()) {
        return true;
    }
    return false;
}

bool dCcD_Check::ChkTgHit() {
    if (cCcD_Obj::ChkTgHit()) {
        return true;
    }
    return false;
}

void dAcBlastboss_c::callback_c::timingB(u32 nodeId, nw4r::g3d::WorldMtxManip *manip, nw4r::g3d::ResMdl mdl) {
    u32 baseNodeIdSkirt = 0;
    u32 baseNodeIdHair = 0;
    int r27;
    int param_4;

    if (nodeId >= B_LAST_BOSS_NODE_skirtA1 && nodeId < B_LAST_BOSS_NODE_skirtAU1) {
        baseNodeIdSkirt = B_LAST_BOSS_NODE_skirtA1;
        param_4 = 0;
    } else if (nodeId >= B_LAST_BOSS_NODE_skirtB1 && nodeId < B_LAST_BOSS_NODE_skirtBU1) {
        baseNodeIdSkirt = B_LAST_BOSS_NODE_skirtB1;
        param_4 = 1;
    } else if (nodeId >= B_LAST_BOSS_NODE_skirtC1 && nodeId < B_LAST_BOSS_NODE_skirtCU1) {
        baseNodeIdSkirt = B_LAST_BOSS_NODE_skirtC1;
        param_4 = 2;
    } else if (nodeId >= B_LAST_BOSS_NODE_skirtD1 && nodeId < B_LAST_BOSS_NODE_skirtDU1) {
        baseNodeIdSkirt = B_LAST_BOSS_NODE_skirtD1;
        param_4 = 3;
    } else if (nodeId >= B_LAST_BOSS_NODE_skirtE1 && nodeId < B_LAST_BOSS_NODE_skirtEU1) {
        baseNodeIdSkirt = B_LAST_BOSS_NODE_skirtE1;
        param_4 = 4;
    } else if (nodeId >= B_LAST_BOSS_NODE_skirtF1 && nodeId < B_LAST_BOSS_NODE_skirtFU1) {
        baseNodeIdSkirt = B_LAST_BOSS_NODE_skirtF1;
        param_4 = 5;
    } else if (nodeId >= B_LAST_BOSS_NODE_skirtG1 && nodeId < B_LAST_BOSS_NODE_skirtGU1) {
        baseNodeIdSkirt = B_LAST_BOSS_NODE_skirtG1;
        param_4 = 6;
    } else if (nodeId >= B_LAST_BOSS_NODE_skirtH1 && nodeId < B_LAST_BOSS_NODE_skirtHU1) {
        baseNodeIdSkirt = B_LAST_BOSS_NODE_skirtH1;
        param_4 = 7;
    }

    if (baseNodeIdSkirt != 0) {
        u32 nodeOffset = nodeId - baseNodeIdSkirt;

        mMtx_c mtx;
        mtx.transS(mpOwner->field_0x1268[param_4].field_0x014[nodeOffset]);
        mtx.XrotM(mpOwner->field_0x1268[param_4].field_0x08C[nodeOffset].x);
        mtx.YrotM(mpOwner->field_0x1268[param_4].field_0x08C[nodeOffset].y);
        mtx.ZrotM(-mpOwner->field_0x1268[param_4].field_0x08C[nodeOffset].z);
        mtx.scaleM(0.95f, 0.95f, 1.0f);
        mtx.YrotM(-0x4000);
        manip->SetMtx(mtx);
    }

    if (nodeId >= B_LAST_BOSS_NODE_hairA1 && nodeId < B_LAST_BOSS_NODE_hairBL1) {
        baseNodeIdHair = B_LAST_BOSS_NODE_hairA1;
        r27 = 0;
    }

    if (baseNodeIdHair != 0) {
        u32 nodeOffset = nodeId - baseNodeIdHair;
        mMtx_c mtx;
        mtx.transS(mpOwner->field_0x2948[r27].field_0x008[nodeOffset]);
        mtx.YrotM(mpOwner->field_0x2948[r27].field_0x044[nodeOffset].y);
        mtx.XrotM(mpOwner->field_0x2948[r27].field_0x044[nodeOffset].x);
        mtx.ZrotM(-mpOwner->field_0x2948[r27].field_0x044[nodeOffset].z);
        mtx.scaleM(0.95f, 0.95f, 1.0f);
        mtx.YrotM(-0x4000);
        manip->SetMtx(mtx);
    }

    if (nodeId == B_LAST_BOSS_NODE_center) {
        mMtx_c mtx;
        manip->GetMtx(mtx);
        mtx.XrotM(-field_0x0E.mVal / 2);
        mtx.ZrotM(-field_0x10.mVal / 2);
        manip->SetMtx(mtx);
    } else if (nodeId == B_LAST_BOSS_NODE_backbone1 || nodeId == B_LAST_BOSS_NODE_backbone2) {
        mMtx_c mtx;
        manip->GetMtx(mtx);
        mtx.YrotM(field_0x0E);
        mtx.XrotM(field_0x2E - field_0x0E);
        mtx.ZrotM(-field_0x10);
        manip->SetMtx(mtx);
    } else if (nodeId == B_LAST_BOSS_NODE_head) {
        mMtx_c mtx;
        manip->GetMtx(mtx);
        mtx.YrotM(field_0x0E * 2 + field_0x08 + field_0x06);
        mtx.ZrotM(field_0x0A - field_0x10 * 2);
        mtx.XrotM(field_0x0C);
        manip->SetMtx(mtx);
    } else if (nodeId == B_LAST_BOSS_NODE_chin) {
        mMtx_c mtx;
        manip->GetMtx(mtx);
        mtx.ZrotM(field_0x12);
        manip->SetMtx(mtx);
    } else if (nodeId >= B_LAST_BOSS_NODE_hairBL1 && nodeId <= B_LAST_BOSS_NODE_hairC3) {
        mMtx_c mtx;
        manip->GetMtx(mtx);

        if (nodeId >= B_LAST_BOSS_NODE_hairC1) {
            mtx.XrotM(field_0x1C[nodeId - B_LAST_BOSS_NODE_hairBL1]);
            mtx.ZrotM(field_0x1C[nodeId - B_LAST_BOSS_NODE_hairBL1]);
        } else {
            mtx.YrotM(field_0x1C[nodeId - B_LAST_BOSS_NODE_hairBL1]);
            mtx.XrotM(field_0x1C[nodeId - B_LAST_BOSS_NODE_hairBL1]);
        }

        manip->SetMtx(mtx);
    } else if (nodeId == B_LAST_BOSS_NODE_armR1) {
        int blah = field_0x04 * mAng(field_0x04 * 0x4300).sin() * 80.0f;

        mMtx_c mtx;
        manip->GetMtx(mtx);

        mtx.XrotM(blah + (int)(300.0f * field_0x30));
        mtx.ZrotM(blah + (int)(200.0f * field_0x30));

        manip->SetMtx(mtx);
    } else if (nodeId == B_LAST_BOSS_NODE_armR2) {
        mMtx_c mtx;
        manip->GetMtx(mtx);
        mtx.ZrotM(field_0x30 * 200.0f);
        manip->SetMtx(mtx);
    } else if (nodeId == B_LAST_BOSS_NODE_wristR) {
        // unused
        int blah = field_0x04 * (f32)mAng(field_0x04 * 0x4300).sin();

        mMtx_c mtx;
        manip->GetMtx(mtx);

        int i2 = 0;
        if (field_0x30 > 0.0f) {
            i2 = field_0x30 * -200.0f;
        }

        mtx.XrotM(i2 + field_0x18);
        mtx.ZrotM(i2 + field_0x1A);

        manip->SetMtx(mtx);
    } else if (nodeId == B_LAST_BOSS_NODE_legR1 || nodeId == B_LAST_BOSS_NODE_legR2) {
        mMtx_c mtx;
        manip->GetMtx(mtx);
        mtx.XrotM(field_0x14);
        if (nodeId == B_LAST_BOSS_NODE_legR2) {
            mtx.XrotM(field_0x14);
        }
        manip->SetMtx(mtx);
    } else if (nodeId == B_LAST_BOSS_NODE_legL1 || nodeId == B_LAST_BOSS_NODE_legL2) {
        mMtx_c mtx;
        manip->GetMtx(mtx);
        mtx.XrotM(field_0x16);
        if (nodeId == B_LAST_BOSS_NODE_legL2) {
            mtx.XrotM(field_0x16);
        }
        manip->SetMtx(mtx);
    }
}

void dAcBlastboss_c::initializeState_Fight() {
    setAnm("WaitBt", 20.0f);
    mAnmRate = 1.0f;
    mSubState = SUB_STATE_0;
    field_0x118A = 0;
    if (mIsPhaseTwo == 0) {
        mTimers[TIMER_2] = cM::rndF(50.0f) + 50.0f;
    } else {
        mTimers[TIMER_2] = cM::rndF(20.0f) + 10.0f;
    }
    mGuardDirection = GUARD_BT;
}
void dAcBlastboss_c::executeState_Fight() {
    dAcPy_c *link = dAcPy_c::GetLinkM();
    field_0x1131 = 1;
    f32 targetSpeed = 0.0f;
    f32 targetAnmRate = 1.5f;
    f32 puVar2 = 0.0f;

    bool b = false;
    s16 diff = mAngle.y - mYAngleToLink;

    if (mLightingStrikeState >= LIGHTNING_STRIKE_TRIGGER_START && mLightingStrikeState <= LIGHTNING_STRIKE_START_LAST) {
        puVar2 = -100.0f;
        mTimers[TIMER_2] = cM::rndF(50.0f) + 100.0f;
    }

    switch (mSubState) {
        case SUB_STATE_0: {
            b = true;
            field_0x118C = 0;
            targetAnmRate = 1.0f;
            field_0x117E = 0;
            if (mXZDistanceToLink > puVar2 + 450.0f) {
                mSubState = SUB_STATE_2;
                break;
            } else if (mXZDistanceToLink < puVar2 + 250.0f + 100.0f) {
                mSubState = SUB_STATE_1;
                mTimers[TIMER_0] = 8;
                mAnmRate = -1.0f;
                field_0x1180 = 0;
                // fall-through...
            } else {
                setAnm("WaitBt", 20.0f);
                mAnmRate = 1.0f;
                if (diff < -0x1000 || diff > 0x1000) {
                    mSubState = SUB_STATE_4;
                    if (diff < 0) {
                        setAnm("TurnL", 10.0f);
                    } else {
                        setAnm("TurnR", 10.0f);
                    }
                }
                break;
            }
        }
        case SUB_STATE_1: {
            b = true;
            setAnm("WalkBt", 20.0f);
            targetSpeed = -5.0f;
            targetAnmRate = -2.5f;
            if (mXZDistanceToLink < 200.0f) {
                field_0x1180++;
                if (field_0x1180 > 40) {
                    mStateMgr.changeState(StateID_PunchAttack);
                    return;
                }
            } else if (mXZDistanceToLink >= puVar2 + 200.0f && mTimers[TIMER_0] == 0) {
                mSubState = SUB_STATE_0;
            }
            break;
        }
        case SUB_STATE_2: {
            b = true;
            field_0x117E = 0;
            if (mXZDistanceToLink > puVar2 + 850.0f) {
                mSubState = SUB_STATE_3;
            } else {
                setAnm("WalkBt", 20.0f);
                if (mMdl.getAnm().getFrame() >= 25.0f && mMdl.getAnm().getFrame() <= 50.0f) {
                    targetSpeed = 5.0f;
                }
                field_0x11A4 = 5.0f;
                targetAnmRate = 1.5f;
            }
            if (mXZDistanceToLink < puVar2 + 400.0f) {
                mSubState = SUB_STATE_0;
                if (mIsPhaseTwo == 0) {
                    mTimers[TIMER_2] = cM::rndF(50.0f) + 50.0f;
                } else {
                    mTimers[TIMER_2] = cM::rndF(20.0f) + 10.0f;
                }
            }
            break;
        }
        case SUB_STATE_3: {
            setAnm("Walk", 20.0f);
            targetSpeed = 7.5f;
            field_0x11A4 = 7.5f;
            targetAnmRate = 1.0f;
            if (mXZDistanceToLink < puVar2 + 750.0f) {
                mSubState = SUB_STATE_2;
            } else if (!link->isRecovering()) {
                field_0x117E++;
                if (field_0x117E > 70) {
                    mStateMgr.changeState(StateID_DashAttack);
                    return;
                }
            } else {
                field_0x117E = 0;
            }
            break;
        }
        case SUB_STATE_4: {
            field_0x117E = 0;
            b = true;
            if (mMdl.getAnm().isStop()) {
                mSubState = SUB_STATE_0;
            }
            break;
        }
    }

    sLib::addCalcScaledDiff(&mAnmRate, targetAnmRate, 1.0f, 0.5f);
    setAnmRate(mAnmRate);
    sLib::addCalcScaledDiff(&mSpeed, targetSpeed, 1.0f, 2.0f);

    if (mSubState != SUB_STATE_0) {
        sLib::addCalcAngle(field_0x118C.ref(), 0x800, 1, 100);
        sLib::addCalcAngle(mAngle.y.ref(), mYAngleToLink, 2, field_0x118C);
    }

    if (b) {
        if (diff > 0x1000) {
            diff = 0x1000;
        } else if (diff < -0x1000) {
            diff = -0x1000;
        }
        field_0x1172 = diff / 2;
        // approx. 160/65536, but maybe hardcoded?
        sLib::addCalcScaledDiff(&mMdlCallback.field_0x30, diff * (1.0f / 409.59937f), 0.5f, 1.0f);
        if (mLightingStrikeState == LIGHTNING_STRIKE_STATE_NONE && !link->isUsingShield() && link->isUsingSword()) {
            mMtx_c rotMtx;
            rotMtx.YrotS(-mYAngleToLink);
            mVec3_c v = mVec3_c(link->getSwordPos() - mPosition);
            MTXMultVec(rotMtx, v, v);
            if (v.x > 150.0f) {
                v.x = 150.0f;
            } else if (v.x < -150.0f) {
                v.x = -150.0f;
            }
            sLib::addCalcAngle(field_0x118A.ref(), 400, 1, 20);
            sLib::addCalcAngle(mMdlCallback.field_0x18.ref(), mAng(v.x * -40.0f), 2, field_0x118A);
            v.y -= 270.0f;
            if (v.y > 100.0f) {
                v.y = 100.0f;
            } else if (v.y < -150.0f) {
                v.y = -150.0f;
            }
            sLib::addCalcAngle(mMdlCallback.field_0x1A.ref(), mAng(v.y * -40.0f), 2, field_0x118A);
        } else {
            field_0x118A = 0;
            sLib::addCalcAngle(mMdlCallback.field_0x18.ref(), 0, 4, 400);
            sLib::addCalcAngle(mMdlCallback.field_0x1A.ref(), 0, 4, 400);
        }
        field_0x1133 = 1;
    }

    if (field_0x117C == 0) {
        if (!checkForRangeAttack()) {
            checkForCloseRangeAttack();
            checkForCounter();
        }
        mCc1.SetTgFlag_0xA(0x1FF);
    } else {
        // TODO
        mCc1.SetTgFlag_0xA(0);
    }

    if (mTimers[TIMER_4] == 1) {
        field_0x1178 = 40;
    } else if (mTimers[TIMER_4] <= 10) {
        mNumConsecutiveSameDirectionAttacks = 0;
        mNumConsecutiveOppositeDirectionAttacks = 0;
        mChanceAttackCounter = 0;
        mLastAttackDir = mSecondLastAttackDirection = -1;
    }
}
void dAcBlastboss_c::finalizeState_Fight() {}

void dAcBlastboss_c::initializeState_Attack() {
    f32 rnd = cM::rnd();
    mSubState = SUB_STATE_0;
    if (rnd < 0.3333f) {
        setAnm("AttackU", 5.0f);
    } else if (rnd < 0.6666f) {
        setAnm("AttackR", 5.0f);
    } else {
        setAnm("AttackL", 5.0f);
    }

    if (mIsSwordEmpowered) {
        mSwordMdl.setDamageMaybe(8);
    } else {
        mSwordMdl.setDamageMaybe(4);
    }
    mSwordMdl.fn_8006B7A0(0x20000);
}
void dAcBlastboss_c::executeState_Attack() {
    dAcPy_c *link = dAcPy_c::GetLinkM();
    field_0x1131 = 1;
    f32 speed = 0.0f;
    // TODO major typing crime here but I'm not sure how this actually works
    cCcD_Obj *atHit = mSwordMdl.mCcList.find((dColliderLinkedList::ccPtmf)&dCcD_Check::ChkAtHit);

    switch (mSubState) {
        case SUB_STATE_0: {
            if (field_0x1152 >= 15 && mMdl.getAnm().checkFrame(7.0f) && cM::rnd() < 0.1f) {
                mStateMgr.changeState(StateID_Fight);
                field_0x113F = 20;
            } else {
                if (mMdl.getAnm().getFrame() < 20.0f) {
                    sLib::addCalcAngle(mAngle.y.ref(), mYAngleToLink, 2, 0x800);
                }

                if (mMdl.getAnm().getFrame() >= 25.0f && mMdl.getAnm().getFrame() <= 28.0f) {
                    mSwordMdl.enable();
                    speed = 20.0f;
                    field_0x11A4 = 20.0f;
                    field_0x113D = 10;
                }
                if (atHit != nullptr) {
                    mSubState = SUB_STATE_1;
                } else {
                    if (mMdl.getAnm().getFrame() > 28.0f) {
                        mSubState = SUB_STATE_1;
                        if (field_0x113E != 0) {
                            setAnmRate(0.5f);
                        }
                    }
                }
            }
            break;
        }
        case SUB_STATE_1: {
            if (mMdl.getAnm().isStop()) {
                mStateMgr.changeState(StateID_Fight);
            }
            break;
        }
    }

    mSpeed = speed;
    if (!mIsPhaseTwo && link->checkCurrentAction(/* BACKFLIP */ 0x62)) {
        field_0x113E = 20;
    }
    if (field_0x113E != 0) {
        if (mIsPhaseTwo) {
            mSubState = SUB_STATE_0;
            mStateMgr.changeState(StateID_CounterAttack);
            return;
        }
        mCc1.SetTgFlag_0xA(0);
    } else {
        if (mpCurrentAnm == "AttackR") {
            mCc1.SetTgFlag_0xA(0xE);
        } else if (mpCurrentAnm == "AttackL") {
            mCc1.SetTgFlag_0xA(0xE0);
        } else {
            mCc1.SetTgFlag_0xA(0x1);
        }
    }
    checkForCounter();
}
void dAcBlastboss_c::finalizeState_Attack() {}

void dAcBlastboss_c::initializeState_CounterAttack() {
    mSwordMdl.fn_8006B7A0(0x20000);
    if (mSubState == 0) {
        setAnm("Counter", 5.0f);
    } else {
        f32 rnd = cM::rnd();
        if (rnd < 0.3333f) {
            setAnm("AttackU", 5.0f);
        } else if (rnd < 0.6666f) {
            setAnm("AttackR", 5.0f);
        } else {
            setAnm("AttackL", 5.0f);
        }
        mMdl.setFrame(5.0f);
    }

    mNumConsecutiveSameDirectionAttacks = 0;
    mNumConsecutiveOppositeDirectionAttacks = 0;
    mLastAttackDir = mSecondLastAttackDirection = -1;
}
void dAcBlastboss_c::executeState_CounterAttack() {
    dAcPy_c *link = dAcPy_c::GetLinkM();
    f32 speed = 0.0f;
    bool b = false;
    field_0x1131 = 1;
    if (mSubState == SUB_STATE_0) {
        if (mMdl.getAnm().getFrame() < 15.0f) {
            speed = -10.0f;
        }
        if (mMdl.getAnm().getFrame() < 10.0f ||
            (mMdl.getAnm().getFrame() > 30.0f && mMdl.getAnm().getFrame() < 40.0f)) {
            sLib::addCalcAngle(mAngle.y.ref(), mYAngleToLink, 2, 0x800);
        }

        if ((mMdl.getAnm().getFrame() >= 20.0f && mMdl.getAnm().getFrame() <= 23.0f) ||
            (mMdl.getAnm().getFrame() >= 43.0f && mMdl.getAnm().getFrame() <= 47.0f)) {
            mSwordMdl.enable();
            speed = 30.0f;
            field_0x11A4 = 20.0f;
        }

        int targetFrame = 42;
        if (mMdl.getAnm().checkFrame(targetFrame) && mXZDistanceToLink > 400.0f) {
            mStateMgr.changeState(StateID_Fight);
            return;
        }
        if (mMdl.getAnm().getFrame() >= 52.0f) {
            b = true;
        }
    } else {
        if (mMdl.getAnm().getFrame() < 20.0f) {
            sLib::addCalcAngle(mAngle.y.ref(), mYAngleToLink, 2, 0x800);
        }
        if (mMdl.getAnm().getFrame() >= 25.0f && mMdl.getAnm().getFrame() <= 28.0f) {
            mSwordMdl.enable();
            speed = 10.0f;
            field_0x11A4 = 20.0f;
            field_0x113D = 5;
        }
        if (mMdl.getAnm().getFrame() >= 33.0f) {
            b = true;
        }
    }

    if (link->checkCurrentAction(/* BACKFLIP */ 0x62)) {
        field_0x113E = 20;
    }
    mCc1.SetTgFlag_0xA(0);
    if (field_0x113E != 0) {
        field_0x1142 = 0;
        field_0x1134 = 0;
    } else if (b) {
        mCc1.SetTgFlag_0xA(0x1FF);
        field_0x1142 = 0;
        checkForCounter();
    }

    if (mMdl.getAnm().isStop()) {
        mStateMgr.changeState(StateID_Fight);
    }
    mSpeed = speed;
}
void dAcBlastboss_c::finalizeState_CounterAttack() {}

void dAcBlastboss_c::initializeState_PunchAttack() {
    setAnm("AttackBreak", 5.0f);
    mSwordMdl.fn_8006B7A0(0x20000);
    mSubState = 0;
}
void dAcBlastboss_c::executeState_PunchAttack() {
    f32 speed = 0.0f;
    field_0x1131 = 1;
    mCc1.SetTgFlag_0xA(0);
    sLib::addCalcAngle(mAngle.y.ref(), mYAngleToLink, 2, 0x800);
    if (mMdl.getAnm().getFrame() < 15.0f) {
        speed = -10.0f;
    }
    if (mMdl.getAnm().getFrame() >= 15.0f && mMdl.getAnm().getFrame() <= 23.0f) {
        speed = 40.0f;
    }
    if (mMdl.getAnm().checkFrame(15.0f)) {
        field_0x1139 = 1;
    }
    if (mMdl.getAnm().checkFrame(23.0f)) {
        field_0x1139 = 0;
    }
    if (mMdl.getAnm().isStop()) {
        mStateMgr.changeState(StateID_Fight);
    }
    mSpeed = speed;
}
void dAcBlastboss_c::finalizeState_PunchAttack() {}

void dAcBlastboss_c::initializeState_DashAttack() {
    mSubState = 0;
    mSwordMdl.setDamageMaybe(8);
    mSwordMdl.fn_8006B7A0(0x20000);
}
void dAcBlastboss_c::executeState_DashAttack() {
    field_0x1131 = 1;
    f32 targetSpeed = 0.0f;
    f32 stepSize = 40.0f; // never set differently
    mCc1.SetTgFlag_0xA(0x1FF);
    sLib::addCalcAngle(mAngle.y.ref(), mYAngleToLink, 2, 0x1000);
    switch (mSubState) {
        case SUB_STATE_0: {
            if (mXZDistanceToLink < 400.0f) {
                mStateMgr.changeState(StateID_CounterAttack);
                return;
            }
            setAnm("AttackJumpStart", 5.0f);
            mSwordMdl.fn_8006B7A0(0x20000);
            mSubState = SUB_STATE_1;
            // fall-through
        }
        case SUB_STATE_1: {
            if (mMdl.getAnm().getFrame() >= 23.0f) {
                field_0x11A4 = 30.0f;
                targetSpeed = 40.0f;
            }
            if (mMdl.getAnm().isStop()) {
                setAnm("AttackJumpLoop", 0.0f);
                mSubState = SUB_STATE_2;
                mTimers[TIMER_0] = 200;
            }
            break;
        }
        case SUB_STATE_2: {
            field_0x11A4 = 30.0f;
            targetSpeed = 80.0f;
            field_0x11B8 = 5.0f;
            if (mXZDistanceToLink < 750.0f) {
                setAnm("AttackJumpEnd", 0.0f);
                mSubState = SUB_STATE_3;
            } else if (mTimers[TIMER_0] == 0) {
                mStateMgr.changeState(StateID_Fight);
            }
            break;
        }
        case SUB_STATE_3: {
            if (mXZDistanceToLink > 300.0f && mMdl.getAnm().getFrame() <= 6.0f) {
                targetSpeed = 80.0f;
            } else {
                stepSize = 40.0f;
            }
            if (mMdl.getAnm().getFrame() >= 6.0f && mMdl.getAnm().getFrame() <= 9.0f) {
                mSwordMdl.enable();
                field_0x113D = 10;
            }

            if (mMdl.getAnm().isStop()) {
                mStateMgr.changeState(StateID_Fight);
            }
            break;
        }
    }

    sLib::addCalcScaledDiff(&mSpeed, targetSpeed, 1.0f, stepSize);
    if (mSpeed > 50.0f) {
        // @bug: effect rotation never updated due to static...
        static mAng3_c sEffRot(0, mRotation.y, 0);
        static mVec3_c sEffScale(1.0f, 1.0f, 1.0f);
        mVec3_c effPos(mPosition.x, 10.0f, mPosition.z);
        mEmitter2.holdEffect(PARTICLE_RESOURCE_ID_MAPPING_858_, effPos, &sEffRot, &sEffScale, nullptr, nullptr);
    }
}
void dAcBlastboss_c::finalizeState_DashAttack() {}

void dAcBlastboss_c::initializeState_SmallAttack() {
    setAnm("AttackSwordBreak", 5.0f);
    mSubState = SUB_STATE_0;
}
void dAcBlastboss_c::executeState_SmallAttack() {
    dAcPy_c *link = dAcPy_c::GetLinkM();
    field_0x1131 = 1;
    f32 targetSpeed = 0.0f;

    mCc1.SetTgFlag_0xA(0);
    switch (mSubState) {
        case SUB_STATE_0: {
            if (mMdl.getAnm().getFrame() >= 31.0f && mMdl.getAnm().getFrame() <= 33.0f) {
                field_0x11A4 = 15.0f;
                if (checkForLinkSwordBySwordHit()) {
                    mVec3_c effPos;

                    effPos = link->vt_0x278() + (link->getSwordPos() - link->vt_0x278()) * 0.4f;
                    dJEffManager_c::spawnEffect(
                        PARTICLE_RESOURCE_ID_MAPPING_388_, effPos, &mRotation, nullptr, nullptr, nullptr, 0, 0
                    );
                    dRumble_c::start(dRumble_c::sRumblePreset4, dRumble_c::FLAG_SLOT0);
                    startSound(SE_BLasBos_SwordBySword);
                    link->onForceOrPreventActionFlags(0x80000);
                    mSubState = SUB_STATE_1;
                }
            }
            break;
        }
    }

    if (mMdl.getAnm().checkFrame(44.0f) && mSubState != SUB_STATE_1) {
        mStateMgr.changeState(StateID_Fight);
        mpCurrentAnm = nullptr;
        setAnm("WaitBt", 40.0f);
        field_0x113E = 10;
        field_0x1134 = 0;
    }

    if (mMdl.getAnm().getFrame() >= 55.0f && mMdl.getAnm().getFrame() <= 59.0f) {
        mSwordMdl.enable();
        targetSpeed = 40.0f;
        field_0x11A4 = 20.0f;
    }

    if (mMdl.getAnm().isStop()) {
        mStateMgr.changeState(StateID_Fight);
    }

    sLib::addCalcScaledDiff(&mSpeed, targetSpeed, 1.0f, 20.0f);
    sLib::addCalcAngle(mAngle.y.ref(), mYAngleToLink, 2, 0x200);
}
void dAcBlastboss_c::finalizeState_SmallAttack() {}

void dAcBlastboss_c::initializeState_ThunderAttack() {
    setAnm("AttackThunder", 5.0f);
    mSubState = SUB_STATE_0;
}
void dAcBlastboss_c::executeState_ThunderAttack() {
    field_0x1131 = 1;
    f32 targetSpeed = 0.0f;
    mCc1.SetTgFlag_0xA(0);
    sLib::addCalcAngle(mAngle.y.ref(), mYAngleToLink, 2, 0x400);
    if (mMdl.getAnm().getFrame() >= 21.0f && mMdl.getAnm().getFrame() <= 25.0f) {
        mSwordMdl.enable();
        field_0x11A4 = 20.0f;
        if (mMdl.getAnm().checkFrame(23.0f)) {
            mIsSwordEmpowered = false;
            mCc1.ClrTg_0x8000000();
            mSwordMdl.fn_8006B800(0);
            mIsThunderBeamAttackActive = true;
            mThunderBeamTimer = 60;
            mThunderBeamReflected = false;
            mThunderBeamRotation = mRotation;
            mVec3_c v(0.0f, 100.0f, 300.0f);
            mMtx_c mtx;
            mtx.YrotS(mRotation.y);
            MTXMultVec(mtx, v, mThunderBeamPosition);
            mThunderBeamPosition += mPosition;

            v.set(0.0f, 0.0f, 50.0f);
            MTXMultVec(mtx, v, mThunderBeamVelocity);
            mLightningCc.setCenter(mThunderBeamPosition);
            setLightningTimerMaybe(30);
            startSound(SE_BLasBos_EmitThunderBeam);
        }
    }
    if (mMdl.getAnm().isStop()) {
        mStateMgr.changeState(StateID_Fight);
    }

    sLib::addCalcScaledDiff(&mSpeed, targetSpeed, 1.0f, 5.0f);
}
void dAcBlastboss_c::finalizeState_ThunderAttack() {}

void dAcBlastboss_c::initializeState_Guard() {
    mSubState = SUB_STATE_0;
    mMdlCallback.field_0x30 = 0.0f;
    mMdlCallback.field_0x18 = 0;
}
void dAcBlastboss_c::executeState_Guard() {
    dAcPy_c *link = dAcPy_c::GetLinkM();
    field_0x1131 = 1;
    f32 targetSpeed = 0.0f;

    static const char *sGuardNames[] = {
        "GuardR", "GuardRU", "GuardRD", "GuardL", "GuardLU", "GuardLD", "GuardU", "GuardD", "GuardC", "WaitBt",
    };

    static const u32 sGuardFlags[] = {
        0x8 | 0x4 | 0x2,
        0x8 | 0x4 | 0x2,
        0x10 | 0x8 | 0x4 | 0x2,
        0x80 | 0x40 | 0x20,
        0x80 | 0x40 | 0x20,
        0x80 | 0x40 | 0x20 | 0x10,
        0x80 | 0x2 | 0x1,
        0x20 | 0x10 | 0x8,
        0x100,
        0x100 | 0x80 | 0x40 | 0x20 | 0x10 | 0x8 | 0x4 | 0x2 | 0x1,
    };

    switch (mSubState) {
        case SUB_STATE_0: {
            if (link->isAttackingJumpSlash()) {
                mSubState = SUB_STATE_2;
                setAnm("WalkBt", 2.0f);
                setAnmRate(-2.0f);
                mTimers[TIMER_0] = 5;
                break;
            } else {
                if (mGuardDirection == GUARD_BT) {
                    setAnm(sGuardNames[mGuardDirection], 3.0f);
                    mTimers[TIMER_0] = 14;
                } else {
                    forceSetAnm(sGuardNames[mGuardDirection], 3.0f);
                }
                mSubState = SUB_STATE_1;
                // fall-through
            }
        }
        case SUB_STATE_1: {
            if (link->isAttackingSpin()) {
                mTimers[TIMER_0] = 5;
            }

            if (mGuardDirection == GUARD_BT) {
                field_0x1133 = 1;
                if (mTimers[TIMER_0] == 0) {
                    mStateMgr.changeState(StateID_Fight);
                    mTimers[TIMER_4] = 15;
                }
            } else if (mMdl.getAnm().getFrame() >= 13.0f) {
                mStateMgr.changeState(StateID_Fight);
                mTimers[TIMER_4] = 15;
            }

            if (!link->isAttackingJumpSlash()) {
                checkForCounter();
            }
            break;
        }
        case SUB_STATE_2: {
            field_0x11A4 = 20.0f;
            targetSpeed = -30.0f;
            if (mTimers[TIMER_0] == 0) {
                mGuardDirection = GUARD_UP;
                forceSetAnm(sGuardNames[mGuardDirection], 3.0);
                mSubState = SUB_STATE_1;
            }
            break;
        }
        case SUB_STATE_10: {
            if (mGuardDirection == GUARD_RIGHT) {
                setAnm("AttackRGuard", 3.0f);
            } else if (mGuardDirection == GUARD_LEFT) {
                setAnm("AttackLGuard", 3.0f);
            } else {
                setAnm("GuardU", 3.0f);
            }
            mSubState = SUB_STATE_11;
            // fall-through
        }
        case SUB_STATE_11: {
            if (mMdl.getAnm().getFrame() >= 13.0f) {
                mStateMgr.changeState(StateID_Fight);
                mTimers[TIMER_4] = 15;
            }
            break;
        }
    }

    sLib::addCalcScaledDiff(&mSpeed, targetSpeed, 1.0f, 5.0f);
    sLib::addCalcAngle(mAngle.y.ref(), mYAngleToLink, 2, 0x1000);

    if (field_0x113E != 0) {
        setAnmRate(0.2f);
    } else {
        setAnmRate(1.0f);
        if (link->isAttacking()) {
            switch (dAcPy_c::GetLinkM()->getSpecificAttackDirection()) {
                case daPlayerActBase_c::ATTACK_DIRECTION_DOWN:      mGuardDirection = GUARD_UP; break;
                case daPlayerActBase_c::ATTACK_DIRECTION_LEFT:      mGuardDirection = GUARD_LEFT; break;
                case daPlayerActBase_c::ATTACK_DIRECTION_DOWNLEFT:  mGuardDirection = GUARD_UPLEFT; break;
                case daPlayerActBase_c::ATTACK_DIRECTION_UPLEFT:    mGuardDirection = GUARD_DOWNLEFT; break;
                case daPlayerActBase_c::ATTACK_DIRECTION_RIGHT:     mGuardDirection = GUARD_RIGHT; break;
                case daPlayerActBase_c::ATTACK_DIRECTION_DOWNRIGHT: mGuardDirection = GUARD_UPRIGHT; break;
                case daPlayerActBase_c::ATTACK_DIRECTION_UPRIGHT:   mGuardDirection = GUARD_DOWNRIGHT; break;
                case daPlayerActBase_c::ATTACK_DIRECTION_UP:        mGuardDirection = GUARD_DOWN; break;
                default:                                            mGuardDirection = GUARD_CENTER; break;
            }
            setAnm(sGuardNames[mGuardDirection], 3.0);
        }
    }

    mCc1.SetTgFlag_0xA(sGuardFlags[mGuardDirection]);
    checkForRangeAttack();
}
void dAcBlastboss_c::finalizeState_Guard() {}

void dAcBlastboss_c::initializeState_GuardBreak() {
    s32 side;
    s32 attackDir = dAcPy_c::GetLinkM()->getSpecificAttackDirection();
    if (attackDir == daPlayerActBase_c::ATTACK_DIRECTION_LEFT ||
        attackDir == daPlayerActBase_c::ATTACK_DIRECTION_DOWNLEFT ||
        attackDir == daPlayerActBase_c::ATTACK_DIRECTION_UPLEFT) {
        side = 0;
    } else if (attackDir == daPlayerActBase_c::ATTACK_DIRECTION_RIGHT ||
               attackDir == daPlayerActBase_c::ATTACK_DIRECTION_DOWNRIGHT ||
               attackDir == daPlayerActBase_c::ATTACK_DIRECTION_UPRIGHT) {
        side = 1;
    } else {
        side = (s32)cM::rndF(1.99f);
    }

    if (side == 0) {
        setAnm("GuardBreak", 5.0f);
    } else {
        setAnm("GuardBreakB", 5.0f);
    }
    mSubState = 0;
    mTimers[TIMER_0] = 3;
}
void dAcBlastboss_c::executeState_GuardBreak() {
    if (mMdl.getAnm().isStop()) {
        mStateMgr.changeState(StateID_Fight);
        mTimers[TIMER_4] = 15;
    }

    if (mTimers[TIMER_0] == 1) {
        mSpeed = -30.0f;
    }
    sLib::addCalcScaled(&mSpeed, 1.0f, 4.0f);
    mCc1.SetTgFlag_0xA(0x1FF);
    checkForCounter();
}
void dAcBlastboss_c::finalizeState_GuardBreak() {}

void dAcBlastboss_c::initializeState_Damage() {
    forceSetAnm("Damage", 5.0f);
    mSubState = SUB_STATE_0;
    mSpeed = -20.0f;
    mChanceAttackCounter++;
    field_0x1142 = 5;
    startSound(SE_BLasBos_VO_DamageNormal);
}
void dAcBlastboss_c::executeState_Damage() {
    dAcPy_c *link = dAcPy_c::GetLinkM();
    sLib::addCalcScaled(&mSpeed, 1.0f, 2.0f);
    if (mMdl.getAnm().isStop()) {
        mStateMgr.changeState(StateID_Fight);
        mTimers[TIMER_4] = 15;
        mTimers[TIMER_2] = 0;
    }
    field_0x113E = 0;
    if (mIsPhaseTwo == 0 && !link->isAttackingSpin()) {
        mCc1.SetTgFlag_0xA(0x1FF);
    }
    checkForCounter();
}
void dAcBlastboss_c::finalizeState_Damage() {}

void dAcBlastboss_c::initializeState_SitDamage() {
    setAnm("Chance", 5.0f);
    mSubState = 0;
    mSpeed = -20.0f;
    mChanceAttackCounter = 0;
    mTimers[TIMER_0] = 80;
    field_0x1142 = 5;
    mChanceCounter++;
}
void dAcBlastboss_c::executeState_SitDamage() {
    sLib::addCalcScaled(&mSpeed, 1.0f, 2.0f);
    switch (mSubState) {
        case SUB_STATE_0: {
            if (mMdl.getAnm().isStop()) {
                setAnm("ChanceLoop", 0.0f);
                mSubState = SUB_STATE_1;
            }
            break;
        }
        case SUB_STATE_1: {
            if (mTimers[TIMER_0] == 0 || mChanceAttackCounter >= 5) {
                setAnm("ChanceReverse", 5.0f);
                mSubState = SUB_STATE_2;
                // fall-through
            } else {
                break;
            }
        }
        case SUB_STATE_2: {
            if (mMdl.getAnm().isStop()) {
                mStateMgr.changeState(StateID_Fight);
                mTimers[TIMER_4] = 15;
            }
            checkForCounter();
            mCc1.SetTgFlag_0xA(0x1FF);
            return;
        }
        case SUB_STATE_5: {
            if (field_0x1137 == 0) {
                forceSetAnm("ChanceDamageU", 2.0f);
            } else if (field_0x1137 == 1) {
                forceSetAnm("ChanceDamageR", 2.0f);
            } else if (field_0x1137 == 2) {
                forceSetAnm("ChanceDamageL", 2.0f);
            } else {
                forceSetAnm("ChanceDamageD", 2.0f);
            }
            mSubState = SUB_STATE_6;
            break;
        }
        case SUB_STATE_6: {
            if (mTimers[TIMER_0] == 0 || mChanceAttackCounter >= 5) {
                setAnm("ChanceReverse", 5.0f);
                mSubState = SUB_STATE_2;
                // fall-through
            } else if (mMdl.getAnm().isStop()) {
                setAnm("ChanceLoop", 0.0f);
                mSubState = SUB_STATE_1;
            }
        }
    }
    mCc1.SetTgFlag_0xA(0);
}
void dAcBlastboss_c::finalizeState_SitDamage() {}

void dAcBlastboss_c::initializeState_Down() {
    setAnm("DownBStart", 5.0f);
    mSubState = SUB_STATE_0;
    mSpeed = -20.0f;
    mAcceleration = -5.0f;
    mVelocity.y = 60.0f;
    mIsSwordEmpowered = false;
    mCc1.ClrTg_0x8000000();
    mSwordMdl.fn_8006B800(0);
}
void dAcBlastboss_c::executeState_Down() {
    mVec3_c v;
    mMtx_c mtx;

    mIsDown = true;
    field_0x1142 = 5;
    bool b = false;
    setLightningTimerMaybe(30);
    mGuardDirection = -1;

    // TODO: Are all these fall-throughs intentional??????
    switch (mSubState) {
        case SUB_STATE_0: {
            if (mMdl.getAnm().isStop()) {
                mSubState = SUB_STATE_1;
                setAnm("DownBLoop", 0.0f);
                field_0x11C0 = 20.0f;
            }
            // fall-through
        }
        case SUB_STATE_1: {
            sLib::addCalcAngle(mAngle.x.ref(), -0x4000, 2, 0x300);
            if (field_0x11BC <= -10.0f && field_0x1140) {
                mSubState = SUB_STATE_2;
                b = true;
                setAnm("DownBEnd", 2.0f);
                mVelocity.y = 30.0f;
            } else {
                field_0x11A4 = -30.0f;
                break;
            }
            // fall-through
        }
        case SUB_STATE_2: {
            if (mVelocity.y < 0.0f) {
                field_0x11C0 = -35.0f;
            }
            sLib::addCalcAngle(mAngle.x.ref(), -0x4000, 2, 0x300);
            if (field_0x11BC <= -10.0f && field_0x1140) {
                b = true;
            }
            if (mMdl.getAnm().isStop()) {
                setAnm("DownLoop", 0.0f);
                mSubState = SUB_STATE_3;
                if (field_0x113B >= 3 || mLightingStrikeState != LIGHTNING_STRIKE_STATE_NONE) {
                    mTimers[TIMER_0] = 70;
                } else if (field_0x113B == 2) {
                    mTimers[TIMER_0] = 40;
                } else {
                    mTimers[TIMER_0] = 25;
                }
                field_0x113B++;
            } else {
                sLib::addCalcScaled(&mSpeed, 1.0f, 1.0f);
                if (mSpeed < 1.0f) {
                    mFinalBlowPosition.set(mChestTranslation.x, mChestTranslation.y, mChestTranslation.z);
                    setInteractionFlags(INTERACT_0x1);
                }
                break;
            }
            // fall-through
        }
        case SUB_STATE_3: {
            mFinalBlowPosition.set(mChestTranslation.x, mChestTranslation.y, mChestTranslation.z);
            sLib::addCalcScaled(&mSpeed, 1.0f, 1.0f);
            if (checkInteractionFlags(INTERACT_0x1000)) {
                setAnm("End", 3.0f);
                mSubState = SUB_STATE_50;
                mLightingStrikeState = LIGHTNING_STRIKE_TRIGGER_END;
                startSound(SE_BLasBos_LastHit);
                mIsDead = true;
                resetInteractionFlags(INTERACT_0x1000);
                resetInteractionFlags(INTERACT_0x1);
            } else if (mTimers[TIMER_0] == 0) {
                if (cM::rnd() < 0.5f || mLightingStrikeState != LIGHTNING_STRIKE_STATE_NONE) {
                    setAnm("DownEscapeR", 10.0f);
                } else {
                    setAnm("DownEscapeL", 10.0f);
                }
                setAnmRate(0.65f);
                mSubState = SUB_STATE_5;
                field_0x11C8 = 0.0f;
                resetInteractionFlags(INTERACT_0x1);
                if (mLightingStrikeState != LIGHTNING_STRIKE_STATE_NONE) {
                    mLightingStrikeState = LIGHTNING_STRIKE_WAIT;
                    field_0x2D00 = 0.0f;
                    dLightEnv_c::GetPInstance()->setField_0x38DC(1);
                }
            }
            break;
        }
        case SUB_STATE_4: {
            if (mMdl.getAnm().getFrame() >= 52.0f) {
                mIsDown = false;
                sLib::addCalcAngle(mAngle.x.ref(), 0, 1, 910);
            }
            if (mMdl.getAnm().getFrame() >= 170.0f) {
                field_0x1131 = 2;
            }
            if (mMdl.getAnm().isStop()) {
                mStateMgr.changeState(StateID_Fight);
                mTimers[TIMER_4] = 15;
            }
            break;
        }
        case SUB_STATE_5: {
            sLib::addCalcAngle(mAngle.x.ref(), 0, 1, 0x400);
            if ((int)mMdl.getAnm().getFrame() <= 14) {
                sLib::addCalcScaledDiff(&field_0x11C8, 15.0f, 1.0f, 5.0f);
                field_0x11A4 = 20.0f;
            } else {
                mIsDown = false;
                sLib::addCalcAngle(mAngle.y.ref(), mYAngleToLink, 2, 0x800);
                sLib::addCalcScaled(&field_0x11C8, 1.0f, 5.0f);
            }
            mtx.YrotS(mRotation.y);
            if (mpCurrentAnm == "DownEscapeR") {
                v.set(-field_0x11C8, 0.0f, 0.0f);
            } else {
                v.set(field_0x11C8, 0.0f, 0.0f);
            }
            MTXMultVec(mtx, v, v);
            mPosition.x += v.x;
            mPosition.z += v.z;

            if (mMdl.getAnm().isStop()) {
                if (mLightingStrikeState != LIGHTNING_STRIKE_STATE_NONE) {
                    setAnm("ThunderDemo", 10.0f);
                    mSubState = SUB_STATE_20;
                } else {
                    if (field_0x113B >= 3 && mXZDistanceToLink < 500.0f) {
                        mStateMgr.changeState(StateID_Attack);
                        mMdl.setFrame(10.0f);
                    } else {
                        mStateMgr.changeState(StateID_Fight);
                        mTimers[TIMER_4] = 15;
                    }
                }
            }
            break;
        }
        case SUB_STATE_10: {
            // TODO: Is this state ever set?
            if (mMdl.getAnm().isStop()) {
                setAnm("DownGetUp", 10.0f);
                mSubState = SUB_STATE_4;
            }
            break;
        }
        case SUB_STATE_20: {
            mIsDown = 0;
            if (mMdl.getAnm().isStop()) {
                mSubState = SUB_STATE_21;
                setAnm("CatchThunderLoop", 0.0f);
            }
            break;
        }
        case SUB_STATE_21: {
            mIsDown = false;
            if (mMdl.getAnm().isStop()) {
                mStateMgr.changeState(StateID_Fight);
                mTimers[TIMER_4] = 15;
                mLightingStrikeState = LIGHTNING_STRIKE_FINISH;
            }
            break;
        }
    }

    if (b) {
        v.set(0.0f, 0.0f, -100.0f);
        mtx.YrotS(mRotation.y);
        MTXMultVec(mtx, v, v);
        v.x += mBackbone1Translation.x;
        v.z += mBackbone1Translation.z;
        v.y = 10.0f;

        static mVec3_c sScale(3.0f, 3.0f, 3.0f);
        dJEffManager_c::spawnEffect(PARTICLE_RESOURCE_ID_MAPPING_91_, v, nullptr, &sScale, nullptr, nullptr, 0, 0);
    }
}
void dAcBlastboss_c::finalizeState_Down() {}

void dAcBlastboss_c::initializeState_Stun() {
    setAnm("Stan", 3.0f);
    mSubState = SUB_STATE_0;
    mChanceAttackCounter = 0;
    mInvulnerabilityTimerMaybe = 10;
    mStunCounter++;
    mIsSwordEmpowered = false;
    mCc1.ClrTg_0x8000000();
    mSwordMdl.fn_8006B800(0);
    startSound(SE_BLasBos_HitThunderBeam);
}
void dAcBlastboss_c::executeState_Stun() {
    setLightningTimerMaybe(40);
    mStunState = 1;
    int frame = mMdl.getAnm().getFrame();
    switch (mSubState) {
        case SUB_STATE_0: {
            if (mMdl.getAnm().isStop()) {
                mSubState = SUB_STATE_1;
                setAnm("StanLoop", 0.0f);
                mTimers[TIMER_0] = 100;
            }
            break;
        }
        case SUB_STATE_1: {
            if (mTimers[TIMER_0] == 0) {
                mSubState = SUB_STATE_2;
                setAnm("StanReverse", 10.0f);
            }
            break;
        }
        case SUB_STATE_2: {
            if (frame <= 58) {
                if (frame == 58) {
                    mStunState = 2;
                }
            } else {
                mStunState = 0;
                if (frame <= 61) {
                    field_0x11A4 = 25.0f;
                }
            }
            if (mMdl.getAnm().isStop()) {
                mStateMgr.changeState(StateID_Fight);
            }
            break;
        }
    }

    sLib::addCalcScaled(&mSpeed, 1.0f, 10.0f);
    if (mChanceAttackCounter >= 7) {
        mCc1.SetTgFlag_0xA(0x1FF);
        checkForCounter();
    } else {
        if (mInvulnerabilityTimerMaybe != 0) {
            mCc1.SetTgFlag_0xA(0x1FF);
        } else {
            mCc1.SetTgFlag_0xA(0);
        }
    }
}
void dAcBlastboss_c::finalizeState_Stun() {}

void dAcBlastboss_c::initializeState_ThunderWait() {
    setAnm("CatchThunderStart", 10.0f);
    mSubState = SUB_STATE_0;
    mGuardDirection = -1;
    mTimers[TIMER_0] = 30;
}
void dAcBlastboss_c::executeState_ThunderWait() {
    dAcPy_c *link = dAcPy_c::GetLinkM();
    mCc1.SetTgFlag_0xA(0);

    switch (mSubState) {
        case SUB_STATE_0: {
            if (mMdl.getAnm().isStop()) {
                mSubState = SUB_STATE_1;
                setAnm("CatchThunderLoop", 0.0f);
            }
            // fall-through
        }
        case SUB_STATE_1: {
            if (link->checkSwordAndMoreStates(0x400000)) {
                mStateMgr.changeState(StateID_Fight);
            } else if (mTimers[TIMER_0] == 0) {
                dLightEnv_c::GetPInstance()->setField_0x38DC(7);
                mSubState = SUB_STATE_5;
                setAnm("CatchThunderEnd", 5.0f);
                mVec3_c vEff;
                vEff.x = mSwordPosition.x;
                vEff.y = mSwordPosition.y + 110.0f;
                vEff.z = mSwordPosition.z;
                dJEffManager_c::spawnEffect(
                    PARTICLE_RESOURCE_ID_MAPPING_624_, vEff, nullptr, nullptr, nullptr, nullptr, 0, 0
                );
                mTimers[TIMER_3] = cM::rndF(100.0f) + 900.0f;
                mIsSwordEmpowered = true;
                mSwordMdl.fn_8006B800(4);
                mCc1.OnTg_0x8000000();
                mLightingFallSwordTimer = 10;
                startSound(SE_BLasBos_ThunderFallSword);
            }
            break;
        }
        case SUB_STATE_5: {
            if (mMdl.getAnm().getFrame() >= 20.0f) {
                mCc1.SetTgFlag_0xA(0x1FF);
                checkForCounter();
            }
            if (mMdl.getAnm().isStop()) {
                mStateMgr.changeState(StateID_Fight);
            }
            break;
        }
    }
    sLib::addCalcScaled(&mSpeed, 1.0f, 4.0f);
}
void dAcBlastboss_c::finalizeState_ThunderWait() {}

bool dAcBlastboss_c::checkDamage() {
    mVec3_c v;
    mVec3_c v2;
    mMtx_c mtx;

    mHealth = 400;
    // TODO: typing crime
    cCcD_Obj *atHit = mSwordMdl.mCcList.find((dColliderLinkedList::ccPtmf)&dCcD_Check::ChkAtHit);
    if (atHit != nullptr && atHit->GetAtFlag0x8()) {
        field_0x113E = 20;
    }

    if (sLib::calcTimer(&field_0x1134) == 0) {
        // TODO: regswap between unk and link.
        // Making this a u16 or s16 fixes regalloc but breaks instructions...
        s32 unk = someEnemyDamageCollisionStuffMaybe(mCcList, nullptr);

        // TODO: typing crime
        cCcD_Obj *tgHit = mCcList.find((dColliderLinkedList::ccPtmf)&dCcD_Check::ChkTgHit);
        if (tgHit == nullptr) {
            return false;
        }

        dAcPy_c *link = dAcPy_c::GetLinkM();
        s32 mLastAttackDirection = link->getSpecificAttackDirection();

        // TODO: enum?
        switch (unk) {
            case 0: {
                break;
            }
            case 6: {
                startSound(SE_BLasBos_SwordBySword);
                // TODO inline?
                if (tgHit->GetTgDamageFlags() & 0x4) {
                    v.x = 0.0f;
                    v.y = 0.0f;
                    v.z = 20.0f;
                    mtx.YrotS(link->mRotation.y);
                    MTXMultVec(mtx, v, v2);
                    mAttackKnockbackX = v2.x;
                    mAttackKnockbackZ = v2.z;
                    mStateMgr.changeState(StateID_Stun);
                    break;
                }

                if (tgHit->ChkTgAtHitType(AT_TYPE_0x800000)) {
                    mGuardDirection = GUARD_UP;
                    mStateMgr.changeState(StateID_Guard);
                    break;
                }
                mMdlCallback.field_0x04.set(7);
                if (field_0x113F != 0) {
                    mSubState = SUB_STATE_1;
                    mStateMgr.changeState(StateID_CounterAttack);
                    break;
                } else if (mGuardDirection == GUARD_BT) {
                    if (classifyAttackDirection(mLastAttackDirection) == classifyAttackDirection(mLastAttackDir)) {
                        mNumConsecutiveOppositeDirectionAttacks++;
                    } else {
                        mNumConsecutiveSameDirectionAttacks++;
                    }
                    if (mNumConsecutiveSameDirectionAttacks >= 2) {
                        mSubState = SUB_STATE_1;
                        mStateMgr.changeState(StateID_CounterAttack);
                        break;
                    }

                    if (classifyAttackDirection(mLastAttackDirection) == 0) {
                        // Left
                        mMdlCallback.field_0x30 += 3.0f;
                        mMdlCallback.field_0x18 += 0x7D0;
                    } else if (classifyAttackDirection(mLastAttackDirection) == 1) {
                        // Right
                        mMdlCallback.field_0x30 -= 3.0f;
                        mMdlCallback.field_0x18 -= 0x7D0;
                    }
                    if (mNumConsecutiveOppositeDirectionAttacks >= 1) {
                        mStateMgr.changeState(StateID_GuardBreak);
                    }
                } else if (mLastAttackDir >= 0) {
                    if (classifyAttackDirection(mLastAttackDirection) == classifyAttackDirection(mLastAttackDir)) {
                        mNumConsecutiveSameDirectionAttacks++;
                    } else {
                        mNumConsecutiveOppositeDirectionAttacks++;
                    }
                    s8 x = 3;
                    if (classifyAttackDirection(mLastAttackDirection) == classifyAttackDirection(mLastAttackDir)) {
                        x = 1;
                    }
                    if (mNumConsecutiveSameDirectionAttacks >= x ||
                        (mSecondLastAttackDirection >= 0 && (classifyAttackDirection(mSecondLastAttackDirection) & 2) ==
                                                                (classifyAttackDirection(mLastAttackDirection) & 2))) {
                        mSubState = SUB_STATE_0;
                        mStateMgr.changeState(StateID_CounterAttack);
                    } else {
                        if (mNumConsecutiveOppositeDirectionAttacks >= 1) {
                            field_0x113E = 25;
                            mNumConsecutiveOppositeDirectionAttacks = 1;
                            field_0x1182 = 20;
                        }
                    }
                }
                v.x = 0.0f;
                v.y = 0.0f;
                v.z = -5.0f;
                mtx.YrotS(mYAngleToLink);
                MTXMultVec(mtx, v, v2);
                mAttackKnockbackX = v2.x;
                mAttackKnockbackZ = v2.z;
                mLastAttackDir = mLastAttackDirection;
                break;
            }
            case 7: {
                break;
            }
            default: {
                if (tgHit != nullptr &&
                    (tgHit->ChkTgAtHitType(AT_TYPE_SLINGSHOT) || tgHit->ChkTgAtHitType(AT_TYPE_CLAWSHOT) ||
                     tgHit->ChkTgAtHitType(AT_TYPE_BOMB) || tgHit->ChkTgAtHitType(AT_TYPE_ARROW) ||
                     tgHit->ChkTgAtHitType(AT_TYPE_WHIP) || tgHit->ChkTgAtHitType(AT_TYPE_0x40))) {
                    mGuardDirection = GUARD_UP;
                    mStateMgr.changeState(StateID_Guard);

                    if (tgHit->ChkTgAtHitType(AT_TYPE_BOMB)) {
                        field_0x11A4 = 30.0f;
                        field_0x11B8 = 15.0f;
                        v.x = 0.0f;
                        v.y = 0.0f;
                        v.z = -10.0f;
                        mtx.YrotS(mRotation.y);
                        MTXMultVec(mtx, v, v2);
                        mAttackKnockbackX = v2.x;
                        mAttackKnockbackZ = v2.z;
                    }

                    break;
                }

                // TODO inline?
                if (tgHit->GetTgDamageFlags() & 4) {
                    v.x = 0.0f;
                    v.y = 0.0f;
                    v.z = 20.0f;
                    mtx.YrotS(link->mRotation.y);
                    MTXMultVec(mtx, v, v2);
                    mAttackKnockbackX = v2.x;
                    mAttackKnockbackZ = v2.z;
                    mStateMgr.changeState(StateID_Stun);
                    break;
                }

                field_0x1152++;
                if (field_0x1152 == 15) {
                    dSndBgmMgr_c::GetInstance()->fn_80372D70(3);
                }

                if (mIsPhaseTwo >= 0) {
                    if (field_0x1152 == 30) {
                        dStageMgr_c::GetInstance()->fn_80199B60(4);
                        mStateMgr.changeState(StateID_Down);
                        mLightingStrikeState = LIGHTNING_STRIKE_START_DEMO;
                        startSound(SE_BLasBos_DownHit);
                        break;
                    }
                } else {
                    if (field_0x1152 == 50) {
                        mStateMgr.changeState(StateID_Down);
                        field_0x1152 = 45;
                        break;
                    }
                }

                if ((!field_0x113E && (mStateMgr.isState(StateID_Attack) || mStateMgr.isState(StateID_SmallAttack) ||
                                       mStateMgr.isState(StateID_CounterAttack))) ||
                    mStateMgr.isState(StateID_ThunderWait)) {
                    field_0x1188 = 10;
                    break;
                }

                if (field_0x113E != 0 && mIsPhaseTwo != 0) {
                    field_0x1188 = 10;
                    break;
                }

                if (mStateMgr.isState(StateID_Stun)) {
                    mNumConsecutiveSameDirectionAttacks = 0;
                    mSecondLastAttackDirection = mLastAttackDirection;
                    mChanceAttackCounter++;

                    v.x = 0.0f;
                    v.y = 0.0f;
                    v.z = -20.0f;
                    mtx.YrotS(mYAngleToLink);
                    MTXMultVec(mtx, v, v2);
                    mAttackKnockbackX = v2.x;
                    mAttackKnockbackZ = v2.z;
                    field_0x1138 = 10;
                    field_0x11B8 = 5.0f;
                    field_0x11A4 = 7.0f;
                    if (mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_LEFT ||
                        mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_DOWNLEFT ||
                        mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_UPLEFT) {
                        field_0x1137 = 1;
                    } else if (mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_RIGHT ||
                               mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_DOWNRIGHT ||
                               mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_UPRIGHT) {
                        field_0x1137 = 2;
                    } else if (mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_STAB) {
                        field_0x1137 = 3;
                    } else {
                        field_0x1137 = 0;
                    }
                    startSound(SE_BLasBos_VO_DamageChance);
                    break;
                }

                if (mStateMgr.isState(StateID_SitDamage)) {
                    mNumConsecutiveSameDirectionAttacks = 0;
                    mSecondLastAttackDirection = mLastAttackDirection;
                    mChanceAttackCounter++;

                    if (mChanceAttackCounter >= 5 && mChanceCounter >= 3) {
                        if (mIsPhaseTwo >= 0) {
                            mChanceAttackCounter = 0;
                            field_0x1152 = 30;
                            dStageMgr_c::GetInstance()->fn_80199B60(4);
                            mStateMgr.changeState(StateID_Down);
                            mLightingStrikeState = LIGHTNING_STRIKE_START_DEMO;
                            startSound(SE_BLasBos_DownHit);
                            break;
                        }
                        mStateMgr.changeState(StateID_Down);
                        field_0x1152 = 45;
                        break;
                    }

                    v.x = 0.0f;
                    v.y = 0.0f;
                    v.z = -20.0f;
                    mtx.YrotS(mYAngleToLink);
                    MTXMultVec(mtx, v, v2);
                    mAttackKnockbackX = v2.x;
                    mAttackKnockbackZ = v2.z;
                    field_0x11B8 = 5.0f;
                    field_0x11A4 = 7.0f;
                    if (mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_LEFT ||
                        mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_DOWNLEFT ||
                        mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_UPLEFT) {
                        field_0x1137 = 1;
                    } else if (mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_RIGHT ||
                               mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_DOWNRIGHT ||
                               mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_UPRIGHT) {
                        field_0x1137 = 2;
                    } else if (mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_DOWN) {
                        field_0x1137 = 3;
                    } else {
                        field_0x1137 = 0;
                    }
                    mSubState = SUB_STATE_5;
                    startSound(SE_BLasBos_VO_DamageChance);
                    break;
                }

                if (mChanceAttackCounter >= 3) {
                    mStateMgr.changeState(StateID_SitDamage);
                    mChanceAttackCounter = 0;
                } else {
                    mStateMgr.changeState(StateID_Damage);
                }

                mNumConsecutiveSameDirectionAttacks = -1;
                mSecondLastAttackDirection = mLastAttackDirection;
                v.x = 0.0f;
                v.y = 0.0f;
                v.z = -20.0f;
                mtx.YrotS(mYAngleToLink);
                MTXMultVec(mtx, v, v2);
                mAttackKnockbackX = v2.x;
                mAttackKnockbackZ = v2.z;
                field_0x1138 = 10;
                field_0x11B8 = 5.0f;
                field_0x11A4 = 7.0f;
                if (mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_LEFT ||
                    mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_DOWNLEFT ||
                    mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_UPLEFT) {
                    field_0x1137 = 1;
                } else if (mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_RIGHT ||
                           mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_DOWNRIGHT ||
                           mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_UPRIGHT) {
                    field_0x1137 = 2;
                } else if (mLastAttackDirection == daPlayerActBase_c::ATTACK_DIRECTION_STAB) {
                    field_0x1137 = 3;
                } else {
                    field_0x1137 = 0;
                }
                break;
            }
        }
    }

    return true;
}

void dAcBlastboss_c::setAnm(const char *name, f32 blend) {
    if (mpCurrentAnm != name) {
        mMdl.setAnm(name, m3d::PLAY_MODE_4, blend);
        mpCurrentAnm = name;
    }
}

void dAcBlastboss_c::forceSetAnm(const char *name, f32 blend) {
    mMdl.setAnm(name, m3d::PLAY_MODE_4, blend);
    mpCurrentAnm = name;
}

void dAcBlastboss_c::setAnmRate(f32 rate) {
    mMdl.setRate(rate);
}

int dAcBlastboss_c::draw() {
    drawModelType1(&mMdl.getModel());
    mSwordMdl.entry(this, nullptr, nullptr);
    if (field_0x114C) {
        drawModelType1(&mThunderMdl);
    }
    return SUCCEEDED;
}

bool dAcBlastboss_c::checkForCloseRangeAttack() {
    dAcPy_c *link = dAcPy_c::GetLinkM();

    bool ret = false;
    if (mXZDistanceToLink < 500.0f) {
        if (mIsPhaseTwo != 0) {
            if (mTimers[TIMER_2] == 0) {
                mStateMgr.changeState(StateID_Attack);
                ret = true;
            }
        } else {
            if (link->isUsingShield()) {
                mLinkCloseRangeShieldTime++;
            } else {
                mLinkCloseRangeShieldTime = 0;
            }
            if (mLinkCloseRangeShieldTime > 40) {
                mStateMgr.changeState(StateID_PunchAttack);
                ret = true;
            } else if (mTimers[TIMER_2] == 0) {
                if (checkForLinkSwordBySwordHit() && cM::rnd() < 0.35f) {
                    mStateMgr.changeState(StateID_SmallAttack);
                    ret = true;
                } else {
                    mStateMgr.changeState(StateID_Attack);
                    ret = true;
                }
                mLinkCloseRangeShieldTime = 0;
            }
        }

    } else {
        mLinkCloseRangeShieldTime = 0;
    }

    return ret;
}

bool dAcBlastboss_c::checkForRangeAttack() {
    dAcPy_c *link = dAcPy_c::GetLinkM();

    bool ret = false;

    if (mIsPhaseTwo != 0 && !link->isRecovering() && mStunCounter != 0 && link->getSwordPos().y > 270.0f) {
        field_0x1162++;
    } else {
        field_0x1162 = 0;
    }

    if (mIsPhaseTwo != 0) {
        if (field_0x1162 >= 20) {
            mStateMgr.changeState(StateID_DashAttack);
            return true;
        } else if (mIsSwordEmpowered) {
            if ((link->checkSwordAndMoreStates(0x400000) && mStunCounter != 0) ||
                (mXZDistanceToLink < 1500.0f && mTimers[TIMER_3] == 0)) {
                mStateMgr.changeState(StateID_ThunderAttack);
                ret = true;
            }
        } else {
            if (link->checkSwordAndMoreStates(0x400000)) {
                if ((mCounter & 0x7) == 0 && mStunCounter != 0) {
                    mStateMgr.changeState(StateID_DashAttack);
                    ret = true;
                }
            } else {
                if (mXZDistanceToLink < 1500.0f && dLightEnv_c::GetPInstance()->getField_0x38DC() == 6) {
                    if (field_0x1162 > 10) {
                        mStateMgr.changeState(StateID_DashAttack);
                        ret = true;
                    } else {
                        mStateMgr.changeState(StateID_ThunderWait);
                        ret = true;
                    }
                }
            }
        }
    }

    return ret;
}

bool dAcBlastboss_c::checkForCounter() {
    bool ret = false;

    if (field_0x113E != 0) {
        return false;
    }

    f32 attackRange = 450.0f;
    if (dAcPy_c::GetLinkM()->isAttackingJumpSlash()) {
        attackRange = 750.0f;
    }

    if (mXZDistanceToLink < attackRange) {
        dAcPy_c *link = dAcPy_c::GetLinkM();
        if (link->checkFlags0x354(0x04)) {
            if (link->isAttackingSpin()) {
                field_0x11A4 = 15.0f;
            } else if (link->isAttacking()) {
                field_0x11A4 = 7.0f;
            }

            s32 attackDir = link->getSpecificAttackDirection();

            if (mStateMgr.isState(StateID_Attack)) {
                if (mpCurrentAnm == "AttackR" &&
                    (attackDir == dAcPy_c::ATTACK_DIRECTION_RIGHT || attackDir == dAcPy_c::ATTACK_DIRECTION_UPRIGHT ||
                     attackDir == dAcPy_c::ATTACK_DIRECTION_DOWNRIGHT)) {
                    mGuardDirection = GUARD_RIGHT;
                } else if (mpCurrentAnm == "AttackL" && (attackDir == dAcPy_c::ATTACK_DIRECTION_LEFT ||
                                                         attackDir == dAcPy_c::ATTACK_DIRECTION_UPLEFT ||
                                                         attackDir == dAcPy_c::ATTACK_DIRECTION_DOWNLEFT)) {
                    mGuardDirection = GUARD_LEFT;
                } else if (mpCurrentAnm == "AttackU" && attackDir == daPlayerActBase_c::ATTACK_DIRECTION_DOWN) {
                    mGuardDirection = GUARD_UP;
                } else {
                    return false;
                }

                mStateMgr.changeState(StateID_Guard);
                mSubState = SUB_STATE_10;
            } else {
                if ((mIsPhaseTwo > 0 || mXZDistanceToLink < 200.0f) && mNumConsecutiveOppositeDirectionAttacks < 1) {
                    mNumConsecutiveOppositeDirectionAttacks = 1;
                }

                switch (attackDir) {
                    case daPlayerActBase_c::ATTACK_DIRECTION_DOWN:      mGuardDirection = GUARD_UP; break;
                    case daPlayerActBase_c::ATTACK_DIRECTION_LEFT:      mGuardDirection = GUARD_LEFT; break;
                    case daPlayerActBase_c::ATTACK_DIRECTION_DOWNLEFT:  mGuardDirection = GUARD_UPLEFT; break;
                    case daPlayerActBase_c::ATTACK_DIRECTION_UPLEFT:    mGuardDirection = GUARD_DOWNLEFT; break;
                    case daPlayerActBase_c::ATTACK_DIRECTION_RIGHT:     mGuardDirection = GUARD_RIGHT; break;
                    case daPlayerActBase_c::ATTACK_DIRECTION_DOWNRIGHT: mGuardDirection = GUARD_UPRIGHT; break;
                    case daPlayerActBase_c::ATTACK_DIRECTION_UPRIGHT:   mGuardDirection = GUARD_DOWNRIGHT; break;
                    case daPlayerActBase_c::ATTACK_DIRECTION_UP:        mGuardDirection = GUARD_DOWN; break;
                    default:                                            mGuardDirection = GUARD_CENTER; break;
                }
                if (!mStateMgr.isState(StateID_Guard)) {
                    mStateMgr.changeState(StateID_Guard);
                }

                mSubState = SUB_STATE_0;
            }
            ret = true;
        }
    }
    return ret;
}

void dAcBlastboss_c::updateMainNodeTransforms() {
    if (field_0x1131 != 0) {
        s16 ang = 0x800;
        if (field_0x1131 == 2) {
            ang = 0x300;
        }
        dAcPy_c *link = dAcPy_c::GetLinkM();
        mVec3_c attentionTarget = link->getPosition();
        attentionTarget.y += 250.0f;
        if (link->isUsingBugnet() && mXZDistanceToLink < 400.0f) {
            field_0x117C = 5;
            mBugNetPos = link->getBugNetPos();
        }

        s16 diff;
        if (field_0x117C != 0) {
            attentionTarget = mBugNetPos;
            mAng targetAngle = cLib::targetAngleY(mPosition, attentionTarget);
            diff = targetAngle - mRotation.y;
        } else {
            diff = mYAngleToLink - mRotation.y;
        }
        if (diff > 0x1800) {
            diff = 0x1800;
        } else if (diff < -0x1800) {
            diff = -0x1800;
        }
        sLib::addCalcAngle(mMdlCallback.field_0x06.ref(), -diff, 2, ang);

        s16 diff2 = cLib::targetAngleX(attentionTarget, mPositionCopy2);
        if (diff2 > 0x1000) {
            diff2 = 0x1000;
        } else if (diff2 < -0x1000) {
            diff2 = -0x1000;
        }
        sLib::addCalcAngle(mMdlCallback.field_0x0A.ref(), diff2, 2, ang);

        if ((mCounter & 0xF) == 0 && cM::rnd() < 0.5f) {
            field_0x1170 = cM::rndFX(2000.0f);
        }
        sLib::addCalcAngle(mMdlCallback.field_0x0C.ref(), field_0x1170, 8, 0x200);
    } else {
        mMdlCallback.field_0x06 = mMdlCallback.field_0x0A = mMdlCallback.field_0x0C = 0;
        if (mLightingStrikeState == LIGHTNING_STRIKE_ENDING) {
            mMdlCallback.field_0x0A = 2000;
        }
    }

    if (field_0x1178 != 0) {
        field_0x1178--;
        s16 target = mAng(field_0x1178 * 0xE00).sin() * 1500.0f;
        sLib::addCalcAngle(mMdlCallback.field_0x08.ref(), target, 2, 200);
    } else {
        sLib::addCalcAngle(mMdlCallback.field_0x08.ref(), 0, 2, 100);
    }

    s16 t3 = 0;
    if (field_0x1182 != 0) {
        field_0x1182--;
        t3 = 4000;
    }
    sLib::addCalcAngle(mMdlCallback.field_0x12.ref(), t3, 4, 200);
    sLib::addCalcAngle(mMdlCallback.field_0x14.ref(), field_0x1172, 2, 0x200);
    sLib::addCalcAngle(mMdlCallback.field_0x16.ref(), field_0x1174, 2, 0x200);

    field_0x1172 = 0;
    field_0x1174 = 0;

    if (field_0x1138 != 0) {
        field_0x1138--;

        field_0x117A = 2000;
        s16 t4 = 0, t5 = 0;
        if (field_0x1137 == 0) {
            field_0x117A = 3000;
            t5 = 5000;
        } else if (field_0x1137 == 1) {
            t5 = 3000;
            t4 = 3000;
        } else if (field_0x1137 == 2) {
            t5 = 3000;
            t4 = -3000;
        } else if (field_0x1137 == 3) {
            field_0x117A = 3000;
            t5 = -5000;
        }

        sLib::addCalcAngle(mMdlCallback.field_0x0E.ref(), t4, 2, field_0x117A);
        sLib::addCalcAngle(mMdlCallback.field_0x10.ref(), t5, 2, field_0x117A);
    } else if (field_0x1188 != 0) {
        field_0x1188--;
        s16 target = mAng(mCounter * 0x6510).sin() * 300.0f;
        mMdlCallback.field_0x2E = target;
    } else {
        sLib::addCalcAngle(mMdlCallback.field_0x0E.ref(), 0, 4, field_0x117A / 4);
        sLib::addCalcAngle(mMdlCallback.field_0x10.ref(), 0, 4, field_0x117A / 4);
    }

    if (field_0x1133 == 0) {
        sLib::addCalcScaled(&mMdlCallback.field_0x30, 0.5f, 0.5f);
        sLib::addCalcAngle(mMdlCallback.field_0x18.ref(), 0, 4, 400);
        sLib::addCalcAngle(mMdlCallback.field_0x1A.ref(), 0, 4, 400);
    }
}

void dAcBlastboss_c::updateSkirtHairTransforms() {
    static const u16 sNodeIds[] = {
        B_LAST_BOSS_NODE_skirtAU1, B_LAST_BOSS_NODE_skirtBU1, B_LAST_BOSS_NODE_skirtCU1, B_LAST_BOSS_NODE_skirtDU1,
        B_LAST_BOSS_NODE_skirtEU1, B_LAST_BOSS_NODE_skirtFU1, B_LAST_BOSS_NODE_skirtGU1, B_LAST_BOSS_NODE_skirtHU1,
    };

    static const s16 sAngs[] = {
        0x2000, 0x2000, 0x4000, 0x6000, 0x8000, 0xA000, 0xC000, 0xE000,
    };

    f32 f = 80.0f;

    if (mpCurrentAnm == "AttackU") {
        if (mMdl.getAnm().getFrame() >= 18.0f && mMdl.getAnm().getFrame() <= 40.0f) {
            f = 150.0f;
        }
    } else if (mpCurrentAnm == "Counter") {
        if (mMdl.getAnm().getFrame() >= 17.0f && mMdl.getAnm().getFrame() <= 68.0f) {
            f = 120.0f;
        }
    } else if (mpCurrentAnm == "TurnR") {
        if (mMdl.getAnm().getFrame() >= 19.0f && mMdl.getAnm().getFrame() <= 30.0f) {
            f = 100.0f;
        }
    } else if (mpCurrentAnm == "AttackJumpEnd") {
        if (mMdl.getAnm().getFrame() <= 18.0f) {
            f = 120.0f;
        }
    } else if (mpCurrentAnm == "AttackJumpStart" || mpCurrentAnm == "AttackJumpLoop") {
        f = 120.0f;
    } else if (mpCurrentAnm == "DownGetUp") {
        // same value
        f = 80.0f;
    }
    sLib::addCalcScaledDiff(&field_0x11B4, f, 0.5f, 20.0f);

    mMtx_c mtx;
    mVec3_c v;
    mVec3_c t(0.0f, 0.0f, 12.0f);
    for (int i = 0; i < (int)ARRAY_LENGTH(field_0x1268); i++) {
        field_0x1268[i].field_0x002++;
        field_0x1268[i].field_0x004 = sAngs[i] + mRotation.y;
        mMdl.getModel().getNodeWorldMtx(sNodeIds[i], mtx);
        mtx.getTranslation(v);
        mtx.YrotS(field_0x1268[i].field_0x004);
        MTXMultVec(mtx, t, field_0x1268[i].field_0x014[0]);
        field_0x1268[i].field_0x014[0] += v;
        if (nw4r::math::FAbs(field_0x1268[i].field_0x010) < 1.0f) {
            // TODO - regswaps between tmp and abs result
            f32 tmp = field_0x1268[(i + 1) & 7].field_0x010;
            if (nw4r::math::FAbs(tmp) > 1.0f) {
                field_0x1268[i].field_0x010 = tmp * 0.5f;
            }
            tmp = field_0x1268[(i - 1) & 7].field_0x010;
            if (nw4r::math::FAbs(tmp) > 1.0f) {
                field_0x1268[i].field_0x010 = tmp * 0.5f;
            }
        }

        f32 f4 = field_0x1268[i].field_0x010 + field_0x11A4;
        if (nw4r::math::FAbs(f4) > 0.1f) {
            if (field_0x11A4 > 0.1f) {
                field_0x1268[i].field_0x006 = mRotation.y + 0x8000 + moreAngs[i];
            } else if (field_0x11A4 < -0.1f) {
                field_0x1268[i].field_0x006 = mRotation.y + 0x8000 - moreAngs[i];
            }

            if (nw4r::math::FAbs(field_0x11A4) > 25.0f) {
                field_0x1268[i].field_0x00A = 8000;
            } else {
                field_0x1268[i].field_0x00A = 5000;
            }

            if (i >= 2 && i <= 6) {
                field_0x1268[i].field_0x00C = f4;
            } else {
                field_0x1268[i].field_0x00C = 0.75f * f4;
            }
            field_0x11B0 = 0.0f;
        } else {
            sLib::addCalcScaled(&field_0x1268[i].field_0x00C, 0.05f, 2.0f);
            sLib::addCalcAngle(&field_0x1268[i].field_0x00A, 1000, 4, 200);
        }
        field_0x1268[i].field_0x008 += field_0x1268[i].field_0x00A;
        updateSkirtTransform(i);
    }

    mMdl.getModel().getNodeWorldMtx(B_LAST_BOSS_NODE_head, mtx);
    mtx.fn_802F1C40(0, 1);
    mAng3_c ang;
    mtx.toRot(ang);
    ang.y += 0x4000;
    ang.x += 0;
    ang.z += 6500;
    t.set(-20.0f, -20.0f, 0.0f);
    for (int i = 0; i < 1; i++) {
        field_0x2948[i].field_0x002 = ang.y;
        field_0x2948[i].field_0x004 = -ang.z;
        field_0x2948[i].field_0x006 = -ang.x;
        mMdl.getModel().getNodeWorldMtx(B_LAST_BOSS_NODE_head, mtx);
        MTXMultVec(mtx, t, field_0x2948[i].field_0x008[0]);
        updateHairTransform(i);
    }

    if (!mIsDown) {
        for (int i = 0; i < 9; i++) {
            f32 f = (i % 3) * 3 + 1;
            if (i < 3) {
                mMdlCallback.field_0x1C[i] = (s16)(f * 500.0f * mAng(mCounter * 0x2600 - (i * 24000)).sin());
            } else if (i < 6) {
                mMdlCallback.field_0x1C[i] = -(s16)(f * 500.0f * mAng(mCounter * 0x2800 - (i * 25000)).sin());
            } else {
                mMdlCallback.field_0x1C[i] = -(s16)(f * 500.0f * mAng(mCounter * 0x2A00 - (i * 26000)).sin());
            }
        }
    }

    if (field_0x114E != 0) {
        holdSound(SE_BLasBos_SkirtAir);
    }
}

void dAcBlastboss_c::updateSkirtTransform(s32 level) {
    // NONMATCHING but probably equivalent

    UnkLastBossCcSph2 &cc = field_0x1268[level];
    mVec3_c *v1 = &cc.field_0x014[1];
    mVec3_c *v2 = &cc.field_0x08C[0];
    mVec3_c *v3 = &cc.field_0x104[1];

    mVec3_c t1(0.0f, 0.0f, 33.25f);

    s32 offset1 = 0;

    mVec3_c d1;

    d1 = mToeTranslation[0] - mBackbone1Translation;

    mMtx_c mtx;
    mtx.YrotS(-mRotation.y);
    MTXMultVec(mtx, d1, d1);
    if (d1.z > 0.0f) {
        offset1 = -1;
    }

    d1 = mToeTranslation[1] - mBackbone1Translation;
    MTXMultVec(mtx, d1, d1);
    if (d1.z > 20.0f) {
        offset1 = 1;
    }

    // hmm yes
    mVec3_c p3;
    mVec3_c v4;
    mVec3_c p2;
    mVec3_c v5;
    mVec3_c p1;

    p1.set(0.0f, 0.0f, 0.0f);
    p2.set(0.0f, 0.0f, 0.0f);
    bool isAttackJumpStart = false;
    if (mpCurrentAnm == "AttackJumpStart") {
        isAttackJumpStart = true;
    }
    f32 f;
    if (level == 7 && offset1 > 0) {
        f = 10.0f;
    } else if (level >= 0 && level <= 1 && offset1 < 0) {
        f = 50.0f;
    } else {
        f = -3.0f;
    }
    if (level >= 0 && level < 2 && isAttackJumpStart) {
        p1.y = 500.0f;
        f = 50.0f;
    }

    sLib::addCalcScaledDiff(&field_0x11D0, f, 1.0f, 2.0f);
    p1.z = field_0x11D0;

    p3.set(0.0f, 0.0f, 2.0f);
    mtx.YrotS(cc.field_0x004);
    MTXMultVec(mtx, p3, v4);
    MTXMultVec(mtx, p1, v5);
    mtx.YrotS(mRotation.y);
    MTXMultVec(mtx, p2, p2);

    f32 fAdd = -15.0f;
    f32 f1 = cc.field_0x00C;
    sLib::addCalcScaledDiff(&field_0x11AC, field_0x11B0, 1.0f, 0.02f);

    s32 offset2 = 0;
    if (offset1 < 0 && (level == 0) || level == 1 || level == 2 || level == 7) {
        offset2 = 1;
    }

    f32 f2 = field_0x11B4;
    u32 u12 = (cc.field_0x002 & 1) + 1;
    if (level >= 2 && level <= 6) {
        u12 += 2;
        cc.mCc.SetR(30.0f);
    } else {
        cc.mCc.SetR(50.0f);
    }

    if (mIsDown) {
        offset2 = 0;
        v4.set(0.0f, 0.0f, 0.0f);
        p2.set(0.0f, 0.0f, 0.0f);
    }

    f1 *= 0.75f;

    static const f32 sFloats[] = {
        15.0f, 15.0f, 15.0f, 15.0f, 13.0f, 11.0f, 9.0f, 7.0f, 5.0f,
    };

    s32 actual = offset2 - 1;

    for (u32 i = 1; i < 10; i++, v1++, v2++, v3++) {
        mtx.YrotS(cc.field_0x006);
        d1.x = 0.0f;
        d1.y = mAng(cc.field_0x008 - i * 8000).sin() * f1;
        d1.z = f1;
        mVec3_c v6;
        MTXMultVec(mtx, d1, v6);
        s32 zero = 0;
        f1 = f1 * (zero * 0.01f + 1.1f);

        f32 fY = fAdd + v1[0].y + v4.y + v3[0].y + v6.y + v5.y;
        if (i >= 1) {
            v5.y = 0.0f;
        } else {
            // @bug unreachable
            v5.y *= 0.5f;
        }

        f32 f4;
        if (mIsDown) {
            f4 = 5.0f;
        } else {
            f4 = sFloats[i - 1];
        }

        if (fY <= f4) {
            fY = f4;
        }

        f32 fX = v1[0].x + v4.x + p2.x + v3[0].x + v6.x + v5.x;
        f32 fZ = v1[0].z + v4.z + p2.z + v3[0].z + v6.z + v5.z;
        if (offset2 != 0) {
            f32 dX = fX - field_0x1250[actual].x;
            f32 dZ = fZ - field_0x1250[actual].z;
            f32 f11 = nw4r::math::FSqrt(dX * dX + dZ * dZ);
            if (f11 < f2) {
                mtx.YrotS(cM::atan2s(dX, dZ));
                d1.set(0.0f, 0.0f, f2);
                MTXMultVec(mtx, d1, d1);
                fX = d1.x + field_0x1250[actual].x;
                fZ = d1.z + field_0x1250[actual].z;
            }
        }

        fX -= v1[-1].x;
        fY -= v1[-1].y;
        fZ -= v1[-1].z;
        v5.x *= 0.4f;
        v5.z *= 0.4f;
        v2[0].x = -cM::atan2s(fY, fZ);
        f32 f8 = nw4r::math::FSqrt(fY * fY + fZ * fZ);

        v2[0].y = cM::atan2s(fX, f8);
        v2[0].z = mRotation.y;

        mtx.XrotS(v2[0].x);
        mtx.YrotM(v2[0].y);
        MTXMultVec(mtx, t1, d1);
        v3[0] = v1[0];
        v1[0] = v1[-1] + d1;
        v3[0] = (v1[0] - v3[0]) * field_0x11AC;
        if (i == u12) {
            cc.field_0x010 = 0.0f;
            cc.mCc.SetC(v1[0]);
            if (cc.mCc.ChkTgHit()) {
                cc.field_0x006 = moreAngs[level] + dAcPy_c::GetLinkM()->mRotation.y;
                cc.field_0x010 = 25.0f;
                field_0x114E = 1;
            }
        }
    }
}

void dAcBlastboss_c::updateHairTransform(s32 level) {
    // NONMATCHING but probably equivalent

    UnkLastBossCcSph1 &cc = field_0x2948[level];
    mVec3_c *v1 = &cc.field_0x008[1];
    mVec3_c *v2 = &cc.field_0x044[0];
    mVec3_c *v3 = &cc.field_0x080[1];

    mMtx_c mtx2;
    mMtx_c mtx;
    mVec3_c t1(0.0f, 0.0f, 47.5f);
    mVec3_c v;
    mVec3_c t2(0.0f, 0.0f, -10.0f);

    mtx.YrotS(cc.field_0x002);
    mtx.XrotM(cc.field_0x004);
    MTXMultVec(mtx, t2, t2);

    f32 f1 = field_0x11B8;
    f32 f2 = -field_0x11B8;
    f32 f4 = 0.9f;
    f32 fAdd = -5.0f;
    f32 fScale = 1.0f;

    for (u32 i = 1; i < 5; i++, v1++, v2++, v3++) {
        v.x = f1 * mAng(mCounter * 12000 - (i * 20000)).sin();
        v.y = f1 * mAng(mCounter * 10000 - (i * 20000)).sin();
        v.z = f2;
        v.x += mAng(mCounter * 10000 - (i * 20000)).sin() * fScale;
        v.y += mAng(mCounter * 12000 - (i * 20000)).sin() * fScale;
        MTXMultVec(mtx, v, v);

        f1 *= 1.6f;

        f32 f13;
        if (field_0x113D != 0) {
            f13 = fAdd + v1[0].y + t2.y + v3[0].y + v.y;
            if (f13 < mPositionCopy2.y) {
                f13 = mPositionCopy2.y;
            }
            f13 = f13 - v1[-1].y;
        } else if (mIsDown) {
            f13 = fAdd + v1[0].y + v3[0].y;
            if (f13 <= 0.0f) {
                f13 = 0.0f;
            }
            f13 = f13 - v1[-1].y;
            v.set(0.0f, 0.0f, 0.0f);
        } else {
            f13 = fAdd + (v1[0].y - v1[-1].y) + t2.y + v3[0].y + v.y;
        }

        f32 fX2 = (v1[0].x - v1[-1].x) + t2.x + v3[0].x + v.x;
        f32 fZ2 = (v1[0].z - v1[-1].z) + t2.z + v3[0].z + v.z;

        t2.x *= 0.6f;
        t2.y *= 0.6f;
        t2.z *= 0.6f;
        v2[0].y = cM::atan2s(fX2, fZ2);
        f32 f8 = nw4r::math::FSqrt(fX2 * fX2 + fZ2 * fZ2);
        v2[0].x = -cM::atan2s(f13, f8);
        v2[0].z = cc.field_0x006;

        mtx2.YrotS(v2[0].y);
        mtx2.XrotM(v2[0].x);
        MTXMultVec(mtx2, t1, v);

        v3[0] = v1[0];
        v1[0] = v1[-1] + v;
        v3[0] = (v1[0] - v3[0]) * f4;
    }

    f32 target = 0.0f;
    cc.mCc.SetC(mPositionCopy2);
    cc.mCc.SetR(30.0f);
    if (cc.mCc.ChkTgHit()) {
        target = 7.0f;
        holdSound(SE_BLasBos_HairAir);
        if (field_0x1178 == 0) {
            field_0x1178 = 18;
        }
    }
    sLib::addCalcScaledDiff(&field_0x11B8, target, 0.2f, 0.7f);
}

bool dAcBlastboss_c::checkForLinkSwordBySwordHit() {
    dAcPy_c *link = dAcPy_c::GetLinkM();

    bool ret = !link->isUsingShield();
    if (ret) {
        ret = link->isUsingSword();
    }
    if (ret) {
        ret = link->getSwordPos().y > 100.0f;
    }
    if (ret) {
        ret = link->getSwordPos().y < 230.0f;
    }
    if (ret) {
        ret = (mPosition - link->getSwordPos()).absXZ() < 260.0f;
    }

    return ret;
}

u8 dAcBlastboss_c::classifyAttackDirection(s32 attackDir) {
    switch (attackDir) {
        case daPlayerActBase_c::ATTACK_DIRECTION_LEFT:
        case daPlayerActBase_c::ATTACK_DIRECTION_DOWNLEFT:
        case daPlayerActBase_c::ATTACK_DIRECTION_UPLEFT:    return 0;

        case daPlayerActBase_c::ATTACK_DIRECTION_RIGHT:
        case daPlayerActBase_c::ATTACK_DIRECTION_DOWNRIGHT:
        case daPlayerActBase_c::ATTACK_DIRECTION_UPRIGHT:   return 1;

        case daPlayerActBase_c::ATTACK_DIRECTION_DOWN:      return 2;
        case daPlayerActBase_c::ATTACK_DIRECTION_UP:        return 3;

        case daPlayerActBase_c::ATTACK_DIRECTION_STAB:      return 4;

        default:                                            return -1;
    }
}

void dAcBlastboss_c::executeLightningStrike() {
    dCamera_c *cam = dScGame_c::getCamera();
    dAcPy_c *link = dAcPy_c::GetLinkM();

    mMtx_c mtx;
    mMtx_c mtx1;
    mVec3_c v;
    mVec3_c effPos;

    bool b1 = false;
    bool b2 = false;
    bool spawnEffect = false;
    dLightEnv_c &lightEnv = dLightEnv_c::GetInstance();

    switch (mLightingStrikeState) {
        case LIGHTNING_STRIKE_START_DEMO: {
            {
                // this works but I don't like it
                static volatile u32 FLAGS_1 = 0x00000001;
                static u32 FLAGS_2 = 0x00100001;
                u32 f1 = FLAGS_1;
                u32 f2 = FLAGS_2;
                Event e = Event("BLS_demo2", 100, f2 & ~f1, nullptr, nullptr);
                EventManager::alsoSetAsCurrentEvent(this, &e, nullptr);
            }
            if (EventManager::isInEvent(this, "BLS_demo2")) {
                field_0x2CEE = 0;
                field_0x2D00 = 0.0f;
                field_0x2CFC = 60.0f;
                mLightingStrikeState = LIGHTNING_STRIKE_DEMO;
                // huh
                field_0x2D10.x = cam->getTarget().x;
                field_0x2D10.y = cam->getTarget().y;
                field_0x2D10.z = cam->getTarget().z;
                field_0x2D04.x = cam->getPosition().x;
                field_0x2D04.y = cam->getPosition().y;
                field_0x2D04.z = cam->getPosition().z;
                mIsPhaseTwo = -1;
                // fall-through
            } else {
                break;
            }
        }
        case LIGHTNING_STRIKE_DEMO: {
            if (field_0x2CEE < 30) {
                b2 = true;
            }
            mtx.YrotS((s16)field_0x2CF4 + mRotation.y);
            v.set(0.0f, 100.0f, 400.0f);
            MTXMultVec(mtx, v, v);
            v += mPosition;
            cLib::addCalcPos2(&field_0x2D04, v, 0.1f, 30.0f);
            if (field_0x2CEE > 5) {
                sLib::addCalcScaledDiff(&field_0x2D00, 1.0f, 1.0f, 0.1f);
                sLib::addCalcScaledDiff(&field_0x2CF4, 6000.0f, 0.05f, field_0x2D00 * 100.0f);
            }
            // fall-through
        }
        case LIGHTNING_STRIKE_WAIT: {
            b1 = true;
            if (field_0x2CEE > 205) {
                sLib::addCalcScaledDiff(&field_0x2D00, 1.0f, 1.0f, 0.1f);
                if (field_0x2CEE >= 290) {
                    if (field_0x2CEE == 290) {
                        field_0x2D00 = 0.0f;
                    }
                    sLib::addCalcScaledDiff(&field_0x2CFC, 50.0f, 0.05f, field_0x2D00);
                    sLib::addCalcScaledDiff(&field_0x2D10.y, 290.0f, 0.1f, field_0x2D00 * 5.0f);
                } else {
                    sLib::addCalcScaledDiff(&field_0x2CFC, 70.0f, 0.05f, field_0x2D00);
                    sLib::addCalcScaledDiff(&field_0x2D10.y, 420.0f, 0.1f, field_0x2D00 * 100.0f);
                    if (field_0x2CEE == 0xFA) {
                        dLightEnv_c::GetPInstance()->setField_0x38DC(7);
                        setAnm("CatchThunderEnd", 5.0f);
                        spawnEffect = true;
                        effPos.set(mSwordPosition.x, mSwordPosition.y + 110.0f, mSwordPosition.z);
                        mIsSwordEmpowered = true;
                        mSwordMdl.fn_8006B800(4);
                        mCc1.OnTg_0x8000000();
                        startSound(SE_BLasBos_ThunderFallSword);
                        mSubState = SUB_STATE_21;
                        mTimers[TIMER_3] = cM::rndF(30.0f) + 200.0f;
                        dRumble_c::start(dRumble_c::sRumblePreset4, dRumble_c::FLAG_ACTIVE | dRumble_c::FLAG_SLOT0);
                        field_0x2CF0 = 15;
                    }
                    if (field_0x2CEE >= 200 && field_0x2CEE <= 250) {
                        b2 = true;
                    }
                }
            } else {
                cLib::addCalcPos2(&field_0x2D10, mChestTranslation, 0.1f, 50.0f);
            }
            break;
        }
        case LIGHTNING_STRIKE_TRIGGER_END: {
            {
                // this works but I don't like it
                static volatile u32 FLAGS_1 = 0x00000001;
                static u32 FLAGS_2 = 0x00100001;
                u32 f1 = FLAGS_1;
                u32 f2 = FLAGS_2;
                Event e = Event("BLS_end", 100, f2 & ~f1, nullptr, nullptr);
                EventManager::alsoSetAsCurrentEvent(this, &e, nullptr);
            }
            if (EventManager::isInEvent(this, "BLS_end")) {
                field_0x2CEE = 0;
                field_0x2D00 = 0.0f;
                field_0x2CFC = 50.0f;
                mLightingStrikeState = LIGHTNING_STRIKE_ENDING;
                link->onFlags_0x360(0x200);
                if (mpSwordBattleGame != nullptr) {
                    mpSwordBattleGame->notifyBossDeath(false);
                }
                StoryflagManager::sInstance->setFlagOrCounterToValue(STORYFLAG_692, 0);
                dStage_c::GetInstance()->getRoom(0)->someLastBossThing(false);
                // fall-through
            } else {
                break;
            }
        }
        case LIGHTNING_STRIKE_ENDING: {
            b1 = true;
            if (field_0x2CEE < 30) {
                b2 = true;
            }
            mtx.YrotS(mRotation.y - 0x4000);
            v.set(0.0f, 100.0f, 1500.0f);
            MTXMultVec(mtx, v, v);
            field_0x2D04 = mBackbone1Translation + v;
            field_0x2D10.x = mBackbone1Translation.x;
            field_0x2D10.y = 400.0f;
            field_0x2D10.z = mBackbone1Translation.z;
            sLib::addCalcScaledDiff(&field_0x2CFC, 60.0f, 0.035f, 0.1f);
            if (field_0x2CEE == 160) {
                field_0x114C = 1;
            }

            if (field_0x2CEE >= 159) {
                if (field_0x2CEE < 170) {
                    sLib::addCalcScaledDiff(&field_0x11C4, cM::rndF(0.3f) + 0.7f, 1.0f, 0.4f);
                } else {
                    sLib::addCalcScaled(&field_0x11C4, 1.0f, 0.02f);
                }
            } else {
                if (field_0x2CEE < 10) {
                    sLib::addCalcScaledDiff(&field_0x11C4, 0.7f, 1.0f, 0.35f);
                } else {
                    sLib::addCalcScaled(&field_0x11C4, 1.0f, 0.035f);
                }
            }

            if (field_0x2CEE == 230) {
                link->offFlags_0x360(0x200);
            }

            if (field_0x2CEE == 270 && mpSwordBattleGame != nullptr) {
                mLightingStrikeState = LIGHTNING_STRIKE_FINISH;
            } else {
                lightEnv.setField_0x5D48(field_0x11C4);
                if (field_0x2CEE == 360) {
                    dScGame_c::GetInstance()->triggerExit(mRoomID, 1);
                }
            }
            break;
        }
        case LIGHTNING_STRIKE_TRIGGER_START: {
            {
                // this works but I don't like it
                static volatile u32 FLAGS_1 = 0x00000001;
                static u32 FLAGS_2 = 0x00100001;
                u32 f1 = FLAGS_1;
                u32 f2 = FLAGS_2;
                Event e = Event("BLS_start", 100, f2 & ~f1, nullptr, nullptr);
                EventManager::alsoSetAsCurrentEvent(this, &e, nullptr);
            }
            if (EventManager::isInEvent(this, "BLS_start")) {
                field_0x2CEE = 0;
                field_0x2D00 = 0.0f;
                field_0x2CFC = 60.0f;
                mLightingStrikeState = LIGHTNING_STRIKE_START;
                field_0x2D10.set(0.0f, 200.0f, -587.0f);
                field_0x2D04.set(0.0f, 197.0f, -207.0f);
                dAcPy_c::GetLinkM()->vt_0x2AC();
                dLytBossCaption_c::GetInstance()->show("BOSS_11_caption", 0);
                // fall-through
            } else {
                break;
            }
        }
        case LIGHTNING_STRIKE_START: {
            b1 = true;
            sLib::addCalcScaledDiff(&field_0x2D00, 1.0f, 1.0f, 0.05f);
            cLib::addCalcPos2(&field_0x2D10, mVec3_c(-2.6f, 118.0f, 298.0f), 0.1f, field_0x2D00 * 80.0f);
            cLib::addCalcPos2(&field_0x2D04, mVec3_c(-132.0f, 89.0f, 654.0f), 0.1f, field_0x2D00 * 80.0f);

            if (field_0x2CEE >= 20) {
                sLib::addCalcScaledDiff(&field_0x2CFC, 40.0f, 0.05f, 0.1f);
            }

            if (field_0x2CEE == 40) {
                dAcPy_c::GetLinkM()->triggerMoveEventMaybe(4, 0, 0, nullptr, 0, 1, 0);
            }

            if (field_0x2CEE == 110) {
                dLytBossCaption_c::GetInstance()->unk_inline();
            }

            if (field_0x2CEE >= 140) {
                EventManager::finishEvent(this, nullptr);
                dScGame_c::getCamera()->fn_8019EA70(true);
                mLightingStrikeState = LIGHTNING_STRIKE_STATE_NONE;
                unsetActorProperty(AC_PROP_0x4);
            }
            break;
        }
        case LIGHTNING_STRIKE_FINISH: {
            EventManager::finishEvent(this, nullptr);
            dScGame_c::getCamera()->fn_8019EA70(true);
            mLightingStrikeState = LIGHTNING_STRIKE_STATE_NONE;
            break;
        }
    }

    if (spawnEffect) {
        dJEffManager_c::spawnEffect(
            PARTICLE_RESOURCE_ID_MAPPING_624_, effPos, nullptr, nullptr, nullptr, nullptr, 0, 0
        );
        mLightingFallSwordTimer = 10;
    }

    if (b1) {
        field_0x2CEE++;
        mVec3_c tmp1 = field_0x2D10;
        if (field_0x2CF0 != 0) {
            field_0x2CF0--;
            s16 val = field_0x2CF0;
            f32 fac = val * 3.0f;
            tmp1.y += mAng(val * 0x1C20).cos() * fac;
        }
        cam->setEventCamView(tmp1, field_0x2D04, field_0x2CFC, 0.0f);
        if (b2) {
            mtx1.transS(0.5f, 0.5f, 0.0f);
            // REGSWAP
            s32 scale = 95;
            mtx1.scaleM(scale / 100.0f, scale / 100.0f, 0.0f);
            mtx1.transM(-0.5f, -0.5f, 0.0f);
            dStageMgr_c::GetInstance()->procfn_800192F0(0x96, mtx1, 0x32);
        }
    }

    if (field_0x114C != 0) {
        mtx.YrotS(cam->getXZAngle());
        v.set(0.0f, 10.0f, 10000.0f);
        MTXMultVec(mtx, v, v);
        v.x += cam->getPosition().x;
        v.z += cam->getPosition().z;
        mtx.transS(v);
        v.set(0.7f, 0.7f, 0.7f);
        mThunderMdl.setScale(v);
        mThunderMdl.setLocalMtx(mtx);
        mThunderAnmMatClr.play();
    }
}

void dAcBlastboss_c::setLightningTimerMaybe(s32 arg) {
    f32 f = arg + cM::rndF(arg * 0.5f);

    if (mStunCounter >= 2) {
        f *= 0.5f;
    } else if (mStunCounter == 1) {
        f *= 0.75f;
    }

    mLightningTimerMaybe = f;
}
