#include "Game/MapObj/CocoNut.hpp" #include "Game/LiveActor/Spine.hpp" #include "Game/NameObj/NameObjArchiveListCollector.hpp" #include "Game/Util.hpp" #include "JSystem/JMath.hpp" inline void negateInternalInline(const TVec3f &src, TVec3f *dst) { JGeometry::negateInternal((f32 *)&src, (f32 *)dst); } CocoNut::CocoNut(const char *pName) : LiveActor(pName), _8C(0.0f), _90(0.0f), _94(0.0f, 1.0f), _D0(55.0f), _D4(false), _138(0), _13C(0), mSpawnPosition(gZeroVec), _14C(false), _150(gZeroVec), mSphericalShadow(false), mRespawnWhenOutOfView(false), _15E(false), mContinueRolling(false) { _A0.identity(); _D8.identity(); _108.identity(); } CocoNut::~CocoNut() { } void CocoNut::init(const JMapInfoIter &rIter) { initMapToolInfo(rIter); initModel(); MR::connectToSceneNoSilhouettedMapObjStrongLight(this); MR::initLightCtrl(this); initSensor(); initBinder(_D0, 0.0f, 0); MR::onCalcGravity(this); initEffect(); initSound(6, 0); if (!mSphericalShadow) { MR::initShadowVolumeCylinder(this, _D0); } else { MR::initShadowVolumeSphere(this, _D0); } MR::setShadowDropLength(this, nullptr, 1500.0f); s32 stack_8; if (MR::getJMapInfoClippingGroupID(rIter, &stack_8)) { MR::setGroupClipping(this, rIter, 32); _15E = true; } else { MR::setClippingFar200m(this); } MR::tryRegisterDemoCast(this, rIter); if (!mSphericalShadow) { initNerve(&NrvCocoNut::CocoNutNrvWaitOnBind::sInstance); } else { initNerve(&NrvCocoNut::CocoNutNrvWait::sInstance); } makeActorAppeared(); } void CocoNut::initAfterPlacement() { TPos3f stack_50; TVec3f gravity(mGravity); MR::makeMtxTR(&stack_50.mMtx[0], this); _94.set(stack_50.mMtx[0][2], stack_50.mMtx[1][2], stack_50.mMtx[2][2]); if (MR::isSameDirection(_94, gravity, 0.01f)) { TVec3f gravityNegated; negateInternalInline(gravity, &gravityNegated); TPos3f stack_20; MR::makeMtxUpNoSupport(&stack_20, gravityNegated); _94.set(stack_20.mMtx[0][2], stack_20.mMtx[1][2], stack_20.mMtx[2][2]); } } void CocoNut::startClipped() { if (isNerve(&NrvCocoNut::CocoNutNrvReplaceReady::sInstance)) { if (!MR::isDemoActive()) { _90 = 0.0f; _8C = 0.0f; _150.zero(); mVelocity.zero(); MR::onBind(this); MR::onCalcGravity(this); MR::showModel(this); MR::validateHitSensors(this); if (!mSphericalShadow) { _D4 = false; setNerve(&NrvCocoNut::CocoNutNrvWait::sInstance); } else { setNerve(&NrvCocoNut::CocoNutNrvWaitOnBind::sInstance); } } } else if (mRespawnWhenOutOfView) { statusToHide(); mPosition.set(mSpawnPosition); setNerve(&NrvCocoNut::CocoNutNrvReplaceReady::sInstance); } LiveActor::startClipped(); } void CocoNut::hit(const TVec3f &a1, f32 a2) { setFrontVec(a1); f32 var_f0; if (a2 < 1.5f) { var_f0 = 1.5f; } else if (a2 > 35.0f) { var_f0 = 35.0f; } else { var_f0 = a2; } _8C = var_f0; if (!isNerve(&NrvCocoNut::CocoNutNrvMove::sInstance)) { setNerve(&NrvCocoNut::CocoNutNrvMove::sInstance); } } bool CocoNut::isPossibleToHit(const TVec3f &a1, const TVec3f &a2, const TVec3f &a3) const { TVec3f stack_2C; TVec3f stack_20; TVec3f stack_14; TVec3f stack_8; stack_2C.sub(a2, a1); if (MR::normalizeOrZero(&stack_2C)) { return false; } if (MR::normalizeOrZero(a3, &stack_14)) { return false; } if (isNerve(&NrvCocoNut::CocoNutNrvMove::sInstance)) { if (MR::normalizeOrZero(mVelocity, &stack_8)) { return false; } stack_20.sub(stack_14, stack_8); if (MR::normalizeOrZero(&stack_20)) { return false; } } else { stack_20.set(stack_14); } return stack_2C.dot(stack_20) < 0.0f; } f32 CocoNut::calcMoveSpeed() const { return !isNerve(&NrvCocoNut::CocoNutNrvMove::sInstance) ? 0.0f : MR::max(_8C, PSVECMag(_150.toCVec())); } void CocoNut::initSensor() { initHitSensor(2); MR::addHitSensor(this, "body", 0x17, 0x10, 65.0f * mScale.x, TVec3f(0.0f, 0.0f, 0.0f)); MR::addHitSensor(this, "eye", 0x7f, 0x10, 1.1f * mScale.x, TVec3f(0.0f, 0.0f, 0.0f)); } void CocoNut::initModel() { initModelManagerWithAnm(getModelName(), nullptr, false); } void CocoNut::initEffect() { initEffectKeeper(0, "CocoNut", false); MR::setEffectHostMtx(this, "RollingSmoke", _D8.toMtxPtr()); MR::setEffectHostMtx(this, "RollingSmokeAttrWater", _D8.toMtxPtr()); MR::setEffectHostMtx(this, "RollingSmokeAttrSand", _D8.toMtxPtr()); MR::setEffectHostMtx(this, "Land", _D8.toMtxPtr()); MR::setEffectHostMtx(this, "LandAttrWater", _D8.toMtxPtr()); MR::setEffectHostMtx(this, "WaterColumn", _D8.toMtxPtr()); MR::setEffectHostMtx(this, getBreakEffectName(), _D8.toMtxPtr()); MR::setEffectHostMtx(this, "SpinHitMark", _108.toMtxPtr()); } #ifdef NON_MATCHING // hell function void CocoNut::updateRotate(float a1) { TMtx34f stack_38; TVec3f stack_2C; TVec3f stack_20; TVec3f stack_14; TVec3f stack_8; negateInternalInline(mGravity, &stack_20); if (!MR::normalizeOrZero(mVelocity, &stack_2C) && !MR::isSameDirection(stack_2C, stack_20, 0.01f)) { PSVECCrossProduct(stack_2C.toCVec(), stack_20.toCVec(), stack_14.toVec()); f32 temp1 = PSVECMag(mVelocity.toCVec()) * -180.0f; f32 temp2 = a1 * temp1; f32 f = PI_180 * (temp2 / (PI * getSize())); stack_38.mMtx[0][3] = 0.0f; stack_38.mMtx[1][3] = 0.0f; stack_38.mMtx[2][3] = 0.0f; stack_8.set(stack_14); PSVECMag(stack_8.toCVec()); PSVECNormalize(stack_8.toCVec(), stack_8.toVec()); f32 fsin = sin(f); f32 fcos = cos(f); f32 rx = stack_8.x; f32 ry = stack_8.y; f32 rz = stack_8.z; f32 fcos1 = 1.0f - fcos; stack_38.mMtx[0][0] = (rx * rx * fcos1) + fcos; stack_38.mMtx[0][1] = fcos1 * rx * ry - (fsin * rz); stack_38.mMtx[0][2] = fcos1 * rx * rz + (fsin * ry); stack_38.mMtx[1][0] = fcos1 * rx * ry + (fsin * rz); stack_38.mMtx[1][1] = (ry * ry * fcos1) + fcos; stack_38.mMtx[1][2] = fcos1 * ry * rz - (fsin * rx); stack_38.mMtx[2][0] = fcos1 * rx * rz - (fsin * ry); stack_38.mMtx[2][1] = fcos1 * ry * rz + (fsin * rx); stack_38.mMtx[2][2] = (rz * rz * fcos1) + fcos; _A0.concat(stack_38, _A0); } } #endif void CocoNut::updateGravity() { TVec3f stack_8; f32 f31 = _13C ? 0.4f : 1.0f; stack_8.set(mGravity); f32 f0 = _90 + f31; f32 f2 = f0 >= 25.0f ? 25.0f : f0; _90 = f2; stack_8.scale((f32)(f64)f2); // real mVelocity.add(stack_8); } #ifdef NON_MATCHING // issues around MR::deleteEffect and PSVECNormalize calls void CocoNut::processMove() { TVec3f stack_2C; TVec3f stack_20; TVec3f stack_14; TVec3f stack_8; if (isOnGround()) { f32 temp_f31 = calcMoveSpeed(); bool temp_r31 = MR::isBindedGroundWater(this); TVec3f *temp_r3_1 = MR::getGroundNormal(this); if (2.5f < _90) { if (mGravity.dot(*temp_r3_1) < 0.0f) { _90 *= 0.5f; s32 var_r5 = 100.0f * (temp_f31 / 35.0f); if (var_r5 > 100) { var_r5 = 100; } if (var_r5 > 0) { if (temp_r31) { MR::startSound(this, "SE_OJ_COCONUT_BOUND_WATER", var_r5, -1); } else { MR::startSound(this, "SE_OJ_COCONUT_BOUND", var_r5, -1); } } } if (_138 >= 10 && 3.0f < _90) { MR::emitEffect(this, "Land"); } } else { _90 = 0.0f; } MR::emitEffect(this, "RollingSmoke"); s32 var_r5_2 = 100.0f * (temp_f31 / 35.0f); if (var_r5_2 > 100) { var_r5_2 = 100; } if (var_r5_2 >= 10) { if (temp_r31) { MR::startLevelSound(this, "SE_OJ_LV_COCONUT_ROLL_WATER", var_r5_2, -1, -1); } else { MR::startLevelSound(this, "SE_OJ_LV_COCONUT_ROLL", var_r5_2, -1, -1); } } _8C *= 0.925f; _14C = MR::calcVelocityAreaMoveOnGround(&stack_2C, this); if (_14C) { _150.set(stack_2C); _150.scaleInline(0.75f); } _138 = 0; _13C = false; updateRotate(1.0f); } else { // volatile? if (_138 < 10) { _138++; } else { MR::deleteEffect(this, "RollingSmoke"); } updateRotate(0.75f); } if (getWallNormal(&stack_20) && _94.dot(stack_20) < 0.0f) { stack_14.set(_94); PSVECMag(stack_14.toCVec()); PSVECNormalize(stack_14.toCVec(), stack_14.toVec()); stack_8.set(stack_20); PSVECMag(stack_8.toCVec()); PSVECNormalize(stack_8.toCVec(), stack_8.toVec()); f32 ok2 = -2.0f * stack_14.dot(stack_8); JMAVECScaleAdd(stack_8.toCVec(), _94.toCVec(), _94.toVec(), ok2); PSVECMag(_94.toCVec()); PSVECNormalize(_94.toCVec(), _94.toVec()); _94.normalize(_94); _8C *= 0.8f; MR::startSound(this, "SE_OJ_COCONUT_BOUND", 100.0f * (calcMoveSpeed() / 35.0f), -1); } setFrontVec(_94); mVelocity.set(_94); mVelocity.scale(_8C); bool ok = _14C && _138 < 10; if (!ok) { _150.scaleInline(0.925f); } mVelocity.add(_150); updateGravity(); } #endif void CocoNut::setFrontVec(const TVec3f &a1) { TVec3f stack_14; TVec3f stack_8(mGravity); if (!MR::normalizeOrZero(a1, &stack_14)) { if (MR::isSameDirection(a1, stack_8, 0.01f)) { _94.set(stack_14); } else { MR::vecKillElement(stack_14, stack_8, &_94); MR::normalize(&_94); } } } bool CocoNut::tryHit(HitSensor *pOtherSensor, HitSensor *pMySensor) { CocoNut *nut = reinterpret_cast(pMySensor->mActor); if (!isNerve(&NrvCocoNut::CocoNutNrvMove::sInstance)) { return false; } f32 moveSpeed = nut->calcMoveSpeed(); if (calcMoveSpeed() < moveSpeed) { return false; } TVec3f *otherSensorPos = &pOtherSensor->mPosition; TVec3f *mySensorPos = &pMySensor->mPosition; if (!nut->isPossibleToHit(*mySensorPos, *otherSensorPos, mVelocity)) { return false; } TVec3f stack_1C; TVec3f stack_10; f32 stack_C; f32 stack_8; calcHitSpeedAndFrontVec(&stack_C, &stack_8, &stack_1C, &stack_10, *otherSensorPos, *mySensorPos); hit(stack_1C, stack_C); nut->hit(stack_10, stack_8); return true; } bool CocoNut::tryPushedFromActor(HitSensor *pOtherSensor, HitSensor *pMySensor) { TVec3f stack_34; TVec3f stack_28; TVec3f stack_1C; TVec3f stack_10; f32 stack_C; f32 stack_8; TVec3f *otherSensorPos = &pOtherSensor->mPosition; TVec3f *mySensorPos = &pMySensor->mPosition; if (_13C) { return false; } if (isNerve(&NrvCocoNut::CocoNutNrvMove::sInstance)) { stack_34.sub(*otherSensorPos, *mySensorPos); MR::normalize(&stack_34); if (0.0f < stack_34.dot(_94)) { return false; } calcHitSpeedAndFrontVec(&stack_C, &stack_8, &stack_28, &stack_1C, *otherSensorPos, *mySensorPos); hit(stack_28, stack_C); } else { f32 mySensorRadius = pMySensor->mRadius; f32 otherSensorRadius = pOtherSensor->mRadius; if (((otherSensorRadius + mySensorRadius) - PSVECDistance(otherSensorPos->toCVec(), mySensorPos->toVec())) < 0.0f) { return false; } stack_10.sub(*otherSensorPos, *mySensorPos); if (MR::normalizeOrZero(&stack_10)) { return false; } hit(stack_10, 1.5f); } return true; } void CocoNut::reviseFrontVec() { HitSensor *sensor; HitSensor *eye = getSensor("eye"); LiveActor *found_actor = nullptr; for (int i = 0; i < eye->mSensorCount; i++) { sensor = eye->mSensors[i]; if ((sensor->isType(0x26) || sensor->isType(0x56)) && !MR::isDead(sensor->mActor)) { found_actor = sensor->mActor; break; } } if (found_actor == nullptr) { return; } TVec3f stack_28(mGravity); TVec3f stack_20; TVec3f stack_14; TVec3f stack_8; stack_14.sub(found_actor->mPosition, this->mPosition); if (!MR::isSameDirection(stack_14, stack_20, 0.01f)) { MR::vecKillElement(stack_14, stack_20, &stack_8); MR::normalize(&stack_8); f32 temp_f31 = stack_8.dot(_94); if (MR::cosDegree(15.0f) < temp_f31) { JMAVECLerp(_94.toCVec(), stack_8.toCVec(), _94.toVec(), 0.8f); } } } void CocoNut::statusToWait() { mVelocity.zero(); _90 = 0.0f; _8C = 0.0f; _150.zero(); MR::validateClipping(this); if (!mSphericalShadow && !MR::isBindedGroundIce(this)) { _D4 = true; MR::offBind(this); MR::offCalcGravity(this); if (!isNerve(&NrvCocoNut::CocoNutNrvWait::sInstance)) { setNerve(&NrvCocoNut::CocoNutNrvWait::sInstance); } } else { setNerve(&NrvCocoNut::CocoNutNrvWaitOnBind::sInstance); } } void CocoNut::tryMoveEnd() { if (isOnGround()) { bool var_r3 = _14C && _138 < 10; if (!var_r3 && calcMoveSpeed() < 1.5f && (!mContinueRolling || !isContactWithOtherCocoNut())) { statusToWait(); return; } } if (sendMsgToBindedSensor()) { setNerve(&NrvCocoNut::CocoNutNrvBreak::sInstance); return; } if (!tryDisappear()) { _8C = MR::max(_8C, 1.5f); } } bool CocoNut::tryDisappear() { TVec3f stack_14; stack_14.scale(-100.0f, mGravity); if (MR::isInWater(this, stack_14)) { setNerve(&NrvCocoNut::CocoNutNrvInWater::sInstance); return true; } if (MR::isInDeath(this, TVec3f(0.0f, 0.0f, 0.0f))) { setNerve(&NrvCocoNut::CocoNutNrvBreak::sInstance); return true; } return false; } bool CocoNut::isValidPushedFromPlayer(const HitSensor *arg0, const HitSensor *arg1) const { if (_90 < 0.0f) { return false; } if (isNerve(&NrvCocoNut::CocoNutNrvMove::sInstance) && MR::isLessStep(this, 15)) { return false; } TVec3f *playerVelocity = MR::getPlayerVelocity(); f32 nutVelocitySquared = mVelocity.squared(); if (playerVelocity->squared() < nutVelocitySquared) { return false; } TVec3f stack_14; stack_14.sub(arg0->mPosition, arg1->mPosition); if (MR::normalizeOrZero(&stack_14)) { return false; } TVec3f stack_8; if (MR::normalizeOrZero(*playerVelocity, &stack_8)) { return false; } if (stack_14.dot(stack_8) < MR::cosDegree(45.0f)) { return false; } return true; } #ifdef NON_MATCHING // the frsqrte is most likely an inlined function. possibly JGeometry::TUtil::sqrt void CocoNut::calcHitSpeedAndFrontVec(f32 *arg0, f32 *arg1, TVec3f *arg2, TVec3f *arg3, const TVec3f &arg4, const TVec3f &arg5) const { TVec3f stack_14; TVec3f stack_8; arg3->sub(arg5, arg4); MR::normalize(arg3); stack_14.set(mGravity); PSVECCrossProduct(arg3->toCVec(), stack_14.toCVec(), arg2->toVec()); MR::normalize(arg2); if (MR::normalizeOrZero(mVelocity, &stack_8)) { stack_8.set(_94); } f32 var_f30 = stack_8.dot(*arg2); if (var_f30 < 0.0f) { negateInternalInline(stack_14, &stack_14); PSVECCrossProduct(arg3->toCVec(), stack_14.toCVec(), arg2->toVec()); MR::normalize(arg2); var_f30 = stack_8.dot(*arg2); } f32 temp_f2 = 1.0f - (var_f30 * var_f30); f32 var_f31; if (temp_f2 > 0.0f) { f32 temp_f31 = __frsqrte(temp_f2); f32 temp_f3 = temp_f31 * temp_f2; var_f31 = -((temp_f3 * temp_f31) - 3.0f) * temp_f3 * 0.5f; } else { var_f31 = temp_f2; } f32 temp_f1_2 = calcMoveSpeed(); *arg1 = temp_f1_2 * var_f31; *arg0 = temp_f1_2 * var_f30; } #endif bool CocoNut::isOnGround() const { if (0.0f < _90 && MR::isOnGround(this)) { TVec3f *groundNormal = MR::getGroundNormal(this); if (groundNormal->dot(mGravity) < MR::cosDegree(120.0f)) { return true; } } return false; } bool CocoNut::getWallNormal(TVec3f *arg0) const { if (MR::isBindedWall(this)) { arg0->set(*MR::getWallNormal(this)); return true; } if (0.0f < _90 && (MR::isOnGround(this)) && !isOnGround()) { arg0->set(*MR::getGroundNormal(this)); return true; } return false; } bool CocoNut::sendMsgToBindedSensor() { if (MR::isBindedGround(this)) { return sendMsgEnemyAttackToBindedSensor(MR::getGroundSensor(this)); } if (MR::isBindedWall(this)) { return sendMsgEnemyAttackToBindedSensor(MR::getWallSensor(this)); } if (MR::isBindedRoof(this)) { return sendMsgEnemyAttackToBindedSensor(MR::getRoofSensor(this)); } return 0; } bool CocoNut::sendMsgEnemyAttackToBindedSensor(HitSensor *arg0) { if (_13C) { return MR::sendMsgEnemyAttack(arg0, getSensor("body")); } return false; } bool CocoNut::isValidReceiveMsg(const HitSensor *a1) const { return ( isNerve(&NrvCocoNut::CocoNutNrvWait::sInstance) || isNerve(&NrvCocoNut::CocoNutNrvWaitOnBind::sInstance) || isNerve(&NrvCocoNut::CocoNutNrvMove::sInstance) ) && a1 == getSensor("body"); } const char *CocoNut::getModelName() { bool watermelonMode = MR::isStarPieceCounterStop(); return watermelonMode ? "Watermelon" : "CocoNut"; } const char *CocoNut::getBreakEffectName() { bool watermelonMode = MR::isStarPieceCounterStop(); return watermelonMode ? "BreakWatermelon" : "CocoNutBreak"; } void CocoNut::makeArchiveList(NameObjArchiveListCollector *pArchiveList, const JMapInfoIter &rIter) { pArchiveList->addArchive(getModelName()); } void CocoNut::calcAndSetBaseMtx() { _A0.mMtx[0][3] = this->mPosition.x; _A0.mMtx[1][3] = this->mPosition.y; _A0.mMtx[2][3] = this->mPosition.z; MR::setBaseTRMtx(this, _A0); if (isNerve(&NrvCocoNut::CocoNutNrvMove::sInstance) && MR::isOnGround(this)) { TVec3f *groundNormal = MR::getGroundNormal(this); f32 temp_f31 = _D0; TVec3f stack_8(*groundNormal); stack_8.scale(temp_f31); TVec3f stack_14; stack_14.sub(mPosition, stack_8); if (MR::isSameDirection(*groundNormal, _94, 0.01f)) { MR::makeMtxUpNoSupportPos(&_D8, *groundNormal, stack_14); } else { MR::makeMtxUpFrontPos(&_D8, *groundNormal, _94, stack_14); } f32 temp_f9 = mScale.x; _D8.mMtx[0][0] *= temp_f9; _D8.mMtx[0][1] *= temp_f9; _D8.mMtx[0][2] *= temp_f9; _D8.mMtx[1][0] *= temp_f9; _D8.mMtx[1][1] *= temp_f9; _D8.mMtx[1][2] *= temp_f9; _D8.mMtx[2][0] *= temp_f9; _D8.mMtx[2][1] *= temp_f9; _D8.mMtx[2][2] *= temp_f9; } } void CocoNut::attackSensor(HitSensor *arg0, HitSensor *arg1) { if (isValidReceiveMsg(arg0)) { if (MR::isSensorPlayer(arg1) && !isValidPushedFromPlayer(arg0, arg1)) { if (!MR::isPlayerHipDropFalling()) { MR::sendMsgPush(arg1, arg0); } } else if (arg1->isType(0x17)) { if (MR::sendMsgPush(arg1, arg0)) { MR::startSound(this, "SE_OJ_COCONUT_HIT", -1, -1); } } else { if (_13C && isNerve(&NrvCocoNut::CocoNutNrvMove::sInstance) && MR::sendMsgToEnemyAttackBlow(arg1, arg0)) { MR::startSound(this, "SE_OJ_COCONUT_HIT", -1, -1); setNerve(&NrvCocoNut::CocoNutNrvBreak::sInstance); } else { MR::sendMsgPush(arg1, arg0); } } } } bool CocoNut::receiveMsgPush(HitSensor *pMySensor, HitSensor *pOtherSensor) { if (!isValidReceiveMsg(pOtherSensor)) { return false; } if (pMySensor->isType(0x17)) { return tryHit(pOtherSensor, pMySensor); } return tryPushedFromActor(pOtherSensor, pMySensor); } bool CocoNut::receiveMsgPlayerAttack(u32 a1, HitSensor *pOtherSensor, HitSensor *pMySensor) { if (!isValidReceiveMsg(pMySensor)) { return false; } if (!MR::isMsgPlayerHitAll(a1) && !MR::isMsgPlayerTrample(a1) && !MR::isMsgPlayerHipDrop(a1) && !MR::isMsgStarPieceReflect(a1)) { return false; } f32 var_f31; f32 var_f30; if (MR::isMsgPlayerSpinAttack(a1)) { var_f31 = 35.0f; var_f30 = -20.0f; MR::startSound(this, "SE_PM_SPIN_HIT", -1, -1); MR::startSound(this, "SE_OJ_COCONUT_LAUNCH", -1, -1); emitEffectSpinHit(pOtherSensor, pMySensor); _13C = true; } else if (MR::isMsgPlayerHipDrop(a1) || MR::isMsgInvincibleAttack(a1)) { var_f31 = 25.0f; var_f30 = -15.0f; MR::startSound(this, "SE_OJ_COCONUT_FLIP_M", -1, -1); if (MR::isMsgPlayerHipDrop(a1)) { MR::sendMsgAwayJump(pOtherSensor, pMySensor); } } else { var_f31 = 10.0f; var_f30 = -5.0f; MR::startSound(this, "SE_OJ_COCONUT_FLIP_S", -1, -1); } _8C = var_f31; _90 = var_f30; TVec3f stack_2C; TVec3f stack_20; TVec3f stack_14; stack_2C.sub(pMySensor->mPosition, pOtherSensor->mPosition); if (MR::isMsgStarPieceReflect(a1) && !MR::normalizeOrZero(stack_2C, &stack_20) && !MR::normalizeOrZero(pOtherSensor->mActor->mVelocity, &stack_14)) { if (stack_20.dot(stack_14) < MR::cosDegree(60.0f)) { TVec3f stack_8(stack_14); stack_8.scale(65.0f); stack_2C.add(stack_8); } } setFrontVec(stack_2C); if (MR::isMsgPlayerSpinAttack(a1)) { reviseFrontVec(); } setNerve(&NrvCocoNut::CocoNutNrvMove::sInstance); return true; } bool CocoNut::receiveOtherMsg(u32 arg0, HitSensor *pOtherSensor, HitSensor *pMySensor) { f32 temp_f0; f32 temp_f1; f32 var_f2; if (!isValidReceiveMsg(pMySensor)) { return false; } if (MR::isMsgPlayerKick(arg0)) { if (isValidPushedFromPlayer(pMySensor, pOtherSensor)) { TVec3f *playerVelocity = MR::getPlayerVelocity(); setFrontVec(*playerVelocity); temp_f1 = _94.dot(*playerVelocity); var_f2 = 18.0f; temp_f0 = 1.1f * temp_f1; _8C = MR::max(var_f2, temp_f0); _90 = -(0.7f * temp_f1); MR::startSound(this, "SE_OJ_COCONUT_FLIP_S", -1, -1); setNerve(&NrvCocoNut::CocoNutNrvMove::sInstance); return true; } } else { if (MR::isMsgHitmarkEmit(arg0)) { return true; } } return false; } void CocoNut::initMapToolInfo(const JMapInfoIter &rIter) { MR::initDefaultPos(this, rIter); MR::getJMapInfoArg0NoInit(rIter, &mSphericalShadow); MR::getJMapInfoArg1NoInit(rIter, &mRespawnWhenOutOfView); MR::getJMapInfoArg2NoInit(rIter, &mContinueRolling); _D0 = 55.0f * mScale.x; mSpawnPosition.set(mPosition); } void CocoNut::statusToHide() { mVelocity.zero(); MR::offBind(this); MR::offCalcGravity(this); MR::hideModel(this); MR::invalidateHitSensors(this); MR::clearHitSensors(this); MR::deleteEffectAll(this); } void CocoNut::emitEffectSpinHit(const HitSensor *pOtherSensor, const HitSensor *pMySensor) { TVec3f point; // point 70% of the way between pOtherSensor and pMySensor JMAVECLerp(pOtherSensor->mPosition.toCVec(), pMySensor->mPosition.toCVec(), point.toVec(), 0.7f); _108.mMtx[0][0] = 1.0f; _108.mMtx[1][0] = 0.0f; _108.mMtx[2][0] = 0.0f; _108.mMtx[0][1] = 0.0f; _108.mMtx[1][1] = 1.0f; _108.mMtx[2][1] = 0.0f; _108.mMtx[0][2] = 0.0f; _108.mMtx[1][3] = 0.0f; _108.mMtx[2][3] = 1.0f; _108.mMtx[0][3] = point.x; _108.mMtx[1][3] = point.y; _108.mMtx[2][3] = point.z; MR::emitEffect(this, "SpinHitMark"); } bool CocoNut::isContactWithOtherCocoNut() const { HitSensor *body = getSensor("body"); for (int i = 0; i < body->mSensorCount; i++) { HitSensor *sensor = body->mSensors[i]; if (body->mSensors[i]->isType(0x17)) { return true; } } return false; } void CocoNut::exeWait() { if (MR::isFirstStep(this)) { MR::deleteEffect(this, "RollingSmoke"); } if (!_D4) { if (tryDisappear()) { return; } updateGravity(); } if (!_D4 && MR::isOnGround(this)) { statusToWait(); } } void CocoNut::exeWaitOnBind() { if (MR::isFirstStep(this)) { MR::deleteEffect(this, "RollingSmoke"); } if (!tryDisappear()) { updateGravity(); if (MR::isOnGround(this)) { statusToWait(); } } } void CocoNut::exeMove() { if (MR::isFirstStep(this)) { _138 = 0; _14C = false; _150.zero(); MR::onBind(this); MR::onCalcGravity(this); MR::calcGravity(this); if (!_15E) { MR::invalidateClipping(this); } } if (_13C && MR::isStep(this, 1)) { MR::stopScene(4); } processMove(); tryMoveEnd(); } void CocoNut::exeInWater() { if (MR::isFirstStep(this)) { mVelocity.zero(); MR::offBind(this); MR::offCalcGravity(this); MR::hideModel(this); MR::invalidateHitSensors(this); MR::clearHitSensors(this); MR::deleteEffectAll(this); TVec3f gravityNegated; negateInternalInline(mGravity, &gravityNegated); MR::makeMtxUpNoSupportPos(&_D8, gravityNegated, mPosition); MR::emitEffect(this, "WaterColumn"); MR::startSound(this, "SE_OJ_FALL_IN_WATER_M", -1, -1); MR::releaseSoundHandle(this, "SE_OJ_FALL_IN_WATER_M"); } if (!MR::isEffectValid(this, "WaterColumn")) { mPosition.set(mSpawnPosition); setNerve(&NrvCocoNut::CocoNutNrvReplaceReady::sInstance); } } void CocoNut::exeBreak() { if (MR::isFirstStep(this)) { statusToHide(); TVec3f gravityNegated; negateInternalInline(mGravity, &gravityNegated); MR::makeMtxUpNoSupportPos(&_D8, gravityNegated, mPosition); MR::emitEffect(this, getBreakEffectName()); } if (!MR::isEffectValid(this, getBreakEffectName())) { mPosition.set(mSpawnPosition); setNerve(&NrvCocoNut::CocoNutNrvReplaceReady::sInstance); } } namespace NrvCocoNut { INIT_NERVE(CocoNutNrvWait) INIT_NERVE(CocoNutNrvWaitOnBind) INIT_NERVE(CocoNutNrvMove) INIT_NERVE(CocoNutNrvInWater) INIT_NERVE(CocoNutNrvBreak) INIT_NERVE(CocoNutNrvReplaceReady) void CocoNutNrvReplaceReady::execute(Spine *pSpine) const { CocoNut *nut = reinterpret_cast(pSpine->mExecutor); if (MR::isFirstStep(nut)) { MR::validateClipping(nut); } } void CocoNutNrvBreak::execute(Spine *pSpine) const { CocoNut *nut = reinterpret_cast(pSpine->mExecutor); nut->exeBreak(); } void CocoNutNrvInWater::execute(Spine *pSpine) const { CocoNut *nut = reinterpret_cast(pSpine->mExecutor); nut->exeInWater(); } void CocoNutNrvMove::execute(Spine *pSpine) const { CocoNut *nut = reinterpret_cast(pSpine->mExecutor); nut->exeMove(); } void CocoNutNrvWaitOnBind::execute(Spine *pSpine) const { CocoNut *nut = reinterpret_cast(pSpine->mExecutor); nut->exeWaitOnBind(); } void CocoNutNrvWait::execute(Spine *pSpine) const { CocoNut *nut = reinterpret_cast(pSpine->mExecutor); nut->exeWait(); } }