From 8e7083625bc74bd6e36c11e437b765ed9822e299 Mon Sep 17 00:00:00 2001 From: Gonzalo-kebab Date: Fri, 13 Feb 2026 17:31:25 +0100 Subject: [PATCH 1/6] update CocoNutBall splits --- config/RMGK01/splits.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/config/RMGK01/splits.txt b/config/RMGK01/splits.txt index 4f046992e..6b409390e 100644 --- a/config/RMGK01/splits.txt +++ b/config/RMGK01/splits.txt @@ -2557,6 +2557,7 @@ Game/Enemy/CocoNutBall.cpp: .ctors start:0x8052EA68 end:0x8052EA6C .rodata start:0x80531E30 end:0x80531E50 .data start:0x8057B348 end:0x8057B500 + .sdata start:0x806B18F0 end:0x806B18F4 .sbss start:0x806B3DA0 end:0x806B3DB8 .sdata2 start:0x806BA2A8 end:0x806BA338 From 07318456595d3d1a5524601a2467bb4ecbe796c7 Mon Sep 17 00:00:00 2001 From: Gonzalo-kebab Date: Mon, 16 Feb 2026 15:57:54 +0100 Subject: [PATCH 2/6] match 97% of CocoNutBall.cpp --- config/RMGK01/splits.txt | 2 +- config/RMGK01/symbols.txt | 2 +- include/Game/Enemy/CocoNutBall.hpp | 54 ++ .../include/JSystem/JGeometry/TMatrix.hpp | 1 - .../include/JSystem/JGeometry/TVec.hpp | 15 +- src/Game/Enemy/CocoNutBall.cpp | 548 ++++++++++++++++++ 6 files changed, 618 insertions(+), 4 deletions(-) create mode 100644 include/Game/Enemy/CocoNutBall.hpp create mode 100644 src/Game/Enemy/CocoNutBall.cpp diff --git a/config/RMGK01/splits.txt b/config/RMGK01/splits.txt index 0abdd9d70..8924d8d21 100644 --- a/config/RMGK01/splits.txt +++ b/config/RMGK01/splits.txt @@ -2560,7 +2560,7 @@ Game/Enemy/CocoNutBall.cpp: .ctors start:0x8052EA68 end:0x8052EA6C .rodata start:0x80531E30 end:0x80531E50 .data start:0x8057B348 end:0x8057B500 - .sdata start:0x806B18F0 end:0x806B18F4 + .sdata start:0x806B18F0 end:0x806B18F8 .sbss start:0x806B3DA0 end:0x806B3DB8 .sdata2 start:0x806BA2A8 end:0x806BA338 diff --git a/config/RMGK01/symbols.txt b/config/RMGK01/symbols.txt index a157cf533..73f5a419b 100644 --- a/config/RMGK01/symbols.txt +++ b/config/RMGK01/symbols.txt @@ -62209,7 +62209,7 @@ hCountTimer__21@unnamed@BombHei_cpp@ = .sdata:0x806B18E0; // type:object size:0x lbl_806B18E4 = .sdata:0x806B18E4; // type:object size:0x4 data:string lbl_806B18E8 = .sdata:0x806B18E8; // type:object size:0x4 data:string lbl_806B18F0 = .sdata:0x806B18F0; // type:object size:0x4 data:string -lbl_806B18F4 = .sdata:0x806B18F4; // type:object size:0x4 +lbl_806B18F4 = .sdata:0x806B18F4; // type:object size:0x4 data:string lbl_806B18F8 = .sdata:0x806B18F8; // type:object size:0x4 data:string lbl_806B18FC = .sdata:0x806B18FC; // type:object size:0x4 lbl_806B1900 = .sdata:0x806B1900; // type:object size:0x4 data:string diff --git a/include/Game/Enemy/CocoNutBall.hpp b/include/Game/Enemy/CocoNutBall.hpp new file mode 100644 index 000000000..5661ca604 --- /dev/null +++ b/include/Game/Enemy/CocoNutBall.hpp @@ -0,0 +1,54 @@ +#include "Game/LiveActor/LiveActor.hpp" +#include "Game/Util/JMapInfo.hpp" +#include "JSystem/JGeometry/TVec.hpp" + +class CocoNutBall : public LiveActor { +public: + CocoNutBall(const char* pName); + + virtual ~CocoNutBall(); + virtual void init(const JMapInfoIter& rIter); + virtual void appear(); + virtual void kill(); + virtual void calcAndSetBaseMtx(); + virtual void attackSensor(HitSensor* pSender, HitSensor* pReceiver); + virtual bool receiveMsgPlayerAttack(u32 msg, HitSensor* pSender, HitSensor* pReceiver); + + void appearAndThrow(const TVec3f&, f32); + void hitBackToPlayer(); + void demoBreak(const TVec3f&); + HitSensor* isBindedAny() const; + bool isValidReceivePunch() const; + bool isNerveTrowToOrFreeze() const; // inline used several places + bool isSensorBody(HitSensor*) const; + void calcHitBackVelocitAndGravity(); + bool isHitBackRight() const; + bool isHitBackFront() const; + void calcHitBackDstPos(TVec3f*, bool, bool); + bool tryToKill(bool); + void setVelocityToPlayer(f32, f32); + void freeze(); + bool isFreezable(); + void processApproachToPlayer(); + void exeThrow(); + void exeHitBackToHost(); + void exeHitBackToPlayer(); + void exeRebound(); + void exeFreeze(); + void exeFreezeRelease(); + + LiveActor* _8C; + TVec3f _90; + s32 _9C; + s32 _A0; + TVec3f _A4; + TVec3f _B0; + bool _BC; + bool _BD; + bool _BE; + f32 _C0; + f32 _C4; + TVec3f _C8; + f32 _D4; + bool _D8; +}; diff --git a/libs/JSystem/include/JSystem/JGeometry/TMatrix.hpp b/libs/JSystem/include/JSystem/JGeometry/TMatrix.hpp index 175bec59f..7291ecbb7 100644 --- a/libs/JSystem/include/JSystem/JGeometry/TMatrix.hpp +++ b/libs/JSystem/include/JSystem/JGeometry/TMatrix.hpp @@ -531,7 +531,6 @@ namespace JGeometry { void getTrans(TVec3f& rDest) const; void setTrans(const TVec3f& rSrc); void setTrans(f32 x, f32 y, f32 z); - void zeroTrans(); void makeRotate(const TVec3f&, f32); void makeQuat(const TQuat4f& rSrc); diff --git a/libs/JSystem/include/JSystem/JGeometry/TVec.hpp b/libs/JSystem/include/JSystem/JGeometry/TVec.hpp index 34233bbb2..82376b536 100644 --- a/libs/JSystem/include/JSystem/JGeometry/TVec.hpp +++ b/libs/JSystem/include/JSystem/JGeometry/TVec.hpp @@ -353,6 +353,10 @@ namespace JGeometry { return ret; } + inline void negate(const TVec3& rVec) { + JMathInlineVEC::PSVECNegate(rVec, this); + } + inline TVec3 negateOperatorInternal() const { TVec3 ret; JGeometry::negateInternal(&this->x, &ret.x); @@ -646,7 +650,16 @@ namespace JGeometry { return lengthinv * oldlength; }; - f32 setLength(const TVec3&, f32); + f32 setLength(const TVec3& rVec, f32 newlength) { + f32 oldlength = rVec.squared(); + if (oldlength <= 0.0000038146973f) { + zero(); + return 0.0f; + } + f32 lengthinv = JGeometry::TUtil< f32 >::inv_sqrt(oldlength); + scale(lengthinv * newlength, rVec); + return lengthinv * oldlength; + }; f32 length() const { return PSVECMag(this); diff --git a/src/Game/Enemy/CocoNutBall.cpp b/src/Game/Enemy/CocoNutBall.cpp new file mode 100644 index 000000000..137162bab --- /dev/null +++ b/src/Game/Enemy/CocoNutBall.cpp @@ -0,0 +1,548 @@ +#include "Game/Enemy/CocoNutBall.hpp" +#include "Game/LiveActor/HitSensor.hpp" +#include "Game/LiveActor/LiveActor.hpp" +#include "Game/LiveActor/Nerve.hpp" +#include "Game/MapObj/CocoNut.hpp" +#include "Game/MapObj/CoconutTree.hpp" +#include "Game/Util/ActorMovementUtil.hpp" +#include "Game/Util/ActorSensorUtil.hpp" +#include "Game/Util/ActorShadowUtil.hpp" +#include "Game/Util/CameraUtil.hpp" +#include "Game/Util/EffectUtil.hpp" +#include "Game/Util/JMapInfo.hpp" +#include "Game/Util/LiveActorUtil.hpp" +#include "Game/Util/MapUtil.hpp" +#include "Game/Util/MathUtil.hpp" +#include "Game/Util/MtxUtil.hpp" +#include "Game/Util/NerveUtil.hpp" +#include "Game/Util/ObjUtil.hpp" +#include "Game/Util/PlayerUtil.hpp" +#include "Game/Util/SoundUtil.hpp" +#include "Game/Util/StarPointerUtil.hpp" +#include "JSystem/JGeometry/TMatrix.hpp" +#include "JSystem/JGeometry/TVec.hpp" +#include "JSystem/JMath/JMATrigonometric.hpp" +#include "math_types.hpp" +#include "revolution/mtx.h" +#include "revolution/types.h" + +namespace { + const f32 cReboundVelocity[] = {0.0f, 15.0f, 5.0f}; +} + +namespace NrvCocoNutBall { + NEW_NERVE(CocoNutBallNrvThrow, CocoNutBall, Throw); + NEW_NERVE(CocoNutBallNrvHitBackToHost, CocoNutBall, HitBackToHost); + NEW_NERVE(CocoNutBallNrvHitBackToPlayer, CocoNutBall, HitBackToPlayer); + NEW_NERVE(CocoNutBallNrvRebound, CocoNutBall, Rebound); + NEW_NERVE(CocoNutBallNrvFreeze, CocoNutBall, Freeze); + NEW_NERVE(CocoNutBallNrvFreezeRelease, CocoNutBall, FreezeRelease); +} // namespace NrvCocoNutBall + +CocoNutBall::CocoNutBall(const char* pName) + : LiveActor(pName), _8C(nullptr), _90(0.0f, -1.0f, 0.0f), _9C(0), _A0(0), _A4(gZeroVec), _B0(gZeroVec), _BC(false), _BD(false), _BE(false), + _C0(450.0f), _C4(0.0f), _C8(0.0f, 1.0f, 0.0f), _D4(10000.0f), _D8(false) { +} + +void CocoNutBall::init(const JMapInfoIter& rIter) { + initModelManagerWithAnm(CocoNut::getModelName(), nullptr, false); + MR::connectToSceneNoSilhouettedMapObjStrongLight(this); + MR::initLightCtrl(this); + initHitSensor(2); + MR::addHitSensor(this, "body", ATYPE_COCO_NUT, 8, 40.0f, TVec3f(0.0f, 0.0f, 0.0f)); + MR::addHitSensor(this, "bind", ATYPE_COCO_NUT, 8, 500.0f, TVec3f(0.0f, 0.0f, 0.0f)); + initBinder(40.0f, 0.0f, 0); + initEffectKeeper(0, "CocoNut", false); + // some weirdness with this TVec here + MR::initStarPointerTarget(this, 150.0f, TVec3f(0.0f, 0.0f, 0.0f)); + initSound(4, false); + MR::initShadowVolumeCylinder(this, 60.0f); + MR::invalidateClipping(this); + initNerve(&NrvCocoNutBall::CocoNutBallNrvThrow::sInstance); + makeActorDead(); +} + +void CocoNutBall::appear() { + LiveActor::appear(); + _9C = 0; + MR::onBind(this); + setNerve(&NrvCocoNutBall::CocoNutBallNrvThrow::sInstance); +} + +void CocoNutBall::kill() { + MR::forceDeleteEffect(this, "CocoNutBlur"); + MR::forceDeleteEffect(this, "CocoNutLight"); + MR::startSound(this, "SE_OJ_COCONUT_BALL_BREAK", -1, -1); + if (_9C > 0) { + MR::sendArbitraryMsg(ACTMES_RUSH_END, _8C->getSensor("body"), getSensor("body")); + } + LiveActor::kill(); + MR::emitEffect(this, CocoNut::getBreakEffectName()); +} + +void CocoNutBall::appearAndThrow(const TVec3f& appearPos, f32 f1) { + mPosition.set(appearPos); + MR::calcGravity(this); + _C8.negate(mGravity); + setVelocityToPlayer(15.0f, f1); + _BD = MR::isHalfProbability(); + appear(); +} + +void CocoNutBall::hitBackToPlayer() { + f32 rand = MR::getRandom(12.5f, 17.5f); + + if (_BD) { + rand = -rand; + _BD = false; + } else { + _BD = true; + } + + setVelocityToPlayer(3.0f * _9C + 15.0f, rand); + setNerve(&NrvCocoNutBall::CocoNutBallNrvHitBackToPlayer::sInstance); +} + +void CocoNutBall::demoBreak(const TVec3f& pos) { + mPosition.set(pos); + mRotation.zero(); + mVelocity.zero(); + + TPos3f TRMtx; + MR::makeMtxTR(TRMtx.toMtxPtr(), this); + MR::setBaseTRMtx(this, TRMtx); + + MR::emitEffect(this, "Hit"); + MR::startSound(this, "SE_OJ_COCONUT_BALL_BREAK", -1, -1); + MR::emitEffect(this, CocoNut::getBreakEffectName()); +} + +void CocoNutBall::calcAndSetBaseMtx() { + if (!MR::isNearZero(mVelocity)) { + TVec3f velocityDirection; + MR::normalize(mVelocity, &velocityDirection); + TPos3f baseTRMtx; + MR::makeMtxFrontUpPos(&baseTRMtx, velocityDirection, _C8, mPosition); + MR::setBaseTRMtx(this, baseTRMtx); + } +} + +bool CocoNutBall::isSensorBody(HitSensor* pSensor) const { + return pSensor == getSensor("body"); +} + +void CocoNutBall::attackSensor(HitSensor* pSender, HitSensor* pReceiver) { + if (isSensorBody(pSender) && MR::isSensorPlayer(pReceiver)) { + if (isNerveTrowToOrFreeze() && MR::sendArbitraryMsg(ACTMES_ENEMY_ATTACK_FLIP_VERYWEAK, pReceiver, pSender)) { + setNerve(&NrvCocoNutBall::CocoNutBallNrvRebound::sInstance); + return; + } + } + + if (MR::isSensorEnemy(pReceiver) && isSensorBody(pSender)) { + if (pReceiver->mHost == _8C) { + if (isNerve(&NrvCocoNutBall::CocoNutBallNrvHitBackToHost::sInstance)) { + if (MR::sendMsgEnemyAttack(pReceiver, pSender)) { + MR::emitEffect(this, "Hit"); + kill(); + } + } + return; + } + + if (isNerve(&NrvCocoNutBall::CocoNutBallNrvThrow::sInstance) || isNerve(&NrvCocoNutBall::CocoNutBallNrvHitBackToHost::sInstance) || + isNerve(&NrvCocoNutBall::CocoNutBallNrvHitBackToPlayer::sInstance)) { + if (MR::sendMsgEnemyAttack(pReceiver, pSender)) { + kill(); + } + } + } +} + +bool CocoNutBall::receiveMsgPlayerAttack(u32 msg, HitSensor* pSender, HitSensor* pReceiver) { + if(MR::isMsgPlayerSpinAttack(msg)) { + if(isValidReceivePunch()) { + setNerve(&NrvCocoNutBall::CocoNutBallNrvHitBackToHost::sInstance); + return true; + } + } else if(isSensorBody(pReceiver) && MR::isMsgStarPieceReflect(msg)){ + return true; + } + + return false; +} + +HitSensor* CocoNutBall::isBindedAny() const { + if (MR::isBindedGround(this)) { + return MR::getGroundSensor(this); + } + + if (MR::isBindedWall(this)) { + return MR::getWallSensor(this); + } + + if (MR::isBindedRoof(this)) { + return MR::getRoofSensor(this); + } + + return nullptr; +} + +bool CocoNutBall::isNerveTrowToOrFreeze() const { + return isNerve(&NrvCocoNutBall::CocoNutBallNrvThrow::sInstance) || isNerve(&NrvCocoNutBall::CocoNutBallNrvHitBackToPlayer::sInstance) || + isNerve(&NrvCocoNutBall::CocoNutBallNrvFreeze::sInstance) || isNerve(&NrvCocoNutBall::CocoNutBallNrvFreezeRelease::sInstance); +} + +bool CocoNutBall::isValidReceivePunch() const { + if (MR::isDead(this) || (!isNerveTrowToOrFreeze() && !isNerve(&NrvCocoNutBall::CocoNutBallNrvRebound::sInstance))) { + return false; + } else { + return PSVECDistance(mPosition, MR::getPlayerCenterPos()) < 400.0f; + } +} + +void CocoNutBall::calcHitBackVelocitAndGravity() { + bool hitBackRight = isHitBackRight(); + bool hitBackFront = isHitBackFront(); + TVec3f hitBackDstPos; + calcHitBackDstPos(&hitBackDstPos, hitBackRight, hitBackFront); + + TVec3f dir; + dir.subInline(hitBackDstPos, mPosition); + TVec3f scaled; + scaled.scale(_C8.dot(dir), _C8); + MR::vecKillElement(dir, _C8, &dir); + f32 f1 = dir.length() / 42.0f; + MR::normalize(&dir); + TVec3f cross; + PSVECCrossProduct(_C8, dir, &cross); + MR::normalize(&cross); + _90.scale(2.2f, mGravity); + TVec3f scaled2; + scaled2.scale(42.0f, dir); + TVec3f scaled3(_90.scaleInline(f1).scaleInline(f1)); + scaled.subInline(scaled.scaleInline(2.0f)); + mVelocity.add(scaled2, scaled.scaleInline(1.0f / (2.0f * f1))); + + if (!hitBackFront) { + f32 scaleFactor = (hitBackRight ? 1.2f : -1.2f); + TVec3f scaled4; + scaled4.scale(scaleFactor * f1 * f1 / (f1 * 2.0f), cross); + mVelocity.addInline(scaled4); + scaled4.scale(scaleFactor, cross); + _90.subInline(scaled4); + } + + _BC = hitBackRight; +} + +bool CocoNutBall::isHitBackRight() const { + TVec3f vec1; + vec1.subInline(_8C->mPosition, *MR::getPlayerPos()); + + TVec3f vec2; + vec2.subInline(mPosition, *MR::getPlayerPos()); + + MR::vecKillElement(vec1, _C8, &vec1); + MR::vecKillElement(vec2, _C8, &vec2); + + MR::normalize(&vec1); + MR::normalize(&vec2); + + TVec3f cross; + PSVECCrossProduct(vec1, vec2, cross); + + return 0.0f < _C8.dot(cross); +} + +bool CocoNutBall::isHitBackFront() const { + TVec3f vec1; + vec1.subInline(*MR::getPlayerPos(), _8C->mPosition); + + TVec3f vec2; + vec2.subInline(mPosition, *MR::getPlayerPos()); + + MR::vecKillElement(vec1, _C8, &vec1); + MR::vecKillElement(vec2, _C8, &vec2); + + MR::normalize(&vec1); + MR::normalize(&vec2); + + return vec1.dot(vec2) < 0.0f; +} + +void CocoNutBall::calcHitBackDstPos(TVec3f* pOut, bool a1, bool a2) { + TVec3f vec1(0.0f, 0.0f, 0.0f); + if (!a2) { + TVec3f cross; + TVec3f vec2; + + vec2.subInline(_8C->mPosition, *MR::getPlayerPos()); + + MR::vecKillElement(vec2, _C8, &vec2); + MR::normalize(&vec2); + + PSVECCrossProduct(_C8, vec2, &cross); + + MR::vecKillElement(cross, _C8, &cross); + MR::normalize(&cross); + + vec1.scale(a1 ? 150.0f : -150.0f, cross); + } + TVec3f scaled; + + scaled.scale(100.0f, _C8); + vec1.addInline(scaled); + + pOut->add(_8C->getSensor("body")->mPosition, vec1); +} + +bool CocoNutBall::tryToKill(bool alwaysKill) { + HitSensor* bindedSensor = isBindedAny(); + + if (bindedSensor != nullptr) { + if (bindedSensor->isType(ATYPE_PUNCH_BOX) && !isNerve(&NrvCocoNutBall::CocoNutBallNrvRebound::sInstance)) { + MR::sendMsgEnemyAttack(bindedSensor, getSensor("body")); + } + kill(); + return true; + } + + if (alwaysKill) { + kill(); + return true; + } + + return false; +} + +void CocoNutBall::setVelocityToPlayer(f32 f1, f32 f2) { + TVec3f vec1; + + if (!_BE) { + vec1.scale(120.0f, _C8); + vec1.addInline(*MR::getPlayerPos()); + } else { + vec1.set(*MR::getPlayerPos()); + } + + TPos3f rotate; + f32 angle = 0.017453292f * f2; + rotate.makeRotateInline(_C8, angle); + TVec3f vec2; + vec2.subInline(vec1, mPosition); + rotate.mult33(vec2); + vec1.add(mPosition, vec2); + + if (_BE) { + // inline max function? + f32 val = vec1.y; + f32 val2 = _C0 + _8C->mPosition.y; + if (val >= _C4 + _8C->mPosition.y) { + val = val; + } else { + val = _C4 + _8C->mPosition.y; + } + vec1.y = val; + + f32 flt = 120.0f; + bool v1 = false; + while (vec1.y < val2 - flt) { + vec2.subInline(vec1, mPosition); + vec2.scale(1.5f); + + if (!MR::getFirstPolyOnLineToMap(nullptr, nullptr, mPosition, vec2)) { + v1 = true; + vec1.y += flt; + break; + } + + vec1.y += 60.0f; + } + + if (!v1) { + vec1.y = val2; + } + } + vec2.subInline(vec1, mPosition); + mVelocity.setLength(vec2, f1); +} + +void CocoNutBall::freeze() { + _A0 = 0; + _A4.set(mPosition); + _B0.set(mVelocity); + setNerve(&NrvCocoNutBall::CocoNutBallNrvFreeze::sInstance); +} + +bool CocoNutBall::isFreezable() { + return !MR::isHiddenModel(this) && MR::isStarPointerPointing2POnPressButton(this, "弱", true, false); +} + +void CocoNutBall::processApproachToPlayer() { + if (MR::changeShowModelFlagSyncNearClipping(this, 300.0f)) { + MR::emitEffect(this, "CocoNutLight"); + } else { + MR::deleteEffect(this, "CocoNutLight"); + } + + if (isNerve(&NrvCocoNutBall::CocoNutBallNrvFreezeRelease::sInstance) || MR::isGreaterStep(this, 20)) { + if (isFreezable()) { + freeze(); + return; + } + } + + if (tryToKill(!MR::isNear(this, _8C, _D4))) { + return; + } +} + +void CocoNutBall::exeThrow() { + if (MR::isFirstStep(this)) { + MR::startBck(this, "SpinX", nullptr); + MR::emitEffect(this, "CocoNutLight"); + } + + if (_D8) { + MR::startSound(this, "SE_BM_OTAKING_SPIT_RALLY_BALL", -1, -1); + } else { + MR::startSound(this, "SE_OJ_COCONUT_BALL_SPIT_OUT", -1, -1); + } + + processApproachToPlayer(); +} + +void CocoNutBall::exeHitBackToHost() { + if (MR::isFirstStep(this)) { + MR::setBckRate(this, 1.0); + MR::deleteEffect(this, "Touch"); + MR::emitEffect(this, "CocoNutBlur"); + MR::emitEffect(this, "CocoNutLight"); + MR::emitEffect(this, "SpinHitMark"); + MR::offBind(this); + MR::startSound(this, "SE_OJ_COCONUT_BALL_VOLLEY", -1, -1); + MR::startSpinHitSound(this); + + if (_D8 && MR::isPlayingStageBgm()) { + if (_9C == 0) { + MR::startSystemME("ME_RALLY_COMBO_FIRST"); + } else { + MR::startSystemME("ME_RALLY_COMBO_SECOND"); + } + } + + _9C++; + calcHitBackVelocitAndGravity(); + } + + if (MR::isStep(this, 1)) { + MR::stopScene(6); + } + + if (MR::isStep(this, 2)) { + MR::shakeCameraWeak(); + } + + if (MR::isGreaterEqualStep(this, 1)) { + mVelocity.addInline(_90); + } + + if (tryToKill(!MR::isNearPlayer(this, 5000.0f))) { + return; + } +} + +void CocoNutBall::exeHitBackToPlayer() { + if (MR::isFirstStep(this)) { + MR::emitEffect(this, "Hit"); + MR::startSound(this, "SE_OJ_COCONUT_BALL_VOLLEY", -1, -1); + + if (_D8 && MR::isPlayingStageBgm()) { + MR::startSystemME("ME_RALLY_COMBO_SECOND"); + } + + MR::onBind(this); + } + + processApproachToPlayer(); +} + +void CocoNutBall::exeRebound() { + if (MR::isFirstStep(this)) { + MR::setBckRate(this, 0.25f); + MR::deleteEffect(this, "CocoNutBlur"); + MR::deleteEffect(this, "CocoNutLight"); + + TVec3f vec1; + vec1.subInline(mPosition, *MR::getPlayerPos()); + + TPos3f pos; + pos.identity(); + + MR::makeMtxUpFront(&pos, _C8, vec1); + + mVelocity.x = ::cReboundVelocity[0]; + mVelocity.y = ::cReboundVelocity[1]; + mVelocity.z = ::cReboundVelocity[2]; + + pos.mult33(mVelocity); + } + + TVec3f twoGravity; + twoGravity.scale(2.0f, mGravity); + mVelocity.addInline(twoGravity); + + if (tryToKill(!MR::isNearPlayer(this, 5000.0f))) { + return; + } +} + +void CocoNutBall::exeFreeze() { + if (MR::isFirstStep(this)) { + mVelocity.zero(); + + MR::deleteEffect(this, "CocoNutBlur"); + MR::deleteEffect(this, "CocoNutLight"); + MR::emitEffect(this, "Touch"); + + if (_A0 == 0) { + MR::startDPDHitSound(); + } + } + _A0++; + MR::startDPDFreezeLevelSound(this); + + f32 cos = JMath::sSinCosTable.cosLap(MR::repeatDegree(_A0 * 75.0f)); + + f32 scaleFactor = ((7.5f * cos) * (20 - getNerveStep())) / 20.0f; + + TVec3f camXDirScaled; + camXDirScaled.set(MR::getCamXdir()); + camXDirScaled.scale(scaleFactor); + mPosition.add(_A4, camXDirScaled); + + if (MR::changeShowModelFlagSyncNearClipping(this, 300.0)) { + MR::emitEffect(this, "Touch"); + } else { + MR::deleteEffect(this, "Touch"); + } + + if (isFreezable()) { + // resets nerve step every frame i guess + setNerve(&NrvCocoNutBall::CocoNutBallNrvFreeze::sInstance); + } else if (MR::isStep(this, 20)) { + setNerve(&NrvCocoNutBall::CocoNutBallNrvFreezeRelease::sInstance); + } +} + +void CocoNutBall::exeFreezeRelease() { + if (MR::isFirstStep(this)) { + mPosition.set(_A4); + mVelocity.set(_B0); + MR::deleteEffect(this, "Touch"); + MR::emitEffect(this, "CocoNutLight"); + } + + processApproachToPlayer(); +} From b8fa0fc68b43d4eb9d8c587f371747a6536f00b1 Mon Sep 17 00:00:00 2001 From: Gonzalo-kebab Date: Mon, 16 Feb 2026 16:06:12 +0100 Subject: [PATCH 3/6] fix regressions --- libs/JSystem/include/JSystem/JGeometry/TMatrix.hpp | 1 + src/Game/Enemy/CocoNutBall.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/JSystem/include/JSystem/JGeometry/TMatrix.hpp b/libs/JSystem/include/JSystem/JGeometry/TMatrix.hpp index 7291ecbb7..175bec59f 100644 --- a/libs/JSystem/include/JSystem/JGeometry/TMatrix.hpp +++ b/libs/JSystem/include/JSystem/JGeometry/TMatrix.hpp @@ -531,6 +531,7 @@ namespace JGeometry { void getTrans(TVec3f& rDest) const; void setTrans(const TVec3f& rSrc); void setTrans(f32 x, f32 y, f32 z); + void zeroTrans(); void makeRotate(const TVec3f&, f32); void makeQuat(const TQuat4f& rSrc); diff --git a/src/Game/Enemy/CocoNutBall.cpp b/src/Game/Enemy/CocoNutBall.cpp index 137162bab..9d64b50bd 100644 --- a/src/Game/Enemy/CocoNutBall.cpp +++ b/src/Game/Enemy/CocoNutBall.cpp @@ -326,7 +326,7 @@ void CocoNutBall::setVelocityToPlayer(f32 f1, f32 f2) { vec1.set(*MR::getPlayerPos()); } - TPos3f rotate; + TRot3f rotate; f32 angle = 0.017453292f * f2; rotate.makeRotateInline(_C8, angle); TVec3f vec2; From ffc65d7babbfbc27dc3835a5d81825eae3959795 Mon Sep 17 00:00:00 2001 From: Gonzalo-kebab Date: Tue, 3 Mar 2026 09:58:33 +0100 Subject: [PATCH 4/6] clean up includes and use math constant --- include/Game/Enemy/CocoNutBall.hpp | 2 -- src/Game/Enemy/CocoNutBall.cpp | 27 ++------------------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/include/Game/Enemy/CocoNutBall.hpp b/include/Game/Enemy/CocoNutBall.hpp index 5661ca604..77ded113b 100644 --- a/include/Game/Enemy/CocoNutBall.hpp +++ b/include/Game/Enemy/CocoNutBall.hpp @@ -1,6 +1,4 @@ #include "Game/LiveActor/LiveActor.hpp" -#include "Game/Util/JMapInfo.hpp" -#include "JSystem/JGeometry/TVec.hpp" class CocoNutBall : public LiveActor { public: diff --git a/src/Game/Enemy/CocoNutBall.cpp b/src/Game/Enemy/CocoNutBall.cpp index 9d64b50bd..c45dfb17b 100644 --- a/src/Game/Enemy/CocoNutBall.cpp +++ b/src/Game/Enemy/CocoNutBall.cpp @@ -1,30 +1,7 @@ #include "Game/Enemy/CocoNutBall.hpp" -#include "Game/LiveActor/HitSensor.hpp" -#include "Game/LiveActor/LiveActor.hpp" -#include "Game/LiveActor/Nerve.hpp" #include "Game/MapObj/CocoNut.hpp" -#include "Game/MapObj/CoconutTree.hpp" -#include "Game/Util/ActorMovementUtil.hpp" -#include "Game/Util/ActorSensorUtil.hpp" -#include "Game/Util/ActorShadowUtil.hpp" -#include "Game/Util/CameraUtil.hpp" -#include "Game/Util/EffectUtil.hpp" -#include "Game/Util/JMapInfo.hpp" -#include "Game/Util/LiveActorUtil.hpp" -#include "Game/Util/MapUtil.hpp" -#include "Game/Util/MathUtil.hpp" -#include "Game/Util/MtxUtil.hpp" -#include "Game/Util/NerveUtil.hpp" -#include "Game/Util/ObjUtil.hpp" -#include "Game/Util/PlayerUtil.hpp" -#include "Game/Util/SoundUtil.hpp" -#include "Game/Util/StarPointerUtil.hpp" -#include "JSystem/JGeometry/TMatrix.hpp" -#include "JSystem/JGeometry/TVec.hpp" +#include "Game/Util.hpp" #include "JSystem/JMath/JMATrigonometric.hpp" -#include "math_types.hpp" -#include "revolution/mtx.h" -#include "revolution/types.h" namespace { const f32 cReboundVelocity[] = {0.0f, 15.0f, 5.0f}; @@ -327,7 +304,7 @@ void CocoNutBall::setVelocityToPlayer(f32 f1, f32 f2) { } TRot3f rotate; - f32 angle = 0.017453292f * f2; + f32 angle = PI_180 * f2; rotate.makeRotateInline(_C8, angle); TVec3f vec2; vec2.subInline(vec1, mPosition); From 545d3c5d2f9954bf0ce027e0f2431c430aec1ca8 Mon Sep 17 00:00:00 2001 From: Gonzalo-kebab Date: Tue, 3 Mar 2026 10:04:20 +0100 Subject: [PATCH 5/6] removed too many includes --- src/Game/Enemy/CocoNutBall.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Game/Enemy/CocoNutBall.cpp b/src/Game/Enemy/CocoNutBall.cpp index c45dfb17b..806135156 100644 --- a/src/Game/Enemy/CocoNutBall.cpp +++ b/src/Game/Enemy/CocoNutBall.cpp @@ -1,4 +1,5 @@ #include "Game/Enemy/CocoNutBall.hpp" +#include "Game/LiveActor/HitSensor.hpp" #include "Game/MapObj/CocoNut.hpp" #include "Game/Util.hpp" #include "JSystem/JMath/JMATrigonometric.hpp" From 1fcdc891cf4ead49ab56373d4cac2ee247f1472d Mon Sep 17 00:00:00 2001 From: Gonzalo-kebab Date: Tue, 3 Mar 2026 21:44:04 +0100 Subject: [PATCH 6/6] improve formatting --- include/Game/Enemy/CocoNutBall.hpp | 2 +- src/Game/Enemy/CocoNutBall.cpp | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/Game/Enemy/CocoNutBall.hpp b/include/Game/Enemy/CocoNutBall.hpp index 77ded113b..de32fa7bf 100644 --- a/include/Game/Enemy/CocoNutBall.hpp +++ b/include/Game/Enemy/CocoNutBall.hpp @@ -17,7 +17,7 @@ class CocoNutBall : public LiveActor { void demoBreak(const TVec3f&); HitSensor* isBindedAny() const; bool isValidReceivePunch() const; - bool isNerveTrowToOrFreeze() const; // inline used several places + bool isNerveTrowToOrFreeze() const; // inline used several places bool isSensorBody(HitSensor*) const; void calcHitBackVelocitAndGravity(); bool isHitBackRight() const; diff --git a/src/Game/Enemy/CocoNutBall.cpp b/src/Game/Enemy/CocoNutBall.cpp index 806135156..ad968382e 100644 --- a/src/Game/Enemy/CocoNutBall.cpp +++ b/src/Game/Enemy/CocoNutBall.cpp @@ -27,8 +27,10 @@ void CocoNutBall::init(const JMapInfoIter& rIter) { MR::connectToSceneNoSilhouettedMapObjStrongLight(this); MR::initLightCtrl(this); initHitSensor(2); + MR::addHitSensor(this, "body", ATYPE_COCO_NUT, 8, 40.0f, TVec3f(0.0f, 0.0f, 0.0f)); MR::addHitSensor(this, "bind", ATYPE_COCO_NUT, 8, 500.0f, TVec3f(0.0f, 0.0f, 0.0f)); + initBinder(40.0f, 0.0f, 0); initEffectKeeper(0, "CocoNut", false); // some weirdness with this TVec here @@ -51,9 +53,11 @@ void CocoNutBall::kill() { MR::forceDeleteEffect(this, "CocoNutBlur"); MR::forceDeleteEffect(this, "CocoNutLight"); MR::startSound(this, "SE_OJ_COCONUT_BALL_BREAK", -1, -1); + if (_9C > 0) { MR::sendArbitraryMsg(ACTMES_RUSH_END, _8C->getSensor("body"), getSensor("body")); } + LiveActor::kill(); MR::emitEffect(this, CocoNut::getBreakEffectName()); } @@ -138,12 +142,12 @@ void CocoNutBall::attackSensor(HitSensor* pSender, HitSensor* pReceiver) { } bool CocoNutBall::receiveMsgPlayerAttack(u32 msg, HitSensor* pSender, HitSensor* pReceiver) { - if(MR::isMsgPlayerSpinAttack(msg)) { - if(isValidReceivePunch()) { + if (MR::isMsgPlayerSpinAttack(msg)) { + if (isValidReceivePunch()) { setNerve(&NrvCocoNutBall::CocoNutBallNrvHitBackToHost::sInstance); return true; } - } else if(isSensorBody(pReceiver) && MR::isMsgStarPieceReflect(msg)){ + } else if (isSensorBody(pReceiver) && MR::isMsgStarPieceReflect(msg)) { return true; }