diff --git a/MakefileNSO b/MakefileNSO index 8921842..1301843 100644 --- a/MakefileNSO +++ b/MakefileNSO @@ -36,6 +36,7 @@ SOURCES := \ source/sead/time \ source/sead \ source/puppets \ + source/speedboot \ source/server/hns \ source/server/sardines \ source/server/freeze-tag \ diff --git a/include/al/util.hpp b/include/al/util.hpp index c8a3481..f9daf3d 100644 --- a/include/al/util.hpp +++ b/include/al/util.hpp @@ -121,6 +121,7 @@ namespace al void setPaneLocalSize( al::IUseLayout* layout, const char* paneName, sead::Vector2f const&); void setPaneLocalScale( al::IUseLayout* layout, const char* paneName, sead::Vector2f const&); void setPaneLocalRotate(al::IUseLayout* layout, const char* paneName, sead::Vector3f const&); + void setPaneVtxColor(al::IUseLayout const* layout, char const* paneName, sead::Color4u8 const&); sead::Vector3f& getPaneLocalTrans(const al::IUseLayout* layout, const char* paneName); void getPaneLocalSize(sead::Vector2f*, const al::IUseLayout* layout, const char* paneName); diff --git a/include/game/WorldList/WorldResourceLoader.h b/include/game/WorldList/WorldResourceLoader.h index 4b0f3f1..9064ea6 100644 --- a/include/game/WorldList/WorldResourceLoader.h +++ b/include/game/WorldList/WorldResourceLoader.h @@ -19,17 +19,17 @@ class WorldResourceLoader : public al::HioNode { void requestLoadWorldResource(int); void createResourcePlayer(void); void tryDestroyWorldResourceOnlyCap(void); - void calcLoadPercent(void); + float calcLoadPercent(void) const; void getLoadWorldId(void); bool tryLoadResource(char const*, char const*, char const*); void loadWorldResource(int, int, bool, char const*); void calcWorldResourceHeapSize(void); al::AsyncFunctorThread* mResourceLoadThread; // 0x08 - sead::Heap* mWorldResHeap = nullptr; // 0x10 + sead::Heap* mSceneHeap = nullptr; // 0x10 sead::Heap* mCapHeap = nullptr; // 0x18 sead::Heap* mWaterfallHeap = nullptr; // 0x20 - int mCurWorld = -1; // 0x28 + int mCurLoadedWorldId = -1; // 0x28 int mCurScenario = -1; // 0x2C bool unkBool = true; // 0x30 bool mIsCancelLoad = true; // 0x31 diff --git a/include/speedboot/CustomBootNerve.hpp b/include/speedboot/CustomBootNerve.hpp new file mode 100644 index 0000000..fcf11e5 --- /dev/null +++ b/include/speedboot/CustomBootNerve.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "al/nerve/Nerve.h" +#include "al/nerve/NerveKeeper.h" +#include "al/util/NerveUtil.h" +#include "game/HakoniwaSequence/HakoniwaSequence.h" + +NERVE_HEADER(HakoniwaSequence, LoadStage); +NERVE_HEADER(HakoniwaSequence, LoadWorldResourceWithBoot); +NERVE_IMPL(HakoniwaSequence, LoadStage); +NERVE_IMPL(HakoniwaSequence, LoadWorldResourceWithBoot); + +namespace speedboot { + class CustomBootNerve : public al::Nerve { + public: + void execute(al::NerveKeeper* keeper) override { + if (al::updateNerveState(keeper->mParent)) { + al::setNerve(keeper->mParent, &nrvHakoniwaSequenceLoadStage); + } + } + }; +} diff --git a/include/speedboot/HakoniwaSequenceSpeedboot.hpp b/include/speedboot/HakoniwaSequenceSpeedboot.hpp new file mode 100644 index 0000000..ecb40ed --- /dev/null +++ b/include/speedboot/HakoniwaSequenceSpeedboot.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "al/nerve/NerveStateBase.h" +#include "al/util/NerveUtil.h" + +#include "game/GameData/GameDataFunction.h" +#include "game/HakoniwaSequence/HakoniwaSequence.h" + +namespace speedboot { + namespace { + NERVE_HEADER(HakoniwaSequenceSpeedboot, InitThread) + NERVE_HEADER(HakoniwaSequenceSpeedboot, LoadStage) + NERVE_HEADER(HakoniwaSequenceSpeedboot, WipeToKill) + } + + struct HakoniwaSequenceSpeedboot : public al::NerveStateBase { + public: + HakoniwaSequenceSpeedboot(HakoniwaSequence* sequence) : al::NerveStateBase("Speedboot"), mSequence(sequence) { + initNerve(&nrvHakoniwaSequenceSpeedbootLoadStage, 0); + } + + void exeInitThread() { + if (al::isFirstStep(this)) { + mSequence->mInitThread->start(); + } + + if (mSequence->mInitThread->isDone()) { + al::setNerve(this, &nrvHakoniwaSequenceSpeedbootLoadStage); + } + } + + bool isDoneLoading() const { + return mSequence->mResourceLoader->isEndLoadWorldResource() + && mSequence->mInitThread->isDone() + ; + } + + void exeLoadStage() { + if (al::isFirstStep(this)) { + mSequence->mInitThread->start(); + const char* name = GameDataFunction::getNextStageName(this->mSequence->mGameDataHolder); + if (name == nullptr) { + name = GameDataFunction::getMainStageName(this->mSequence->mGameDataHolder, 0); + } + int scenario = GameDataFunction::calcNextScenarioNo(this->mSequence->mGameDataHolder); + if (scenario == -1) { + scenario = 1; + } + int world = this->mSequence->mGameDataHolder.mData->mWorldList->tryFindWorldIndexByStageName(name); + if (world > -1) { + mSequence->mResourceLoader->requestLoadWorldHomeStageResource(world, scenario); + } + } + + if (isDoneLoading()) { + al::setNerve(this, &nrvHakoniwaSequenceSpeedbootWipeToKill); + } + } + + void exeWipeToKill() { + if (al::isFirstStep(this)) { + mSequence->mWipeHolder->startClose("FadeWhite", -1); + } + + if (mSequence->mWipeHolder->isCloseEnd()) { + kill(); + } + } + + private: + HakoniwaSequence* mSequence; + }; + + namespace { + NERVE_IMPL(HakoniwaSequenceSpeedboot, InitThread); + NERVE_IMPL(HakoniwaSequenceSpeedboot, LoadStage); + NERVE_IMPL(HakoniwaSequenceSpeedboot, WipeToKill); + } +} diff --git a/include/speedboot/SpeedbootLoad.hpp b/include/speedboot/SpeedbootLoad.hpp new file mode 100644 index 0000000..9a44f3f --- /dev/null +++ b/include/speedboot/SpeedbootLoad.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "al/layout/LayoutActor.h" +#include "al/layout/LayoutInitInfo.h" +#include "al/util/NerveUtil.h" + +#include "game/WorldList/WorldResourceLoader.h" + +#include "sead/math/seadVector.h" + +namespace speedboot { + class SpeedbootLoad : public al::LayoutActor { + public: + SpeedbootLoad( + WorldResourceLoader* resourceLoader, + const al::LayoutInitInfo& initInfo, + float autoCloseAfter + ); + + void exeAppear(); + void exeWait(); + void exeDecrease(); + void exeEnd(); + + float mTime = 0.f; + float mProgression = 0.f; + float mRotTime = 0.f; + + // Online logo part + sead::Vector2f mOnlineLogoTrans = sead::Vector2f::zero; + sead::Vector2f mOnlineLogoTransTarget = sead::Vector2f::zero; + float mOnlineLogoScale = 0.f; + float mOnlineLogoScaleTarget = 0.f; + float mOnlineCreditScale = 1.f; + float mOnlineCreditScaleTarget = 1.f; + + // Freze tag logo root + float mFreezeLogoTransX = 1000.f; + float mFreezeLogoTransXTarget = 1000.f; + + // Borders + sead::Vector2f mFreezeBorder = { 700.f, 420.f }; + sead::Vector2f mFreezeBorderTarget = { 700.f, 420.f }; + + // Backgrounds + float mFreezeBGTransX = 0.f; + float mFreezeBGTransXTarget = 0.f; + + private: + float mAutoCloseAfter = 0.f; + WorldResourceLoader* worldResourceLoader; + }; + + namespace { + NERVE_HEADER(SpeedbootLoad, Appear) + NERVE_HEADER(SpeedbootLoad, Wait) + NERVE_HEADER(SpeedbootLoad, Decrease) + NERVE_HEADER(SpeedbootLoad, End) + } +} diff --git a/patches/codehook.slpatch b/patches/codehook.slpatch index 9e099d2..22d2c33 100644 --- a/patches/codehook.slpatch +++ b/patches/codehook.slpatch @@ -132,3 +132,8 @@ B59E28 B seadPrintHook // sead::system::print 538A94 B freezePlayerHitPointDamage // Reimplements the damage function, disabling it's functionality in Freeze Tag 51B280 B freezeKidsMode // Forces kids mode to be enabled during Freeze Tag 1CFBA0 BL freezeMoonHitboxDisable // When mode enabled, disable hitboxes with moons to avoid softlocks + +// custom bootscreen hooks +50EF28 BL speedboot::hakoniwaSetNerveSetup +50EB88 ORR w2, wzr, #0x1f // nerve state count +50EB64 BL speedboot::prepareLayoutInitInfo diff --git a/romfs/LayoutData/SpeedbootLoad.szs b/romfs/LayoutData/SpeedbootLoad.szs new file mode 100644 index 0000000..406bed1 Binary files /dev/null and b/romfs/LayoutData/SpeedbootLoad.szs differ diff --git a/source/speedboot/BootHooks.cpp b/source/speedboot/BootHooks.cpp new file mode 100644 index 0000000..67d8c1f --- /dev/null +++ b/source/speedboot/BootHooks.cpp @@ -0,0 +1,40 @@ + +#include "al/layout/LayoutInitInfo.h" +#include "al/nerve/Nerve.h" +#include "al/util/NerveUtil.h" +#include "game/HakoniwaSequence/HakoniwaSequence.h" + +#include "speedboot/SpeedbootLoad.hpp" +#include "speedboot/CustomBootNerve.hpp" +#include "speedboot/HakoniwaSequenceSpeedboot.hpp" + +namespace speedboot { + CustomBootNerve nrvSpeedboot; + const bool speedbootAutoload = false; // set this to true, to automatically load the game, which skips the main menu (this has issues with empty save files) + + al::LayoutInitInfo copiedInitInfo; + + HakoniwaSequenceSpeedboot* deezNutsState; + + extern "C" void _ZN10BootLayoutC1ERKN2al14LayoutInitInfoE(BootLayout* layout, const al::LayoutInitInfo& layoutInitInfo); + + void prepareLayoutInitInfo(BootLayout* layout, const al::LayoutInitInfo& layoutInitInfo) { + register HakoniwaSequence* sequence asm("x19"); + new SpeedbootLoad( + sequence->mResourceLoader, + layoutInitInfo, + speedbootAutoload ? 0.f : 10.f + ); + _ZN10BootLayoutC1ERKN2al14LayoutInitInfoE(layout, layoutInitInfo); + } + + void hakoniwaSetNerveSetup(al::IUseNerve* useNerve, al::Nerve* nerve) { + if (!speedbootAutoload) { + return; + } + al::setNerve(useNerve, &nrvSpeedboot); + auto* sequence = static_cast(useNerve); + deezNutsState = new HakoniwaSequenceSpeedboot(sequence); + al::initNerveState(useNerve, deezNutsState, &nrvSpeedboot, "Speedboot"); + } +} diff --git a/source/speedboot/SpeedbootLoad.cpp b/source/speedboot/SpeedbootLoad.cpp new file mode 100644 index 0000000..20a7e8a --- /dev/null +++ b/source/speedboot/SpeedbootLoad.cpp @@ -0,0 +1,131 @@ +#include "speedboot/SpeedbootLoad.hpp" + +#include "al/util.hpp" +#include "al/util/LayoutUtil.h" +#include "al/util/LiveActorUtil.h" +#include "al/util/MathUtil.h" + +#include "sead/gfx/seadColor.h" +#include "sead/math/seadMathCalcCommon.h" +#include "sead/prim/seadSafeString.h" + +#include "server/DeltaTime.hpp" + +namespace speedboot { + SpeedbootLoad::SpeedbootLoad( + WorldResourceLoader* resourceLoader, + const al::LayoutInitInfo& initInfo, + float autoCloseAfter + ) : al::LayoutActor("SpeedbootLoad"), worldResourceLoader(resourceLoader), mAutoCloseAfter(autoCloseAfter) { + al::initLayoutActor(this, initInfo, "SpeedbootLoad", nullptr); + initNerve(&nrvSpeedbootLoadAppear, 0); + appear(); + } + + void SpeedbootLoad::exeAppear() { + if (al::isFirstStep(this)) { + al::startAction(this, "Appear", nullptr); + } + + if (al::isActionEnd(this, nullptr)) { + al::setNerve(this, &nrvSpeedbootLoadWait); + } + } + + void SpeedbootLoad::exeWait() { + if (al::isActionEnd(this, nullptr)) { + al::setNerve(this, &nrvSpeedbootLoadDecrease); + } + } + + void SpeedbootLoad::exeDecrease() { + mTime += 0.016666f; + + mProgression = ( + mAutoCloseAfter <= 0.1f + ? worldResourceLoader->calcLoadPercent() / 100.0f + : mTime / mAutoCloseAfter + ); + + float rotation = cosf(mTime) * 3; + + // Debug stuff + sead::WFormatFixedSafeString<0x40> string(u"Time: %.02f\nSine Value: %.02f", mTime, rotation); + al::setPaneString(this, "TxtDebug", string.cstr(), 0); + + if (mProgression < 1.f) { + // Target setup + if ( + (mAutoCloseAfter <= 0.1f && mTime < 7.f) + || (mAutoCloseAfter > 0.1f && mTime < (mAutoCloseAfter * 0.5f)) + ) { + mOnlineLogoTransTarget = { 0.f, 0.f }; + mOnlineLogoScaleTarget = 1.f; + mOnlineCreditScaleTarget = 1.f; + + mFreezeLogoTransXTarget = 1000.f; + + mFreezeBorderTarget = sead::Vector2f(700.f, 420.f); + + mFreezeBGTransXTarget = 0.f; + } else { + mOnlineLogoTransTarget = { -520.f, 260.f }; + mOnlineLogoScaleTarget = 0.3f; + mOnlineCreditScaleTarget = 0.f; + + mFreezeLogoTransXTarget = 0.f; + + mFreezeBorderTarget = sead::Vector2f(640.f, 360.f); + + mFreezeBGTransXTarget = -1280.f; + } + + // SMOO logo + mOnlineLogoTrans.x = al::lerpValue(mOnlineLogoTrans.x, mOnlineLogoTransTarget.x, 0.06f); + mOnlineLogoTrans.y = al::lerpValue(mOnlineLogoTrans.y, mOnlineLogoTransTarget.y, 0.06f); + mOnlineLogoScale = al::lerpValue(mOnlineLogoScale, mOnlineLogoScaleTarget, 0.06f); + mOnlineCreditScale = al::lerpValue(mOnlineCreditScale, mOnlineCreditScaleTarget, 0.06f); + al::setPaneLocalTrans(this, "PartOnlineLogo", mOnlineLogoTrans); + al::setPaneLocalScale(this, "PartOnlineLogo", { mOnlineLogoScale, mOnlineLogoScale }); + al::setPaneLocalScale(this, "PicCraftyBossCredit", { mOnlineCreditScale, mOnlineCreditScale }); + + // Freeze-Tag logo + mFreezeLogoTransX = al::lerpValue(mFreezeLogoTransX, mFreezeLogoTransXTarget, 0.04f); + al::setPaneLocalTrans(this, "FreezeLogoRoot", { mFreezeLogoTransX, 0.f }); + al::setPaneLocalRotate(this, "PicFreezeLogo", { 0.f, 0.f, rotation }); + + // Freeze borders + mFreezeBorder.x = al::lerpValue(mFreezeBorder.x, mFreezeBorderTarget.x, 0.02f); + mFreezeBorder.y = al::lerpValue(mFreezeBorder.y, mFreezeBorderTarget.y, 0.02f); + al::setPaneLocalTrans(this, "PicFreezeEdgeLeft", { -mFreezeBorder.x, 0.f }); + al::setPaneLocalTrans(this, "PicFreezeEdgeRight", { mFreezeBorder.x, 0.f }); + al::setPaneLocalTrans(this, "PicFreezeEdgeTop", { 0.f, mFreezeBorder.y }); + al::setPaneLocalTrans(this, "PicFreezeEdgeBot", { 0.f, -mFreezeBorder.y }); + + // Freeze BG + mFreezeBGTransX = al::lerpValue(mFreezeBGTransX, mFreezeBGTransXTarget, 0.04f); + al::setPaneLocalTrans(this, "BackgroundsRoot", { mFreezeBGTransX, 0.f }); + } + + if (mProgression >= 1.f) { + al::setNerve(this, &nrvSpeedbootLoadEnd); + } + } + + void SpeedbootLoad::exeEnd() { + if (al::isFirstStep(this)) { + al::startAction(this, "End", nullptr); + } + + if (al::isActionEnd(this, nullptr)) { + kill(); + } + } + + namespace { + NERVE_IMPL(SpeedbootLoad, Appear) + NERVE_IMPL(SpeedbootLoad, Wait) + NERVE_IMPL(SpeedbootLoad, Decrease) + NERVE_IMPL(SpeedbootLoad, End) + } +}