From 0f550e5aae2c34e675390478233b4f5c43ebada5 Mon Sep 17 00:00:00 2001 From: guymakinggames <66076221+guymakinggames@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:55:05 +0100 Subject: [PATCH] MapObj: Implement `RocketFlower` (#1024) --- data/file_list.yml | 50 +++---- src/MapObj/RocketFlower.cpp | 237 ++++++++++++++++++++++++++++++ src/MapObj/RocketFlower.h | 48 ++++++ src/MapObj/RocketFlowerFunction.h | 17 +++ src/Scene/ProjectActorFactory.cpp | 3 +- 5 files changed, 329 insertions(+), 26 deletions(-) create mode 100644 src/MapObj/RocketFlower.cpp create mode 100644 src/MapObj/RocketFlower.h create mode 100644 src/MapObj/RocketFlowerFunction.h diff --git a/data/file_list.yml b/data/file_list.yml index deb712fe..0e7a7041 100644 --- a/data/file_list.yml +++ b/data/file_list.yml @@ -82302,103 +82302,103 @@ MapObj/RocketFlower.o: - offset: 0x2fd628 size: 128 label: _ZN12RocketFlowerC2EPKc - status: NotDecompiled + status: Matching - offset: 0x2fd6a8 size: 140 label: _ZN12RocketFlowerC1EPKc - status: NotDecompiled + status: Matching - offset: 0x2fd734 size: 268 label: _ZN12RocketFlower4initERKN2al13ActorInitInfoE - status: NotDecompiled + status: Matching - offset: 0x2fd840 size: 28 label: _ZN12RocketFlower18initAfterPlacementEv - status: NotDecompiled + status: Matching - offset: 0x2fd85c size: 156 label: _ZN12RocketFlower12attackSensorEPN2al9HitSensorES2_ - status: NotDecompiled + status: Matching - offset: 0x2fd8f8 size: 84 label: _ZNK12RocketFlower13isEnableEquipEv - status: NotDecompiled + status: Matching - offset: 0x2fd94c size: 664 label: _ZN12RocketFlower10receiveMsgEPKN2al9SensorMsgEPNS0_9HitSensorES5_ - status: NotDecompiled + status: Matching - offset: 0x2fdbe4 size: 116 label: _ZN12RocketFlower15terminateFollowEv - status: NotDecompiled + status: Matching - offset: 0x2fdc58 size: 76 label: _ZN12RocketFlower7exeWaitEv - status: NotDecompiled + status: Matching - offset: 0x2fdca4 size: 100 label: _ZN12RocketFlower13exeWaitFollowEv - status: NotDecompiled + status: Matching - offset: 0x2fdd08 size: 412 label: _ZN12RocketFlower19trySyncFlyingCapPosEv - status: NotDecompiled + status: Matching - offset: 0x2fdea4 size: 324 label: _ZN12RocketFlower9exeFollowEv - status: NotDecompiled + status: Matching - offset: 0x2fdfe8 size: 108 label: _ZN12RocketFlower15appearFlowerSubEv - status: NotDecompiled + status: Matching - offset: 0x2fe054 size: 164 label: _ZN12RocketFlower13exeWaitAttachEv - status: NotDecompiled + status: Matching - offset: 0x2fe0f8 size: 60 label: _ZN12RocketFlower9exeAttachEv - status: NotDecompiled + status: Matching - offset: 0x2fe134 size: 48 label: _ZN12RocketFlower19setFollowFlowerPoseERKN4sead4QuatIfEERKNS0_7Vector3IfEE - status: NotDecompiled + status: Matching - offset: 0x2fe164 size: 16 label: _ZN12RocketFlower9disappearEv - status: NotDecompiled + status: Matching - offset: 0x2fe174 size: 124 label: _ZN12RocketFlower14disappearForceEv - status: NotDecompiled + status: Matching - offset: 0x2fe1f0 size: 104 label: _ZN12RocketFlower7controlEv - status: NotDecompiled + status: Matching - offset: 0x2fe258 size: 80 label: _ZNK12_GLOBAL__N_119RocketFlowerNrvWait7executeEPN2al11NerveKeeperE - status: NotDecompiled + status: Matching guess: true - offset: 0x2fe2a8 size: 64 label: _ZNK12_GLOBAL__N_121RocketFlowerNrvAttach7executeEPN2al11NerveKeeperE - status: NotDecompiled + status: Matching guess: true - offset: 0x2fe2e8 size: 104 label: _ZNK12_GLOBAL__N_125RocketFlowerNrvWaitFollow7executeEPN2al11NerveKeeperE - status: NotDecompiled + status: Matching guess: true - offset: 0x2fe350 size: 8 label: _ZNK12_GLOBAL__N_121RocketFlowerNrvFollow7executeEPN2al11NerveKeeperE - status: NotDecompiled + status: Matching guess: true - offset: 0x2fe358 size: 8 label: _ZNK12_GLOBAL__N_125RocketFlowerNrvWaitAttach7executeEPN2al11NerveKeeperE - status: NotDecompiled + status: Matching guess: true MapObj/RocketFlowerEquipWatcher.o: '.text': @@ -135906,7 +135906,7 @@ Scene/ProjectActorFactory.o: - offset: 0x4bd180 size: 52 label: _ZN2al19createActorFunctionI12RocketFlowerEEPNS_9LiveActorEPKc - status: NotDecompiled + status: Matching lazy: true - offset: 0x4bd1b4 size: 52 diff --git a/src/MapObj/RocketFlower.cpp b/src/MapObj/RocketFlower.cpp new file mode 100644 index 00000000..953d0722 --- /dev/null +++ b/src/MapObj/RocketFlower.cpp @@ -0,0 +1,237 @@ +#include "MapObj/RocketFlower.h" + +#include "Library/Collision/PartsConnectorUtil.h" +#include "Library/Effect/EffectSystemInfo.h" +#include "Library/LiveActor/ActorActionFunction.h" +#include "Library/LiveActor/ActorAnimFunction.h" +#include "Library/LiveActor/ActorClippingFunction.h" +#include "Library/LiveActor/ActorInitUtil.h" +#include "Library/LiveActor/ActorMovementFunction.h" +#include "Library/LiveActor/ActorPoseUtil.h" +#include "Library/LiveActor/ActorSensorUtil.h" +#include "Library/Math/MathUtil.h" +#include "Library/Nerve/NerveSetupUtil.h" +#include "Library/Nerve/NerveUtil.h" + +#include "MapObj/RocketFlowerFunction.h" +#include "Util/PlayerUtil.h" +#include "Util/SensorMsgFunction.h" + +namespace { +NERVE_IMPL(RocketFlower, Wait); +NERVE_IMPL(RocketFlower, Attach); +NERVE_IMPL(RocketFlower, WaitFollow); +NERVE_IMPL(RocketFlower, Follow); +NERVE_IMPL(RocketFlower, WaitAttach); + +NERVES_MAKE_STRUCT(RocketFlower, Wait, Attach, WaitFollow, Follow, WaitAttach); +} // namespace + +RocketFlower::RocketFlower(const char* name) : al::LiveActor(name) {} + +void RocketFlower::init(const al::ActorInitInfo& info) { + al::initActor(this, info); + al::initNerve(this, &NrvRocketFlower.Wait, 0); + makeActorAlive(); + + mMtxConnector = al::tryCreateMtxConnector(this, info); + + mFlowerSub = new al::LiveActor("ロケットフラワーの花"); + al::initChildActorWithArchiveNameNoPlacementInfo(mFlowerSub, info, "RocketFlowerDash", nullptr); + al::startAction(mFlowerSub, "Wait"); + mFlowerSub->makeActorDead(); + + RocketFlowerFunction::createRocketFlowerEquipWatcherIfNotExist(this, info); + al::setHitSensorPosPtr(this, "Equip", al::getTransPtr(mFlowerSub)); + al::startAction(this, "Wait"); +} + +void RocketFlower::initAfterPlacement() { + if (mMtxConnector) + al::attachMtxConnectorToCollision(mMtxConnector, this, false); +} + +void RocketFlower::attackSensor(al::HitSensor* self, al::HitSensor* other) { + if (!al::isSensorName(self, "Equip")) + return; + + if (isEnableEquip()) { + if (rs::sendMsgRocketFlowerExtension(other, self)) + al::setNerve(this, &NrvRocketFlower.Attach); + } +} + +bool RocketFlower::isEnableEquip() const { + if (al::isNerve(this, &NrvRocketFlower.Follow) && !al::isNewNerve(this)) + return true; + + return al::isNerve(this, &NrvRocketFlower.WaitAttach); +} + +bool RocketFlower::receiveMsg(const al::SensorMsg* message, al::HitSensor* other, + al::HitSensor* self) { + if (al::isMsgPlayerSpinAttack(message)) { + if (al::isNerve(this, &NrvRocketFlower.Wait)) { + al::invalidateClipping(this); + al::setNerve(this, &NrvRocketFlower.WaitFollow); + } + } else if (rs::isMsgCapItemGet(message)) { + if (al::isNerve(this, &NrvRocketFlower.Wait)) { + al::invalidateClipping(this); + al::setNerve(this, &NrvRocketFlower.Follow); + return true; + } + } else if (al::isMsgPlayerObjTouch(message) || rs::isMsgBlowObjAttack(message) || + rs::isMsgFireDamageAll(message) || rs::isMsgHammerBrosHammerEnemyAttack(message) || + rs::isMsgHammerBrosHammerHackAttack(message) || rs::isMsgHosuiAttack(message) || + al::isMsgEnemyAttack(message) || al::isMsgKickStoneAttack(message) || + rs::isMsgRadishAttack(message) || rs::isMsgSeedAttack(message) || + rs::isMsgTankBullet(message) || rs::isMsgGamaneBulletThrough(message) || + rs::isMsgHackAttackPoison(message) || rs::isMsgYoshiTongueAttack(message)) { + if (al::isSensorMapObj(self)) { + if (mReactionFrame == 0) + al::startAction(this, "Reaction"); + + mReactionFrame = 30; + } + } else if (rs::isMsgCapAttack(message)) { + if (al::isSensorMapObj(self) && !al::isNerve(this, &NrvRocketFlower.Wait) && + !al::isNerve(this, &NrvRocketFlower.WaitFollow)) { + if (mReactionFrame == 0) + al::startAction(this, "Reaction"); + + mReactionFrame = 30; + } + } else if (al::isMsgPlayerPutOnEquipment(message) && al::isSensorName(self, "Equip") && + isEnableEquip()) { + if (RocketFlowerFunction::requestEquipRocketFlower(this, other)) { + al::setNerve(this, &NrvRocketFlower.Attach); + return true; + } + + terminateFollow(); + } + + return false; +} + +void RocketFlower::terminateFollow() { + al::invalidateHitSensor(this, "Equip"); + al::validateClipping(this); + mFlowerSub->kill(); + al::startAction(this, "Appear"); + mReactionFrame = 30; + al::startHitReaction(this, "復帰"); + al::setNerve(this, &NrvRocketFlower.Wait); +} + +void RocketFlower::exeWait() { + if (al::isFirstStep(this)) { + al::startVisAnim(this, "Show"); + al::invalidateHitSensor(this, "Equip"); + } +} + +void RocketFlower::exeWaitFollow() { + if (trySyncFlyingCapPos()) { + al::setNerve(this, &NrvRocketFlower.Follow); + return; + } + + if (al::isGreaterEqualStep(this, 30)) { + al::validateClipping(this); + al::setNerve(this, &NrvRocketFlower.Wait); + } +} + +bool RocketFlower::trySyncFlyingCapPos() { + sead::Vector3f flyingCapPos; + if (!rs::tryGetFlyingCapPos(&flyingCapPos, this)) + return false; + + flyingCapPos += 50.0f * sead::Vector3f::ey; + al::resetPosition(mFlowerSub, flyingCapPos); + + sead::Vector3f toPlayerHead = rs::getPlayerHeadPos(this) - flyingCapPos; + if (al::tryNormalizeOrZero(&toPlayerHead)) { + sead::Quatf quat; + quat.makeVectorRotation(sead::Vector3f::ez, toPlayerHead); + al::setQuat(mFlowerSub, quat); + } + + return true; +} + +void RocketFlower::exeFollow() { + if (al::isFirstStep(this)) { + appearFlowerSub(); + mFollowLostFrame = 0; + } + + if (trySyncFlyingCapPos()) { + mFollowLostFrame = 0; + return; + } + + if (rs::isEquipCapCatched(this)) { + al::validateHitSensor(this, "Equip"); + al::setNerve(this, &NrvRocketFlower.WaitAttach); + return; + } + + if (mFollowLostFrame >= 16) + terminateFollow(); + + mFollowLostFrame++; +} + +void RocketFlower::appearFlowerSub() { + al::startVisAnim(this, "Hide"); + mFlowerSub->appear(); + al::startAction(mFlowerSub, "Wait"); + al::startAction(this, "ReactionCap"); + mReactionFrame = 30; + al::startHitReaction(this, "花が取れた"); +} + +void RocketFlower::exeWaitAttach() { + rs::tryCalcPlayerModelHeadJointPos(al::getTransPtr(mFlowerSub), this); + + if (al::isGreaterEqualStep(this, 20)) + terminateFollow(); +} + +void RocketFlower::exeAttach() { + if (al::isFirstStep(this)) + al::startAction(mFlowerSub, "Dash"); +} + +void RocketFlower::setFollowFlowerPose(const sead::Quatf& quat, const sead::Vector3f& trans) { + al::setQuat(mFlowerSub, quat); + al::resetPosition(mFlowerSub, trans); +} + +void RocketFlower::disappear() { + al::startHitReaction(mFlowerSub, "消滅"); +} + +void RocketFlower::disappearForce() { + al::tryKillEmitterAndParticleAll(mFlowerSub); + al::invalidateHitSensor(this, "Equip"); + al::validateClipping(this); + mFlowerSub->kill(); + al::startAction(this, "Wait"); + mReactionFrame = 30; + al::setNerve(this, &NrvRocketFlower.Wait); +} + +void RocketFlower::control() { + if (al::isActionOneTime(this) && al::isActionEnd(this)) + al::startAction(this, "Wait"); + + if (mReactionFrame != 0) + mReactionFrame--; + + if (mMtxConnector) + al::connectPoseQT(this, mMtxConnector); +} diff --git a/src/MapObj/RocketFlower.h b/src/MapObj/RocketFlower.h new file mode 100644 index 00000000..032ea743 --- /dev/null +++ b/src/MapObj/RocketFlower.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include "Library/LiveActor/LiveActor.h" + +namespace al { +struct ActorInitInfo; +class HitSensor; +class MtxConnector; +class SensorMsg; +} // namespace al + +class RocketFlower : public al::LiveActor { +public: + RocketFlower(const char* name); + + void init(const al::ActorInitInfo& info) override; + void initAfterPlacement() override; + void attackSensor(al::HitSensor* self, al::HitSensor* other) override; + bool receiveMsg(const al::SensorMsg* message, al::HitSensor* other, + al::HitSensor* self) override; + void control() override; + + bool isEnableEquip() const; + void terminateFollow(); + + void exeWait(); + void exeWaitFollow(); + bool trySyncFlyingCapPos(); + void exeFollow(); + void appearFlowerSub(); + void exeWaitAttach(); + void exeAttach(); + + void setFollowFlowerPose(const sead::Quatf& quat, const sead::Vector3f& trans); + void disappear(); + void disappearForce(); + +private: + al::MtxConnector* mMtxConnector = nullptr; + al::LiveActor* mFlowerSub = nullptr; + s32 mReactionFrame = 0; + u32 mFollowLostFrame = 0; +}; + +static_assert(sizeof(RocketFlower) == 0x120); diff --git a/src/MapObj/RocketFlowerFunction.h b/src/MapObj/RocketFlowerFunction.h new file mode 100644 index 00000000..10edf57a --- /dev/null +++ b/src/MapObj/RocketFlowerFunction.h @@ -0,0 +1,17 @@ +#pragma once + +namespace al { +struct ActorInitInfo; +class HitSensor; +class LiveActor; +} // namespace al + +class RocketFlower; + +namespace RocketFlowerFunction { + +void createRocketFlowerEquipWatcherIfNotExist(const al::LiveActor* actor, + const al::ActorInitInfo& info); +bool requestEquipRocketFlower(RocketFlower* flower, al::HitSensor* sensor); + +} // namespace RocketFlowerFunction diff --git a/src/Scene/ProjectActorFactory.cpp b/src/Scene/ProjectActorFactory.cpp index f6deb8ed..1bf74abc 100644 --- a/src/Scene/ProjectActorFactory.cpp +++ b/src/Scene/ProjectActorFactory.cpp @@ -95,6 +95,7 @@ #include "MapObj/PoleGrabCeil.h" #include "MapObj/ReactionMapParts.h" #include "MapObj/RiseMapPartsHolder.h" +#include "MapObj/RocketFlower.h" #include "MapObj/RouletteSwitch.h" #include "MapObj/SaveFlagCheckObj.h" #include "MapObj/ShineTowerRocket.h" @@ -672,7 +673,7 @@ const al::NameToCreator sProjectActorFactoryEntries[] {"RiseMapParts", nullptr}, {"ReactionMapParts", al::createActorFunction}, {"RiseMapPartsHolder", al::createActorFunction}, - {"RocketFlower", nullptr}, + {"RocketFlower", al::createActorFunction}, {"RollingCubeMapParts", al::createActorFunction}, {"RippleFixMapParts", nullptr}, {"RotateMapParts", al::createActorFunction},