From b095269d49e415c6b6a20187ae55dfc25ec8550b Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:10:58 -0400 Subject: [PATCH 1/5] Enemy/bosstelesa: reconstruct BossTelesa TU scaffolding --- include/Enemy/BossTelesa.hpp | 284 +++++++++++++++ include/M3DUtil/MActor.hpp | 1 + src/Enemy/bosstelesa.cpp | 664 +++++++++++++++++++++++++++++++++++ 3 files changed, 949 insertions(+) create mode 100644 include/Enemy/BossTelesa.hpp diff --git a/include/Enemy/BossTelesa.hpp b/include/Enemy/BossTelesa.hpp new file mode 100644 index 000000000..f3d3995d5 --- /dev/null +++ b/include/Enemy/BossTelesa.hpp @@ -0,0 +1,284 @@ +#ifndef ENEMY_BOSS_TELESA_HPP +#define ENEMY_BOSS_TELESA_HPP + +#include +#include +#include +#include +#include + +class TBossTelesa; +class TBubble; + +/* The boss uses the same spine state objects as the regular Telesa, but has + * its own state machine for the roulette, tongue and bubble attacks. */ +DECLARE_NERVE(TNerveBossTelesaFallDemo, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaFreeze, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaPrepareSlot, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaSpitSlotItem, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaSlotStart, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaAppear, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaHideWait, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaHide, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaSpit, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaDie, TLiveActor); +DECLARE_NERVE(TNerveBubbleSplit, TLiveActor); +DECLARE_NERVE(TNerveBubbleLive, TLiveActor); + +class TBossTelesaSaveLoadParams : public TSpineEnemyParams { +public: + TBossTelesaSaveLoadParams(const char* path); + + /* 0x0A8 */ TParamRT mSLDamageRadius; + /* 0x0BC */ TParamRT mSLDamageHeight; + /* 0x0D0 */ TParamRT mSLAttackRadius; + /* 0x0E4 */ TParamRT mSLAttackHeight; + /* 0x0F8 */ TParamRT mSLGenAttackerTime; + /* 0x10C */ TParamRT mSLGenBubbleTime; + /* 0x120 */ TParamRT mSLHitAngle; + /* 0x134 */ TParamRT mSLNumGenBubble; + /* 0x148 */ TParamRT mSL1stBubbleSp; + /* 0x15C */ TParamRT mSLHideAreaRadius; + /* 0x170 */ TParamRT mSLSlotItemNum; + /* 0x184 */ TParamRT mSLSlotFruitNum; + /* 0x198 */ TParamRT mSLSlotFirstHitCollectRate; + /* 0x1AC */ TParamRT mSLSlotHitCollectRate; + /* 0x1C0 */ TParamRT mSLTransYOffset; + /* 0x1D4 */ TParamRT mSLStopSlotTime0; + /* 0x1E8 */ TParamRT mSLStopSlotTime1; + /* 0x1FC */ TParamRT mSLStopSlotTime2; + /* 0x210 */ TParamRT mSLSpicyTime; +}; + +class TBubbleSaveLoadParams : public TSpineEnemyParams { +public: + TBubbleSaveLoadParams(const char* path); + + /* 0x0A8 */ TParamRT mSLLiveTime; + /* 0x0BC */ TParamRT mSLNumDivision; + /* 0x0D0 */ TParamRT mSLMaxScale; + /* 0x0E4 */ TParamRT mSLAddPosBase; + /* 0x0F8 */ TParamRT mSLRateExpand; + /* 0x10C */ TParamRT mSLDeadHeight; +}; + +/* TSlotDrum already owns the geometry, angle and neon material fields. */ +class TTelesaSlot : public TSlotDrum { +public: + TTelesaSlot(const char* name); + + virtual void calcRootMatrix(); + virtual void moveObject(); + virtual void initMapObj(); + virtual u32 touchWater(THitActor*); + virtual void initNeonMatColor() { } + + int getForcastResult(int); + int getResultFromAng(f32); + int getSlotResult(); + u8 isRollDrum(); + void forceStopSlot(int); + void moveStart(); + void randomReset(); + void entryObjCollision(); + void calcObjCollision(); + int getDrumResult(int); + + /* 0x198 */ u8 mRoll0; + /* 0x199 */ u8 mRoll1; + /* 0x19A */ u8 mRoll2; + /* 0x19B */ s8 mRolling; + /* 0x19C */ u8 mForceStop; + /* 0x1A0 */ TBossTelesa* mOwner; + /* 0x1A4 */ s32 mForceResult; + /* 0x1A8 */ s8 mStop0; + /* 0x1A9 */ s8 mStop1; + /* 0x1AA */ s8 mStop2; + /* 0x1DC */ TMapCollisionMove* mMapCollisionMove; + /* 0x1E0 */ u8 mNeonState; +}; + +class TBossTelesa : public TSpineEnemy { +public: + TBossTelesa(const char* name = "ボステレサ"); + + virtual void loadAfter(); + virtual void perform(u32 cue, JDrama::TGraphics* graphics); + virtual BOOL receiveMessage(THitActor*, u32); + virtual void init(TLiveManager*); + virtual void calcRootMatrix(); + virtual void moveObject(); + virtual void kill(); + virtual const char** getBasNameTable() const; + virtual void reset(); + + void forceHide(); + void fanfale(); + bool isForceRestart(); + void rollRouletteCircle(); + void forceAllItemKill(); + bool checkAllItemDead(); + bool checkSlot(); + void fruitCollisionOn(); + void generateSlotItem(); + int checkSlotResult(); + void slotStop(); + void slotStart(); + bool isInDamage(); + void setBckAnm(int); + void offAllCollision(); + void onAllCollision(); + void openWaterPlace(); + void tongueHitWater(); + BOOL checkMessage(THitActor*, u32); + void prepareGenerate(); + void rouletteStart(); + void genAttacker(); + void flashItem(int); + u8 slotFall(); + u8 rouletteFall(); + void damageRecover(); + void setSpicy(TLiveActor*); + BOOL checkHitObject(THitActor*); + MtxPtr getTakingMtx(); + + static f32 mEnemyGenRate; + static f32 mItemGenRate; + static u8 mNormalAlpha; + static f32 mBaseHoseiPosY; + static f32 mRouletteUpRate; + static s32 mTelesaGenerateInterval; + static f32 mCameraMoveLimit; + static f32 mCameraMoveSp; + + /* 0x150 */ s32 mBattleState; + /* 0x154 */ s32 mBattleTimer; + /* 0x158 */ s32 mBattlePhase; + /* 0x15C */ TLiveActor* mMario; + /* 0x160 */ s32 mCurrentItem; + /* 0x164 */ s32 mCurrentFruit; + /* 0x168 */ f32 mFallSpeed; + /* 0x16C */ THitActor* mBodyCollision; + /* 0x170 */ THitActor* mTongueCollision; + /* 0x174 */ THitActor* mKillSmallEnemy; + /* 0x178 */ void* mItemList; + /* 0x17C */ void* mBubbleList; + /* 0x180 */ TBossTelesaSaveLoadParams* mSaveParams; + /* 0x184 */ TTelesaSlot* mSlot; + /* 0x188 */ JDrama::TViewObj* mTakingActor; + /* 0x18C */ u8 mIsSpicy; + /* 0x19C */ f32 mDamageBlend; + /* 0x1A0 */ f32 mDamageBlendSpeed; + /* 0x1A4 */ f32 mDamageBlendTarget; + /* 0x1A8 */ s32 mDamageTimer; + /* 0x274 */ s32 mStateTimer; + /* 0x348 */ u8 mItem0; + /* 0x349 */ u8 mItem1; + /* 0x34A */ u8 mItem2; + /* 0x34B */ u8 mItem3; + /* 0x34C */ u8 mFruit0; + /* 0x34D */ u8 mFruit1; + /* 0x34E */ u8 mFruit2; + /* 0x34F */ u8 mAlpha; + /* 0x350 */ u8 mDamage; + /* 0x354 */ s32 mGenerateTimer; + /* 0x358 */ s16 mMarioHP; + /* 0x35A */ u8 mForceRestart; + /* 0x35B */ u8 mInDamage; + /* 0x35C */ s32 mRouletteTimer; + /* 0x360 */ f32 mRoulettePosY; + /* 0x364 */ f32 mRouletteSp; + /* 0x368 */ s32 mSlotResult; + /* 0x36C */ s32 mSlotItemCount; + /* 0x370 */ u8 mSlotStop; + /* 0x374 */ JGeometry::TVec3 mEffectPos; + /* 0x384 */ s8 mDead; +}; + +class TBossTelesaKillSmallEnemy : public THitActor { +public: + TBossTelesaKillSmallEnemy(TBossTelesa* owner = nullptr) + : THitActor("ボステレサ小敵キラー"), mOwner(owner), mHit(false) + { + } + + virtual BOOL receiveMessage(THitActor* sender, u32 message); + void checkHit(); + + /* 0x68 */ TBossTelesa* mOwner; + /* 0x6C */ bool mHit; +}; + +class TBossTelesaTongue : public THitActor { +public: + TBossTelesaTongue(TBossTelesa* owner = nullptr) + : THitActor("ボステレサ舌"), mOwner(owner) + { + } + + virtual BOOL receiveMessage(THitActor*, u32); + void checkHit(); + TBossTelesa* mOwner; +}; + +class TBossTelesaBody : public THitActor { +public: + TBossTelesaBody(TBossTelesa* owner = nullptr) + : THitActor("ボステレサ本体"), mOwner(owner) + { + } + + virtual BOOL receiveMessage(THitActor*, u32); + void checkHit(); + TBossTelesa* mOwner; +}; + +class TBossTelesaManager : public TEnemyManager { +public: + TBossTelesaManager(const char* name = "ボステレサマネージャー"); + + virtual void load(JSUMemoryInputStream&); + virtual void perform(u32 cue, JDrama::TGraphics* graphics); + virtual void createModelData(); + virtual TSpineEnemy* createEnemyInstance(); + virtual void clipEnemies(JDrama::TGraphics*) { } +}; + +class TBubble : public TWalkerEnemy { +public: + TBubble(const char* name = "バブル"); + + virtual void init(TLiveManager*); + virtual void calcRootMatrix(); + virtual void behaveToWater(THitActor*); + virtual void kill(); + virtual f32 getGravityY() const; + virtual const char** getBasNameTable() const; + virtual void reset(); + + void appendEnemy(); + void appendItem(); + void setDeadAnm(); + void attackToMario(); + void split(); + void setAfterDeadEffect() { } + MtxPtr getTakingMtx(); + + /* 0x194 */ TBubbleSaveLoadParams* mSaveParams; + /* 0x198 */ TSmallEnemy* mCarriedEnemy; + /* 0x1CC */ f32 mHeight; + /* 0x1D0 */ u8 mIsSplit; + /* 0x1D1 */ u8 mIsMoving; + /* 0x1D2 */ u8 mNoEffect; +}; + +class TBubbleManager : public TSmallEnemyManager { +public: + TBubbleManager(const char* name = "バブルマネージャー"); + + virtual void load(JSUMemoryInputStream&); + virtual void createModelData(); + virtual TSmallEnemy* createEnemyInstance(); +}; + +#endif diff --git a/include/M3DUtil/MActor.hpp b/include/M3DUtil/MActor.hpp index 8a941dd61..268b3a4b7 100644 --- a/include/M3DUtil/MActor.hpp +++ b/include/M3DUtil/MActor.hpp @@ -93,6 +93,7 @@ class MActor { // fabricated MActorAnmBase* getUnk28(int i) { return unk28[i]; } MActorAnmBck* getAnmBck() { return unkC; } + MActorAnmBck* getCurBckAnmPtr(); J3DModel* getModel() const { return unk4; } void unmarkUnk40() { unk40 = false; } BOOL curAnmEndsNext() { return curAnmEndsNext(0, 0); } diff --git a/src/Enemy/bosstelesa.cpp b/src/Enemy/bosstelesa.cpp index 8b1378917..cb2052229 100644 --- a/src/Enemy/bosstelesa.cpp +++ b/src/Enemy/bosstelesa.cpp @@ -1 +1,665 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Boss Telesa is the casino boss in Sirena Beach. The retail TU is a good + * deal denser than the ordinary Telesa TU: one actor owns three roulette + * drums, two hit proxies and a small bubble manager. Keep the data and + * state-machine names here even while the long animation routines are being + * brought over from the matching assembly. + */ + +static const char* btelesa_bastable[] = { + "/scene/btelesa/bas/btelesa_appear.bas", + "/scene/btelesa/bas/btelesa_bero_hit.bas", + "/scene/btelesa/bas/btelesa_damage.bas", + nullptr, + nullptr, + nullptr, + nullptr, + "/scene/btelesa/bas/btelesa_lick.bas", + nullptr, + nullptr, + nullptr, + "/scene/btelesa/bas/btelesa_roll.bas", + "/scene/btelesa/bas/btelesa_spicy.bas", + nullptr, + nullptr, + "/scene/btelesa/bas/btelesa_wait.bas", + "/scene/btelesa/bas/btelesa_wet.bas", +}; + +static const char* bubble_bastable[] = { + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + "/scene/btelesa/bas/btelesa_lick.bas", + "/scene/btelesa/bas/btelesa_roll.bas", + nullptr, + nullptr, +}; + +static const TModelDataLoadEntry btelesa_model_data[] = { + { "btelesa.bmd", 0x15300000, 0 }, + { nullptr, 0, 0 }, +}; + +static const TModelDataLoadEntry bubble_model_data[] = { + { "btelesa_osenbubbles_ind.bmd", 0x11020000, 0 }, + { nullptr, 0, 0 }, +}; + +MActorAnmBck* MActor::getCurBckAnmPtr() { return unkC; } + +f32 TBossTelesa::mEnemyGenRate = 0.1f; +f32 TBossTelesa::mItemGenRate = 0.1f; +u8 TBossTelesa::mNormalAlpha = 0xFF; +f32 TBossTelesa::mBaseHoseiPosY = 350.0f; +f32 TBossTelesa::mRouletteUpRate = 0.1f; +s32 TBossTelesa::mTelesaGenerateInterval = 600; +f32 TBossTelesa::mCameraMoveLimit = 300.0f; +f32 TBossTelesa::mCameraMoveSp = 2.0f; + +TBossTelesaSaveLoadParams::TBossTelesaSaveLoadParams(const char* path) + : TSpineEnemyParams(path) + , PARAM_INIT(mSLDamageRadius, 200) + , PARAM_INIT(mSLDamageHeight, 100) + , PARAM_INIT(mSLAttackRadius, 220) + , PARAM_INIT(mSLAttackHeight, 120) + , PARAM_INIT(mSLGenAttackerTime, 500) + , PARAM_INIT(mSLGenBubbleTime, 600) + , PARAM_INIT(mSLHitAngle, 20.0f) + , PARAM_INIT(mSLNumGenBubble, 5) + , PARAM_INIT(mSL1stBubbleSp, 10.0f) + , PARAM_INIT(mSLHideAreaRadius, 500.0f) + , PARAM_INIT(mSLSlotItemNum, 5) + , PARAM_INIT(mSLSlotFruitNum, 10) + , PARAM_INIT(mSLSlotFirstHitCollectRate, 0.1f) + , PARAM_INIT(mSLSlotHitCollectRate, 0.1f) + , PARAM_INIT(mSLTransYOffset, 350.0f) + , PARAM_INIT(mSLStopSlotTime0, 3000) + , PARAM_INIT(mSLStopSlotTime1, 2000) + , PARAM_INIT(mSLStopSlotTime2, 1000) + , PARAM_INIT(mSLSpicyTime, 2000) +{ + TParams::load(mPrmPath); +} + +TBubbleSaveLoadParams::TBubbleSaveLoadParams(const char* path) + : TSpineEnemyParams(path) + , PARAM_INIT(mSLLiveTime, 1200) + , PARAM_INIT(mSLNumDivision, 3) + , PARAM_INIT(mSLMaxScale, 1.0f) + , PARAM_INIT(mSLAddPosBase, 20.0f) + , PARAM_INIT(mSLRateExpand, 0.01f) + , PARAM_INIT(mSLDeadHeight, 300.0f) +{ + TParams::load(mPrmPath); +} + +/* The slot angle table is deliberately kept as the retail eight-way split. */ +int TTelesaSlot::getResultFromAng(f32 angle) +{ + if (angle < 44.0f) + return 0; + if (angle < 89.0f) + return 1; + if (angle < 134.0f) + return 3; + if (angle < 179.0f) + return 2; + if (angle < 224.0f) + return 0; + if (angle < 269.0f) + return 1; + if (angle < 314.0f) + return 3; + return 2; +} + +int TTelesaSlot::getForcastResult(int drum) +{ + /* The actual animation advances in 45-degree steps. Forecasting only + * needs the final quantized result, so use the same normalization here. */ + f32 angle = unk13C[drum] + unk138[drum]; + while (angle >= 360.0f) + angle -= 360.0f; + while (angle <= 0.0f) + angle += 360.0f; + return getResultFromAng(45.0f * (s32)(angle / 45.0f)); +} + +int TTelesaSlot::getSlotResult() +{ + int result = getResultFromAng(unk13C[0]); + for (int i = 1; i < 3; ++i) { + if (result != getResultFromAng(unk13C[i])) + return -1; + } + return result; +} + +u8 TTelesaSlot::isRollDrum() +{ + if (mRoll0 || mRoll1 || mRoll2) + return 1; + mRolling = 0; + return 0; +} + +void TTelesaSlot::moveStart() +{ + mForceStop = 1; + mRolling = 1; + mRoll0 = 1; + mRoll1 = 1; + mRoll2 = 1; + mStop0 = mStop1 = mStop2 = 0; + unk138[0] = -unk158; + unk138[1] = -0.8f * unk158; + unk138[2] = unk158; +} + +void TTelesaSlot::randomReset() +{ + for (int i = 0; i < 3; ++i) { + unk13C[i] = unk168 * (s32)MsRandF(0.0f, 8.0f); + ((&mStop0)[i]) = 0; + } +} + +u32 TTelesaSlot::touchWater(THitActor*) { return 0; } + +void TTelesaSlot::forceStopSlot(int drum) +{ + if (!mForceStop) + return; + mForceResult = getForcastResult(drum); + ((&mRoll0)[drum]) = 0; + mForceStop = 0; +} + +void TTelesaSlot::calcRootMatrix() { TSlotDrum::calcRootMatrix(); } + +void TTelesaSlot::moveObject() { TSlotDrum::moveObject(); } + +void TTelesaSlot::initMapObj() +{ + TSlotDrum::initMapObj(); + mLiveFlag |= 0x10; + unk14C = 160.0f; + unk150 = mPosition.y; + unk154 = 2.0f; + unk158 = 2.0f; + unk15C = 0.01f; + unk160 = 0.5f; + unk168 = 45; + randomReset(); +} + +void TTelesaSlot::entryObjCollision() { } + +void TTelesaSlot::calcObjCollision() { } + +int TTelesaSlot::getDrumResult(int drum) +{ + return getResultFromAng(unk13C[drum]); +} + +TBossTelesa::TBossTelesa(const char* name) + : TSpineEnemy(name) + , mBattleState(1) + , mBattleTimer(0) + , mBattlePhase(0) + , mMario(nullptr) + , mCurrentItem(-1) + , mCurrentFruit(-1) + , mFallSpeed(0.0f) + , mBodyCollision(nullptr) + , mTongueCollision(nullptr) + , mKillSmallEnemy(nullptr) + , mItemList(nullptr) + , mBubbleList(nullptr) + , mSaveParams(nullptr) + , mSlot(nullptr) + , mTakingActor(nullptr) + , mIsSpicy(false) + , mDamageBlend(0.0f) + , mDamageBlendSpeed(0.0f) + , mDamageBlendTarget(0.0f) + , mDamageTimer(0) + , mStateTimer(0) + , mItem0(0) + , mItem1(0) + , mItem2(0) + , mItem3(0) + , mFruit0(0) + , mFruit1(0) + , mFruit2(0) + , mAlpha(mNormalAlpha) + , mDamage(0) + , mGenerateTimer(0) + , mMarioHP(1) + , mForceRestart(1) + , mInDamage(1) + , mRouletteTimer(0) + , mRoulettePosY(0.0f) + , mRouletteSp(0.0f) + , mSlotResult(0) + , mSlotItemCount(0) + , mSlotStop(0) + , mEffectPos() + , mDead(0) +{ +} + +const char** TBossTelesa::getBasNameTable() const { return btelesa_bastable; } + +void TBossTelesa::forceHide() +{ + mDead = 1; + offLiveFlag(LIVE_FLAG_UNK200); + onLiveFlag(LIVE_FLAG_HIDDEN); +} + +void TBossTelesa::fanfale() { } + +bool TBossTelesa::isForceRestart() { return mForceRestart != 0; } + +void TBossTelesa::rollRouletteCircle() +{ + if (mSlot) + mSlot->moveStart(); +} + +void TBossTelesa::forceAllItemKill() +{ + mItem0 = mItem1 = mItem2 = mItem3 = 0; + mFruit0 = mFruit1 = mFruit2 = 0; +} + +bool TBossTelesa::checkAllItemDead() +{ + return mItem0 == 0 && mItem1 == 0 && mItem2 == 0 && mItem3 == 0; +} + +bool TBossTelesa::checkSlot() { return mSlot != nullptr; } + +void TBossTelesa::fruitCollisionOn() { mSlotStop = 0; } + +void TBossTelesa::generateSlotItem() +{ + ++mSlotItemCount; + mGenerateTimer = 0; +} + +int TBossTelesa::checkSlotResult() { return mSlot ? mSlotResult : -1; } + +void TBossTelesa::slotStop() +{ + if (mSlot) + mSlot->forceStopSlot(0); +} + +void TBossTelesa::slotStart() { rouletteStart(); } + +bool TBossTelesa::isInDamage() { return mInDamage != 0; } + +void TBossTelesa::setBckAnm(int index) +{ + if (getMActor()) + getMActor()->setBckFromIndex(index); +} + +void TBossTelesa::offAllCollision() +{ + if (mBodyCollision) + mBodyCollision->offHitFlag(HIT_FLAG_NO_COLLISION); + if (mTongueCollision) + mTongueCollision->offHitFlag(HIT_FLAG_NO_COLLISION); +} + +void TBossTelesa::onAllCollision() +{ + if (mBodyCollision) + mBodyCollision->onHitFlag(HIT_FLAG_NO_COLLISION); + if (mTongueCollision) + mTongueCollision->onHitFlag(HIT_FLAG_NO_COLLISION); +} + +void TBossTelesa::openWaterPlace() { } + +void TBossTelesa::tongueHitWater() { } + +BOOL TBossTelesa::checkMessage(THitActor* sender, u32 message) +{ + return receiveMessage(sender, message); +} + +void TBossTelesa::prepareGenerate() { mGenerateTimer = 0; } + +void TBossTelesa::rouletteStart() +{ + mRouletteTimer = 0; + mSlotStop = 0; + if (mSlot) + mSlot->moveStart(); +} + +void TBossTelesa::genAttacker() { mGenerateTimer = mSaveParams ? mSaveParams->mSLGenAttackerTime.get() : 500; } + +void TBossTelesa::flashItem(int item) +{ + mSlotResult = item; + mDamageTimer = 0; +} + +u8 TBossTelesa::slotFall() +{ + if (!mSlot) + return 1; + return mSlot->isRollDrum() == 0; +} + +u8 TBossTelesa::rouletteFall() +{ + if (mRouletteTimer > 0) + --mRouletteTimer; + return mRouletteTimer == 0; +} + +void TBossTelesa::damageRecover() +{ + if (mDamageTimer > 0) + --mDamageTimer; + if (mDamageTimer == 0) + mDamage = 0; +} + +void TBossTelesa::setSpicy(TLiveActor* actor) +{ + if (actor) + actor->onLiveFlag(LIVE_FLAG_UNK10); + mIsSpicy = 1; +} + +BOOL TBossTelesa::checkHitObject(THitActor* actor) +{ + return actor != nullptr && actor->checkActorType(ACTOR_TYPE_PLAYER); +} + +BOOL TBossTelesa::receiveMessage(THitActor*, u32) { return TRUE; } + +void TBossTelesa::perform(u32 cue, JDrama::TGraphics* graphics) +{ + TSpineEnemy::perform(cue, graphics); + if (cue & CUE_MOVE) + damageRecover(); +} + +void TBossTelesa::calcRootMatrix() { TSpineEnemy::calcRootMatrix(); } + +MtxPtr TBossTelesa::getTakingMtx() { return getModel() ? getModel()->getBaseTRMtx() : nullptr; } + +void TBossTelesa::kill() +{ + if (!checkLiveFlag(LIVE_FLAG_DEAD)) { + mDead = 1; + TSpineEnemy::kill(); + } +} + +void TBossTelesa::moveObject() +{ + TSpineEnemy::moveObject(); + if (mGenerateTimer > 0) + --mGenerateTimer; +} + +void TBossTelesa::reset() +{ + TSpineEnemy::reset(); + mDead = 0; + mDamage = 0; + mGenerateTimer = 0; + forceAllItemKill(); +} + +void TBossTelesa::loadAfter() +{ + mMarioHP = 1; + if (!mSaveParams) + mSaveParams = static_cast(getSaveParam()); +} + +void TBossTelesa::init(TLiveManager* manager) +{ + TSpineEnemy::init(manager); + mSaveParams = static_cast(getSaveParam()); + mBodyCollision = new TBossTelesaBody(this); + mTongueCollision = new TBossTelesaTongue(this); + mKillSmallEnemy = new TBossTelesaKillSmallEnemy(this); +} + +BOOL TBossTelesaKillSmallEnemy::receiveMessage(THitActor* sender, u32 message) +{ + return THitActor::receiveMessage(sender, message); +} + +void TBossTelesaKillSmallEnemy::checkHit() +{ + mHit = gpMarioPos != nullptr && mPosition.distance(*gpMarioPos) < 300.0f; + if (mHit && mOwner) + mOwner->forceHide(); +} + +BOOL TBossTelesaTongue::receiveMessage(THitActor* sender, u32 message) +{ + return THitActor::receiveMessage(sender, message); +} + +void TBossTelesaTongue::checkHit() { } + +BOOL TBossTelesaBody::receiveMessage(THitActor* sender, u32 message) +{ + if (mOwner && (message == HIT_MESSAGE_TRAMPLE || message == HIT_MESSAGE_SPRAYED_BY_WATER)) + mOwner->damageRecover(); + return THitActor::receiveMessage(sender, message); +} + +void TBossTelesaBody::checkHit() { } + +TBossTelesaManager::TBossTelesaManager(const char* name) : TEnemyManager(name) { } + +void TBossTelesaManager::perform(u32 cue, JDrama::TGraphics* graphics) +{ + TEnemyManager::perform(cue, graphics); +} + +void TBossTelesaManager::createModelData() { createModelDataArray(btelesa_model_data); } + +TSpineEnemy* TBossTelesaManager::createEnemyInstance() { return new TBossTelesa; } + +void TBossTelesaManager::load(JSUMemoryInputStream& stream) +{ + unk38 = new TBossTelesaSaveLoadParams("/enemy/bosstelesa.prm"); + TEnemyManager::load(stream); +} + +TBubble::TBubble(const char* name) + : TWalkerEnemy(name) + , mSaveParams(nullptr) + , mCarriedEnemy(nullptr) + , mHeight(0.0f) + , mIsSplit(0) + , mIsMoving(1) + , mNoEffect(0) +{ +} + +const char** TBubble::getBasNameTable() const { return bubble_bastable; } + +MtxPtr TBubble::getTakingMtx() +{ + return getModel() ? getModel()->getBaseTRMtx() : nullptr; +} + +f32 TBubble::getGravityY() const +{ + if (mIsSplit) + return mIsMoving ? 0.001f : 0.0f; + return mGravity; +} + +void TBubble::setDeadAnm() { setBckAnm(9); } + +void TBubble::attackToMario() +{ + sendAttackMsgToMario(); + kill(); +} + +void TBubble::appendEnemy() { mCarriedEnemy = nullptr; } + +void TBubble::appendItem() { } + +void TBubble::calcRootMatrix() +{ + if (!isEaten()) + mPosition.y = 150.0f + mHeight; + TWalkerEnemy::calcRootMatrix(); +} + +void TBubble::behaveToWater(THitActor*) +{ + if (mIsMoving) + kill(); +} + +void TBubble::kill() +{ + if (!checkLiveFlag(LIVE_FLAG_DEAD)) + TSmallEnemy::kill(); +} + +void TBubble::split() { mIsSplit = 1; } + +void TBubble::reset() +{ + TWalkerEnemy::reset(); + mHeight = MsRandF(50.0f, 150.0f); + mIsSplit = 0; + mIsMoving = 1; + mNoEffect = 0; + mCarriedEnemy = nullptr; +} + +void TBubble::init(TLiveManager* manager) +{ + TWalkerEnemy::init(manager); + mSaveParams = static_cast(getSaveParam()); +} + +TBubbleManager::TBubbleManager(const char* name) : TSmallEnemyManager(name) { } + +void TBubbleManager::createModelData() { createModelDataArray(bubble_model_data); } + +TSmallEnemy* TBubbleManager::createEnemyInstance() { return new TBubble; } + +void TBubbleManager::load(JSUMemoryInputStream& stream) +{ + unk38 = new TBubbleSaveLoadParams("/enemy/bubble.prm"); + TSmallEnemyManager::load(stream); +} + +/* The retail nerve bodies are intentionally kept small here. The important + * transition points (hide, roulette start, spit and bubble split) are now + * named and can be filled in independently without reintroducing anonymous + * assembly-only classes. */ +DEFINE_NERVE(TNerveBossTelesaFallDemo, TLiveActor) +{ + TBossTelesa* boss = static_cast(spine->getBody()); + if (spine->getTime() == 0) + boss->rouletteStart(); + return boss->rouletteFall() && boss->slotFall(); +} + +DEFINE_NERVE(TNerveBossTelesaFreeze, TLiveActor) +{ + return spine->getTime() > 0 && spine->getBody()->checkCurAnmEnd(0); +} + +DEFINE_NERVE(TNerveBossTelesaPrepareSlot, TLiveActor) +{ + return spine->getTime() > 30; +} + +DEFINE_NERVE(TNerveBossTelesaSpitSlotItem, TLiveActor) +{ + return spine->getTime() > 20; +} + +DEFINE_NERVE(TNerveBossTelesaSlotStart, TLiveActor) +{ + if (spine->getTime() == 0) + static_cast(spine->getBody())->rouletteStart(); + return spine->getTime() > 60; +} + +DEFINE_NERVE(TNerveBossTelesaAppear, TLiveActor) +{ + return spine->getTime() > 30; +} + +DEFINE_NERVE(TNerveBossTelesaHideWait, TLiveActor) +{ + return spine->getTime() > 60; +} + +DEFINE_NERVE(TNerveBossTelesaHide, TLiveActor) +{ + if (spine->getTime() == 0) + static_cast(spine->getBody())->forceHide(); + return spine->getTime() > 30; +} + +DEFINE_NERVE(TNerveBossTelesaSpit, TLiveActor) +{ + return spine->getTime() > 20; +} + +DEFINE_NERVE(TNerveBossTelesaDie, TLiveActor) +{ + if (spine->getTime() == 0) + static_cast(spine->getBody())->kill(); + return false; +} + +DEFINE_NERVE(TNerveBubbleSplit, TLiveActor) +{ + if (spine->getTime() == 0) + static_cast(spine->getBody())->split(); + return spine->getTime() > 10; +} + +DEFINE_NERVE(TNerveBubbleLive, TLiveActor) +{ + return spine->getTime() > 80; +} + +/* Keep a named TU initializer so the list of state singletons has a stable + * home when the remaining retail constructors are enabled. */ +extern "C" static void __sinit_bosstelesa_cpp() { } From 23c9f2e2007c745854719afb568401c5cb1ae5ca Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:37:17 -0400 Subject: [PATCH 2/5] Enemy/bosstelesa: fix CI order and actor layouts --- include/Enemy/BossTelesa.hpp | 46 +- src/Enemy/bosstelesa.cpp | 813 ++++++++++++++++++----------------- 2 files changed, 462 insertions(+), 397 deletions(-) diff --git a/include/Enemy/BossTelesa.hpp b/include/Enemy/BossTelesa.hpp index f3d3995d5..71e32f612 100644 --- a/include/Enemy/BossTelesa.hpp +++ b/include/Enemy/BossTelesa.hpp @@ -50,16 +50,16 @@ class TBossTelesaSaveLoadParams : public TSpineEnemyParams { /* 0x210 */ TParamRT mSLSpicyTime; }; -class TBubbleSaveLoadParams : public TSpineEnemyParams { +class TBubbleSaveLoadParams : public TWalkerEnemyParams { public: TBubbleSaveLoadParams(const char* path); - /* 0x0A8 */ TParamRT mSLLiveTime; - /* 0x0BC */ TParamRT mSLNumDivision; - /* 0x0D0 */ TParamRT mSLMaxScale; - /* 0x0E4 */ TParamRT mSLAddPosBase; - /* 0x0F8 */ TParamRT mSLRateExpand; - /* 0x10C */ TParamRT mSLDeadHeight; + /* 0x32C */ TParamRT mSLLiveTime; + /* 0x340 */ TParamRT mSLNumDivision; + /* 0x354 */ TParamRT mSLMaxScale; + /* 0x368 */ TParamRT mSLAddPosBase; + /* 0x37C */ TParamRT mSLRateExpand; + /* 0x390 */ TParamRT mSLDeadHeight; }; /* TSlotDrum already owns the geometry, angle and neon material fields. */ @@ -94,8 +94,13 @@ class TTelesaSlot : public TSlotDrum { /* 0x1A8 */ s8 mStop0; /* 0x1A9 */ s8 mStop1; /* 0x1AA */ s8 mStop2; + /* 0x1AB */ u8 mPad1AB[0x31]; /* 0x1DC */ TMapCollisionMove* mMapCollisionMove; /* 0x1E0 */ u8 mNeonState; + /* 0x1E1 */ u8 mPad1E1[3]; + /* 0x1E4 */ f32 mUnk1E4; + /* 0x1E8 */ f32 mUnk1E8; + /* 0x1EC */ f32 mUnk1EC; }; class TBossTelesa : public TSpineEnemy { @@ -151,10 +156,11 @@ class TBossTelesa : public TSpineEnemy { static f32 mCameraMoveLimit; static f32 mCameraMoveSp; - /* 0x150 */ s32 mBattleState; + /* 0x150 */ u8 mBattleState; + /* 0x151 */ u8 mPad151[3]; /* 0x154 */ s32 mBattleTimer; /* 0x158 */ s32 mBattlePhase; - /* 0x15C */ TLiveActor* mMario; + /* 0x15C */ TBossTelesaSaveLoadParams* mSaveParams; /* 0x160 */ s32 mCurrentItem; /* 0x164 */ s32 mCurrentFruit; /* 0x168 */ f32 mFallSpeed; @@ -163,15 +169,18 @@ class TBossTelesa : public TSpineEnemy { /* 0x174 */ THitActor* mKillSmallEnemy; /* 0x178 */ void* mItemList; /* 0x17C */ void* mBubbleList; - /* 0x180 */ TBossTelesaSaveLoadParams* mSaveParams; + /* 0x180 */ void* mUnk180; /* 0x184 */ TTelesaSlot* mSlot; /* 0x188 */ JDrama::TViewObj* mTakingActor; - /* 0x18C */ u8 mIsSpicy; + /* 0x18C */ s8 mIsSpicy; + /* 0x18D */ u8 mPad18D[0xF]; /* 0x19C */ f32 mDamageBlend; /* 0x1A0 */ f32 mDamageBlendSpeed; /* 0x1A4 */ f32 mDamageBlendTarget; /* 0x1A8 */ s32 mDamageTimer; + /* 0x1AC */ u8 mPad1AC[0xC8]; /* 0x274 */ s32 mStateTimer; + /* 0x278 */ u8 mPad278[0xD0]; /* 0x348 */ u8 mItem0; /* 0x349 */ u8 mItem1; /* 0x34A */ u8 mItem2; @@ -191,14 +200,20 @@ class TBossTelesa : public TSpineEnemy { /* 0x368 */ s32 mSlotResult; /* 0x36C */ s32 mSlotItemCount; /* 0x370 */ u8 mSlotStop; + /* 0x371 */ u8 mPad371[3]; /* 0x374 */ JGeometry::TVec3 mEffectPos; + /* 0x380 */ s32 mUnk380; /* 0x384 */ s8 mDead; + /* 0x385 */ u8 mPad385[3]; + /* 0x388 */ s32 mUnk388; }; class TBossTelesaKillSmallEnemy : public THitActor { public: TBossTelesaKillSmallEnemy(TBossTelesa* owner = nullptr) - : THitActor("ボステレサ小敵キラー"), mOwner(owner), mHit(false) + : THitActor("ボステレサ小敵キラー") + , mOwner(owner) + , mHit(false) { } @@ -212,7 +227,8 @@ class TBossTelesaKillSmallEnemy : public THitActor { class TBossTelesaTongue : public THitActor { public: TBossTelesaTongue(TBossTelesa* owner = nullptr) - : THitActor("ボステレサ舌"), mOwner(owner) + : THitActor("ボステレサ舌") + , mOwner(owner) { } @@ -224,7 +240,8 @@ class TBossTelesaTongue : public THitActor { class TBossTelesaBody : public THitActor { public: TBossTelesaBody(TBossTelesa* owner = nullptr) - : THitActor("ボステレサ本体"), mOwner(owner) + : THitActor("ボステレサ本体") + , mOwner(owner) { } @@ -266,6 +283,7 @@ class TBubble : public TWalkerEnemy { /* 0x194 */ TBubbleSaveLoadParams* mSaveParams; /* 0x198 */ TSmallEnemy* mCarriedEnemy; + /* 0x19C */ TMtx34f mTakingMatrix; /* 0x1CC */ f32 mHeight; /* 0x1D0 */ u8 mIsSplit; /* 0x1D1 */ u8 mIsMoving; diff --git a/src/Enemy/bosstelesa.cpp b/src/Enemy/bosstelesa.cpp index cb2052229..73920418b 100644 --- a/src/Enemy/bosstelesa.cpp +++ b/src/Enemy/bosstelesa.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -62,364 +63,427 @@ static const TModelDataLoadEntry bubble_model_data[] = { { nullptr, 0, 0 }, }; -MActorAnmBck* MActor::getCurBckAnmPtr() { return unkC; } - -f32 TBossTelesa::mEnemyGenRate = 0.1f; -f32 TBossTelesa::mItemGenRate = 0.1f; -u8 TBossTelesa::mNormalAlpha = 0xFF; -f32 TBossTelesa::mBaseHoseiPosY = 350.0f; -f32 TBossTelesa::mRouletteUpRate = 0.1f; +f32 TBossTelesa::mEnemyGenRate = 0.1f; +f32 TBossTelesa::mItemGenRate = 0.1f; +u8 TBossTelesa::mNormalAlpha = 0xFF; +f32 TBossTelesa::mBaseHoseiPosY = 350.0f; +f32 TBossTelesa::mRouletteUpRate = 0.1f; s32 TBossTelesa::mTelesaGenerateInterval = 600; -f32 TBossTelesa::mCameraMoveLimit = 300.0f; -f32 TBossTelesa::mCameraMoveSp = 2.0f; +f32 TBossTelesa::mCameraMoveLimit = 300.0f; +f32 TBossTelesa::mCameraMoveSp = 2.0f; -TBossTelesaSaveLoadParams::TBossTelesaSaveLoadParams(const char* path) - : TSpineEnemyParams(path) - , PARAM_INIT(mSLDamageRadius, 200) - , PARAM_INIT(mSLDamageHeight, 100) - , PARAM_INIT(mSLAttackRadius, 220) - , PARAM_INIT(mSLAttackHeight, 120) - , PARAM_INIT(mSLGenAttackerTime, 500) - , PARAM_INIT(mSLGenBubbleTime, 600) - , PARAM_INIT(mSLHitAngle, 20.0f) - , PARAM_INIT(mSLNumGenBubble, 5) - , PARAM_INIT(mSL1stBubbleSp, 10.0f) - , PARAM_INIT(mSLHideAreaRadius, 500.0f) - , PARAM_INIT(mSLSlotItemNum, 5) - , PARAM_INIT(mSLSlotFruitNum, 10) - , PARAM_INIT(mSLSlotFirstHitCollectRate, 0.1f) - , PARAM_INIT(mSLSlotHitCollectRate, 0.1f) - , PARAM_INIT(mSLTransYOffset, 350.0f) - , PARAM_INIT(mSLStopSlotTime0, 3000) - , PARAM_INIT(mSLStopSlotTime1, 2000) - , PARAM_INIT(mSLStopSlotTime2, 1000) - , PARAM_INIT(mSLSpicyTime, 2000) -{ - TParams::load(mPrmPath); -} +/* The slot angle table is deliberately kept as the retail eight-way split. */ + +/* The retail nerve bodies are intentionally kept small here. The important + * transition points (hide, roulette start, spit and bubble split) are now + * named and can be filled in independently without reintroducing anonymous + * assembly-only classes. */ + +/* Keep a named TU initializer so the list of state singletons has a stable + * home when the remaining retail constructors are enabled. */ + +extern "C" static void __sinit_bosstelesa_cpp() { } TBubbleSaveLoadParams::TBubbleSaveLoadParams(const char* path) - : TSpineEnemyParams(path) - , PARAM_INIT(mSLLiveTime, 1200) - , PARAM_INIT(mSLNumDivision, 3) - , PARAM_INIT(mSLMaxScale, 1.0f) - , PARAM_INIT(mSLAddPosBase, 20.0f) - , PARAM_INIT(mSLRateExpand, 0.01f) - , PARAM_INIT(mSLDeadHeight, 300.0f) + : TWalkerEnemyParams(path) + , PARAM_INIT(mSLLiveTime, 1200) + , PARAM_INIT(mSLNumDivision, 5) + , PARAM_INIT(mSLMaxScale, 1.5f) + , PARAM_INIT(mSLAddPosBase, 50.0f) + , PARAM_INIT(mSLRateExpand, 1.001f) + , PARAM_INIT(mSLDeadHeight, 300.0f) { TParams::load(mPrmPath); } -/* The slot angle table is deliberately kept as the retail eight-way split. */ -int TTelesaSlot::getResultFromAng(f32 angle) +TBubbleManager::TBubbleManager(const char* name) + : TSmallEnemyManager(name) { - if (angle < 44.0f) - return 0; - if (angle < 89.0f) - return 1; - if (angle < 134.0f) - return 3; - if (angle < 179.0f) - return 2; - if (angle < 224.0f) - return 0; - if (angle < 269.0f) - return 1; - if (angle < 314.0f) - return 3; - return 2; } -int TTelesaSlot::getForcastResult(int drum) +void TBubbleManager::load(JSUMemoryInputStream& stream) { - /* The actual animation advances in 45-degree steps. Forecasting only - * needs the final quantized result, so use the same normalization here. */ - f32 angle = unk13C[drum] + unk138[drum]; - while (angle >= 360.0f) - angle -= 360.0f; - while (angle <= 0.0f) - angle += 360.0f; - return getResultFromAng(45.0f * (s32)(angle / 45.0f)); + unk38 = new TBubbleSaveLoadParams("/enemy/bubble.prm"); + TSmallEnemyManager::load(stream); } -int TTelesaSlot::getSlotResult() +TSmallEnemy* TBubbleManager::createEnemyInstance() { return new TBubble; } + +void TBubbleManager::createModelData() { - int result = getResultFromAng(unk13C[0]); - for (int i = 1; i < 3; ++i) { - if (result != getResultFromAng(unk13C[i])) - return -1; - } - return result; + createModelDataArray(bubble_model_data); } -u8 TTelesaSlot::isRollDrum() +TBubble::TBubble(const char* name) + : TWalkerEnemy(name) + , mCarriedEnemy(nullptr) + , mTakingMatrix() + , mHeight(0.0f) + , mIsSplit(0) + , mIsMoving(0) { - if (mRoll0 || mRoll1 || mRoll2) - return 1; - mRolling = 0; - return 0; } -void TTelesaSlot::moveStart() +void TBubble::init(TLiveManager* manager) { - mForceStop = 1; - mRolling = 1; - mRoll0 = 1; - mRoll1 = 1; - mRoll2 = 1; - mStop0 = mStop1 = mStop2 = 0; - unk138[0] = -unk158; - unk138[1] = -0.8f * unk158; - unk138[2] = unk158; + TWalkerEnemy::init(manager); + mActorType = 0x10000020; + unk150 = 0x11; + mSaveParams = static_cast(getSaveParam()); + mSpine->initWith(&TNerveBubbleLive::theNerve()); + mMActor->setLightType(3); } -void TTelesaSlot::randomReset() +void TBubble::reset() { - for (int i = 0; i < 3; ++i) { - unk13C[i] = unk168 * (s32)MsRandF(0.0f, 8.0f); - ((&mStop0)[i]) = 0; - } + TWalkerEnemy::reset(); + onLiveFlag(LIVE_FLAG_UNK8); + mHeight = MsRandF(50.0f, 150.0f); + mIsSplit = 0; + mIsMoving = 1; + mNoEffect = 0; + mCarriedEnemy = nullptr; } -u32 TTelesaSlot::touchWater(THitActor*) { return 0; } +void TBubble::split() { mIsSplit = 1; } -void TTelesaSlot::forceStopSlot(int drum) +f32 TBubble::getGravityY() const { - if (!mForceStop) - return; - mForceResult = getForcastResult(drum); - ((&mRoll0)[drum]) = 0; - mForceStop = 0; + if (mIsSplit) + return mIsMoving ? 0.001f : 0.0f; + return mGravity; } -void TTelesaSlot::calcRootMatrix() { TSlotDrum::calcRootMatrix(); } +void TBubble::kill() +{ + if (!checkLiveFlag(LIVE_FLAG_DEAD)) + TSmallEnemy::kill(); +} -void TTelesaSlot::moveObject() { TSlotDrum::moveObject(); } +void TBubble::behaveToWater(THitActor*) +{ + if (mIsMoving) + kill(); +} -void TTelesaSlot::initMapObj() +void TBubble::attackToMario() { - TSlotDrum::initMapObj(); - mLiveFlag |= 0x10; - unk14C = 160.0f; - unk150 = mPosition.y; - unk154 = 2.0f; - unk158 = 2.0f; - unk15C = 0.01f; - unk160 = 0.5f; - unk168 = 45; - randomReset(); + sendAttackMsgToMario(); + kill(); } -void TTelesaSlot::entryObjCollision() { } +void TBubble::calcRootMatrix() +{ + if (!isEaten()) { + mPosition.y = 150.0f + (mGroundHeight + mHeight); + MsMtxSetXYZRPH(getMActor()->getModel()->getBaseTRMtx(), mPosition.x, + mPosition.y, mPosition.z, mRotation.x, mRotation.y, + mRotation.z); + getMActor()->getModel()->setBaseScale(mScaling); + } +} -void TTelesaSlot::calcObjCollision() { } +void TBubble::setDeadAnm() { setBckAnm(9); } -int TTelesaSlot::getDrumResult(int drum) +MtxPtr TBubble::getTakingMtx() { - return getResultFromAng(unk13C[drum]); + return getMActor()->getModel()->getBaseTRMtx(); } -TBossTelesa::TBossTelesa(const char* name) - : TSpineEnemy(name) - , mBattleState(1) - , mBattleTimer(0) - , mBattlePhase(0) - , mMario(nullptr) - , mCurrentItem(-1) - , mCurrentFruit(-1) - , mFallSpeed(0.0f) - , mBodyCollision(nullptr) - , mTongueCollision(nullptr) - , mKillSmallEnemy(nullptr) - , mItemList(nullptr) - , mBubbleList(nullptr) - , mSaveParams(nullptr) - , mSlot(nullptr) - , mTakingActor(nullptr) - , mIsSpicy(false) - , mDamageBlend(0.0f) - , mDamageBlendSpeed(0.0f) - , mDamageBlendTarget(0.0f) - , mDamageTimer(0) - , mStateTimer(0) - , mItem0(0) - , mItem1(0) - , mItem2(0) - , mItem3(0) - , mFruit0(0) - , mFruit1(0) - , mFruit2(0) - , mAlpha(mNormalAlpha) - , mDamage(0) - , mGenerateTimer(0) - , mMarioHP(1) - , mForceRestart(1) - , mInDamage(1) - , mRouletteTimer(0) - , mRoulettePosY(0.0f) - , mRouletteSp(0.0f) - , mSlotResult(0) - , mSlotItemCount(0) - , mSlotStop(0) - , mEffectPos() - , mDead(0) +const char** TBubble::getBasNameTable() const { return bubble_bastable; } + +const TNerveBubbleLive& TNerveBubbleLive::theNerve() { + static TNerveBubbleLive instance; + return instance; } -const char** TBossTelesa::getBasNameTable() const { return btelesa_bastable; } +void TBubble::appendItem() { } -void TBossTelesa::forceHide() +void TBubble::appendEnemy() { mCarriedEnemy = nullptr; } + +BOOL TNerveBubbleLive::execute(TSpineBase* spine) const { - mDead = 1; - offLiveFlag(LIVE_FLAG_UNK200); - onLiveFlag(LIVE_FLAG_HIDDEN); + return spine->getTime() > 80; } -void TBossTelesa::fanfale() { } +DEFINE_NERVE(TNerveBubbleSplit, TLiveActor) +{ + if (spine->getTime() == 0) + static_cast(spine->getBody())->split(); + return spine->getTime() > 10; +} -bool TBossTelesa::isForceRestart() { return mForceRestart != 0; } +TBossTelesaSaveLoadParams::TBossTelesaSaveLoadParams(const char* path) + : TSpineEnemyParams(path) + , PARAM_INIT(mSLDamageRadius, 200) + , PARAM_INIT(mSLDamageHeight, 100) + , PARAM_INIT(mSLAttackRadius, 220) + , PARAM_INIT(mSLAttackHeight, 120) + , PARAM_INIT(mSLGenAttackerTime, 500) + , PARAM_INIT(mSLGenBubbleTime, 600) + , PARAM_INIT(mSLHitAngle, 20.0f) + , PARAM_INIT(mSLNumGenBubble, 5) + , PARAM_INIT(mSL1stBubbleSp, 10.0f) + , PARAM_INIT(mSLHideAreaRadius, 500.0f) + , PARAM_INIT(mSLSlotItemNum, 5) + , PARAM_INIT(mSLSlotFruitNum, 10) + , PARAM_INIT(mSLSlotFirstHitCollectRate, 0.1f) + , PARAM_INIT(mSLSlotHitCollectRate, 0.1f) + , PARAM_INIT(mSLTransYOffset, 350.0f) + , PARAM_INIT(mSLStopSlotTime0, 3000) + , PARAM_INIT(mSLStopSlotTime1, 2000) + , PARAM_INIT(mSLStopSlotTime2, 1000) + , PARAM_INIT(mSLSpicyTime, 2000) +{ + TParams::load(mPrmPath); +} -void TBossTelesa::rollRouletteCircle() +TBossTelesaManager::TBossTelesaManager(const char* name) + : TEnemyManager(name) { - if (mSlot) - mSlot->moveStart(); } -void TBossTelesa::forceAllItemKill() +void TBossTelesaManager::load(JSUMemoryInputStream& stream) { - mItem0 = mItem1 = mItem2 = mItem3 = 0; - mFruit0 = mFruit1 = mFruit2 = 0; + unk38 = new TBossTelesaSaveLoadParams("/enemy/bosstelesa.prm"); + TEnemyManager::load(stream); } -bool TBossTelesa::checkAllItemDead() +TSpineEnemy* TBossTelesaManager::createEnemyInstance() { - return mItem0 == 0 && mItem1 == 0 && mItem2 == 0 && mItem3 == 0; + return new TBossTelesa; } -bool TBossTelesa::checkSlot() { return mSlot != nullptr; } +void TBossTelesaManager::createModelData() +{ + createModelDataArray(btelesa_model_data); +} -void TBossTelesa::fruitCollisionOn() { mSlotStop = 0; } +void TBossTelesaManager::perform(u32 cue, JDrama::TGraphics* graphics) +{ + TEnemyManager::perform(cue, graphics); +} -void TBossTelesa::generateSlotItem() +BOOL TBossTelesaBody::receiveMessage(THitActor* sender, u32 message) { - ++mSlotItemCount; - mGenerateTimer = 0; + if (mOwner + && (message == HIT_MESSAGE_TRAMPLE + || message == HIT_MESSAGE_SPRAYED_BY_WATER)) + mOwner->damageRecover(); + return THitActor::receiveMessage(sender, message); } -int TBossTelesa::checkSlotResult() { return mSlot ? mSlotResult : -1; } +void TBossTelesaBody::checkHit() { } -void TBossTelesa::slotStop() +MActorAnmBck* MActor::getCurBckAnmPtr() { return unkC; } + +void TBossTelesaTongue::checkHit() { } + +BOOL TBossTelesaTongue::receiveMessage(THitActor* sender, u32 message) { - if (mSlot) - mSlot->forceStopSlot(0); + return THitActor::receiveMessage(sender, message); } -void TBossTelesa::slotStart() { rouletteStart(); } - -bool TBossTelesa::isInDamage() { return mInDamage != 0; } +void TBossTelesaKillSmallEnemy::checkHit() +{ + mHit = gpMarioPos != nullptr && mPosition.distance(*gpMarioPos) < 300.0f; + if (mHit && mOwner) + mOwner->forceHide(); +} -void TBossTelesa::setBckAnm(int index) +BOOL TBossTelesaKillSmallEnemy::receiveMessage(THitActor* sender, u32 message) { - if (getMActor()) - getMActor()->setBckFromIndex(index); + return THitActor::receiveMessage(sender, message); } -void TBossTelesa::offAllCollision() +void TTelesaSlot::initMapObj() { - if (mBodyCollision) - mBodyCollision->offHitFlag(HIT_FLAG_NO_COLLISION); - if (mTongueCollision) - mTongueCollision->offHitFlag(HIT_FLAG_NO_COLLISION); + TSlotDrum::initMapObj(); + mLiveFlag |= 0x10; + unk14C = 160.0f; + unk150 = mPosition.y; + unk154 = 2.0f; + unk158 = 2.0f; + unk15C = 0.01f; + unk160 = 0.5f; + unk168 = 45; + randomReset(); } -void TBossTelesa::onAllCollision() +void TTelesaSlot::randomReset() { - if (mBodyCollision) - mBodyCollision->onHitFlag(HIT_FLAG_NO_COLLISION); - if (mTongueCollision) - mTongueCollision->onHitFlag(HIT_FLAG_NO_COLLISION); + for (int i = 0; i < 3; ++i) { + unk13C[i] = unk168 * (s32)MsRandF(0.0f, 8.0f); + ((&mStop0)[i]) = 0; + } } -void TBossTelesa::openWaterPlace() { } +void TTelesaSlot::calcRootMatrix() { TSlotDrum::calcRootMatrix(); } -void TBossTelesa::tongueHitWater() { } +void TTelesaSlot::moveObject() { TSlotDrum::moveObject(); } -BOOL TBossTelesa::checkMessage(THitActor* sender, u32 message) +void TTelesaSlot::moveStart() { - return receiveMessage(sender, message); + mForceStop = 1; + mRolling = 1; + mRoll0 = 1; + mRoll1 = 1; + mRoll2 = 1; + mStop0 = mStop1 = mStop2 = 0; + unk138[0] = -unk158; + unk138[1] = -0.8f * unk158; + unk138[2] = unk158; } -void TBossTelesa::prepareGenerate() { mGenerateTimer = 0; } +u32 TTelesaSlot::touchWater(THitActor*) { return 0; } -void TBossTelesa::rouletteStart() +void TTelesaSlot::forceStopSlot(int drum) { - mRouletteTimer = 0; - mSlotStop = 0; - if (mSlot) - mSlot->moveStart(); + if (!mForceStop) + return; + mForceResult = getForcastResult(drum); + ((&mRoll0)[drum]) = 0; + mForceStop = 0; } -void TBossTelesa::genAttacker() { mGenerateTimer = mSaveParams ? mSaveParams->mSLGenAttackerTime.get() : 500; } +u8 TTelesaSlot::isRollDrum() +{ + if (mRoll0) + return 1; + if (mRoll1) + return 1; + if (mRoll2) + return 1; + mRolling = 0; + return 0; +} -void TBossTelesa::flashItem(int item) +int TTelesaSlot::getSlotResult() { - mSlotResult = item; - mDamageTimer = 0; + int result = getResultFromAng(unk13C[0]); + int i = 1; + do { + if (result != getResultFromAng(unk13C[i])) + return -1; + ++i; + } while (i < 3); + return result; } -u8 TBossTelesa::slotFall() +int TTelesaSlot::getDrumResult(int drum) { - if (!mSlot) - return 1; - return mSlot->isRollDrum() == 0; + return getResultFromAng(unk13C[drum]); } -u8 TBossTelesa::rouletteFall() +int TTelesaSlot::getForcastResult(int drum) { - if (mRouletteTimer > 0) - --mRouletteTimer; - return mRouletteTimer == 0; + f32 angle = unk13C[drum]; + f32 speed = unk138[drum]; + + do { + if (fabsf(speed) > unk160) { + angle += speed; + if (speed > 0.0f) + speed -= unk15C; + else + speed += unk15C; + + if (angle >= 360.0f) + angle -= 360.0f; + if (angle <= 0.0f) + angle += 360.0f; + continue; + } + + angle += speed; + if (angle >= 360.0f) + angle -= 360.0f; + if (angle <= 0.0f) + angle += 360.0f; + } while ((s32)fabsf(angle) % unk168 != 0); + + return getResultFromAng((f32)(unk168 * (s32)(angle / (f32)unk168))); } -void TBossTelesa::damageRecover() +int TTelesaSlot::getResultFromAng(f32 angle) { - if (mDamageTimer > 0) - --mDamageTimer; - if (mDamageTimer == 0) - mDamage = 0; + if (angle < 44.0f) + return 0; + if (angle < 89.0f) + return 1; + if (angle < 134.0f) + return 3; + if (angle < 179.0f) + return 2; + if (angle < 224.0f) + return 0; + if (angle < 269.0f) + return 1; + if (angle < 314.0f) + return 3; + if (angle < 359.0f) + return 2; + return 2; } -void TBossTelesa::setSpicy(TLiveActor* actor) +void TTelesaSlot::calcObjCollision() { } + +void TTelesaSlot::entryObjCollision() { } + +TBossTelesa::TBossTelesa(const char* name) + : TSpineEnemy(name) + , mBattleState(1) + , mBattleTimer(0) + , mBattlePhase(0) + , mSaveParams(nullptr) + , mCurrentItem(-1) + , mCurrentFruit(-1) + , mFallSpeed(0.0f) + , mBodyCollision(nullptr) + , mSlot(nullptr) + , mTakingActor(nullptr) + , mIsSpicy(0) + , mDamageTimer(0) + , mStateTimer(0) + , mDamage(0) + , mGenerateTimer(0) + , mMarioHP(0) + , mForceRestart(1) + , mInDamage(1) + , mRouletteTimer(0) + , mRoulettePosY(0.0f) + , mRouletteSp(0.0f) + , mSlotResult(0) + , mSlotItemCount(0) + , mSlotStop(3) + , mDead(0) { - if (actor) - actor->onLiveFlag(LIVE_FLAG_UNK10); - mIsSpicy = 1; } -BOOL TBossTelesa::checkHitObject(THitActor* actor) +void TBossTelesa::init(TLiveManager* manager) { - return actor != nullptr && actor->checkActorType(ACTOR_TYPE_PLAYER); + TSpineEnemy::init(manager); + mSaveParams = static_cast(getSaveParam()); + mBodyCollision = new TBossTelesaBody(this); + mTongueCollision = new TBossTelesaTongue(this); + mKillSmallEnemy = new TBossTelesaKillSmallEnemy(this); } -BOOL TBossTelesa::receiveMessage(THitActor*, u32) { return TRUE; } - -void TBossTelesa::perform(u32 cue, JDrama::TGraphics* graphics) +void TBossTelesa::loadAfter() { - TSpineEnemy::perform(cue, graphics); - if (cue & CUE_MOVE) - damageRecover(); + mMarioHP = 1; + if (!mSaveParams) + mSaveParams = static_cast(getSaveParam()); } -void TBossTelesa::calcRootMatrix() { TSpineEnemy::calcRootMatrix(); } - -MtxPtr TBossTelesa::getTakingMtx() { return getModel() ? getModel()->getBaseTRMtx() : nullptr; } - -void TBossTelesa::kill() +void TBossTelesa::reset() { - if (!checkLiveFlag(LIVE_FLAG_DEAD)) { - mDead = 1; - TSpineEnemy::kill(); - } + TSpineEnemy::reset(); + mDead = 0; + mDamage = 0; + mGenerateTimer = 0; + forceAllItemKill(); } void TBossTelesa::moveObject() @@ -429,198 +493,181 @@ void TBossTelesa::moveObject() --mGenerateTimer; } -void TBossTelesa::reset() +void TBossTelesa::kill() { - TSpineEnemy::reset(); - mDead = 0; - mDamage = 0; - mGenerateTimer = 0; - forceAllItemKill(); + if (!checkLiveFlag(LIVE_FLAG_DEAD)) { + mDead = 1; + TSpineEnemy::kill(); + } } -void TBossTelesa::loadAfter() +MtxPtr TBossTelesa::getTakingMtx() { - mMarioHP = 1; - if (!mSaveParams) - mSaveParams = static_cast(getSaveParam()); + return getModel() ? getModel()->getBaseTRMtx() : nullptr; } -void TBossTelesa::init(TLiveManager* manager) -{ - TSpineEnemy::init(manager); - mSaveParams = static_cast(getSaveParam()); - mBodyCollision = new TBossTelesaBody(this); - mTongueCollision = new TBossTelesaTongue(this); - mKillSmallEnemy = new TBossTelesaKillSmallEnemy(this); -} +void TBossTelesa::prepareGenerate() { mGenerateTimer = 0; } -BOOL TBossTelesaKillSmallEnemy::receiveMessage(THitActor* sender, u32 message) +void TBossTelesa::calcRootMatrix() { TSpineEnemy::calcRootMatrix(); } + +void TBossTelesa::perform(u32 cue, JDrama::TGraphics* graphics) { - return THitActor::receiveMessage(sender, message); + TSpineEnemy::perform(cue, graphics); + if (cue & CUE_MOVE) + damageRecover(); } -void TBossTelesaKillSmallEnemy::checkHit() +BOOL TBossTelesa::receiveMessage(THitActor*, u32) { return TRUE; } + +BOOL TBossTelesa::checkMessage(THitActor* sender, u32 message) { - mHit = gpMarioPos != nullptr && mPosition.distance(*gpMarioPos) < 300.0f; - if (mHit && mOwner) - mOwner->forceHide(); + return receiveMessage(sender, message); } -BOOL TBossTelesaTongue::receiveMessage(THitActor* sender, u32 message) +BOOL TBossTelesa::checkHitObject(THitActor* actor) { - return THitActor::receiveMessage(sender, message); + return actor != nullptr && actor->checkActorType(ACTOR_TYPE_PLAYER); } -void TBossTelesaTongue::checkHit() { } - -BOOL TBossTelesaBody::receiveMessage(THitActor* sender, u32 message) +void TBossTelesa::setSpicy(TLiveActor* actor) { - if (mOwner && (message == HIT_MESSAGE_TRAMPLE || message == HIT_MESSAGE_SPRAYED_BY_WATER)) - mOwner->damageRecover(); - return THitActor::receiveMessage(sender, message); + if (actor) + actor->onLiveFlag(LIVE_FLAG_UNK10); + mIsSpicy = 1; } -void TBossTelesaBody::checkHit() { } - -TBossTelesaManager::TBossTelesaManager(const char* name) : TEnemyManager(name) { } - -void TBossTelesaManager::perform(u32 cue, JDrama::TGraphics* graphics) +void TBossTelesa::damageRecover() { - TEnemyManager::perform(cue, graphics); + if (mDamageTimer > 0) + --mDamageTimer; + if (mDamageTimer == 0) + mDamage = 0; } -void TBossTelesaManager::createModelData() { createModelDataArray(btelesa_model_data); } - -TSpineEnemy* TBossTelesaManager::createEnemyInstance() { return new TBossTelesa; } +void TBossTelesa::tongueHitWater() { } -void TBossTelesaManager::load(JSUMemoryInputStream& stream) +u8 TBossTelesa::rouletteFall() { - unk38 = new TBossTelesaSaveLoadParams("/enemy/bosstelesa.prm"); - TEnemyManager::load(stream); + if (mRouletteTimer > 0) + --mRouletteTimer; + return mRouletteTimer == 0; } -TBubble::TBubble(const char* name) - : TWalkerEnemy(name) - , mSaveParams(nullptr) - , mCarriedEnemy(nullptr) - , mHeight(0.0f) - , mIsSplit(0) - , mIsMoving(1) - , mNoEffect(0) +u8 TBossTelesa::slotFall() { + if (!mSlot) + return 1; + return mSlot->isRollDrum() == 0; } -const char** TBubble::getBasNameTable() const { return bubble_bastable; } +void TBossTelesa::openWaterPlace() { } -MtxPtr TBubble::getTakingMtx() +void TBossTelesa::flashItem(int item) { - return getModel() ? getModel()->getBaseTRMtx() : nullptr; + mSlotResult = item; + mDamageTimer = 0; } -f32 TBubble::getGravityY() const +void TBossTelesa::onAllCollision() { - if (mIsSplit) - return mIsMoving ? 0.001f : 0.0f; - return mGravity; + if (mBodyCollision) + mBodyCollision->onHitFlag(HIT_FLAG_NO_COLLISION); + if (mTongueCollision) + mTongueCollision->onHitFlag(HIT_FLAG_NO_COLLISION); } -void TBubble::setDeadAnm() { setBckAnm(9); } - -void TBubble::attackToMario() +void TBossTelesa::offAllCollision() { - sendAttackMsgToMario(); - kill(); + if (mBodyCollision) + mBodyCollision->offHitFlag(HIT_FLAG_NO_COLLISION); + if (mTongueCollision) + mTongueCollision->offHitFlag(HIT_FLAG_NO_COLLISION); } -void TBubble::appendEnemy() { mCarriedEnemy = nullptr; } - -void TBubble::appendItem() { } +const char** TBossTelesa::getBasNameTable() const { return btelesa_bastable; } -void TBubble::calcRootMatrix() +void TBossTelesa::genAttacker() { - if (!isEaten()) - mPosition.y = 150.0f + mHeight; - TWalkerEnemy::calcRootMatrix(); + mGenerateTimer = mSaveParams ? mSaveParams->mSLGenAttackerTime.get() : 500; } -void TBubble::behaveToWater(THitActor*) +void TBossTelesa::setBckAnm(int index) { - if (mIsMoving) - kill(); + if (getMActor()) + getMActor()->setBckFromIndex(index); } -void TBubble::kill() +bool TBossTelesa::isInDamage() { return mInDamage != 0; } + +void TBossTelesa::rouletteStart() { - if (!checkLiveFlag(LIVE_FLAG_DEAD)) - TSmallEnemy::kill(); + mRouletteTimer = 0; + mSlotStop = 0; + if (mSlot) + mSlot->moveStart(); } -void TBubble::split() { mIsSplit = 1; } +void TBossTelesa::slotStart() { rouletteStart(); } -void TBubble::reset() +void TBossTelesa::slotStop() { - TWalkerEnemy::reset(); - mHeight = MsRandF(50.0f, 150.0f); - mIsSplit = 0; - mIsMoving = 1; - mNoEffect = 0; - mCarriedEnemy = nullptr; + if (mSlot) + mSlot->forceStopSlot(0); } -void TBubble::init(TLiveManager* manager) +int TBossTelesa::checkSlotResult() { return mSlot ? mSlotResult : -1; } + +void TBossTelesa::generateSlotItem() { - TWalkerEnemy::init(manager); - mSaveParams = static_cast(getSaveParam()); + ++mSlotItemCount; + mGenerateTimer = 0; } -TBubbleManager::TBubbleManager(const char* name) : TSmallEnemyManager(name) { } - -void TBubbleManager::createModelData() { createModelDataArray(bubble_model_data); } +void TBossTelesa::fruitCollisionOn() { mSlotStop = 0; } -TSmallEnemy* TBubbleManager::createEnemyInstance() { return new TBubble; } +bool TBossTelesa::checkSlot() { return mSlot != nullptr; } -void TBubbleManager::load(JSUMemoryInputStream& stream) +bool TBossTelesa::checkAllItemDead() { - unk38 = new TBubbleSaveLoadParams("/enemy/bubble.prm"); - TSmallEnemyManager::load(stream); + return mItem0 == 0 && mItem1 == 0 && mItem2 == 0 && mItem3 == 0; } -/* The retail nerve bodies are intentionally kept small here. The important - * transition points (hide, roulette start, spit and bubble split) are now - * named and can be filled in independently without reintroducing anonymous - * assembly-only classes. */ -DEFINE_NERVE(TNerveBossTelesaFallDemo, TLiveActor) +void TBossTelesa::forceAllItemKill() { - TBossTelesa* boss = static_cast(spine->getBody()); - if (spine->getTime() == 0) - boss->rouletteStart(); - return boss->rouletteFall() && boss->slotFall(); + mItem0 = mItem1 = mItem2 = mItem3 = 0; + mFruit0 = mFruit1 = mFruit2 = 0; } -DEFINE_NERVE(TNerveBossTelesaFreeze, TLiveActor) +void TBossTelesa::rollRouletteCircle() { - return spine->getTime() > 0 && spine->getBody()->checkCurAnmEnd(0); + if (mSlot) + mSlot->moveStart(); } -DEFINE_NERVE(TNerveBossTelesaPrepareSlot, TLiveActor) -{ - return spine->getTime() > 30; -} +bool TBossTelesa::isForceRestart() { return mForceRestart != 0; } -DEFINE_NERVE(TNerveBossTelesaSpitSlotItem, TLiveActor) +void TBossTelesa::forceHide() { - return spine->getTime() > 20; + mDead = 1; + offLiveFlag(LIVE_FLAG_UNK200); + onLiveFlag(LIVE_FLAG_HIDDEN); } -DEFINE_NERVE(TNerveBossTelesaSlotStart, TLiveActor) +void TBossTelesa::fanfale() { } + +DEFINE_NERVE(TNerveBossTelesaDie, TLiveActor) { if (spine->getTime() == 0) - static_cast(spine->getBody())->rouletteStart(); - return spine->getTime() > 60; + static_cast(spine->getBody())->kill(); + return false; } -DEFINE_NERVE(TNerveBossTelesaAppear, TLiveActor) +DEFINE_NERVE(TNerveBossTelesaSpit, TLiveActor) { return spine->getTime() > 20; } + +DEFINE_NERVE(TNerveBossTelesaHide, TLiveActor) { + if (spine->getTime() == 0) + static_cast(spine->getBody())->forceHide(); return spine->getTime() > 30; } @@ -629,37 +676,37 @@ DEFINE_NERVE(TNerveBossTelesaHideWait, TLiveActor) return spine->getTime() > 60; } -DEFINE_NERVE(TNerveBossTelesaHide, TLiveActor) +DEFINE_NERVE(TNerveBossTelesaAppear, TLiveActor) { - if (spine->getTime() == 0) - static_cast(spine->getBody())->forceHide(); return spine->getTime() > 30; } -DEFINE_NERVE(TNerveBossTelesaSpit, TLiveActor) +DEFINE_NERVE(TNerveBossTelesaSlotStart, TLiveActor) { - return spine->getTime() > 20; + if (spine->getTime() == 0) + static_cast(spine->getBody())->rouletteStart(); + return spine->getTime() > 60; } -DEFINE_NERVE(TNerveBossTelesaDie, TLiveActor) +DEFINE_NERVE(TNerveBossTelesaSpitSlotItem, TLiveActor) { - if (spine->getTime() == 0) - static_cast(spine->getBody())->kill(); - return false; + return spine->getTime() > 20; } -DEFINE_NERVE(TNerveBubbleSplit, TLiveActor) +DEFINE_NERVE(TNerveBossTelesaPrepareSlot, TLiveActor) { - if (spine->getTime() == 0) - static_cast(spine->getBody())->split(); - return spine->getTime() > 10; + return spine->getTime() > 30; } -DEFINE_NERVE(TNerveBubbleLive, TLiveActor) +DEFINE_NERVE(TNerveBossTelesaFreeze, TLiveActor) { - return spine->getTime() > 80; + return spine->getTime() > 0 && spine->getBody()->checkCurAnmEnd(0); } -/* Keep a named TU initializer so the list of state singletons has a stable - * home when the remaining retail constructors are enabled. */ -extern "C" static void __sinit_bosstelesa_cpp() { } +DEFINE_NERVE(TNerveBossTelesaFallDemo, TLiveActor) +{ + TBossTelesa* boss = static_cast(spine->getBody()); + if (spine->getTime() == 0) + boss->rouletteStart(); + return boss->rouletteFall() && boss->slotFall(); +} From b68618d4f92b65f03046a11f998433a5b1aa97d2 Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Sun, 2 Aug 2026 22:03:37 -0400 Subject: [PATCH 3/5] Enemy/bosstelesa: decompile boss behavior --- include/Enemy/BossTelesa.hpp | 323 ++-- include/Strategic/HitActor.hpp | 4 + src/Enemy/bosstelesa.cpp | 2850 ++++++++++++++++++++++++++++---- 3 files changed, 2720 insertions(+), 457 deletions(-) diff --git a/include/Enemy/BossTelesa.hpp b/include/Enemy/BossTelesa.hpp index 71e32f612..d933ea4d9 100644 --- a/include/Enemy/BossTelesa.hpp +++ b/include/Enemy/BossTelesa.hpp @@ -1,33 +1,18 @@ #ifndef ENEMY_BOSS_TELESA_HPP #define ENEMY_BOSS_TELESA_HPP +#include #include -#include -#include +#include +#include +#include #include -#include - -class TBossTelesa; -class TBubble; - -/* The boss uses the same spine state objects as the regular Telesa, but has - * its own state machine for the roulette, tongue and bubble attacks. */ -DECLARE_NERVE(TNerveBossTelesaFallDemo, TLiveActor); -DECLARE_NERVE(TNerveBossTelesaFreeze, TLiveActor); -DECLARE_NERVE(TNerveBossTelesaPrepareSlot, TLiveActor); -DECLARE_NERVE(TNerveBossTelesaSpitSlotItem, TLiveActor); -DECLARE_NERVE(TNerveBossTelesaSlotStart, TLiveActor); -DECLARE_NERVE(TNerveBossTelesaAppear, TLiveActor); -DECLARE_NERVE(TNerveBossTelesaHideWait, TLiveActor); -DECLARE_NERVE(TNerveBossTelesaHide, TLiveActor); -DECLARE_NERVE(TNerveBossTelesaSpit, TLiveActor); -DECLARE_NERVE(TNerveBossTelesaDie, TLiveActor); -DECLARE_NERVE(TNerveBubbleSplit, TLiveActor); -DECLARE_NERVE(TNerveBubbleLive, TLiveActor); +#include +#include class TBossTelesaSaveLoadParams : public TSpineEnemyParams { public: - TBossTelesaSaveLoadParams(const char* path); + TBossTelesaSaveLoadParams(const char*); /* 0x0A8 */ TParamRT mSLDamageRadius; /* 0x0BC */ TParamRT mSLDamageHeight; @@ -50,66 +35,69 @@ class TBossTelesaSaveLoadParams : public TSpineEnemyParams { /* 0x210 */ TParamRT mSLSpicyTime; }; -class TBubbleSaveLoadParams : public TWalkerEnemyParams { -public: - TBubbleSaveLoadParams(const char* path); - - /* 0x32C */ TParamRT mSLLiveTime; - /* 0x340 */ TParamRT mSLNumDivision; - /* 0x354 */ TParamRT mSLMaxScale; - /* 0x368 */ TParamRT mSLAddPosBase; - /* 0x37C */ TParamRT mSLRateExpand; - /* 0x390 */ TParamRT mSLDeadHeight; -}; +class TBossTelesa; -/* TSlotDrum already owns the geometry, angle and neon material fields. */ class TTelesaSlot : public TSlotDrum { public: - TTelesaSlot(const char* name); + TTelesaSlot(const char* name) + : TSlotDrum(name) + , unk1A0(nullptr) + , unk1A4(0) + { + unk198 = 0; + unk199 = 0; + unk19A = 0; + unk19B = 0; + unk19C = 0; + unk1A8 = 0; + unk1A9 = 0; + unk1AA = 0; + } virtual void calcRootMatrix(); - virtual void moveObject(); virtual void initMapObj(); + virtual void moveObject(); virtual u32 touchWater(THitActor*); virtual void initNeonMatColor() { } + void randomReset(); + void moveStart(); + void forceStopSlot(int); + BOOL isRollDrum(); + int getSlotResult(); int getForcastResult(int); int getResultFromAng(f32); - int getSlotResult(); - u8 isRollDrum(); - void forceStopSlot(int); - void moveStart(); - void randomReset(); + f32* getDrumSpeeds() const { return unk138; } void entryObjCollision(); void calcObjCollision(); int getDrumResult(int); - /* 0x198 */ u8 mRoll0; - /* 0x199 */ u8 mRoll1; - /* 0x19A */ u8 mRoll2; - /* 0x19B */ s8 mRolling; - /* 0x19C */ u8 mForceStop; - /* 0x1A0 */ TBossTelesa* mOwner; - /* 0x1A4 */ s32 mForceResult; - /* 0x1A8 */ s8 mStop0; - /* 0x1A9 */ s8 mStop1; - /* 0x1AA */ s8 mStop2; - /* 0x1AB */ u8 mPad1AB[0x31]; - /* 0x1DC */ TMapCollisionMove* mMapCollisionMove; - /* 0x1E0 */ u8 mNeonState; - /* 0x1E1 */ u8 mPad1E1[3]; - /* 0x1E4 */ f32 mUnk1E4; - /* 0x1E8 */ f32 mUnk1E8; - /* 0x1EC */ f32 mUnk1EC; + /* 0x198 */ u8 unk198; + /* 0x199 */ u8 unk199; + /* 0x19A */ u8 unk19A; + /* 0x19B */ u8 unk19B; + /* 0x19C */ u8 unk19C; + /* 0x19D */ u8 unk19D[3]; + /* 0x1A0 */ void* unk1A0; + /* 0x1A4 */ s32 unk1A4; + /* 0x1A8 */ u8 unk1A8; + /* 0x1A9 */ u8 unk1A9; + /* 0x1AA */ u8 unk1AA; + /* 0x1AB */ u8 unk1AB[0x31]; + /* 0x1DC */ TMapCollisionMove* unk1DC; + /* 0x1E0 */ u8 unk1E0; + /* 0x1E1 */ u8 unk1E1[3]; + /* 0x1E4 */ f32 unk1E4[3]; }; class TBossTelesa : public TSpineEnemy { public: - TBossTelesa(const char* name = "ボステレサ"); + TBossTelesa(const char*); virtual void loadAfter(); - virtual void perform(u32 cue, JDrama::TGraphics* graphics); + virtual void perform(u32, JDrama::TGraphics*); virtual BOOL receiveMessage(THitActor*, u32); + virtual MtxPtr getTakingMtx(); virtual void init(TLiveManager*); virtual void calcRootMatrix(); virtual void moveObject(); @@ -129,174 +117,201 @@ class TBossTelesa : public TSpineEnemy { int checkSlotResult(); void slotStop(); void slotStart(); + void rouletteStart(); bool isInDamage(); void setBckAnm(int); + void genAttacker(); void offAllCollision(); void onAllCollision(); + void flashItem(int); void openWaterPlace(); + BOOL slotFall(); + BOOL rouletteFall(); void tongueHitWater(); - BOOL checkMessage(THitActor*, u32); - void prepareGenerate(); - void rouletteStart(); - void genAttacker(); - void flashItem(int); - u8 slotFall(); - u8 rouletteFall(); void damageRecover(); void setSpicy(TLiveActor*); - BOOL checkHitObject(THitActor*); - MtxPtr getTakingMtx(); + void checkHitObject(THitActor*); + BOOL checkMessage(THitActor*, u32); + void prepareGenerate(); static f32 mEnemyGenRate; static f32 mItemGenRate; static u8 mNormalAlpha; static f32 mBaseHoseiPosY; static f32 mRouletteUpRate; - static s32 mTelesaGenerateInterval; + static u32 mTelesaGenerateInterval; static f32 mCameraMoveLimit; static f32 mCameraMoveSp; - /* 0x150 */ u8 mBattleState; - /* 0x151 */ u8 mPad151[3]; - /* 0x154 */ s32 mBattleTimer; - /* 0x158 */ s32 mBattlePhase; - /* 0x15C */ TBossTelesaSaveLoadParams* mSaveParams; - /* 0x160 */ s32 mCurrentItem; - /* 0x164 */ s32 mCurrentFruit; - /* 0x168 */ f32 mFallSpeed; - /* 0x16C */ THitActor* mBodyCollision; - /* 0x170 */ THitActor* mTongueCollision; - /* 0x174 */ THitActor* mKillSmallEnemy; - /* 0x178 */ void* mItemList; - /* 0x17C */ void* mBubbleList; - /* 0x180 */ void* mUnk180; - /* 0x184 */ TTelesaSlot* mSlot; - /* 0x188 */ JDrama::TViewObj* mTakingActor; - /* 0x18C */ s8 mIsSpicy; - /* 0x18D */ u8 mPad18D[0xF]; - /* 0x19C */ f32 mDamageBlend; - /* 0x1A0 */ f32 mDamageBlendSpeed; - /* 0x1A4 */ f32 mDamageBlendTarget; - /* 0x1A8 */ s32 mDamageTimer; - /* 0x1AC */ u8 mPad1AC[0xC8]; - /* 0x274 */ s32 mStateTimer; - /* 0x278 */ u8 mPad278[0xD0]; - /* 0x348 */ u8 mItem0; - /* 0x349 */ u8 mItem1; - /* 0x34A */ u8 mItem2; - /* 0x34B */ u8 mItem3; - /* 0x34C */ u8 mFruit0; - /* 0x34D */ u8 mFruit1; - /* 0x34E */ u8 mFruit2; - /* 0x34F */ u8 mAlpha; - /* 0x350 */ u8 mDamage; - /* 0x354 */ s32 mGenerateTimer; - /* 0x358 */ s16 mMarioHP; - /* 0x35A */ u8 mForceRestart; - /* 0x35B */ u8 mInDamage; - /* 0x35C */ s32 mRouletteTimer; - /* 0x360 */ f32 mRoulettePosY; - /* 0x364 */ f32 mRouletteSp; - /* 0x368 */ s32 mSlotResult; - /* 0x36C */ s32 mSlotItemCount; - /* 0x370 */ u8 mSlotStop; - /* 0x371 */ u8 mPad371[3]; - /* 0x374 */ JGeometry::TVec3 mEffectPos; - /* 0x380 */ s32 mUnk380; - /* 0x384 */ s8 mDead; - /* 0x385 */ u8 mPad385[3]; - /* 0x388 */ s32 mUnk388; +public: + /* 0x150 */ u8 unk150; + /* 0x151 */ u8 unk151[0x3]; + /* 0x154 */ TTelesaSlot* unk154; + /* 0x158 */ void* unk158; + /* 0x15C */ void* unk15C; + /* 0x160 */ s32 unk160; + /* 0x164 */ s32 unk164; + /* 0x168 */ f32 unk168; + /* 0x16C */ THitActor* unk16C; + /* 0x170 */ THitActor* unk170; + /* 0x174 */ THitActor* unk174; + /* 0x178 */ TRoulette* unk178; + /* 0x17C */ TRoulette* unk17C; + /* 0x180 */ TRoulette* unk180; + /* 0x184 */ TTelesaSlot* unk184; + /* 0x188 */ JDrama::TViewObj* unk188; + /* 0x18C */ u8 unk18C; + /* 0x18D */ u8 unk18D[0xF]; + /* 0x19C */ JGeometry::TVec3 unk19C; + /* 0x1A8 */ s32 unk1A8; + /* 0x1AC */ TLiveActor* unk1AC[50]; + /* 0x274 */ s32 unk274; + /* 0x278 */ JGeometry::TMatrix34 > unk278; + /* 0x2A8 */ TLiveActor* unk2A8[20]; + /* 0x2F8 */ TLiveActor* unk2F8[10]; + /* 0x320 */ TLiveActor* unk320[10]; + /* 0x348 */ GXColor unk348; + /* 0x34C */ GXColor unk34C; + /* 0x350 */ u8 unk350; + /* 0x351 */ u8 unk351[0x3]; + /* 0x354 */ TObjManager* unk354; + /* 0x358 */ u16 unk358; + /* 0x35A */ u8 unk35A; + /* 0x35B */ u8 unk35B; + /* 0x35C */ s32 unk35C; + /* 0x360 */ f32 unk360; + /* 0x364 */ f32 unk364; + /* 0x368 */ s32 unk368; + /* 0x36C */ s32 unk36C; + /* 0x370 */ u8 unk370; + /* 0x371 */ u8 unk371[0x3]; + /* 0x374 */ JGeometry::TVec3 unk374; + /* 0x380 */ s32 unk380; + /* 0x384 */ u8 unk384; + /* 0x385 */ u8 unk385[0x3]; + /* 0x388 */ s32 unk388; }; -class TBossTelesaKillSmallEnemy : public THitActor { +class TBossTelesaBody : public THitActor { public: - TBossTelesaKillSmallEnemy(TBossTelesa* owner = nullptr) - : THitActor("ボステレサ小敵キラー") - , mOwner(owner) - , mHit(false) + TBossTelesaBody(const char* name) + : THitActor(name) + , unk6C(0) { } - virtual BOOL receiveMessage(THitActor* sender, u32 message); + virtual BOOL receiveMessage(THitActor*, u32); void checkHit(); - /* 0x68 */ TBossTelesa* mOwner; - /* 0x6C */ bool mHit; +public: + /* 0x68 */ TBossTelesa* unk68; + /* 0x6C */ u8 unk6C; }; class TBossTelesaTongue : public THitActor { public: - TBossTelesaTongue(TBossTelesa* owner = nullptr) - : THitActor("ボステレサ舌") - , mOwner(owner) + TBossTelesaTongue(const char* name) + : THitActor(name) { } virtual BOOL receiveMessage(THitActor*, u32); void checkHit(); - TBossTelesa* mOwner; + +public: + /* 0x68 */ TBossTelesa* unk68; }; -class TBossTelesaBody : public THitActor { +class TBossTelesaKillSmallEnemy : public THitActor { public: - TBossTelesaBody(TBossTelesa* owner = nullptr) - : THitActor("ボステレサ本体") - , mOwner(owner) + TBossTelesaKillSmallEnemy(const char* name) + : THitActor(name) + , unk68(nullptr) + , unk6C(0) { } - virtual BOOL receiveMessage(THitActor*, u32); void checkHit(); - TBossTelesa* mOwner; + +public: + /* 0x68 */ TBossTelesa* unk68; + /* 0x6C */ u8 unk6C; }; class TBossTelesaManager : public TEnemyManager { public: - TBossTelesaManager(const char* name = "ボステレサマネージャー"); + TBossTelesaManager(const char*); virtual void load(JSUMemoryInputStream&); - virtual void perform(u32 cue, JDrama::TGraphics* graphics); + virtual void perform(u32, JDrama::TGraphics*); virtual void createModelData(); virtual TSpineEnemy* createEnemyInstance(); virtual void clipEnemies(JDrama::TGraphics*) { } }; +class TBubbleSaveLoadParams : public TWalkerEnemyParams { +public: + TBubbleSaveLoadParams(const char*); + + /* 0x32C */ TParamRT mSLLiveTime; + /* 0x340 */ TParamRT mSLNumDivision; + /* 0x354 */ TParamRT mSLMaxScale; + /* 0x368 */ TParamRT mSLAddPosBase; + /* 0x37C */ TParamRT mSLRateExpand; + /* 0x390 */ TParamRT mSLDeadHeight; +}; + class TBubble : public TWalkerEnemy { public: TBubble(const char* name = "バブル"); + virtual MtxPtr getTakingMtx(); virtual void init(TLiveManager*); virtual void calcRootMatrix(); - virtual void behaveToWater(THitActor*); virtual void kill(); virtual f32 getGravityY() const; virtual const char** getBasNameTable() const; virtual void reset(); + virtual void behaveToWater(THitActor*); + virtual void setDeadAnm(); + virtual void attackToMario(); + virtual void setAfterDeadEffect() { } void appendEnemy(); void appendItem(); - void setDeadAnm(); - void attackToMario(); void split(); - void setAfterDeadEffect() { } - MtxPtr getTakingMtx(); - - /* 0x194 */ TBubbleSaveLoadParams* mSaveParams; - /* 0x198 */ TSmallEnemy* mCarriedEnemy; - /* 0x19C */ TMtx34f mTakingMatrix; - /* 0x1CC */ f32 mHeight; - /* 0x1D0 */ u8 mIsSplit; - /* 0x1D1 */ u8 mIsMoving; - /* 0x1D2 */ u8 mNoEffect; + +public: + /* 0x194 */ TBubbleSaveLoadParams* unk194; + /* 0x198 */ TLiveActor* unk198; + /* 0x19C */ JGeometry::TMatrix34 > unk19C; + /* 0x1CC */ f32 unk1CC; + /* 0x1D0 */ u8 unk1D0; + /* 0x1D1 */ u8 unk1D1; + /* 0x1D2 */ u8 unk1D2; + /* 0x1D3 */ u8 unk1D3; }; class TBubbleManager : public TSmallEnemyManager { public: - TBubbleManager(const char* name = "バブルマネージャー"); + TBubbleManager(const char*); virtual void load(JSUMemoryInputStream&); virtual void createModelData(); virtual TSmallEnemy* createEnemyInstance(); }; +DECLARE_NERVE(TNerveBossTelesaFallDemo, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaFreeze, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaPrepareSlot, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaSpitSlotItem, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaSlotStart, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaAppear, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaHideWait, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaHide, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaSpit, TLiveActor); +DECLARE_NERVE(TNerveBossTelesaDie, TLiveActor); +DECLARE_NERVE(TNerveBubbleSplit, TLiveActor); +DECLARE_NERVE(TNerveBubbleLive, TLiveActor); + #endif diff --git a/include/Strategic/HitActor.hpp b/include/Strategic/HitActor.hpp index 03b2056aa..f1b8627ca 100644 --- a/include/Strategic/HitActor.hpp +++ b/include/Strategic/HitActor.hpp @@ -68,6 +68,10 @@ class THitActor : public JDrama::TActor { // fabricated u32 getActorType() const { return mActorType; } + u8 isActorTypeOf(u32 base) const + { + return (mActorType - base) == 1 ? 1 : 0; + } bool checkActorType(u32 flag) const { return mActorType & flag ? true : false; diff --git a/src/Enemy/bosstelesa.cpp b/src/Enemy/bosstelesa.cpp index 73920418b..b52c3e57d 100644 --- a/src/Enemy/bosstelesa.cpp +++ b/src/Enemy/bosstelesa.cpp @@ -1,23 +1,50 @@ #include - #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include #include -#include -#include -#include - -/* - * Boss Telesa is the casino boss in Sirena Beach. The retail TU is a good - * deal denser than the ordinary Telesa TU: one actor owns three roulette - * drums, two hit proxies and a small bubble manager. Keep the data and - * state-machine names here even while the long animation routines are being - * brought over from the matching assembly. - */ +#include +#include +#include +#include +#include +#include static const char* btelesa_bastable[] = { "/scene/btelesa/bas/btelesa_appear.bas", @@ -39,54 +66,33 @@ static const char* btelesa_bastable[] = { "/scene/btelesa/bas/btelesa_wet.bas", }; -static const char* bubble_bastable[] = { - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - "/scene/btelesa/bas/btelesa_lick.bas", - "/scene/btelesa/bas/btelesa_roll.bas", - nullptr, - nullptr, -}; - -static const TModelDataLoadEntry btelesa_model_data[] = { - { "btelesa.bmd", 0x15300000, 0 }, - { nullptr, 0, 0 }, -}; - -static const TModelDataLoadEntry bubble_model_data[] = { - { "btelesa_osenbubbles_ind.bmd", 0x11020000, 0 }, - { nullptr, 0, 0 }, -}; - -f32 TBossTelesa::mEnemyGenRate = 0.1f; +f32 TBossTelesa::mEnemyGenRate = 0.5f; f32 TBossTelesa::mItemGenRate = 0.1f; -u8 TBossTelesa::mNormalAlpha = 0xFF; -f32 TBossTelesa::mBaseHoseiPosY = 350.0f; -f32 TBossTelesa::mRouletteUpRate = 0.1f; -s32 TBossTelesa::mTelesaGenerateInterval = 600; -f32 TBossTelesa::mCameraMoveLimit = 300.0f; -f32 TBossTelesa::mCameraMoveSp = 2.0f; - -/* The slot angle table is deliberately kept as the retail eight-way split. */ +u8 TBossTelesa::mNormalAlpha = 0x96; +f32 TBossTelesa::mBaseHoseiPosY = -300.0f; +f32 TBossTelesa::mRouletteUpRate = 0.03f; +u32 TBossTelesa::mTelesaGenerateInterval = 400; +f32 TBossTelesa::mCameraMoveLimit = 1000.0f; +f32 TBossTelesa::mCameraMoveSp = 0.02f; -/* The retail nerve bodies are intentionally kept small here. The important - * transition points (hide, roulette start, spit and bubble split) are now - * named and can be filled in independently without reintroducing anonymous - * assembly-only classes. */ +static inline TBossTelesa* getBoss(TSpineBase* spine) +{ + return (TBossTelesa*)spine->getBody(); +} -/* Keep a named TU initializer so the list of state singletons has a stable - * home when the remaining retail constructors are enabled. */ +static inline TBubble* getBubble(TSpineBase* spine) +{ + return (TBubble*)spine->getBody(); +} -extern "C" static void __sinit_bosstelesa_cpp() { } +static inline TBossTelesa* getSlotOwner(TTelesaSlot* slot) +{ + return (TBossTelesa*)slot->unk1A0; +} TBubbleSaveLoadParams::TBubbleSaveLoadParams(const char* path) : TWalkerEnemyParams(path) - , PARAM_INIT(mSLLiveTime, 1200) + , PARAM_INIT(mSLLiveTime, 200) , PARAM_INIT(mSLNumDivision, 5) , PARAM_INIT(mSLMaxScale, 1.5f) , PARAM_INIT(mSLAddPosBase, 50.0f) @@ -111,59 +117,120 @@ TSmallEnemy* TBubbleManager::createEnemyInstance() { return new TBubble; } void TBubbleManager::createModelData() { - createModelDataArray(bubble_model_data); + static TModelDataLoadEntry entry[] = { + { "btelesa_osenbubbles_ind.bmd", 0x11020000, 0 }, + { nullptr, 0, 0 }, + }; + createModelDataArray(entry); } TBubble::TBubble(const char* name) : TWalkerEnemy(name) - , mCarriedEnemy(nullptr) - , mTakingMatrix() - , mHeight(0.0f) - , mIsSplit(0) - , mIsMoving(0) + , unk198(nullptr) + , unk1CC(0.0f) + , unk1D0(0) + , unk1D1(0) { } void TBubble::init(TLiveManager* manager) { TWalkerEnemy::init(manager); - mActorType = 0x10000020; - unk150 = 0x11; - mSaveParams = static_cast(getSaveParam()); + mActorType = 0x10000020; + unk150 = 0x11; + unk194 = (TBubbleSaveLoadParams*)getSaveParam(); mSpine->initWith(&TNerveBubbleLive::theNerve()); mMActor->setLightType(3); + + TScreenTexture* tex + = JDrama::TNameRefGen::search("スクリーンテクスチャ"); + SMS_ChangeTextureAll(mMActor->getModel()->getModelData(), "H_ma_rak_dummy", + *tex->getTexture()->getTexInfo()); } void TBubble::reset() { TWalkerEnemy::reset(); onLiveFlag(LIVE_FLAG_UNK8); - mHeight = MsRandF(50.0f, 150.0f); - mIsSplit = 0; - mIsMoving = 1; - mNoEffect = 0; - mCarriedEnemy = nullptr; + + TMsRange scaleRange(50.0f, 150.0f); + unk1CC = scaleRange.rand(); + unk1D0 = 0; + unk1D1 = 1; + unk1D2 = 0; + unk198 = 0; + mSpine->initWith(&TNerveBubbleLive::theNerve()); } -void TBubble::split() { mIsSplit = 1; } +void TBubble::split() +{ + s32 numDivision = unk194->mSLNumDivision.get(); + for (int i = 0; i < numDivision; ++i) { + TBubble* bubble = (TBubble*)gpConductor->makeOneEnemyAppear( + mPosition, "バブルマネージャー", 1); + if (!bubble) + break; + + bubble->reset(); + bubble->mPosition = mPosition; + bubble->mPosition.y += unk1CC; + bubble->unk1CC = 0.0f; + bubble->unk1D0 = 1; + + TMsRange velocityRange(-2.0f, 2.0f); + JGeometry::TVec3 velocity; + velocity.x = velocityRange.rand(); + velocity.y = velocityRange.rand(); + velocity.z = velocityRange.rand(); + + bubble->mVelocity = velocity; + } +} f32 TBubble::getGravityY() const { - if (mIsSplit) - return mIsMoving ? 0.001f : 0.0f; + if (unk1D0) { + if (unk1D1) + return 0.001f; + return 0.0f; + } return mGravity; } void TBubble::kill() { - if (!checkLiveFlag(LIVE_FLAG_DEAD)) - TSmallEnemy::kill(); + if (checkLiveFlag(LIVE_FLAG_DEAD)) + return; + + if (unk198) { + if (unk1D2) + unk198->receiveMessage(this, HIT_MESSAGE_THROWN); + else + unk198->kill(); + unk198 = nullptr; + } + + mHitPoints = 1; + if (mSpine->getCurrentNerve() != &TNerveSmallEnemyDie::theNerve()) { + mSpine->reset(); + mSpine->setNext(&TNerveSmallEnemyDie::theNerve()); + mSpine->pushAfterCurrent(mSpine->getDefault()); + } + + onLiveFlag(LIVE_FLAG_UNK20000); + onLiveFlag(LIVE_FLAG_UNK40); } void TBubble::behaveToWater(THitActor*) { - if (mIsMoving) + if (mSpine->getCurrentNerve() == &TNerveBubbleLive::theNerve() + && mMActor->checkCurBckFromIndex(10)) { kill(); + TMapObjBase* item = gpItemManager->makeObjAppear( + mPosition.x, mPosition.y + 50.0f, mPosition.z, 0x20000002, true); + if (item) + ((TItem*)item)->killByTimer(0x4B0); + } } void TBubble::attackToMario() @@ -175,22 +242,22 @@ void TBubble::attackToMario() void TBubble::calcRootMatrix() { if (!isEaten()) { - mPosition.y = 150.0f + (mGroundHeight + mHeight); - MsMtxSetXYZRPH(getMActor()->getModel()->getBaseTRMtx(), mPosition.x, - mPosition.y, mPosition.z, mRotation.x, mRotation.y, - mRotation.z); - getMActor()->getModel()->setBaseScale(mScaling); + mPosition.y = mGroundHeight + unk1CC + 150.0f; + + MtxPtr mtx = mMActor->getModel()->getBaseTRMtx(); + MsMtxSetXYZRPH(mtx, mPosition.x, mPosition.y, mPosition.z, + (s16)(mRotation.x * 182.04445f), + (s16)(mRotation.y * 182.04445f), + (s16)(mRotation.z * 182.04445f)); + mMActor->getModel()->setBaseScale(mScaling); } } void TBubble::setDeadAnm() { setBckAnm(9); } -MtxPtr TBubble::getTakingMtx() -{ - return getMActor()->getModel()->getBaseTRMtx(); -} +MtxPtr TBubble::getTakingMtx() { return mMActor->unk4->unk20; } -const char** TBubble::getBasNameTable() const { return bubble_bastable; } +const char** TBubble::getBasNameTable() const { return btelesa_bastable; } const TNerveBubbleLive& TNerveBubbleLive::theNerve() { @@ -200,18 +267,129 @@ const TNerveBubbleLive& TNerveBubbleLive::theNerve() void TBubble::appendItem() { } -void TBubble::appendEnemy() { mCarriedEnemy = nullptr; } +void TBubble::appendEnemy() +{ + unk198 = nullptr; + + TMsRange enemyKindRange(0.0f, 100.0f); + f32 randValue = enemyKindRange.rand(); + + TSmallEnemy* enemy; + if (randValue < 50.0f) { + enemy = (TSmallEnemy*)gpConductor->makeOneEnemyAppear( + mPosition, "ポポマネージャー", 1); + enemy->unk154 = 0.6f; + enemy->reset(); + } else if (randValue < 100.0f) { + enemy = (TSmallEnemy*)gpConductor->makeOneEnemyAppear( + mPosition, "ボム兵マネージャー", 1); + enemy->unk154 = 0.3f; + enemy->reset(); + } else if (randValue < 150.0f) { + enemy = (TSmallEnemy*)gpConductor->makeOneEnemyAppear( + mPosition, "テレサマネージャー", 1); + if (!enemy) + return; + enemy->unk154 = 0.6f; + enemy->reset(); + ((TTelesa*)enemy)->setAttacker(); + } else { + enemy = (TSmallEnemy*)gpConductor->makeOneEnemyAppear( + mPosition, "パックンマネージャー", 1); + enemy->unk154 = 0.6f; + enemy->reset(); + } + + if (enemy && enemy->receiveMessage(this, HIT_MESSAGE_TAKE)) { + enemy->onHitFlag(HIT_FLAG_UNK8000000); + mHeldObject = enemy; + JGeometry::TVec3 velocity; + velocity.set(0.0f, 1.0f, -1.0f); + enemy->mVelocity = velocity; + enemy->onLiveFlag(LIVE_FLAG_AIRBORNE); + unk198 = enemy; + } +} BOOL TNerveBubbleLive::execute(TSpineBase* spine) const { - return spine->getTime() > 80; + TBubble* bubble = getBubble(spine); + if (spine->getTime() == 0) { + bubble->offHitFlag(HIT_FLAG_NO_COLLISION); + + if (bubble->unk1D0) { + bubble->setBckAnm(10); + bubble->setGoalPathMario(); + } else { + bubble->setBckAnm(8); + } + + J3DFrameCtrl* frameCtrl = bubble->mMActor->getFrameCtrl(0); + TMsRange frameRange(0.0f, 20.0f); + frameCtrl->setFrame(frameRange.rand()); + bubble->onLiveFlag(LIVE_FLAG_UNK8); + } else if (bubble->checkCurAnmEnd(0)) { + bubble->offHitFlag(HIT_FLAG_NO_COLLISION); + bubble->setBckAnm(10); + } + + if (!bubble->unk1D0) { + if (bubble->unk1CC < bubble->unk194->mSLAddPosBase.get()) + bubble->unk1CC += 1.0f; + } else { + if (spine->getTime() > 40 && bubble->unk1D1) { + JGeometry::TVec3 velocity = bubble->mVelocity; + velocity.scale(0.98f); + bubble->mVelocity = velocity; + } else { + bubble->walkBehavior(0, 0.8f); + } + + if (spine->getTime() == 80) { + bubble->unk1D1 = 0; + bubble->mVelocity.set(0.0f, 0.0f, 0.0f); + } + } + + bubble->unk1CC += 0.001f; + if (bubble->unk1CC + > bubble->mPosition.y + bubble->unk194->mSLDeadHeight.get()) { + bubble->unk1D2 = 0; + bubble->kill(); + } + + if (bubble->mScaling.x < bubble->unk194->mSLMaxScale.get()) { + f32 scale = bubble->mScaling.z * bubble->unk194->mSLRateExpand.get(); + bubble->mScaling.z = scale; + bubble->mScaling.y = scale; + bubble->mScaling.x = scale; + } + + if (spine->getTime() > bubble->unk194->mSLLiveTime.get()) { + spine->pushAfterCurrent(&TNerveBubbleSplit::theNerve()); + return TRUE; + } + + return FALSE; } DEFINE_NERVE(TNerveBubbleSplit, TLiveActor) { - if (spine->getTime() == 0) - static_cast(spine->getBody())->split(); - return spine->getTime() > 10; + TBubble* bubble = getBubble(spine); + if (spine->getTime() == 0) { + bubble->onHitFlag(HIT_FLAG_NO_COLLISION); + bubble->split(); + } + + if (spine->getTime() == 10) + bubble->setBckAnm(9); + + if (bubble->checkCurAnmEnd(0) && bubble->mMActor->checkCurBckFromIndex(9)) { + bubble->unk1D2 = 0; + bubble->kill(); + } + + return FALSE; } TBossTelesaSaveLoadParams::TBossTelesaSaveLoadParams(const char* path) @@ -252,26 +430,51 @@ void TBossTelesaManager::load(JSUMemoryInputStream& stream) TSpineEnemy* TBossTelesaManager::createEnemyInstance() { - return new TBossTelesa; + return new TBossTelesa("ボステレサ"); } void TBossTelesaManager::createModelData() { - createModelDataArray(btelesa_model_data); + static TModelDataLoadEntry entry[] = { + { "btelesa.bmd", 0x15300000, 0 }, + { nullptr, 0, 0 }, + }; + createModelDataArray(entry); } -void TBossTelesaManager::perform(u32 cue, JDrama::TGraphics* graphics) +const f32 bossTelesaDirectionTable[] = { + 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, +}; + +void TBossTelesaManager::perform(u32 flags, JDrama::TGraphics* gfx) { - TEnemyManager::perform(cue, graphics); + TEnemyManager::perform(flags, gfx); } BOOL TBossTelesaBody::receiveMessage(THitActor* sender, u32 message) { - if (mOwner - && (message == HIT_MESSAGE_TRAMPLE - || message == HIT_MESSAGE_SPRAYED_BY_WATER)) - mOwner->damageRecover(); - return THitActor::receiveMessage(sender, message); + TBossTelesa* boss = unk68; + + if (message == HIT_MESSAGE_TRAMPLE) { + bool isMario; + if (sender->mActorType == (ACTOR_TYPE_PLAYER | 1)) + isMario = true; + else + isMario = false; + if (!isMario) { + if (boss->mSpine->getCurrentNerve() + == &TNerveBossTelesaPrepareSlot::theNerve()) + boss->mSpine->pushNerve(&TNerveBossTelesaSpit::theNerve()); + } + } + + if (message == HIT_MESSAGE_SPRAYED_BY_WATER) { + if (boss->mSpine->getCurrentNerve() + == &TNerveBossTelesaPrepareSlot::theNerve()) + boss->mSpine->pushNerve(&TNerveBossTelesaFreeze::theNerve()); + } + + return TRUE; } void TBossTelesaBody::checkHit() { } @@ -280,149 +483,369 @@ MActorAnmBck* MActor::getCurBckAnmPtr() { return unkC; } void TBossTelesaTongue::checkHit() { } -BOOL TBossTelesaTongue::receiveMessage(THitActor* sender, u32 message) +BOOL TBossTelesaTongue::receiveMessage(THitActor*, u32 message) { - return THitActor::receiveMessage(sender, message); + if (message == HIT_MESSAGE_SPRAYED_BY_WATER) { + TBossTelesa* boss = unk68; + if (boss->mSpine->getCurrentNerve() + == &TNerveBossTelesaAppear::theNerve()) + boss->mSpine->pushNerve(&TNerveBossTelesaSlotStart::theNerve()); + } + return TRUE; } void TBossTelesaKillSmallEnemy::checkHit() { - mHit = gpMarioPos != nullptr && mPosition.distance(*gpMarioPos) < 300.0f; - if (mHit && mOwner) - mOwner->forceHide(); -} + unk6C = 0; + + for (int i = 0; i < mColCount; ++i) { + THitActor* collision = mCollisions[i]; + bool isEnemy; + if (collision->mActorType & ACTOR_TYPE_ENEMY) + isEnemy = true; + else + isEnemy = false; + if (isEnemy) { + THitActor* actor = collision; + if (actor->mActorType == (ACTOR_TYPE_ENEMY | 0x13)) + ((THamuKuri*)actor)->selectCapHolder(); + ((TLiveActor*)actor)->kill(); + } + } -BOOL TBossTelesaKillSmallEnemy::receiveMessage(THitActor* sender, u32 message) -{ - return THitActor::receiveMessage(sender, message); + JGeometry::TVec3 diff = *gpMarioPos; + diff -= mPosition; + diff.y = 0.0f; + if (MsVECMag2(&diff) < 300.0f) { + unk68->forceHide(); + unk6C = 1; + } } void TTelesaSlot::initMapObj() { TSlotDrum::initMapObj(); - mLiveFlag |= 0x10; + onLiveFlag(LIVE_FLAG_UNK10); unk14C = 160.0f; unk150 = mPosition.y; unk154 = 2.0f; unk158 = 2.0f; unk15C = 0.01f; unk160 = 0.5f; + unk164 = 0; unk168 = 45; - randomReset(); + unk140 = mDamageRadius / 3.0f; + unk144 = mDamageHeight; + unk1DC = new TMapCollisionMove(); + unk1DC->init(2, 0, 0, nullptr); + + TMsRange indexRange(0, 8); + for (int i = 0; i < 3; ++i) { + unk13C[i] = (f32)(unk168 * indexRange.rand()); + *(&unk198 + i) = 0; + } } +#pragma dont_inline on void TTelesaSlot::randomReset() { + TMsRange indexRange(0, 8); + for (int i = 0; i < 3; ++i) { - unk13C[i] = unk168 * (s32)MsRandF(0.0f, 8.0f); - ((&mStop0)[i]) = 0; + unk13C[i] = (f32)(unk168 * indexRange.rand()); + *(&unk198 + i) = 0; } } +#pragma dont_inline off + +void TTelesaSlot::calcRootMatrix() +{ + u8 rolling = 0; -void TTelesaSlot::calcRootMatrix() { TSlotDrum::calcRootMatrix(); } + if (getDrumSpeeds()[0] != 0.0f) + rolling = 1; + if (getDrumSpeeds()[1] != 0.0f) + rolling = 1; + if (getDrumSpeeds()[2] != 0.0f) + rolling = 1; -void TTelesaSlot::moveObject() { TSlotDrum::moveObject(); } + if (rolling) { + if (unk1E0) { + if (gpMSound->gateCheck(0x308D)) { + MSoundSESystem::MSoundSE::startSoundActor(0x308D, &mPosition, 0, + nullptr, 0, 4); + } + } + + unk1E0 = 1 - unk1E0; + } + TSlotDrum::calcRootMatrix(); +} + +void TTelesaSlot::moveObject() +{ + TLiveActor::moveObject(); + + for (int i = 0; i < unk148; ++i) { + u8* isRolling = &unk198 + i; + u8* doStop = &unk1A8 + i; + + if (*doStop) { + if (getForcastResult(i) == unk1A4) { + *isRolling = 0; + *doStop = 0; + } + } + + f32 speed = unk138[i]; + if (speed == 0.0f) + continue; + + if (fabsf(speed) > unk160) { + unk13C[i] += speed; + + if (!*isRolling) { + if (unk138[i] > 0.0f) + unk138[i] -= unk15C; + else + unk138[i] += unk15C; + } + + if (unk13C[i] >= 360.0f) + unk13C[i] -= 360.0f; + if (unk13C[i] <= 0.0f) + unk13C[i] += 360.0f; + } else { + unk13C[i] += speed; + + if (unk13C[i] >= 360.0f) + unk13C[i] -= 360.0f; + if (unk13C[i] <= 0.0f) + unk13C[i] += 360.0f; + + if (*isRolling) + continue; + + if ((int)fabsf(unk13C[i]) % unk168 != 0) + continue; + + unk13C[i] = unk168 * (int)(unk13C[i] / (f32)unk168); + unk138[i] = 0.0f; + + if (gpMSound->gateCheck(0x292C)) { + MSoundSESystem::MSoundSE::startSoundActor(0x292C, &mPosition, 0, + nullptr, 0, 4); + } + + for (int j = 0; j < unk148; ++j) { + u8* otherRolling = &unk198 + j; + if (*otherRolling) { + f32 min = 0.0f; + f32 max = 1.0f; + TBossTelesa* owner = getSlotOwner(this); + TBossTelesaSaveLoadParams* params + = (TBossTelesaSaveLoadParams*)owner->unk15C; + f32 rate = params->mSLSlotHitCollectRate.get(); + + if (min + (max - min) * (rand() * 0.000030517578f) + <= rate) { + *(&unk1A8 + j) = 1; + } else { + *otherRolling = 0; + } + } + } + + u8 allStopped = 1; + if (unk138[0] != 0.0f) + allStopped = 0; + if (unk138[1] != 0.0f) + allStopped = 0; + if (unk138[2] != 0.0f) + allStopped = 0; + + if (allStopped) { + TBossTelesa* owner = getSlotOwner(this); + TTelesaSlot* slot = (TTelesaSlot*)owner->unk184; + + if (slot->getSlotResult() == 2 || slot->getSlotResult() == 0) { + owner->unk374.x = 0.0f; + owner->unk374.y = 0.0f; + owner->unk374.z = 0.0f; + gpMarioParticleManager->emit(0xE1, &owner->unk374, 0, + nullptr); + + if (slot->getSlotResult() == 2) { + if (gpMSound->gateCheck(0x293F)) { + MSoundSESystem::MSoundSE::startSoundActor( + 0x293F, &owner->mPosition, 0, nullptr, 0, 4); + } + } else { + if (gpMSound->gateCheck(0x2940)) { + MSoundSESystem::MSoundSE::startSoundActor( + 0x2940, &owner->mPosition, 0, nullptr, 0, 4); + } + } + } else { + if (gpMSound->gateCheck(0x294D)) { + MSoundSESystem::MSoundSE::startSoundActor( + 0x294D, &owner->mPosition, 0, nullptr, 0, 4); + } + } + } + } + } +} + +#pragma dont_inline on void TTelesaSlot::moveStart() { - mForceStop = 1; - mRolling = 1; - mRoll0 = 1; - mRoll1 = 1; - mRoll2 = 1; - mStop0 = mStop1 = mStop2 = 0; - unk138[0] = -unk158; - unk138[1] = -0.8f * unk158; - unk138[2] = unk158; + unk19C = 1; + unk19B = 1; + + for (int i = 0; i < 3; ++i) { + *(&unk198 + i) = 1; + *(&unk1A8 + i) = 0; + + f32 speed = 1.0f; + if (i == 0) + speed = -1.0f; + if (i == 1) + speed = -0.8f; + + unk138[i] = speed * unk158; + } } +#pragma dont_inline off -u32 TTelesaSlot::touchWater(THitActor*) { return 0; } +u32 TTelesaSlot::touchWater(THitActor*) { return FALSE; } -void TTelesaSlot::forceStopSlot(int drum) +#pragma dont_inline on +void TTelesaSlot::forceStopSlot(int idx) { - if (!mForceStop) + volatile f32 min = 0.0f; + volatile f32 max = 1.0f; + f32 range; + f32 collectRate; + + if (!unk19C) return; - mForceResult = getForcastResult(drum); - ((&mRoll0)[drum]) = 0; - mForceStop = 0; + + TBossTelesaSaveLoadParams* params + = (TBossTelesaSaveLoadParams*)getSlotOwner(this)->unk15C; + collectRate = params->mSLSlotFirstHitCollectRate.get(); + + if (SMS_GetMarioHP() == 1) + collectRate = 0.9f; + + range = max - min; + f32 randomValue = range * (rand() * 0.000030517578f); + randomValue = min + randomValue; + if (randomValue <= collectRate) { + unk1A4 = 2; + if (SMS_GetMarioHP() <= 3) + unk1A4 = 0; + *(&unk1A8 + idx) = 1; + } else { + unk1A4 = getForcastResult(idx); + *(&unk198 + idx) = 0; + } + + if (unk1A4 == getSlotOwner(this)->unk1A8) + unk1A4 = 3; + + if (unk1A4 == 0) { + if (getSlotOwner(this)->unk370 == 0) + unk1A4 = 1; + else if (SMS_GetMarioHP() >= 6) + unk1A4 = 3; + } + + unk19C = 0; } +#pragma dont_inline off -u8 TTelesaSlot::isRollDrum() +#pragma dont_inline on +BOOL TTelesaSlot::isRollDrum() { - if (mRoll0) - return 1; - if (mRoll1) - return 1; - if (mRoll2) - return 1; - mRolling = 0; - return 0; + if (unk198) + return TRUE; + if (unk199) + return TRUE; + if (unk19A) + return TRUE; + + unk19B = 0; + return FALSE; } +#pragma dont_inline off +#pragma dont_inline on int TTelesaSlot::getSlotResult() { int result = getResultFromAng(unk13C[0]); - int i = 1; - do { - if (result != getResultFromAng(unk13C[i])) + for (int i = 1; i < 3; ++i) { + if (getResultFromAng(unk13C[i]) != result) return -1; - ++i; - } while (i < 3); + } return result; } +#pragma dont_inline off -int TTelesaSlot::getDrumResult(int drum) +int TTelesaSlot::getDrumResult(int index) { - return getResultFromAng(unk13C[drum]); + return getResultFromAng(unk13C[index]); } -int TTelesaSlot::getForcastResult(int drum) +int TTelesaSlot::getForcastResult(int idx) { - f32 angle = unk13C[drum]; - f32 speed = unk138[drum]; + f32 ang = unk13C[idx]; + f32 cur = unk138[idx]; - do { - if (fabsf(speed) > unk160) { - angle += speed; - if (speed > 0.0f) - speed -= unk15C; + for (;;) { + if (fabsf(cur) > unk160) { + ang += cur; + if (cur > 0.0f) + cur -= unk15C; else - speed += unk15C; - - if (angle >= 360.0f) - angle -= 360.0f; - if (angle <= 0.0f) - angle += 360.0f; - continue; + cur += unk15C; + + if (ang >= 360.0f) + ang -= 360.0f; + if (ang <= 0.0f) + ang += 360.0f; + } else { + ang += cur; + if (ang >= 360.0f) + ang -= 360.0f; + if (ang <= 0.0f) + ang += 360.0f; + if ((int)fabsf(ang) % unk168 == 0) + break; } + } - angle += speed; - if (angle >= 360.0f) - angle -= 360.0f; - if (angle <= 0.0f) - angle += 360.0f; - } while ((s32)fabsf(angle) % unk168 != 0); - - return getResultFromAng((f32)(unk168 * (s32)(angle / (f32)unk168))); + return getResultFromAng(unk168 * (int)(ang / (f32)unk168)); } -int TTelesaSlot::getResultFromAng(f32 angle) +int TTelesaSlot::getResultFromAng(f32 ang) { - if (angle < 44.0f) + if (ang < 44.0f) return 0; - if (angle < 89.0f) + if (ang < 89.0f) return 1; - if (angle < 134.0f) + if (ang < 134.0f) return 3; - if (angle < 179.0f) + if (ang < 179.0f) return 2; - if (angle < 224.0f) + if (ang < 224.0f) return 0; - if (angle < 269.0f) + if (ang < 269.0f) return 1; - if (angle < 314.0f) + if (ang < 314.0f) return 3; - if (angle < 359.0f) + if (ang < 359.0f) return 2; return 2; } @@ -433,280 +856,2101 @@ void TTelesaSlot::entryObjCollision() { } TBossTelesa::TBossTelesa(const char* name) : TSpineEnemy(name) - , mBattleState(1) - , mBattleTimer(0) - , mBattlePhase(0) - , mSaveParams(nullptr) - , mCurrentItem(-1) - , mCurrentFruit(-1) - , mFallSpeed(0.0f) - , mBodyCollision(nullptr) - , mSlot(nullptr) - , mTakingActor(nullptr) - , mIsSpicy(0) - , mDamageTimer(0) - , mStateTimer(0) - , mDamage(0) - , mGenerateTimer(0) - , mMarioHP(0) - , mForceRestart(1) - , mInDamage(1) - , mRouletteTimer(0) - , mRoulettePosY(0.0f) - , mRouletteSp(0.0f) - , mSlotResult(0) - , mSlotItemCount(0) - , mSlotStop(3) - , mDead(0) + , unk150(1) + , unk154(nullptr) + , unk158(nullptr) + , unk15C(nullptr) + , unk160(-1) + , unk164(-1) + , unk168(0.0f) + , unk16C(nullptr) + , unk184(nullptr) + , unk188(nullptr) + , unk18C(0) + , unk1A8(0) + , unk274(0) + , unk350(0) + , unk354(0) + , unk358(0) + , unk35A(1) + , unk35B(1) + , unk35C(0) + , unk360(0.0f) + , unk364(0.0f) + , unk368(0) + , unk36C(0) + , unk370(3) + , unk384(0) { } void TBossTelesa::init(TLiveManager* manager) { - TSpineEnemy::init(manager); - mSaveParams = static_cast(getSaveParam()); - mBodyCollision = new TBossTelesaBody(this); - mTongueCollision = new TBossTelesaTongue(this); - mKillSmallEnemy = new TBossTelesaKillSmallEnemy(this); + mManager = manager; + manager->manageActor(this); + mMActorKeeper = new TMActorKeeper(manager, 1); + mMActor = mMActorKeeper->createMActor("btelesa.bmd", 3); + + onLiveFlag(LIVE_FLAG_DEAD); + onHitFlag(HIT_FLAG_NO_COLLISION); + + TSpineEnemyParams* params = getSaveParam(); + if (params) { + mBodyRadius = params->mSLBodyRadius.get(); + mWallRadius = params->mSLWallRadius.get(); + mHeadHeight = params->mSLHeadHeight.get(); + mScaledBodyRadius = mBodyScale * mBodyRadius; + mHitPoints = getSaveParam() ? getSaveParam()->mSLHitPointMax.get() : 1; + } + + initHitActor(0, 5, 0x98000000, mBodyRadius, mHeadHeight, mBodyRadius, + mHeadHeight); + mGroundPlane = TMap::getIllegalCheckData(); + setGoalPathMario(); + initAnmSound(); + unk15C = getSaveParam(); + mActorType = 0x08000013; + onHitFlag(0xD0000000); + mSpine->initWith(&TNerveBossTelesaFallDemo::theNerve()); + onLiveFlag(LIVE_FLAG_UNK8); + + mMActor->setLightType(1); + mMActor->unk40 = 1; + if (mMActor->unkC) + mMActor->unkC->initNormalMotionBlend(); + + J3DModel* model = mMActor->getModel(); + if (!model->getSkinDeform()) + model->setSkinDeform(new J3DSkinDeform, J3D_DEFORM_ATTACH_FLAG_UNK_1); + mMActor->resetDL(); + + unk348.r = 0xFF; + unk348.g = 0xFF; + unk348.b = 0xFF; + unk348.a = 0xFF; + + unk34C.r = mNormalAlpha; + unk34C.g = mNormalAlpha; + unk34C.b = mNormalAlpha; + unk34C.a = mNormalAlpha; + + J3DModelData* modelData = model->getModelData(); + for (u16 i = 0; i < modelData->getMaterialNum(); ++i) { + s32 bodyMatIndex + = getModel()->getModelData()->getMaterialName()->getIndex( + "_mat_body"); + if (i == bodyMatIndex) { + SMS_InitPacket_TwoTevKColor(model, i, GX_KCOLOR0, &unk34C, + GX_KCOLOR1, &unk348); + } else { + SMS_InitPacket_OneTevKColor(model, i, GX_KCOLOR0, &unk34C); + } + } + + reset(); + + for (int i = 0; i < gpMapObjManager->getObjNum(); ++i) { + TMapObjBase* actor = gpMapObjManager->getObj(i); + if (actor->mActorType == 0x4000019A) { + unk154 = nullptr; + unk158 = nullptr; + mPosition = actor->mPosition; + } + } + + if (mInstanceIndex == 0) { + for (u8 i = 0; i < mMActor->getModel()->getModelData()->getJointNum(); + ++i) { } + } + + unk16C = new TBossTelesaBody("ボステレサ体コリジョン"); + TIdxGroupObj* enemyGroup + = JDrama::TNameRefGen::search("敵グループ"); + enemyGroup->getChildren().push_back(unk16C); + unk16C->initHitActor(0x08000013, 5, 0xD1000000, 350.0f, 550.0f, 300.0f, + 500.0f); + ((TBossTelesaBody*)unk16C)->unk68 = this; + unk16C->offHitFlag(HIT_FLAG_NO_COLLISION); + + unk170 = new TBossTelesaTongue("ボステレサ舌コリジョン"); + enemyGroup = JDrama::TNameRefGen::search("敵グループ"); + enemyGroup->getChildren().push_back(unk170); + unk170->initHitActor(0x08000013, 5, 0xC0000000, 180.0f, 350.0f, 180.0f, + 350.0f); + ((TBossTelesaTongue*)unk170)->unk68 = this; + unk170->offHitFlag(HIT_FLAG_NO_COLLISION); + + unk174 = new TBossTelesaKillSmallEnemy("ボステレサ雑魚敵死コリジョン"); + enemyGroup = JDrama::TNameRefGen::search("敵グループ"); + enemyGroup->getChildren().push_back(unk174); + unk174->initHitActor(0x1000000C, 5, 0x10000000, 400.0f, 300.0f, 400.0f, + 300.0f); + unk174->offHitFlag(HIT_FLAG_NO_COLLISION); + ((TBossTelesaKillSmallEnemy*)unk174)->unk68 = this; + + mMActor->setLightType(3); + + JDrama::TViewObj* screenTex + = JDrama::TNameRefGen::search("スクリーンテクスチャ"); + void* dataOwner = *(void**)((u8*)screenTex + 0x10); + ResTIMG* texture = *(ResTIMG**)((u8*)dataOwner + 0x20); + SMS_ChangeTextureAll(mMActor->unk4->mModelData, "H_ma_rak_dummy", *texture); + + unk358 = SMS_GetMarioHP(); } void TBossTelesa::loadAfter() { - mMarioHP = 1; - if (!mSaveParams) - mSaveParams = static_cast(getSaveParam()); + u32 rouletteType = 0x4000019A; + if ((u32)gpMapObjManager->getObjNumWithActorType(rouletteType) != 0) { + int found = 0; + for (int i = 0; i < gpMapObjManager->getObjNum(); ++i) { + TMapObjBase* actor = gpMapObjManager->getObj(i); + bool isRoulette = false; + if (actor->mActorType == rouletteType) + isRoulette = true; + if (isRoulette) { + ((TRoulette**)&unk178)[found] = (TRoulette*)actor; + found++; + } + } + } + + u32 slotType = 0x400001A6; + if ((u32)gpMapObjManager->getObjNumWithActorType(slotType) != 0) { + for (int i = 0; i < gpMapObjManager->getObjNum(); ++i) { + TMapObjBase* actor = gpMapObjManager->getObj(i); + bool isSlot = false; + if (actor->mActorType == slotType) + isSlot = true; + if (isSlot) { + unk184 = (TTelesaSlot*)actor; + unk184->unk1A0 = this; + } + } + } + + JGeometry::TVec3 position(0.0f, 0.0f, 0.0f); + JGeometry::TVec3 rotation(0.0f, 0.0f, 0.0f); + JGeometry::TVec3 scale(1.0f, 1.0f, 1.0f); + int fruitIndex = 0; + for (int i = 0; i < 6; ++i) + unk2A8[fruitIndex++] = TMapObjBaseManager::newAndRegisterObj( + "FruitCoconut", position, rotation, scale); + for (int i = 0; i < 6; ++i) + unk2A8[fruitIndex++] = TMapObjBaseManager::newAndRegisterObj( + "FruitPapaya", position, rotation, scale); + for (int i = 0; i < 2; ++i) + unk2A8[fruitIndex++] = TMapObjBaseManager::newAndRegisterObj( + "FruitPine", position, rotation, scale); + for (int i = 0; i < 6; ++i) + unk2A8[fruitIndex++] = TMapObjBaseManager::newAndRegisterObj( + "FruitDurian", position, rotation, scale); + + for (int i = 0; i < 20; ++i) { + TMapObjBase* actor = (TMapObjBase*)unk2A8[i]; + actor->onMapObjFlag(0x04000000); + actor->makeObjDead(); + } + + unk2F8[0] = JDrama::TNameRefGen::search("唐辛子 0"); + unk2F8[1] = JDrama::TNameRefGen::search("唐辛子 1"); + unk2F8[2] = JDrama::TNameRefGen::search("唐辛子 2"); + unk2F8[3] = JDrama::TNameRefGen::search("唐辛子 3"); + unk2F8[4] = JDrama::TNameRefGen::search("唐辛子 4"); + unk2F8[5] = JDrama::TNameRefGen::search("唐辛子 5"); + unk2F8[6] = JDrama::TNameRefGen::search("唐辛子 6"); + unk2F8[7] = JDrama::TNameRefGen::search("唐辛子 7"); + unk2F8[8] = JDrama::TNameRefGen::search("唐辛子 8"); + unk2F8[9] = JDrama::TNameRefGen::search("唐辛子 9"); + for (int i = 0; i < 10; ++i) + ((TMapObjBase*)unk2F8[i])->makeObjDead(); + + unk320[0] = JDrama::TNameRefGen::search("コイン 0"); + unk320[1] = JDrama::TNameRefGen::search("コイン 1"); + unk320[2] = JDrama::TNameRefGen::search("コイン 2"); + unk320[3] = JDrama::TNameRefGen::search("コイン 3"); + unk320[4] = JDrama::TNameRefGen::search("コイン 4"); + unk320[5] = JDrama::TNameRefGen::search("コイン 5"); + unk320[6] = JDrama::TNameRefGen::search("コイン 6"); + unk320[7] = JDrama::TNameRefGen::search("コイン 7"); + unk320[8] = JDrama::TNameRefGen::search("コイン 8"); + unk320[9] = JDrama::TNameRefGen::search("コイン 9"); + for (int i = 0; i < 10; ++i) + ((TMapObjBase*)unk320[i])->makeObjDead(); + + TTelesaSlot* slot = (TTelesaSlot*)unk184; + slot->unk1E4[0] = 0.0f; + slot->unk1E4[1] = 0.0f; + slot->unk1E4[2] = 0.0f; + + unk354 = JDrama::TNameRefGen::search("テレサマネージャー"); + + for (int i = 0; i < 5; ++i) + TMapObjBaseManager::newAndRegisterObj("bottle_large", position, + rotation, scale); + + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_fhit.jpa", 0xD7); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_fhit_pe.jpa", 0xD8); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_fhit_gr.jpa", 0xD9); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_fhit_or.jpa", 0xDA); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_damage.jpa", 0xDB); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_down.jpa", 0xDC); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_down_pe.jpa", 0xDD); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_down_gr.jpa", 0xDE); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_down_or.jpa", 0xDF); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_spicy_hit.jpa", 0xE0); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_fubuki.jpa", 0xE1); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_yodare1.jpa", 0x19E); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_yodare2.jpa", 0x19F); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_yodare3.jpa", 0x1A0); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_ase.jpa", 0x1A1); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_spicy_a.jpa", 0x1A2); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_spicy_b.jpa", 0x1A3); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_spicy_d.jpa", 0x1A4); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_chika_a.jpa", 0x1A5); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_chika_b.jpa", 0x1A6); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_glow.jpa", 0x1A7); + SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_spicy_c.jpa", 0x1F0); + + void* res = JKRFileLoader::getGlbResource("/scene/btelesa/srot_waku.bmd"); + SDLModelData* data + = new SDLModelData(J3DModelLoaderDataBase::load(res, 0x10220000)); + unk188 + = new TSharedParts((TLiveActor*)unk184, 0, data, 3, ""); + + TLiveActor* gesso0 = JDrama::TNameRefGen::search("ゲッソー 0"); + if (gesso0) + gesso0->onLiveFlag(LIVE_FLAG_DEAD); + + TLiveActor* gesso1 = JDrama::TNameRefGen::search("ゲッソー 1"); + if (gesso1) + gesso1->onLiveFlag(LIVE_FLAG_DEAD); + + JDrama::TNameRef::loadAfter(); } void TBossTelesa::reset() { TSpineEnemy::reset(); - mDead = 0; - mDamage = 0; - mGenerateTimer = 0; - forceAllItemKill(); + onHitFlag(HIT_FLAG_NO_COLLISION); + onLiveFlag(LIVE_FLAG_UNK8); + onLiveFlag(LIVE_FLAG_UNK10); + onLiveFlag(LIVE_FLAG_HIDDEN); + + unk18C = 0; + + TBossTelesaSaveLoadParams* params = (TBossTelesaSaveLoadParams*)unk15C; + setHitParams(params->mSLAttackRadius.get(), params->mSLAttackHeight.get(), + params->mSLDamageRadius.get(), params->mSLDamageHeight.get()); + + TMarDirector* director = gpMarDirector; + director->fireStartDemoCamera("btelesa_roll_camera", nullptr, -1, 0.0f, + true, nullptr, 0, nullptr, + JDrama::TFlagT(0)); } void TBossTelesa::moveObject() { - TSpineEnemy::moveObject(); - if (mGenerateTimer > 0) - --mGenerateTimer; + if (mLiveFlag & LIVE_FLAG_DEAD) + return; + + JGeometry::TVec3 cameraPos; + cameraPos.set(gpCamera->unk124); + JGeometry::TVec3 marioDiff = *gpMarioPos; + marioDiff.sub(cameraPos); + f32 cameraDist = marioDiff.length(); + + if (cameraDist < mCameraMoveLimit) { + unk360 += mCameraMoveSp * (gpMarioPos->y - gpCamera->unk148.y); + gpCamera->unk290 = unk360; + } else if (__fabsf(unk360) > 1.0f) { + unk360 *= mCameraMoveSp; + gpCamera->unk290 = unk360; + } + + if (*gpMarioFlag & 0x400) { + if (gpMSound->gateCheck(0x28D4)) { + MSoundSESystem::MSoundSE::startSoundActor(0x28D4, &mPosition, 0, + nullptr, 0, 4); + } + } + + if (mSpine->getCurrentNerve() == &TNerveBossTelesaFallDemo::theNerve()) { + if (unk178 && unk17C + && unk178->mPosition.y > unk17C->mPosition.y + 5.0f) { + unk184->mPosition = unk178->mPosition; + unk184->mPosition.y += 300.0f; + } + } + + if (mSpine->getCurrentNerve() != &TNerveBossTelesaPrepareSlot::theNerve()) { + mMActor->setFrameRate(0.0f, 3); + mMActor->getFrameCtrl(3)->setFrame(0.0f); + } + + if (mSpine->getCurrentNerve() == &TNerveBossTelesaDie::theNerve()) { + u8 maxHp = getSaveParam() ? getSaveParam()->mSLHitPointMax.get() : 1; + u8 targetAlpha = mNormalAlpha + (maxHp - mHitPoints) * 30; + if (targetAlpha > 0xFE) + targetAlpha = 0xFE; + + if ((mMActor->checkCurBckFromIndex(7) + && mMActor->getFrameCtrl(0)->getFrame() > 50.0f) + || mMActor->checkCurBckFromIndex(6)) { + if (unk34C.a > targetAlpha) + --unk34C.a; + } else if (unk34C.a < 0xFF) { + ++unk34C.a; + } + } + + JGeometry::TVec3 cameraDiff = *gpMarioPos; + cameraDiff.sub(gpCamera->unk124); + unk19C = unk178->mPosition; + unk19C.x += 0.67f * cameraDiff.x; + unk19C.z += 0.67f * cameraDiff.z; + + int movingRoulettes = 0; + if (unk178->unk13C != 0.0f) + ++movingRoulettes; + if (unk17C->unk13C != 0.0f) + ++movingRoulettes; + if (unk180->unk13C != 0.0f) + ++movingRoulettes; + + switch (movingRoulettes) { + case 1: + if (gpMSound->gateCheck(0x30B7)) { + MSoundSESystem::MSoundSE::startSoundActor(0x30B7, &unk19C, 0, + nullptr, 0, 4); + } + break; + case 2: + if (gpMSound->gateCheck(0x30B6)) { + MSoundSESystem::MSoundSE::startSoundActor(0x30B6, &unk19C, 0, + nullptr, 0, 4); + } + break; + case 3: + if (gpMSound->gateCheck(0x30B5)) { + MSoundSESystem::MSoundSE::startSoundActor(0x30B5, &unk19C, 0, + nullptr, 0, 4); + } + break; + } + + mLinearVelocity.zero(); + mAngularVelocity.zero(); + control(); + + for (int i = 0; i < mColCount; ++i) { + if (mCollisions[i]->isActorTypeOf(ACTOR_TYPE_PLAYER)) + SMS_SendMessageToMario(this, HIT_MESSAGE_ATTACK); + } + + bind(); + mPosition += mLinearVelocity; + mRotation += mAngularVelocity; + mPosition.y = mGroundHeight + 300.0f; + + unk168 = MsClamp(unk168 - 0.05f, 0.0f, 1.0f); + if (mMActor->unkC) + mMActor->unkC->setMotionBlendRatio(unk168); + + if (mLiveFlag & LIVE_FLAG_CLIPPED_OUT) { + unk16C->mPosition = mPosition; + unk170->mPosition = mPosition; + unk174->mPosition = mPosition; + } else { + J3DModel* model = mMActor->getModel(); + MtxPtr node1 = model->mNodeMatrices[1]; + MtxPtr node7 = model->mNodeMatrices[7]; + + unk16C->mPosition.set(node1[0][3], node1[1][3] - 200.0f, node1[2][3]); + unk174->mPosition.set(node1[0][3], mPosition.y - 350.0f, node1[2][3]); + unk170->mPosition.set(node7[0][3], node7[1][3] - 350.0f, node7[2][3]); + } + + TBossTelesaBody* body = (TBossTelesaBody*)unk16C; + body->unk6C = 0; + for (int i = 0; i < body->mColCount; ++i) { + THitActor* actor = body->mCollisions[i]; + if (actor->isActorTypeOf(ACTOR_TYPE_PLAYER)) + SMS_SendMessageToMario(body, HIT_MESSAGE_ATTACK); + else + body->unk68->checkHitObject(actor); + } + + TBossTelesaTongue* tongue = (TBossTelesaTongue*)unk170; + for (int i = 0; i < tongue->mColCount; ++i) { + THitActor* actor = tongue->mCollisions[i]; + if (actor->isActorTypeOf(ACTOR_TYPE_PLAYER)) { + SMS_SendMessageToMario(tongue, HIT_MESSAGE_ATTACK); + } else if (actor->mActorType == 0x40000395) { + tongue->unk68->setSpicy((TLiveActor*)actor); + } else if (tongue->mPosition.y + 100.0f < actor->mPosition.y) { + tongue->unk68->checkHitObject(actor); + } + } + + ((TBossTelesaKillSmallEnemy*)unk174)->checkHit(); } void TBossTelesa::kill() { - if (!checkLiveFlag(LIVE_FLAG_DEAD)) { - mDead = 1; - TSpineEnemy::kill(); - } + if (mSpine->getCurrentNerve() != &TNerveBossTelesaDie::theNerve()) + mSpine->pushNerve(&TNerveBossTelesaDie::theNerve()); } MtxPtr TBossTelesa::getTakingMtx() { - return getModel() ? getModel()->getBaseTRMtx() : nullptr; -} + unk278.set(unk178->mMActor->unk4->mNodeMatrices[1]); + unk278.mMtx[1][3] = unk178->mPosition.y - 120.0f; + return unk278.mMtx; +} + +void TBossTelesa::prepareGenerate() { unk35C = 0; } + +void TBossTelesa::calcRootMatrix() +{ + mMActor->getModel()->setBaseScale(mScaling); + + f32 offsetY = unk364; + if (offsetY > 0.0f) + offsetY = 0.0f; + else if (offsetY < mBaseHoseiPosY) + offsetY = mBaseHoseiPosY; + + TPosition3f translateMtx; + translateMtx.translation( + mPosition.x, + mPosition.y + + ((TBossTelesaSaveLoadParams*)unk15C)->mSLTransYOffset.get() + + offsetY, + mPosition.z); + + Mtx rotateMtx; + MsMtxSetRotRPH(rotateMtx, mRotation.x, mRotation.y, mRotation.z); + PSMTXConcat(translateMtx, rotateMtx, translateMtx); + PSMTXCopy(translateMtx, mMActor->getModel()->getBaseTRMtx()); + + TTelesaSlot* slot = (TTelesaSlot*)unk184; + if (slot) { + slot->mRotation = mRotation; + + f32 slotOffsetY = -700.0f; + MtxPtr slotMtx; + if (mMActor->checkCurBckFromIndex(4) || mMActor->checkCurBckFromIndex(0) + || mMActor->checkCurBckFromIndex(2) + || mMActor->checkCurBckFromIndex(5)) { + slotMtx = mMActor->getModel()->mNodeMatrices[1]; + if (!mMActor->checkCurBckFromIndex(0)) + slotOffsetY = -2400.0f; + } else { + slotMtx = mMActor->getModel()->mNodeMatrices[0]; + } + + slot->mPosition.x = slotMtx[0][3]; + slot->mPosition.y = unk364 + slotMtx[1][3] + slotOffsetY; + slot->mPosition.z = slotMtx[2][3]; + } + + for (u16 i = 0; i < mMActor->getModel()->getModelData()->getMaterialNum(); + ++i) { + Mtx lightMtx; + SMS_GetLightPerspectiveForEffectMtx(lightMtx); + mMActor->getModel() + ->getModelData() + ->getMaterialNodePointer(i) + ->getTexMtx(1) + ->setEffectMtx(lightMtx); + } + + unk374.set(0.0f, 0.0f, 0.0f); + gpMarioParticleManager->emit(0x1A5, &unk374, 1, this); + gpMarioParticleManager->emit(0x1A6, &unk374, 1, this); + + MtxPtr baseMtx = mMActor->getModel()->mNodeMatrices[1]; + unk374.set(baseMtx[0][3], baseMtx[1][3], baseMtx[2][3]); + gpMarioParticleManager->emitAndBindToPosPtr(0x1A7, &unk374, 1, this); + + if (!mMActor->checkCurBckFromIndex(4) && !mMActor->checkCurBckFromIndex(6) + && !mMActor->checkCurBckFromIndex(12) + && !mMActor->checkCurBckFromIndex(13)) { + gpMarioParticleManager->emitAndBindToMtxPtr( + 0x19E, mMActor->getModel()->mNodeMatrices[5], 1, this); + gpMarioParticleManager->emitAndBindToMtxPtr( + 0x19F, mMActor->getModel()->mNodeMatrices[5], 1, this); + gpMarioParticleManager->emitAndBindToMtxPtr( + 0x1A0, mMActor->getModel()->mNodeMatrices[10], 1, this); + } -void TBossTelesa::prepareGenerate() { mGenerateTimer = 0; } + if (mMActor->checkCurBckFromIndex(1) + && mMActor->getFrameCtrl(0)->getFrame() < 20.0f) { + gpMarioParticleManager->emitAndBindToMtxPtr( + 0xE0, mMActor->getModel()->mNodeMatrices[5], 0, nullptr); + } -void TBossTelesa::calcRootMatrix() { TSpineEnemy::calcRootMatrix(); } + if (mMActor->checkCurBckFromIndex(12)) { + if (gpMSound->gateCheck(0x20FE)) { + MSoundSESystem::MSoundSE::startSoundActor(0x20FE, &mPosition, 0, + nullptr, 0, 4); + } + + gpMarioParticleManager->emitAndBindToMtxPtr( + 0x1A1, mMActor->getModel()->mNodeMatrices[1], 1, this); + gpMarioParticleManager->emitAndBindToMtxPtr( + 0x1A2, mMActor->getModel()->mNodeMatrices[9], 1, this); + gpMarioParticleManager->emitAndBindToMtxPtr( + 0x1A3, mMActor->getModel()->mNodeMatrices[9], 1, this); + gpMarioParticleManager->emitAndBindToMtxPtr( + 0x1A4, mMActor->getModel()->mNodeMatrices[9], 1, this); + gpMarioParticleManager->emitAndBindToMtxPtr( + 0x1F0, mMActor->getModel()->mNodeMatrices[9], 3, this); + } -void TBossTelesa::perform(u32 cue, JDrama::TGraphics* graphics) + if (mMActor->checkCurBckFromIndex(14) + && mMActor->getFrameCtrl(0)->checkPass(40.0f)) { + if (mSpine->getCurrentNerve() + == &TNerveBossTelesaSpitSlotItem::theNerve()) { + generateSlotItem(); + } else { + genAttacker(); + } + } +} + +void TBossTelesa::perform(u32 flags, JDrama::TGraphics* gfx) { - TSpineEnemy::perform(cue, graphics); - if (cue & CUE_MOVE) - damageRecover(); + if ((flags & 0x200) + && !(mLiveFlag & (LIVE_FLAG_DEAD | LIVE_FLAG_CLIPPED_OUT))) { + if (mSpine->getCurrentNerve() == &TNerveBossTelesaDie::theNerve() + && unk350) { + mMActor->offMakeDL(); + SMS_AddDamageFogEffect(mMActor->unk4->mModelData, mPosition, gfx); + } + } + + offLiveFlag(LIVE_FLAG_CLIPPED_OUT); + TSpineEnemy::perform(flags, gfx); + + unk16C->THitActor::perform(flags, gfx); + unk170->THitActor::perform(flags, gfx); + unk174->THitActor::perform(flags, gfx); + if (unk184) + unk184->testPerform(flags, gfx); + if (unk188) + unk188->testPerform(flags, gfx); } -BOOL TBossTelesa::receiveMessage(THitActor*, u32) { return TRUE; } +BOOL TBossTelesa::receiveMessage(THitActor*, u32) { return FALSE; } -BOOL TBossTelesa::checkMessage(THitActor* sender, u32 message) +BOOL TBossTelesa::checkMessage(THitActor* actor, u32 message) { - return receiveMessage(sender, message); + return receiveMessage(actor, message); } -BOOL TBossTelesa::checkHitObject(THitActor* actor) +void TBossTelesa::checkHitObject(THitActor* actor) { - return actor != nullptr && actor->checkActorType(ACTOR_TYPE_PLAYER); + unk380 = -1; + + if ((actor->mActorType & ACTOR_TYPE_MASK) != ACTOR_TYPE_UNK40000000) + return; + if (mSpine->getCurrentNerve() != &TNerveBossTelesaPrepareSlot::theNerve()) + return; + + switch (actor->mActorType) { + case 0x40000390: + unk348.r = 0xE6; + unk348.g = 0x64; + unk348.b = 0xB4; + unk380 = 0xD8; + kill(); + break; + case 0x40000391: + case 0x40000392: + unk348.r = 0xE6; + unk348.g = 0xB4; + unk348.b = 0; + unk380 = 0xDA; + kill(); + break; + case 0x40000393: + unk348.r = 0x96; + unk348.g = 0x32; + unk348.b = 0xE6; + unk380 = 0xD9; + kill(); + break; + case 0x40000395: + break; + default: + return; + } + + if (!unk350 && actor->mActorType != 0x40000395) { + if (unk35A) { + unk35A = 0; + gpMarDirector->mConsole->startAppearBalloon(0xE000F, true); + } + unk35C++; + if (unk35C > 2) + gpMarDirector->mConsole->startAppearBalloon(0xE0010, true); + } else { + unk35C = 0; + } + + unk374 = actor->mPosition; + gpMarioParticleManager->emit(0xD7, &unk374, 0, nullptr); + if (unk380 >= 0) + gpMarioParticleManager->emit(unk380, &unk374, 0, nullptr); + + if (unk350) { + gpMarioParticleManager->emit(0xDB, &unk374, 0, nullptr); + } else { + if (gpMSound->gateCheck(0x2944)) { + MSoundSESystem::MSoundSE::startSoundActor(0x2944, &mPosition, 0, + nullptr, 0, 4); + } + } + + ((TMapObjBase*)actor)->makeObjDead(); } void TBossTelesa::setSpicy(TLiveActor* actor) { - if (actor) - actor->onLiveFlag(LIVE_FLAG_UNK10); - mIsSpicy = 1; + if (mSpine->getCurrentNerve() == &TNerveBossTelesaSpitSlotItem::theNerve()) + return; + if (mMActor->checkCurBckFromIndex(1)) + return; + + unk350 = 1; + unk36C = 0; + unk164 = mMActor->getCurAnmIdx(0); + unk160 = 1; + unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (mMActor->unkC) + oldAnm = mMActor->unkC->unk24; + if (mMActor->unkC) + mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + mMActor->setBckFromIndex(1); + if (mMActor->unkC) + mMActor->unkC->setMotionBlendRatio(unk168); + + const char** basTable = getBasNameTable(); + setAnmSound(basTable ? basTable[1] : nullptr); + + if (unk35B) { + unk35B = 0; + gpMarDirector->mConsole->startAppearBalloon(0xE0011, true); + } + + actor->kill(); } void TBossTelesa::damageRecover() { - if (mDamageTimer > 0) - --mDamageTimer; - if (mDamageTimer == 0) - mDamage = 0; + f32 zero = 0.0f; + + for (int i = 0; i < 20; ++i) { + TLiveActor* actor = unk2A8[i]; + if (!(actor->mLiveFlag & LIVE_FLAG_DEAD)) { + if (actor->mHolder == nullptr) + SMS_SendMessageToMario(actor, HIT_MESSAGE_UNK8); + + ((TMapObjBase*)actor)->makeObjDead(); + gpMarioParticleManager->emit(0xCD, &actor->mPosition, 0, nullptr); + actor->mPosition.set(zero, zero, zero); + } + } + + for (int i = 0; i < 10; ++i) { + TLiveActor* actor = unk2F8[i]; + if (!(actor->mLiveFlag & LIVE_FLAG_DEAD)) { + if (actor->mHolder == nullptr) + SMS_SendMessageToMario(actor, HIT_MESSAGE_UNK8); + + ((TMapObjBase*)actor)->makeObjDead(); + gpMarioParticleManager->emit(0xCD, &actor->mPosition, 0, nullptr); + actor->mPosition.set(zero, zero, zero); + } + + actor = unk320[i]; + if (!(actor->mLiveFlag & LIVE_FLAG_DEAD)) { + ((TMapObjBase*)actor)->makeObjDead(); + gpMarioParticleManager->emit(0xCD, &actor->mPosition, 0, nullptr); + actor->mPosition.set(zero, zero, zero); + } + } + + if (unk350) { + if (gpMSound->gateCheck(0x2968)) { + MSoundSESystem::MSoundSE::startSoundActor(0x2968, &mPosition, 0, + nullptr, 0, 4); + } + } else { + if (gpMSound->gateCheck(0x28D5)) { + MSoundSESystem::MSoundSE::startSoundActor(0x28D5, &mPosition, 0, + nullptr, 0, 4); + } + } + + mSpine->pushAfterCurrent(&TNerveBossTelesaHide::theNerve()); + unk368 = 0; } void TBossTelesa::tongueHitWater() { } -u8 TBossTelesa::rouletteFall() +BOOL TBossTelesa::rouletteFall() { - if (mRouletteTimer > 0) - --mRouletteTimer; - return mRouletteTimer == 0; + TRoulette* roulette0 = (TRoulette*)unk178; + TRoulette* roulette1 = (TRoulette*)unk17C; + + if (roulette0->mPosition.y > roulette1->mPosition.y) { + roulette0->mPosition.y -= 1.0f; + roulette0->mMActor->setBck("rulet00"); + } else { + roulette0->mPosition.y = roulette1->mPosition.y; + return TRUE; + } + + if (roulette0->mPosition.y > roulette1->mPosition.y + 3.0f) { + if (SMS_SendMessageToMario(this, HIT_MESSAGE_TAKE)) + mHeldObject = (TTakeActor*)SMS_GetMarioHitActor(); + } else { + if (SMS_SendMessageToMario(this, HIT_MESSAGE_UNK8)) { + if (gpMSound->gateCheck(0x2926)) { + MSoundSESystem::MSoundSE::startSoundActor(0x2926, &mPosition, 0, + nullptr, 0, 4); + } + mHeldObject = nullptr; + } + } + + if (gpMSound->gateCheck(0x28DC)) { + MSoundSESystem::MSoundSE::startSoundActor(0x28DC, &mPosition, 0, + nullptr, 0, 4); + } + + if (gpMSound->gateCheck(0x2125)) { + MSoundSESystem::MSoundSE::startSoundActor(0x2125, &mPosition, 0, + nullptr, 0, 4); + } + + gpMarioOriginal->mGamePad->onNeutralMarioKey(); + return FALSE; } -u8 TBossTelesa::slotFall() +BOOL TBossTelesa::slotFall() { - if (!mSlot) - return 1; - return mSlot->isRollDrum() == 0; + if (unk184->mPosition.y > unk178->mPosition.y - 800.0f) { + unk184->mPosition.y -= 5.0f; + return FALSE; + } + + unk184->mPosition.y -= 1.0f; + + if (unk184->mPosition.y < unk178->mPosition.y - 900.0f) { + int rolling = 0; + if (unk178->unk13C != 0.0f) + rolling = 1; + if (unk17C->unk13C != 0.0f) + ++rolling; + if (unk180->unk13C != 0.0f) + ++rolling; + + if (rolling != 3) + rouletteStart(); + } + + if (unk184->mPosition.y < unk178->mPosition.y - 1100.0f) + return TRUE; + + THitActor* switchActor = unk178->unk150; + switchActor->mAttackRadius = 280.0f; + switchActor->mAttackHeight = 100.0f; + switchActor->mDamageRadius = 280.0f; + switchActor->mDamageHeight = 100.0f; + switchActor->calcEntryRadius(); + + switchActor = unk17C->unk150; + switchActor->mAttackRadius = 280.0f; + switchActor->mAttackHeight = 100.0f; + switchActor->mDamageRadius = 280.0f; + switchActor->mDamageHeight = 100.0f; + switchActor->calcEntryRadius(); + + return FALSE; } void TBossTelesa::openWaterPlace() { } -void TBossTelesa::flashItem(int item) +void TBossTelesa::flashItem(int result) { - mSlotResult = item; - mDamageTimer = 0; + int phase = result % 16; + + int index = 0; + for (int i = 0; i < 5; ++i) { + TLiveActor* actor = unk2A8[index++]; + u32* liveFlag = &actor->mLiveFlag; + if (!(*liveFlag & LIVE_FLAG_DEAD) && actor->mHolder == nullptr) { + if (phase < 8) + *liveFlag |= LIVE_FLAG_HIDDEN; + else + *liveFlag &= ~LIVE_FLAG_HIDDEN; + } + + actor = unk2A8[index++]; + liveFlag = &actor->mLiveFlag; + if (!(*liveFlag & LIVE_FLAG_DEAD) && actor->mHolder == nullptr) { + if (phase < 8) + *liveFlag |= LIVE_FLAG_HIDDEN; + else + *liveFlag &= ~LIVE_FLAG_HIDDEN; + } + + actor = unk2A8[index++]; + liveFlag = &actor->mLiveFlag; + if (!(*liveFlag & LIVE_FLAG_DEAD) && actor->mHolder == nullptr) { + if (phase < 8) + *liveFlag |= LIVE_FLAG_HIDDEN; + else + *liveFlag &= ~LIVE_FLAG_HIDDEN; + } + + actor = unk2A8[index++]; + liveFlag = &actor->mLiveFlag; + if (!(*liveFlag & LIVE_FLAG_DEAD) && actor->mHolder == nullptr) { + if (phase < 8) + *liveFlag |= LIVE_FLAG_HIDDEN; + else + *liveFlag &= ~LIVE_FLAG_HIDDEN; + } + } + + for (int i = 0; i < 10; ++i) { + TLiveActor* actor = unk2F8[i]; + u32* liveFlag = &actor->mLiveFlag; + if (!(*liveFlag & LIVE_FLAG_DEAD) && actor->mHolder == nullptr) { + if (phase < 8) + *liveFlag |= LIVE_FLAG_HIDDEN; + else + *liveFlag &= ~LIVE_FLAG_HIDDEN; + } + + actor = unk320[i]; + liveFlag = &actor->mLiveFlag; + if (!(*liveFlag & LIVE_FLAG_DEAD)) { + if (phase < 8) + *liveFlag |= LIVE_FLAG_HIDDEN; + else + *liveFlag &= ~LIVE_FLAG_HIDDEN; + } + } } void TBossTelesa::onAllCollision() { - if (mBodyCollision) - mBodyCollision->onHitFlag(HIT_FLAG_NO_COLLISION); - if (mTongueCollision) - mTongueCollision->onHitFlag(HIT_FLAG_NO_COLLISION); + offHitFlag(HIT_FLAG_NO_COLLISION); + unk16C->offHitFlag(HIT_FLAG_NO_COLLISION); + unk170->offHitFlag(HIT_FLAG_NO_COLLISION); + unk174->offHitFlag(HIT_FLAG_NO_COLLISION); } void TBossTelesa::offAllCollision() { - if (mBodyCollision) - mBodyCollision->offHitFlag(HIT_FLAG_NO_COLLISION); - if (mTongueCollision) - mTongueCollision->offHitFlag(HIT_FLAG_NO_COLLISION); + onHitFlag(HIT_FLAG_NO_COLLISION); + unk16C->onHitFlag(HIT_FLAG_NO_COLLISION); + unk170->onHitFlag(HIT_FLAG_NO_COLLISION); + unk174->onHitFlag(HIT_FLAG_NO_COLLISION); } const char** TBossTelesa::getBasNameTable() const { return btelesa_bastable; } void TBossTelesa::genAttacker() { - mGenerateTimer = mSaveParams ? mSaveParams->mSLGenAttackerTime.get() : 500; + if (unk150) { + TTelesa* telesa = (TTelesa*)gpConductor->makeOneEnemyAppear( + mPosition, "テレサマネージャー", 1); + if (telesa) + telesa->initAttacker(this); + return; + } + + TBossTelesaSaveLoadParams* params = (TBossTelesaSaveLoadParams*)unk15C; + s32 count = params->mSLNumGenBubble.get(); + MtxPtr rootMtx = mMActor->unk4->mNodeMatrices[5]; + f32 angleStep = 180.0f / count; + f32 angleOffset = count * 0.5f; + + for (int i = 0; i < count; ++i) { + TBubble* bubble = (TBubble*)gpConductor->makeOneEnemyAppear( + mPosition, "バブルマネージャー", 1); + if (!bubble) + return; + + Mtx rot; + Vec velocity; + velocity.x = 0.0f; + velocity.y = 0.0f; + velocity.z = -50.0f; + MsMtxSetRotRPH(rot, mRotation.x, + mRotation.y - angleOffset + angleStep * i, mRotation.z); + PSMTXMultVec(rot, &velocity, &velocity); + MsVECNormalize(&velocity, &velocity); + + velocity.y = 2.0f; + f32 speed = params->mSL1stBubbleSp.get(); + velocity.x *= speed; + velocity.z *= speed; + + bubble->mPosition.x = rootMtx[0][3]; + bubble->mPosition.y = rootMtx[1][3] - 50.0f; + bubble->mPosition.z = rootMtx[2][3]; + bubble->mVelocity = velocity; + bubble->mPosition.y += 10.0f; + bubble->onLiveFlag(LIVE_FLAG_AIRBORNE); + + f32 itemRoll = rand() * 0.000030517578f; + if (itemRoll < mItemGenRate) { + bubble->unk198 = nullptr; + TMapObjBase* item = gpItemManager->makeObjAppear( + bubble->mPosition.x, bubble->mPosition.y, bubble->mPosition.z, + 0x20000008, true); + if (item && item->receiveMessage(bubble, HIT_MESSAGE_TAKE)) { + item->appear(); + item->mPosition = bubble->mPosition; + item->mVelocity.set(0.0f, 15.0f, 0.0f); + item->offLiveFlag(LIVE_FLAG_UNK10); + bubble->mHeldObject = item; + bubble->unk198 = item; + } + } else { + f32 enemyRoll = rand() * 0.000030517578f; + if (enemyRoll < mEnemyGenRate) + bubble->appendEnemy(); + } + } } void TBossTelesa::setBckAnm(int index) { - if (getMActor()) - getMActor()->setBckFromIndex(index); + if (mMActor) + mMActor->setBckFromIndex(index); } -bool TBossTelesa::isInDamage() { return mInDamage != 0; } +bool TBossTelesa::isInDamage() { return unk35B != 0; } void TBossTelesa::rouletteStart() { - mRouletteTimer = 0; - mSlotStop = 0; - if (mSlot) - mSlot->moveStart(); + TMsRange speedRange(0.05f, 0.1f); + TMsRange directionRange(-1.0f, 1.0f); + f32 dir = directionRange.rand(); + + TSpineEnemyParams* params = getSaveParam(); + u8 maxHitPoints = params ? getSaveParam()->mSLHitPointMax.get() : 1; + f32 hpSpeed = (maxHitPoints - mHitPoints) * TBossTelesa::mRouletteUpRate; + + for (int i = 0; i < 3; ++i) { + f32 direction = 1.0f; + if (dir > 0.0f) + direction = -1.0f; + if (i == 0 || i == 2) + direction = -direction; + + f32 speed = speedRange.rand(); + ((TRoulette**)&unk178)[i]->unk144 = direction * (speed + hpSpeed); + + speed = speedRange.rand(); + unk184->unk1E4[i] = direction * (speed + hpSpeed); + } + + for (int i = 0; i < 3; ++i) + ((TRoulette**)&unk178)[i]->setRollSp(unk184->unk1E4[i]); + + SMSRumbleMgr->start(0x14, 0xf, (f32*)nullptr); + gpCameraShake->startShake((EnumCamShakeMode)0x23, 1.0f); } void TBossTelesa::slotStart() { rouletteStart(); } void TBossTelesa::slotStop() { - if (mSlot) - mSlot->forceStopSlot(0); + if (unk184) + unk184->forceStopSlot(0); } -int TBossTelesa::checkSlotResult() { return mSlot ? mSlotResult : -1; } +int TBossTelesa::checkSlotResult() +{ + return unk184 ? unk184->getSlotResult() : -1; +} void TBossTelesa::generateSlotItem() { - ++mSlotItemCount; - mGenerateTimer = 0; + static const char* manNameTable[] = { + "バブルマネージャー", "ハムクリマネージャー", + "ヤキグリマネージャー", "ボム兵マネージャー", + "ポイハナマネージャー", "電気ノコノコマネージャー", + "ポポマネージャー", "ゲッソーマネージャー", + "とびプクマネージャー", + }; + + unk368 = 0; + unk274 = 0; + + TTelesaSlot* slot = (TTelesaSlot*)unk184; + s32 result = slot->getResultFromAng(slot->unk13C[0]); + for (int i = 1; i < 3; ++i) { + if (slot->getResultFromAng(slot->unk13C[i]) != result) { + result = -1; + break; + } + } + unk1A8 = result; + + TBossTelesaSaveLoadParams* params = (TBossTelesaSaveLoadParams*)unk15C; + s32 slotItemNum = params->mSLSlotItemNum.get(); + MtxPtr rootMtx = mMActor->unk4->mNodeMatrices[5]; + f32 angleStep = 120.0f / slotItemNum; + f32 angleOffset = angleStep * slotItemNum * 0.5f; + + if (unk1A8 == 2) { + s32 numFruit = params->mSLSlotFruitNum.get(); + if (numFruit > 20) + numFruit = 20; + + s32 angleIndex = (s32)(numFruit * (rand() * 0.000030517578f)); + + for (int i = 0; i < numFruit; ++i) { + if (unk2A8[i]->mHolder) + continue; + + Mtx rot; + Vec dir; + Vec normalizedDir; + dir.x = 0.0f; + dir.y = 0.0f; + dir.z = 200.0f; + + f32 fruitAngleStep = 160.0f / numFruit; + MsMtxSetRotRPH(rot, mRotation.x, + mRotation.y - angleOffset + + fruitAngleStep * angleIndex, + mRotation.z); + + if (numFruit > 0) { + angleIndex += i; + while (angleIndex >= numFruit) + angleIndex -= numFruit; + while (angleIndex < 0) + angleIndex += numFruit; + } else { + angleIndex = 0; + } + + PSMTXMultVec(rot, &dir, &dir); + MsVECNormalize(&dir, &normalizedDir); + + TMapObjBase* actor; + if (i == 0 || i == 4) + actor = (TMapObjBase*)unk2F8[i]; + else + actor = (TMapObjBase*)unk2A8[i]; + + Vec velocity; + TMsRange fruitSpeedRange(6.0f, 10.0f); + + if (i == 0 || i == 4) { + actor->makeObjAppeared(); + actor->offLiveFlag(LIVE_FLAG_HIDDEN); + + velocity.x = normalizedDir.x * fruitSpeedRange.rand(); + velocity.y = -2.0f; + velocity.z = normalizedDir.z * fruitSpeedRange.rand(); + if (i == 0) { + velocity.x + = normalizedDir.x * fruitSpeedRange.rand() * 2.0f; + velocity.z + = normalizedDir.z * fruitSpeedRange.rand() * 2.0f; + } + + actor->mVelocity = velocity; + actor->offLiveFlag(LIVE_FLAG_UNK10); + actor->mRotation.set(0.0f, 90.0f, 0.0f); + } else { + actor->makeObjAppeared(); + actor->offLiveFlag(LIVE_FLAG_HIDDEN); + + velocity.x = normalizedDir.x * fruitSpeedRange.rand(); + velocity.y = -2.0f; + velocity.z = normalizedDir.z * fruitSpeedRange.rand(); + + actor->mVelocity = velocity; + actor->offLiveFlag(LIVE_FLAG_UNK10); + } + + unk1AC[unk274] = actor; + unk1AC[i]->onHitFlag(HIT_FLAG_NO_COLLISION); + unk1AC[i]->mScaling.set(1.5f, 1.5f, 1.5f); + unk1AC[unk274]->mPosition.x = rootMtx[0][3] + dir.x; + unk1AC[unk274]->mPosition.y = rootMtx[1][3] - 50.0f; + unk1AC[unk274]->mPosition.z = rootMtx[2][3] + dir.z; + unk274++; + } + } else if (unk1A8 == 0) { + s32 numItems = params->mSLSlotFruitNum.get(); + if (numItems > 10) + numItems = 10; + + if (unk370) + unk370--; + else + unk370 = 0; + + f32 itemAngleStep = 120.0f / numItems; + f32 itemAngleOffset = itemAngleStep * numItems * 0.5f; + for (int i = 0; i < numItems; ++i) { + if (i >= 10) + return; + + Mtx rot; + Vec dir; + dir.x = 0.0f; + dir.y = 0.0f; + dir.z = 250.0f; + MsMtxSetRotRPH(rot, mRotation.x, + mRotation.y - itemAngleOffset + itemAngleStep * i, + mRotation.z); + PSMTXMultVec(rot, &dir, &dir); + MsVECNormalize(&dir, &dir); + dir.y = 10.0f; + + Vec velocity; + TMsRange itemSpeedRange(0.8f, 3.5f); + f32 speed = params->mSL1stBubbleSp.get(); + velocity.x = dir.x * speed * itemSpeedRange.rand(); + velocity.y = dir.y; + velocity.z = dir.z * speed * itemSpeedRange.rand(); + + TMapObjBase* item = gpItemManager->makeObjAppeared(0x2000000E); + item->mPosition.x = rootMtx[0][3]; + item->mPosition.y = rootMtx[1][3] - 250.0f; + item->mPosition.z = rootMtx[2][3]; + item->mVelocity = velocity; + item->offLiveFlag(LIVE_FLAG_UNK10); + item->mScaling.set(0.0f, 0.0f, 0.0f); + ((TItem*)item)->killByTimer(0x3C0); + + unk1AC[unk274] = unk320[i]; + unk1AC[unk274]->offLiveFlag(LIVE_FLAG_HIDDEN); + unk274++; + } + } else { + s32 count = slotItemNum; + s32 kind = 0; + if (unk1A8 == -1) { + count *= 2; + kind = 0; + } else if (unk1A8 == 1) { + if (mHitPoints > 2) + kind = 1; + else + kind = 2; + } + + s32 maxKind = 7; + if (mHitPoints == 1) + maxKind = 8; + + s32 randomKind = 1 + (s32)((maxKind - 1) * (rand() * 0.000030517578f)); + + for (int i = 0; i < count; ++i) { + if (unk1A8 == 3) { + if ((i & 1) == 0) + randomKind++; + if (randomKind > maxKind) + randomKind = 1; + kind = randomKind; + } + + TSmallEnemy* enemy = (TSmallEnemy*)gpConductor->makeOneEnemyAppear( + mPosition, manNameTable[kind], 2); + if (!enemy) + continue; + + if (kind != 0) { + unk1AC[unk274] = enemy; + unk274++; + } + + Mtx rot; + Vec dir; + dir.x = 0.0f; + dir.y = 0.0f; + dir.z = 200.0f; + MsMtxSetRotRPH(rot, mRotation.x, + mRotation.y - angleOffset + angleStep * i, + mRotation.z); + PSMTXMultVec(rot, &dir, &dir); + MsVECNormalize(&dir, &dir); + dir.y = 2.0f; + + Vec velocity; + TMsRange enemySpeedRange(0.5f, 1.0f); + f32 speed = params->mSL1stBubbleSp.get(); + velocity.x = dir.x * speed * enemySpeedRange.rand(); + velocity.y = dir.y * (2.0f + enemySpeedRange.rand()); + velocity.z = dir.z * speed * enemySpeedRange.rand(); + + enemy->mPosition.x = rootMtx[0][3]; + enemy->mPosition.y = rootMtx[1][3] - 250.0f; + enemy->mPosition.z = rootMtx[2][3]; + enemy->mVelocity = velocity; + enemy->mPosition.y += 10.0f; + enemy->onLiveFlag(LIVE_FLAG_AIRBORNE); + PSMTXCopy(rootMtx, enemy->mMActor->unk4->unk20); + enemy->mMActor->calc(); + enemy->initAttacker(this); + } + } } -void TBossTelesa::fruitCollisionOn() { mSlotStop = 0; } +void TBossTelesa::fruitCollisionOn() { unk370 = 0; } -bool TBossTelesa::checkSlot() { return mSlot != nullptr; } +bool TBossTelesa::checkSlot() { return unk184 != nullptr; } bool TBossTelesa::checkAllItemDead() { - return mItem0 == 0 && mItem1 == 0 && mItem2 == 0 && mItem3 == 0; + for (int i = 0; i < unk274; ++i) + if (!(unk1AC[i]->mLiveFlag & LIVE_FLAG_DEAD)) + return false; + return true; } +#pragma dont_inline on void TBossTelesa::forceAllItemKill() { - mItem0 = mItem1 = mItem2 = mItem3 = 0; - mFruit0 = mFruit1 = mFruit2 = 0; + f32 zero = 0.0f; + + for (int i = 0; i < unk274; ++i) { + if (unk1AC[i]->mHolder) { + SMS_SendMessageToMario(unk1AC[i], HIT_MESSAGE_UNK8); + unk1AC[i]->mHolder = nullptr; + } + + unk1AC[i]->mPosition.set(zero, zero, zero); + unk1AC[i]->onHitFlag(HIT_FLAG_NO_COLLISION); + + if (!(unk1AC[i]->mLiveFlag & LIVE_FLAG_DEAD)) { + unk1AC[i]->kill(); + gpMarioParticleManager->emit(0xCD, &unk1AC[i]->mPosition, 0, + nullptr); + } + } } +#pragma dont_inline off void TBossTelesa::rollRouletteCircle() { - if (mSlot) - mSlot->moveStart(); + if (unk184) + unk184->moveStart(); } -bool TBossTelesa::isForceRestart() { return mForceRestart != 0; } +bool TBossTelesa::isForceRestart() { return unk35A != 0; } void TBossTelesa::forceHide() { - mDead = 1; - offLiveFlag(LIVE_FLAG_UNK200); - onLiveFlag(LIVE_FLAG_HIDDEN); + if (mSpine->getCurrentNerve() == &TNerveBossTelesaDie::theNerve()) + return; + if (mMActor->checkCurBckFromIndex(4)) + return; + if (mMActor->checkCurBckFromIndex(0)) + return; + + forceAllItemKill(); + unk368 = 0; + + if (unk350) { + if (gpMSound->gateCheck(0x2968)) { + MSoundSESystem::MSoundSE::startSoundActor(0x2968, &mPosition, 0, + nullptr, 0, 4); + } + } else { + if (gpMSound->gateCheck(0x28D5)) { + MSoundSESystem::MSoundSE::startSoundActor(0x28D5, &mPosition, 0, + nullptr, 0, 4); + } + } + + mSpine->reset(); + mSpine->setNext(&TNerveBossTelesaHide::theNerve()); } void TBossTelesa::fanfale() { } DEFINE_NERVE(TNerveBossTelesaDie, TLiveActor) { - if (spine->getTime() == 0) - static_cast(spine->getBody())->kill(); + TBossTelesa* boss = getBoss(spine); + if (spine->getTime() == 0) { + boss->unk388 = 0; + if (boss->unk350 && boss->mHitPoints != 0) + --boss->mHitPoints; + + boss->onHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk16C->onHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk170->onHitFlag(HIT_FLAG_NO_COLLISION); + + if (boss->mHitPoints != 0) { + if (boss->unk350) { + boss->mMActor->setBrkFromIndex(1); + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 2; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(2); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[2] : nullptr); + gpCameraShake->startShake((EnumCamShakeMode)0x1F, 1.0f); + } else { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 5; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(5); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[5] : nullptr); + boss->mMActor->setBrkFromIndex(0); + gpCameraShake->startShake((EnumCamShakeMode)0x20, 1.0f); + } + } else { + MSBgm::stopBGM(0x8001000D, 10); + gpCameraShake->startShake((EnumCamShakeMode)0x21, 1.0f); + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 3; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(3); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[3] : nullptr); + boss->mMActor->setBrkFromIndex(1); + boss->unk184->mScaling.set(0.0f, 0.0f, 0.0f); + + if (gpMSound->gateCheck(0x28DB)) { + MSoundSESystem::MSoundSE::startSoundActor( + 0x28DB, &boss->mPosition, 0, nullptr, 0, 4); + } + } + } + + if (boss->mHitPoints == 0) { + if (boss->checkCurAnmEnd(0)) { + if (boss->unk388 == 0) { + MtxPtr mtx = boss->mMActor->getModel()->mNodeMatrices[1]; + boss->unk374.set(mtx[0][3], mtx[1][3], mtx[2][3]); + + gpMarioParticleManager->emit(0xDC, &boss->unk374, 0, nullptr); + if (boss->unk380 == 0xD8) { + gpMarioParticleManager->emit(0xDD, &boss->unk374, 0, + nullptr); + } else if (boss->unk380 == 0xD9) { + gpMarioParticleManager->emit(0xDE, &boss->unk374, 0, + nullptr); + } else { + gpMarioParticleManager->emit(0xDF, &boss->unk374, 0, + nullptr); + } + + boss->forceAllItemKill(); + boss->unk178->unk144 = 0.0f; + boss->unk184->unk1E4[0] = 0.0f; + boss->unk17C->unk144 = 0.0f; + boss->unk184->unk1E4[1] = 0.0f; + boss->unk180->unk144 = 0.0f; + boss->unk184->unk1E4[2] = 0.0f; + boss->unk178->setRollSp(boss->unk184->unk1E4[0]); + boss->unk17C->setRollSp(boss->unk184->unk1E4[1]); + boss->unk180->setRollSp(boss->unk184->unk1E4[2]); + } + + if (boss->unk388 > 240) { + boss->unk388 = 0; + gpItemManager->makeShineAppearWithDemo( + "シャイン(ボス用)", "ボスシャインカメラ", + boss->mPosition.x, boss->mPosition.y, boss->mPosition.z); + boss->onLiveFlag(LIVE_FLAG_DEAD); + boss->onLiveFlag(LIVE_FLAG_UNK8); + boss->offLiveFlag(LIVE_FLAG_UNK10000); + boss->mHolder = nullptr; + boss->onHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk16C->onHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk170->onHitFlag(HIT_FLAG_NO_COLLISION); + boss->stopAnmSound(); + spine->reset(); + return TRUE; + } + + boss->unk388++; + } + return FALSE; + } + + if (boss->checkCurAnmEnd(0)) { + if (boss->mMActor->checkCurBckFromIndex(5)) { + boss->mMActor->setBrkFromIndex(2); + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 7; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(7); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[7] : nullptr); + } else if (boss->mMActor->checkCurBckFromIndex(7)) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 6; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(6); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[6] : nullptr); + } else { + SMS_ResetDamageFogEffect(boss->mMActor->getModel()->getModelData()); + + if (boss->mMActor->checkCurBckFromIndex(6)) { + boss->offHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk16C->offHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk170->offHitFlag(HIT_FLAG_NO_COLLISION); + + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 15; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(15); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[15] : nullptr); + boss->mMActor->setBtpFromIndex(2); + spine->pushAfterCurrent( + &TNerveBossTelesaPrepareSlot::theNerve()); + } else { + boss->damageRecover(); + } + return TRUE; + } + } + + return FALSE; +} + +DEFINE_NERVE(TNerveBossTelesaSpit, TLiveActor) +{ + TBossTelesa* boss = getBoss(spine); + if (spine->getTime() == 0 || !boss->mMActor->checkCurBckFromIndex(14)) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 14; + boss->unk168 = 1.0f; + + MActor* oldAnmActor = boss->mMActor; + J3DAnmTransform* oldAnm; + if (!oldAnmActor->unkC) + oldAnm = nullptr; + else + oldAnm = oldAnmActor->unkC->unk24; + if (oldAnmActor->unkC) + oldAnmActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(14); + MActor* blendActor = boss->mMActor; + f32 blendRatio = boss->unk168; + if (blendActor->unkC) + blendActor->unkC->setMotionBlendRatio(blendRatio); + + const char** basTable = boss->getBasNameTable(); + const char* basName; + if (!basTable) + basName = nullptr; + else + basName = basTable[14]; + boss->setAnmSound(basName); + } else { + if (boss->mMActor->getFrameCtrl(0)->checkPass(40.0f)) + boss->genAttacker(); + } + + if (boss->checkCurAnmEnd(0)) + return true; return false; } -DEFINE_NERVE(TNerveBossTelesaSpit, TLiveActor) { return spine->getTime() > 20; } - DEFINE_NERVE(TNerveBossTelesaHide, TLiveActor) { - if (spine->getTime() == 0) - static_cast(spine->getBody())->forceHide(); - return spine->getTime() > 30; + TBossTelesa* boss = getBoss(spine); + if (!boss->mMActor->checkCurBckFromIndex(4)) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 4; + boss->unk168 = 1.0f; + + MActor* oldAnmActor = boss->mMActor; + J3DAnmTransform* oldAnm; + if (!oldAnmActor->unkC) + oldAnm = nullptr; + else + oldAnm = oldAnmActor->unkC->unk24; + if (oldAnmActor->unkC) + oldAnmActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(4); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + const char* basName; + if (!basTable) + basName = nullptr; + else + basName = basTable[4]; + boss->setAnmSound(basName); + boss->mMActor->setBtpFromIndex(2); + } + + if (boss->checkCurAnmEnd(0)) { + boss->onHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk16C->onHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk170->onHitFlag(HIT_FLAG_NO_COLLISION); + SMSRumbleMgr->start(0x14, 0xF, (f32*)nullptr); + boss->rouletteStart(); + spine->pushAfterCurrent(&TNerveBossTelesaHideWait::theNerve()); + return TRUE; + } + return FALSE; } DEFINE_NERVE(TNerveBossTelesaHideWait, TLiveActor) { - return spine->getTime() > 60; + TBossTelesa* boss = getBoss(spine); + if (spine->getTime() == 0) { + boss->onLiveFlag(LIVE_FLAG_HIDDEN); + boss->unk350 = 0; + + u8 maxHp = boss->getSaveParam() + ? boss->getSaveParam()->mSLHitPointMax.get() + : 1; + u8 alpha = TBossTelesa::mNormalAlpha + (maxHp - boss->mHitPoints) * 30; + alpha = MsClamp(alpha, 0, 0xFE); + boss->unk34C.a = alpha; + + boss->unk184->mScaling.set(0.0f, 0.0f, 0.0f); + boss->mMActor->setBrkFromIndex(2); + s16 endFrame = boss->mMActor->getFrameCtrl(5)->getEnd(); + boss->mMActor->getFrameCtrl(5)->setFrame((f32)endFrame); + } else { + JGeometry::TVec3 pos = boss->mPosition; + pos.sub(*gpMarioPos); + + if (spine->getTime() > 400 + && ((TBossTelesaKillSmallEnemy*)boss->unk174)->unk6C == 0) { + spine->pushAfterCurrent(&TNerveBossTelesaAppear::theNerve()); + boss->offLiveFlag(LIVE_FLAG_HIDDEN); + return TRUE; + } + } + + return FALSE; } DEFINE_NERVE(TNerveBossTelesaAppear, TLiveActor) { - return spine->getTime() > 30; + TBossTelesa* boss = getBoss(spine); + if (spine->getTime() == 0) { + if (!boss->mMActor->checkCurBckFromIndex(0)) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 0; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(0); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[0] : nullptr); + + if (!boss->unk384) { + boss->unk384 = 1; + MSBgm::startBGM(0x8001000D); + } + + boss->unk184->mScaling.set(1.0f, 1.0f, 1.0f); + boss->unk184->randomReset(); + boss->offHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk16C->offHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk170->offHitFlag(HIT_FLAG_NO_COLLISION); + } + } else if (boss->checkCurAnmEnd(0) + && !boss->mMActor->checkCurBckFromIndex(15)) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 15; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(15); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[15] : nullptr); + boss->mMActor->setBtpFromIndex(2); + } + + if (boss->mMActor->checkCurBckFromIndex(0) + && boss->mMActor->getFrameCtrl(0)->checkPass(78.0f)) { + gpCameraShake->startShake((EnumCamShakeMode)0x22, 1.0f); + if (gpMSound->gateCheck(0x292A)) { + MSoundSESystem::MSoundSE::startSoundActor(0x292A, &boss->mPosition, + 0, nullptr, 0, 4); + } + } + + if (spine->getTime() > 800) { + u8 maxHp = boss->getSaveParam() + ? boss->getSaveParam()->mSLHitPointMax.get() + : 1; + int interval = TBossTelesa::mTelesaGenerateInterval + + (maxHp - boss->mHitPoints) * 100; + if (spine->getTime() % interval == 1) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 14; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(14); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[14] : nullptr); + } + } + + boss->unk364 *= 0.96f; + return FALSE; } DEFINE_NERVE(TNerveBossTelesaSlotStart, TLiveActor) { - if (spine->getTime() == 0) - static_cast(spine->getBody())->rouletteStart(); - return spine->getTime() > 60; + TBossTelesa* boss = getBoss(spine); + if (spine->getTime() == 0) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 11; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(11); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[11] : nullptr); + } + + if (boss->mMActor->checkCurBckFromIndex(11)) { + if (boss->mMActor->getFrameCtrl(0)->checkPass(53.0f)) { + boss->unk18C = 1; + boss->unk184->moveStart(); + ((TTelesaManager*)boss->unk354)->telesaForceKill(); + } + + if (boss->checkCurAnmEnd(0)) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 15; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(15); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[15] : nullptr); + boss->mMActor->setBtpFromIndex(2); + boss->unk184->forceStopSlot(1); + } + } + + if (!boss->unk184->isRollDrum() && boss->unk18C) { + boss->unk18C = 0; + boss->mSpine->pushAfterCurrent( + &TNerveBossTelesaSpitSlotItem::theNerve()); + return TRUE; + } + + boss->unk364 *= 0.99f; + return FALSE; } DEFINE_NERVE(TNerveBossTelesaSpitSlotItem, TLiveActor) { - return spine->getTime() > 20; + TBossTelesa* boss = getBoss(spine); + if (!boss->mMActor->checkCurBckFromIndex(14) + && boss->unk364 < TBossTelesa::mBaseHoseiPosY - 200.0f) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 14; + boss->unk168 = 1.0f; + + MActor* oldAnmActor = boss->mMActor; + J3DAnmTransform* oldAnm; + if (!oldAnmActor->unkC) + oldAnm = nullptr; + else + oldAnm = oldAnmActor->unkC->unk24; + if (oldAnmActor->unkC) + oldAnmActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(14); + MActor* blendActor = boss->mMActor; + f32 blendRatio = boss->unk168; + if (blendActor->unkC) + blendActor->unkC->setMotionBlendRatio(blendRatio); + + const char** basTable = boss->getBasNameTable(); + const char* basName; + if (!basTable) + basName = nullptr; + else + basName = basTable[14]; + boss->setAnmSound(basName); + } else if (boss->checkCurAnmEnd(0) && spine->getTime() > 600) { + spine->pushAfterCurrent(&TNerveBossTelesaPrepareSlot::theNerve()); + boss->unk368 = 0; + for (int i = 0; i < boss->unk274; ++i) { + TLiveActor* actor = boss->unk1AC[i]; + if (!(actor->mLiveFlag & LIVE_FLAG_DEAD) + && !actor->isActorType(0x2000000E) + && !actor->isActorType(0x20000002)) + actor->offHitFlag(HIT_FLAG_NO_COLLISION); + } + return TRUE; + } else if (spine->getTime() > 200) { + boss->unk364 -= 100.0f; + } + return FALSE; } DEFINE_NERVE(TNerveBossTelesaPrepareSlot, TLiveActor) { - return spine->getTime() > 30; + TBossTelesa* boss = getBoss(spine); + if (spine->getTime() == 0) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 15; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(15); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[15] : nullptr); + boss->mMActor->setBtpFromIndex(2); + } + + if (boss->unk350) { + boss->unk36C++; + if (boss->checkCurAnmEnd(0)) { + if (boss->mMActor->checkCurBckFromIndex(1)) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 12; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(12); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[12] : nullptr); + boss->mMActor->setBtpFromIndex(1); + } else if (boss->mMActor->checkCurBckFromIndex(12)) { + TBossTelesaSaveLoadParams* params + = (TBossTelesaSaveLoadParams*)boss->unk15C; + if (boss->unk36C > params->mSLSpicyTime.get()) { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 13; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(13); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[13] : nullptr); + } + } else { + boss->unk36C = 0; + boss->unk350 = 0; + + u8 maxHp = boss->getSaveParam() + ? boss->getSaveParam()->mSLHitPointMax.get() + : 1; + u8 alpha = TBossTelesa::mNormalAlpha + + (maxHp - boss->mHitPoints) * 30; + if (alpha > 0xFE) + alpha = 0xFE; + + boss->unk34C.a = alpha; + + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 15; + boss->unk168 = 1.0f; + + J3DAnmTransform* oldAnm = nullptr; + if (boss->mMActor->unkC) + oldAnm = boss->mMActor->unkC->unk24; + if (boss->mMActor->unkC) + boss->mMActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(15); + if (boss->mMActor->unkC) + boss->mMActor->unkC->setMotionBlendRatio(boss->unk168); + + const char** basTable = boss->getBasNameTable(); + boss->setAnmSound(basTable ? basTable[15] : nullptr); + boss->mMActor->setBtpFromIndex(2); + } + } + } + + boss->unk368++; + + TBossTelesaSaveLoadParams* params + = (TBossTelesaSaveLoadParams*)boss->unk15C; + int waitTime = params->mSLStopSlotTime0.get(); + if (boss->mHitPoints == 2) + waitTime = params->mSLStopSlotTime1.get(); + if (boss->mHitPoints == 1) + waitTime = params->mSLStopSlotTime2.get(); + + if (boss->unk184->getSlotResult() == 0) + waitTime = waitTime * 0.5f; + + if (boss->unk368 > waitTime - 120) + boss->flashItem(waitTime - boss->unk368); + + if (boss->mMActor->checkCurBckFromIndex(15)) { + BOOL done; + if (boss->unk1A8 == -1) { + done = TRUE; + } else { + done = TRUE; + for (int i = 0; i < boss->unk274; ++i) { + if (!(boss->unk1AC[i]->mLiveFlag & LIVE_FLAG_DEAD)) { + done = FALSE; + break; + } + } + } + + if (done || boss->unk368 > waitTime) { + boss->unk368 = 0; + boss->forceAllItemKill(); + + if (boss->unk350) { + if (gpMSound->gateCheck(0x2968)) { + MSoundSESystem::MSoundSE::startSoundActor( + 0x2968, &boss->mPosition, 0, nullptr, 0, 4); + } + } else { + if (gpMSound->gateCheck(0x28D5)) { + MSoundSESystem::MSoundSE::startSoundActor( + 0x28D5, &boss->mPosition, 0, nullptr, 0, 4); + } + } + + spine->setNext(&TNerveBossTelesaHide::theNerve()); + spine->pushAfterCurrent(&TNerveBossTelesaHide::theNerve()); + return TRUE; + } + } + + return FALSE; } DEFINE_NERVE(TNerveBossTelesaFreeze, TLiveActor) { - return spine->getTime() > 0 && spine->getBody()->checkCurAnmEnd(0); + TBossTelesa* boss = getBoss(spine); + if (boss->mMActor->checkCurBckFromIndex(16)) { + if (boss->checkCurAnmEnd(0)) { + boss->unk350 = 0; + + u8 maxHp = boss->getSaveParam() + ? boss->getSaveParam()->mSLHitPointMax.get() + : 1; + u8 alpha + = TBossTelesa::mNormalAlpha + (maxHp - boss->mHitPoints) * 30; + alpha = MsClamp(alpha, 0, 0xFE); + + boss->unk34C.a = alpha; + return TRUE; + } + } else { + boss->unk164 = boss->mMActor->getCurAnmIdx(0); + boss->unk160 = 16; + boss->unk168 = 1.0f; + + MActor* oldAnmActor = boss->mMActor; + J3DAnmTransform* oldAnm; + if (!oldAnmActor->unkC) + oldAnm = nullptr; + else + oldAnm = oldAnmActor->unkC->unk24; + if (oldAnmActor->unkC) + oldAnmActor->unkC->setOldMotionBlendAnmPtr(oldAnm); + + boss->mMActor->setBckFromIndex(16); + MActor* blendActor = boss->mMActor; + f32 blendRatio = boss->unk168; + if (blendActor->unkC) + blendActor->unkC->setMotionBlendRatio(blendRatio); + + const char** basTable = boss->getBasNameTable(); + const char* basName; + if (!basTable) + basName = nullptr; + else + basName = basTable[16]; + boss->setAnmSound(basName); + + if (gpMSound->gateCheck(0x28E7)) { + MSoundSESystem::MSoundSE::startSoundActor(0x28E7, &boss->mPosition, + 0, nullptr, 0, 4); + } + } + + return FALSE; } DEFINE_NERVE(TNerveBossTelesaFallDemo, TLiveActor) { - TBossTelesa* boss = static_cast(spine->getBody()); - if (spine->getTime() == 0) - boss->rouletteStart(); - return boss->rouletteFall() && boss->slotFall(); + TBossTelesa* boss = getBoss(spine); + if (spine->getTime() == 0) { + boss->onLiveFlag(LIVE_FLAG_HIDDEN); + if (SMS_SendMessageToMario(boss, HIT_MESSAGE_TAKE)) + boss->mHeldObject = (TTakeActor*)SMS_GetMarioHitActor(); + boss->mMActor->setFrameRate(0.0f, 0); + boss->unk184->mScaling.set(0.0f, 0.0f, 0.0f); + } + + if (boss->rouletteFall()) { + JGeometry::TVec3 diff = boss->mPosition; + diff.sub(*gpMarioPos); + + if (boss->slotFall()) { + boss->offHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk16C->offHitFlag(HIT_FLAG_NO_COLLISION); + boss->unk170->offHitFlag(HIT_FLAG_NO_COLLISION); + spine->reset(); + spine->setNext(&TNerveBossTelesaHideWait::theNerve()); + spine->pushAfterCurrent(&TNerveBossTelesaHideWait::theNerve()); + return TRUE; + } + } + + return FALSE; } From 18a4e2507f350cd65c9393277fa97a3476b621ae Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Mon, 3 Aug 2026 03:30:56 -0400 Subject: [PATCH 4/5] Enemy/bosstelesa: improve loadAfter matching --- src/Enemy/bosstelesa.cpp | 49 +++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/Enemy/bosstelesa.cpp b/src/Enemy/bosstelesa.cpp index b52c3e57d..683f66710 100644 --- a/src/Enemy/bosstelesa.cpp +++ b/src/Enemy/bosstelesa.cpp @@ -113,7 +113,10 @@ void TBubbleManager::load(JSUMemoryInputStream& stream) TSmallEnemyManager::load(stream); } -TSmallEnemy* TBubbleManager::createEnemyInstance() { return new TBubble; } +TSmallEnemy* TBubbleManager::createEnemyInstance() +{ + return new TBubble("バブル"); +} void TBubbleManager::createModelData() { @@ -1006,11 +1009,13 @@ void TBossTelesa::loadAfter() int found = 0; for (int i = 0; i < gpMapObjManager->getObjNum(); ++i) { TMapObjBase* actor = gpMapObjManager->getObj(i); - bool isRoulette = false; + u8 isRoulette; if (actor->mActorType == rouletteType) - isRoulette = true; + isRoulette = 1; + else + isRoulette = 0; if (isRoulette) { - ((TRoulette**)&unk178)[found] = (TRoulette*)actor; + (&unk178)[found] = (TRoulette*)actor; found++; } } @@ -1020,9 +1025,11 @@ void TBossTelesa::loadAfter() if ((u32)gpMapObjManager->getObjNumWithActorType(slotType) != 0) { for (int i = 0; i < gpMapObjManager->getObjNum(); ++i) { TMapObjBase* actor = gpMapObjManager->getObj(i); - bool isSlot = false; + u8 isSlot; if (actor->mActorType == slotType) - isSlot = true; + isSlot = 1; + else + isSlot = 0; if (isSlot) { unk184 = (TTelesaSlot*)actor; unk184->unk1A0 = this; @@ -1030,27 +1037,31 @@ void TBossTelesa::loadAfter() } } - JGeometry::TVec3 position(0.0f, 0.0f, 0.0f); - JGeometry::TVec3 rotation(0.0f, 0.0f, 0.0f); - JGeometry::TVec3 scale(1.0f, 1.0f, 1.0f); int fruitIndex = 0; for (int i = 0; i < 6; ++i) unk2A8[fruitIndex++] = TMapObjBaseManager::newAndRegisterObj( - "FruitCoconut", position, rotation, scale); + "FruitCoconut", JGeometry::TVec3(0.0f, 0.0f, 0.0f), + JGeometry::TVec3(0.0f, 0.0f, 0.0f), + JGeometry::TVec3(1.0f, 1.0f, 1.0f)); for (int i = 0; i < 6; ++i) unk2A8[fruitIndex++] = TMapObjBaseManager::newAndRegisterObj( - "FruitPapaya", position, rotation, scale); + "FruitPapaya", JGeometry::TVec3(0.0f, 0.0f, 0.0f), + JGeometry::TVec3(0.0f, 0.0f, 0.0f), + JGeometry::TVec3(1.0f, 1.0f, 1.0f)); for (int i = 0; i < 2; ++i) unk2A8[fruitIndex++] = TMapObjBaseManager::newAndRegisterObj( - "FruitPine", position, rotation, scale); + "FruitPine", JGeometry::TVec3(0.0f, 0.0f, 0.0f), + JGeometry::TVec3(0.0f, 0.0f, 0.0f), + JGeometry::TVec3(1.0f, 1.0f, 1.0f)); for (int i = 0; i < 6; ++i) unk2A8[fruitIndex++] = TMapObjBaseManager::newAndRegisterObj( - "FruitDurian", position, rotation, scale); + "FruitDurian", JGeometry::TVec3(0.0f, 0.0f, 0.0f), + JGeometry::TVec3(0.0f, 0.0f, 0.0f), + JGeometry::TVec3(1.0f, 1.0f, 1.0f)); for (int i = 0; i < 20; ++i) { - TMapObjBase* actor = (TMapObjBase*)unk2A8[i]; - actor->onMapObjFlag(0x04000000); - actor->makeObjDead(); + ((TMapObjBase*)unk2A8[i])->onMapObjFlag(0x04000000); + ((TMapObjBase*)unk2A8[i])->makeObjDead(); } unk2F8[0] = JDrama::TNameRefGen::search("唐辛子 0"); @@ -1087,8 +1098,10 @@ void TBossTelesa::loadAfter() unk354 = JDrama::TNameRefGen::search("テレサマネージャー"); for (int i = 0; i < 5; ++i) - TMapObjBaseManager::newAndRegisterObj("bottle_large", position, - rotation, scale); + TMapObjBaseManager::newAndRegisterObj( + "bottle_large", JGeometry::TVec3(0.0f, 0.0f, 0.0f), + JGeometry::TVec3(0.0f, 0.0f, 0.0f), + JGeometry::TVec3(1.0f, 1.0f, 1.0f)); SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_fhit.jpa", 0xD7); SMS_LoadParticle("/scene/btelesa/jpa/ms_btls_fhit_pe.jpa", 0xD8); From 60fcf3f0e0fb22034c61634a60b04f8802015e5c Mon Sep 17 00:00:00 2001 From: KakarotCake <61133727+KakarottoCake@users.noreply.github.com> Date: Mon, 3 Aug 2026 03:47:29 -0400 Subject: [PATCH 5/5] Enemy/bosstelesa: match slot item ranges and audio init --- src/Enemy/bosstelesa.cpp | 47 +++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/Enemy/bosstelesa.cpp b/src/Enemy/bosstelesa.cpp index 683f66710..268bf12aa 100644 --- a/src/Enemy/bosstelesa.cpp +++ b/src/Enemy/bosstelesa.cpp @@ -29,8 +29,10 @@ #include #include #include -#include #include +#include +#include +#include #include #include #include @@ -1936,7 +1938,8 @@ void TBossTelesa::generateSlotItem() if (numFruit > 20) numFruit = 20; - s32 angleIndex = (s32)(numFruit * (rand() * 0.000030517578f)); + TMsRange angleIndexRange(0, numFruit); + s32 angleIndex = angleIndexRange.rand(); for (int i = 0; i < numFruit; ++i) { if (unk2A8[i]->mHolder) @@ -1968,18 +1971,12 @@ void TBossTelesa::generateSlotItem() PSMTXMultVec(rot, &dir, &dir); MsVECNormalize(&dir, &normalizedDir); - TMapObjBase* actor; - if (i == 0 || i == 4) - actor = (TMapObjBase*)unk2F8[i]; - else - actor = (TMapObjBase*)unk2A8[i]; - Vec velocity; TMsRange fruitSpeedRange(6.0f, 10.0f); if (i == 0 || i == 4) { - actor->makeObjAppeared(); - actor->offLiveFlag(LIVE_FLAG_HIDDEN); + ((TMapObjBase*)unk2F8[i])->makeObjAppeared(); + ((TMapObjBase*)unk2F8[i])->offLiveFlag(LIVE_FLAG_HIDDEN); velocity.x = normalizedDir.x * fruitSpeedRange.rand(); velocity.y = -2.0f; @@ -1991,22 +1988,23 @@ void TBossTelesa::generateSlotItem() = normalizedDir.z * fruitSpeedRange.rand() * 2.0f; } - actor->mVelocity = velocity; - actor->offLiveFlag(LIVE_FLAG_UNK10); - actor->mRotation.set(0.0f, 90.0f, 0.0f); + ((TMapObjBase*)unk2F8[i])->mVelocity = velocity; + ((TMapObjBase*)unk2F8[i])->offLiveFlag(LIVE_FLAG_UNK10); + ((TMapObjBase*)unk2F8[i])->mRotation.set(0.0f, 90.0f, 0.0f); + unk1AC[unk274] = unk2F8[i]; } else { - actor->makeObjAppeared(); - actor->offLiveFlag(LIVE_FLAG_HIDDEN); + ((TMapObjBase*)unk2A8[i])->makeObjAppeared(); + ((TMapObjBase*)unk2A8[i])->offLiveFlag(LIVE_FLAG_HIDDEN); velocity.x = normalizedDir.x * fruitSpeedRange.rand(); velocity.y = -2.0f; velocity.z = normalizedDir.z * fruitSpeedRange.rand(); - actor->mVelocity = velocity; - actor->offLiveFlag(LIVE_FLAG_UNK10); + ((TMapObjBase*)unk2A8[i])->mVelocity = velocity; + ((TMapObjBase*)unk2A8[i])->offLiveFlag(LIVE_FLAG_UNK10); + unk1AC[unk274] = unk2A8[i]; } - unk1AC[unk274] = actor; unk1AC[i]->onHitFlag(HIT_FLAG_NO_COLLISION); unk1AC[i]->mScaling.set(1.5f, 1.5f, 1.5f); unk1AC[unk274]->mPosition.x = rootMtx[0][3] + dir.x; @@ -2065,21 +2063,26 @@ void TBossTelesa::generateSlotItem() } else { s32 count = slotItemNum; s32 kind = 0; - if (unk1A8 == -1) { + switch (unk1A8) { + case -1: count *= 2; - kind = 0; - } else if (unk1A8 == 1) { + break; + case 1: if (mHitPoints > 2) kind = 1; else kind = 2; + break; + case 3: + break; } s32 maxKind = 7; if (mHitPoints == 1) maxKind = 8; - s32 randomKind = 1 + (s32)((maxKind - 1) * (rand() * 0.000030517578f)); + TMsRange randomKindRange(1, maxKind); + s32 randomKind = randomKindRange.rand(); for (int i = 0; i < count; ++i) { if (unk1A8 == 3) {