From 633e51654b9a68e1ed5458edf598a249ae268ce9 Mon Sep 17 00:00:00 2001 From: Shishu the Dragon <183069616+ShishuTheDragon@users.noreply.github.com> Date: Wed, 2 Jul 2025 00:04:38 +1200 Subject: [PATCH] Enemy: Implement `Kuribo2D` (#664) --- data/file_list.yml | 52 +++---- src/Enemy/Kuribo2D.cpp | 251 ++++++++++++++++++++++++++++++ src/Enemy/Kuribo2D.h | 42 +++++ src/Scene/ProjectActorFactory.cpp | 3 +- tools/check-format.py | 5 +- 5 files changed, 325 insertions(+), 28 deletions(-) create mode 100644 src/Enemy/Kuribo2D.cpp create mode 100644 src/Enemy/Kuribo2D.h diff --git a/data/file_list.yml b/data/file_list.yml index 1aa4f367..5dac8f76 100644 --- a/data/file_list.yml +++ b/data/file_list.yml @@ -35638,112 +35638,112 @@ Enemy/Kuribo2D.o: - offset: 0x149b78 size: 188 label: _ZN8Kuribo2DC2EPKc - status: NotDecompiled + status: Matching - offset: 0x149c34 size: 184 label: _ZN8Kuribo2DC1EPKc - status: NotDecompiled + status: Matching - offset: 0x149cec size: 352 label: _ZN8Kuribo2D4initERKN2al13ActorInitInfoE - status: NotDecompiled + status: Matching - offset: 0x149e4c size: 112 label: _ZN8Kuribo2D9startWalkEv - status: NotDecompiled + status: Matching - offset: 0x149ebc size: 84 label: _ZN8Kuribo2D5resetEv - status: NotDecompiled + status: Matching - offset: 0x149f10 size: 228 label: _ZN8Kuribo2D12attackSensorEPN2al9HitSensorES2_ - status: NotDecompiled + status: Matching - offset: 0x149ff4 size: 360 label: _ZN8Kuribo2D10receiveMsgEPKN2al9SensorMsgEPNS0_9HitSensorES5_ - status: NotDecompiled + status: Matching - offset: 0x14a15c size: 76 label: _ZN8Kuribo2D7controlEv - status: NotDecompiled + status: Matching - offset: 0x14a1a8 size: 204 label: _ZN8Kuribo2D17appearByGeneratorERKN4sead7Vector3IfEES4_ - status: NotDecompiled + status: Matching - offset: 0x14a274 size: 16 label: _ZNK8Kuribo2D6isWaitEv - status: NotDecompiled + status: Matching - offset: 0x14a284 size: 60 label: _ZN8Kuribo2D7exeWaitEv - status: NotDecompiled + status: Matching - offset: 0x14a2c0 size: 196 label: _ZN8Kuribo2D20exeFallAfterGenerateEv - status: NotDecompiled + status: Matching - offset: 0x14a384 size: 508 label: _ZN8Kuribo2D7exeWalkEv - status: NotDecompiled + status: Matching - offset: 0x14a580 size: 112 label: _ZN8Kuribo2D12exePressDownEv - status: NotDecompiled + status: Matching - offset: 0x14a5f0 size: 228 label: _ZN8Kuribo2D11exeBlowDownEv - status: NotDecompiled + status: Matching - offset: 0x14a6d4 size: 8 label: _ZNK8Kuribo2D23getActorDimensionKeeperEv - status: NotDecompiled + status: Matching lazy: true - offset: 0x14a6dc size: 8 label: _ZThn264_NK8Kuribo2D23getActorDimensionKeeperEv - status: NotDecompiled + status: Matching lazy: true - offset: 0x14a6e4 size: 8 label: _ZNK12_GLOBAL__N_115Kuribo2DNrvWalk7executeEPN2al11NerveKeeperE - status: NotDecompiled + status: Matching guess: true - offset: 0x14a6ec size: 64 label: _ZNK12_GLOBAL__N_115Kuribo2DNrvWait7executeEPN2al11NerveKeeperE - status: NotDecompiled + status: Matching guess: true - offset: 0x14a72c size: 116 label: _ZNK12_GLOBAL__N_120Kuribo2DNrvPressDown7executeEPN2al11NerveKeeperE - status: NotDecompiled + status: Matching guess: true - offset: 0x14a7a0 size: 8 label: _ZNK12_GLOBAL__N_119Kuribo2DNrvBlowDown7executeEPN2al11NerveKeeperE - status: NotDecompiled + status: Matching guess: true - offset: 0x14a7a8 size: 8 label: _ZNK12_GLOBAL__N_128Kuribo2DNrvFallAfterGenerate7executeEPN2al11NerveKeeperE - status: NotDecompiled + status: Matching guess: true - offset: 0x14a7b0 size: 28 label: _ZNK2al10FunctorV0MIP8Kuribo2DMS1_FvvEEclEv - status: NotDecompiled + status: Matching lazy: true - offset: 0x14a7cc size: 76 label: _ZNK2al10FunctorV0MIP8Kuribo2DMS1_FvvEE5cloneEv - status: NotDecompiled + status: Matching lazy: true - offset: 0x14a818 size: 4 label: _ZN2al10FunctorV0MIP8Kuribo2DMS1_FvvEED0Ev - status: NotDecompiled + status: Matching lazy: true Enemy/KuriboGenerator2D.o: '.text': @@ -134704,7 +134704,7 @@ Scene/ProjectActorFactory.o: - offset: 0x4b93f0 size: 52 label: _ZN2al19createActorFunctionI8Kuribo2DEEPNS_9LiveActorEPKc - status: NotDecompiled + status: Matching lazy: true - offset: 0x4b9424 size: 52 diff --git a/src/Enemy/Kuribo2D.cpp b/src/Enemy/Kuribo2D.cpp new file mode 100644 index 00000000..c057636b --- /dev/null +++ b/src/Enemy/Kuribo2D.cpp @@ -0,0 +1,251 @@ +#include "Enemy/Kuribo2D.h" + +#include "Library/Item/ItemUtil.h" +#include "Library/LiveActor/ActorActionFunction.h" +#include "Library/LiveActor/ActorAreaFunction.h" +#include "Library/LiveActor/ActorClippingFunction.h" +#include "Library/LiveActor/ActorCollisionFunction.h" +#include "Library/LiveActor/ActorFlagFunction.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 "Library/Stage/StageSwitchUtil.h" +#include "Library/Thread/FunctorV0M.h" + +#include "Util/ActorDimensionUtil.h" +#include "Util/ItemUtil.h" +#include "Util/SensorMsgFunction.h" + +namespace { +NERVE_IMPL(Kuribo2D, Walk) +NERVE_IMPL(Kuribo2D, Wait) +NERVE_IMPL(Kuribo2D, PressDown) +NERVE_IMPL(Kuribo2D, BlowDown) +NERVE_IMPL(Kuribo2D, FallAfterGenerate) + +NERVES_MAKE_STRUCT(Kuribo2D, Walk, Wait, PressDown, BlowDown, FallAfterGenerate) +} // namespace + +Kuribo2D::Kuribo2D(const char* name) : al::LiveActor(name) {} + +void Kuribo2D::init(const al::ActorInitInfo& initInfo) { + using Kuribo2DFunctor = al::FunctorV0M; + + al::initActorWithArchiveName(this, initInfo, "Kuribo2D", nullptr); + al::initNerve(this, &NrvKuribo2D.Walk, 0); + rs::createAndSetFilter2DOnly(this); + + mDimensionKeeper = rs::createDimensionKeeper(this); + rs::updateDimensionKeeper(mDimensionKeeper); + if (!rs::isIn2DArea(this)) { + makeActorDead(); + return; + } + + rs::snap2D(this, this, 500.0f); + + if (al::listenStageSwitchOnStart(this, Kuribo2DFunctor(this, &Kuribo2D::startWalk))) + al::setNerve(this, &NrvKuribo2D.Wait); + + mInitTrans.set(al::getTrans(this)); + mInitFront.set(al::getFront(this)); + al::listenStageSwitchOn(this, "SwitchReset", Kuribo2DFunctor(this, &Kuribo2D::reset)); + makeActorAlive(); +} + +void Kuribo2D::startWalk() { + al::onCollide(this); + al::validateClipping(this); + rs::updateDimensionKeeper(mDimensionKeeper); + rs::snap2D(this, this, 500.0f); + if (al::isOnGround(this, 0)) + al::setNerve(this, &NrvKuribo2D.Walk); + else + al::setNerve(this, &NrvKuribo2D.FallAfterGenerate); +} + +void Kuribo2D::reset() { + al::setTrans(this, mInitTrans); + al::setFront(this, mInitFront); + al::onCollide(this); + appear(); + al::setNerve(this, &NrvKuribo2D.Walk); +} + +void Kuribo2D::attackSensor(al::HitSensor* self, al::HitSensor* other) { + if (al::isNerve(this, &NrvKuribo2D.PressDown) || al::isNerve(this, &NrvKuribo2D.BlowDown)) + return; + + if (al::isSensorEnemyBody(self) && al::isSensorEnemyBody(other) && + rs::sendMsgPush2D(other, self)) { + if (al::isFaceToTargetDegreeH(this, al::getSensorPos(other), al::getFront(this), 5.0f)) { + al::turnFront(this, 180.0f); + al::setVelocityToFront(this, 2.5f); + } + } + + if (al::isSensorEnemyAttack(self)) + rs::sendMsgEnemyAttack2D(other, self); +} + +bool Kuribo2D::receiveMsg(const al::SensorMsg* message, al::HitSensor* other, al::HitSensor* self) { + if (al::isMsgPlayerDisregard(message)) + return true; + + if (al::isNerve(this, &NrvKuribo2D.PressDown) || al::isNerve(this, &NrvKuribo2D.BlowDown)) + return false; + + if (rs::isMsgBlockUpperPunch2D(message)) + return receiveDefeatMsg(message, other, self, &NrvKuribo2D.BlowDown); + + if (rs::isMsgPush2D(message)) { + if (al::isFaceToTargetDegreeH(this, al::getSensorPos(other), al::getFront(this), 5.0f)) { + al::turnFront(this, 180.0f); + al::addVelocityToFront(this, 5.0f); + } + return true; + } + + if (rs::isMsgPlayerTrample2D(message)) + return receiveDefeatMsg(message, other, self, &NrvKuribo2D.PressDown); + + if (rs::isMsgKouraAttack2D(message)) + return receiveDefeatMsg(message, other, self, &NrvKuribo2D.BlowDown); + + return false; +} + +void Kuribo2D::control() { + if (al::isInDeathArea(this)) { + al::startHitReaction(this, "消滅"); + kill(); + } +} + +void Kuribo2D::appearByGenerator(const sead::Vector3f& trans, const sead::Vector3f& front) { + al::setTrans(this, trans); + al::setFront(this, front); + rs::updateDimensionKeeper(mDimensionKeeper); + if (!rs::isIn2DArea(this)) { + kill(); + return; + } + + sead::Vector3f dimensionGravity; + rs::calcDimensionGravity(&dimensionGravity, this, al::getGravity(this)); + al::setGravity(this, dimensionGravity); + al::setVelocityZero(this); + al::offCollide(this); + al::invalidateHitSensors(this); + appear(); + al::setNerve(this, &NrvKuribo2D.Wait); +} + +bool Kuribo2D::isWait() const { + return al::isNerve(this, &NrvKuribo2D.Wait); +} + +void Kuribo2D::exeWalk() { + if (al::isFirstStep(this)) { + al::startAction(this, "Walk"); + al::setVelocityToFront(this, 2.5f); + } + + rs::updateDimensionKeeper(mDimensionKeeper); + if (!rs::isIn2DArea(this)) { + kill(); + return; + } + + rs::snap2D(this, this, 500.0f); + + if (al::isCollidedWall(this)) { + const sead::Vector3f& wallNormal = al::getCollidedWallNormal(this); + if (wallNormal.dot(al::getFront(this)) < 0.1f) { + al::turnFront(this, 180.0f); + al::addVelocityToFront(this, 5.0f); + updateCollider(); + } + } + + al::addVelocityToGravity(this, 0.65f); + al::scaleVelocityDirection(this, al::getGravity(this), 0.98f); + al::reboundVelocityFromCollision(this, 0.0f, 0.0f, 1.0f); + + sead::Vector3f horizVel = al::getVelocity(this); + al::verticalizeVec(&horizVel, al::getGravity(this), horizVel); + f32 horizSpeed = horizVel.length(); + if (horizSpeed != 0.0f && !al::isNear(horizSpeed, 2.5f)) { + if (al::tryNormalizeOrZero(&horizVel)) + al::scaleVelocityDirection(this, horizVel, 2.5f / horizSpeed); + } +} + +void Kuribo2D::exeWait() { + if (al::isFirstStep(this)) + al::startAction(this, "Wait"); +} + +void Kuribo2D::exePressDown() { + if (al::isFirstStep(this)) { + al::startAction(this, "PressDown"); + al::setVelocityZero(this); + } + + if (al::isActionEnd(this)) { + al::startHitReaction(this, "消滅"); + kill(); + } +} + +void Kuribo2D::exeBlowDown() { + sead::Vector3f dimensionGravity; + rs::calcDimensionGravity(&dimensionGravity, this, al::getGravity(this)); + + if (al::isFirstStep(this)) { + al::startAction(this, "Down"); + al::offCollide(this); + al::invalidateClipping(this); + al::addVelocityToDirection(this, -dimensionGravity, 15.6f); + } + + al::addVelocityToDirection(this, dimensionGravity, 0.73f); + al::scaleVelocity(this, 0.98f); + + if (al::isGreaterEqualStep(this, 270)) { + al::onCollide(this); + kill(); + } +} + +void Kuribo2D::exeFallAfterGenerate() { + rs::updateDimensionKeeper(mDimensionKeeper); + if (!rs::isIn2DArea(this)) { + kill(); + return; + } + + rs::snap2D(this, this, 500.0f); + + if (al::isOnGround(this, 0)) { + al::setNerve(this, &NrvKuribo2D.Walk); + return; + } + + al::addVelocityToGravity(this, 0.65f); + al::scaleVelocityDirection(this, al::getGravity(this), 0.98f); + al::reboundVelocityFromCollision(this, 0.0f, 0.0f, 1.0f); +} + +bool Kuribo2D::receiveDefeatMsg(const al::SensorMsg* message, al::HitSensor* other, + al::HitSensor* self, al::Nerve* nextNerve) { + rs::setAppearItemFactorAndOffsetByMsg(this, message, other); + rs::requestHitReactionToAttacker(message, self, other); + al::appearItem(this); + al::setNerve(this, nextNerve); + return true; +} diff --git a/src/Enemy/Kuribo2D.h b/src/Enemy/Kuribo2D.h new file mode 100644 index 00000000..c4a2fb42 --- /dev/null +++ b/src/Enemy/Kuribo2D.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Library/LiveActor/LiveActor.h" + +#include "Util/IUseDimension.h" + +namespace al { +class Nerve; +} + +class Kuribo2D : public al::LiveActor, public IUseDimension { +public: + Kuribo2D(const char* name); + + void init(const al::ActorInitInfo& initInfo) override; + void startWalk(); + void reset(); + 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; + void appearByGenerator(const sead::Vector3f& trans, const sead::Vector3f& front); + bool isWait() const; + + void exeWalk(); + void exeWait(); + void exePressDown(); + void exeBlowDown(); + void exeFallAfterGenerate(); + + ActorDimensionKeeper* getActorDimensionKeeper() const override { return mDimensionKeeper; } + +private: + ActorDimensionKeeper* mDimensionKeeper = nullptr; + sead::Vector3f mInitTrans = sead::Vector3f::zero; + sead::Vector3f mInitFront = sead::Vector3f::zero; + + inline bool receiveDefeatMsg(const al::SensorMsg* message, al::HitSensor* other, + al::HitSensor* self, al::Nerve* nextNerve); +}; + +static_assert(sizeof(Kuribo2D) == 0x130); diff --git a/src/Scene/ProjectActorFactory.cpp b/src/Scene/ProjectActorFactory.cpp index db208d3a..c7d2a033 100644 --- a/src/Scene/ProjectActorFactory.cpp +++ b/src/Scene/ProjectActorFactory.cpp @@ -32,6 +32,7 @@ #include "Boss/Mofumofu/MofumofuScrap.h" #include "Enemy/Gamane.h" #include "Enemy/KaronWing.h" +#include "Enemy/Kuribo2D.h" #include "Enemy/Mummy.h" #include "Enemy/Nokonoko2D.h" #include "Enemy/Pecho.h" @@ -345,7 +346,7 @@ const al::NameToCreator sProjectActorFactoryEntries[] {"KoopaLv2", nullptr}, {"KoopaLv3", nullptr}, {"KoopaShip", nullptr}, - {"Kuribo2D3D", nullptr}, + {"Kuribo2D3D", al::createActorFunction}, {"KuriboGenerator2D3D", nullptr}, {"KuriboGirl", al::createActorFunction}, {"KuriboPossessed", nullptr}, diff --git a/tools/check-format.py b/tools/check-format.py index 35e311e2..cda1ce9b 100755 --- a/tools/check-format.py +++ b/tools/check-format.py @@ -414,10 +414,11 @@ def header_check_line(line, path, visibility, should_start_class, is_in_struct): "Functions ending with an underscore are either protected or private!", path) elif visibility == 2: # private if line == "};" or line == "" or line == "union {" or line.startswith("struct") or line.startswith("enum"): return - if "(" in line and ")" in line: return newline = line if "=" in line: newline = line.split("=")[0].strip() + elif "(" in line or ")" in line: + return elif line.endswith(";"): newline = line.split(";")[0].strip() else: @@ -571,6 +572,8 @@ def main(): for dir in [project_root/'lib'/'al', project_root/'src']: for root, _, files in os.walk(dir): for file in files: + if os.path.basename(file) == ".DS_Store": + continue file_path = os.path.join(root, file) file_str = str(file_path) if args.run_clang_format: