From 9b7e8541d9fc7e9dbee54dce5045b995ff0662d1 Mon Sep 17 00:00:00 2001 From: Caladius Date: Mon, 15 Dec 2025 21:52:52 -0500 Subject: [PATCH 1/4] Tree Movement Code --- soh/soh/Enhancements/Holiday/Fredomato.cpp | 41 ++++++++++++++-------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/soh/soh/Enhancements/Holiday/Fredomato.cpp b/soh/soh/Enhancements/Holiday/Fredomato.cpp index c29aeadaba3..8dac6732cad 100644 --- a/soh/soh/Enhancements/Holiday/Fredomato.cpp +++ b/soh/soh/Enhancements/Holiday/Fredomato.cpp @@ -409,22 +409,37 @@ void OnSceneInit() { SpawnCollectionPoint(); } -void TestEffect() { - //gSaveContext.health = 0; +float distanceToTree = 500.0f; +float treeMoveSpeed = 50.0f; + +void MoveTreeActors(void* treeActor) { + Actor* tree = (Actor*)treeActor; + CollisionPoly* outPoly; + s32 bgId; + f32 treePosY = tree->world.pos.y; + f32 floorY = treePosY; + + if (tree->xzDistToPlayer <= distanceToTree && tree->params < 11) { + // Snap to floor, or remove if over void + treePosY += 200.0f; + floorY = BgCheck_EntityRaycastFloor4(&gPlayState->colCtx, &outPoly, &bgId, tree, &tree->world.pos); + + if (floorY > BGCHECK_Y_MIN) { + tree->world.pos.y = floorY; + } - Vec3f pos = FindValidPos(2000.0f); - if (pos.y == 9999.0f) { - return; + tree->world.pos.x++; + tree->world.pos.z++; } - - Actor* treetest = - Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_ANA, pos.x, pos.y, pos.z, 0, 0, 0, 0, false); - midoGrottoInit = false; - DoorAna_SetupAction((DoorAna*)treetest, RandomGrotto_WaitOpen); - treetest->draw = RandomGrotto_Draw; } static void OnConfigurationChanged() { + // New Fred Ketchmas + COND_ID_HOOK(OnActorUpdate, ACTOR_EN_WOOD02, CVarGetInteger(CVAR("FredTest.Enabled"), 0), [](void* actorRef) { + MoveTreeActors(actorRef); + }); + + COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("FredsQuest.Enabled"), 0), OnSceneInit); COND_HOOK(OnPlayerUpdate, CVarGetInteger(CVAR("RandomTraps.Enabled"), 0), []() { @@ -448,9 +463,7 @@ static void OnConfigurationChanged() { } }); - COND_HOOK(OnPlayerUpdate, CVarGetInteger(CVAR("FredTest.Enabled"), 0), []() { - TestEffect(); - }); + } static void RegisterMenu() { From 69026c41cc2ef5aa7593153ccddeda2877cec977 Mon Sep 17 00:00:00 2001 From: Caladius Date: Tue, 16 Dec 2025 18:48:44 -0500 Subject: [PATCH 2/4] Movement Math --- soh/soh/Enhancements/Holiday/Fredomato.cpp | 96 +++++++++++++++++++--- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/soh/soh/Enhancements/Holiday/Fredomato.cpp b/soh/soh/Enhancements/Holiday/Fredomato.cpp index 8dac6732cad..5767ec9673f 100644 --- a/soh/soh/Enhancements/Holiday/Fredomato.cpp +++ b/soh/soh/Enhancements/Holiday/Fredomato.cpp @@ -410,29 +410,101 @@ void OnSceneInit() { } float distanceToTree = 500.0f; -float treeMoveSpeed = 50.0f; +float treeMoveSpeed = 1.0f; void MoveTreeActors(void* treeActor) { Actor* tree = (Actor*)treeActor; CollisionPoly* outPoly; s32 bgId; - f32 treePosY = tree->world.pos.y; - f32 floorY = treePosY; - if (tree->xzDistToPlayer <= distanceToTree && tree->params < 11) { - // Snap to floor, or remove if over void - treePosY += 200.0f; - floorY = BgCheck_EntityRaycastFloor4(&gPlayState->colCtx, &outPoly, &bgId, tree, &tree->world.pos); + f32 treePosY = tree->world.pos.y + 200.0f; + f32 floorY = BgCheck_EntityRaycastFloor4(&gPlayState->colCtx, &outPoly, &bgId, tree, &tree->world.pos); - if (floorY > BGCHECK_Y_MIN) { - tree->world.pos.y = floorY; - } + if (floorY > BGCHECK_Y_MIN) { + tree->world.pos.y = floorY; + } - tree->world.pos.x++; - tree->world.pos.z++; + if (tree->xzDistToPlayer > distanceToTree || tree->params >= 11) { + return; } + + /* ---- DESIRED FLEE DIRECTION ---- */ + Vec3f dir; + Player* player = GET_PLAYER(gPlayState); + + dir.x = tree->world.pos.x - player->actor.world.pos.x; + dir.z = tree->world.pos.z - player->actor.world.pos.z; + + dir.x += Rand_CenteredFloat(30.0f); + dir.z += Rand_CenteredFloat(30.0f); + + f32 len = sqrtf(dir.x * dir.x + dir.z * dir.z); + if (len < 0.01f) { + return; + } + + dir.x /= len; + dir.z /= len; + + /* ---- HORIZONTAL MOVE ATTEMPT ---- */ + Vec3f start = tree->world.pos; + Vec3f end = start; + + end.x += dir.x * treeMoveSpeed; + end.z += dir.z * treeMoveSpeed; + + Vec3f hitPos; + + /* ---- WALL CHECK ONLY (NO FLOORS!) ---- */ + if (BgCheck_EntityLineTest1(&gPlayState->colCtx, &start, &end, &hitPos, &outPoly, true, /* chkWall */ + false, /* chkFloor */ + false, /* chkCeil */ + false, &bgId)) { + + /* ---- IGNORE FLOORS / SLOPES ---- */ + if (fabsf(outPoly->normal.y) < 0.3f) { + + /* Back up slightly from wall */ + Vec3f normal = { outPoly->normal.x, 0.0f, outPoly->normal.z }; + + f32 nLen = sqrtf(normal.x * normal.x + normal.z * normal.z); + if (nLen > 0.01f) { + normal.x /= nLen; + normal.z /= nLen; + } + + /* Snap to impact point */ + end = hitPos; + end.x += normal.x * 2.0f; + end.z += normal.z * 2.0f; + + /* ---- SLIDE VECTOR ---- */ + f32 dot = dir.x * normal.x + dir.z * normal.z; + dir.x -= normal.x * dot; + dir.z -= normal.z * dot; + + len = sqrtf(dir.x * dir.x + dir.z * dir.z); + if (len > 0.01f) { + dir.x /= len; + dir.z /= len; + + /* Remaining slide distance */ + end.x += dir.x * treeMoveSpeed; + end.z += dir.z * treeMoveSpeed; + } + } else { + /* Hit floor-like poly — cancel horizontal move */ + end = start; + } + } + + /* ---- APPLY FINAL POSITION ---- */ + tree->world.pos.x = end.x; + tree->world.pos.z = end.z; } + + static void OnConfigurationChanged() { // New Fred Ketchmas COND_ID_HOOK(OnActorUpdate, ACTOR_EN_WOOD02, CVarGetInteger(CVAR("FredTest.Enabled"), 0), [](void* actorRef) { From db9201de85012e1657253cb82046cb77804707cf Mon Sep 17 00:00:00 2001 From: Caladius Date: Tue, 16 Dec 2025 18:50:01 -0500 Subject: [PATCH 3/4] clang --- soh/soh/Enhancements/Holiday/Fredomato.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/soh/soh/Enhancements/Holiday/Fredomato.cpp b/soh/soh/Enhancements/Holiday/Fredomato.cpp index 5767ec9673f..b63185212ee 100644 --- a/soh/soh/Enhancements/Holiday/Fredomato.cpp +++ b/soh/soh/Enhancements/Holiday/Fredomato.cpp @@ -503,14 +503,10 @@ void MoveTreeActors(void* treeActor) { tree->world.pos.z = end.z; } - - static void OnConfigurationChanged() { // New Fred Ketchmas - COND_ID_HOOK(OnActorUpdate, ACTOR_EN_WOOD02, CVarGetInteger(CVAR("FredTest.Enabled"), 0), [](void* actorRef) { - MoveTreeActors(actorRef); - }); - + COND_ID_HOOK(OnActorUpdate, ACTOR_EN_WOOD02, CVarGetInteger(CVAR("FredTest.Enabled"), 0), + [](void* actorRef) { MoveTreeActors(actorRef); }); COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("FredsQuest.Enabled"), 0), OnSceneInit); @@ -534,8 +530,6 @@ static void OnConfigurationChanged() { *should = false; } }); - - } static void RegisterMenu() { @@ -549,8 +543,7 @@ static void RegisterMenu() { SohGui::mSohMenu->AddWidget(path, "FredTest", WIDGET_CVAR_CHECKBOX) .CVar(CVAR("FredTest.Enabled")) - .Options(UIWidgets::CheckboxOptions().Tooltip( - "Aaaaaaaaah!")); + .Options(UIWidgets::CheckboxOptions().Tooltip("Aaaaaaaaah!")); SohGui::mSohMenu->AddWidget(path, "Trap Lifetime (Seconds)", WIDGET_CVAR_SLIDER_INT) .CVar(CVAR("RandomTraps.Lifetime")) From 0b0f63421fd1e9fdf3afdd4a4dc771783bc825b5 Mon Sep 17 00:00:00 2001 From: Caladius Date: Tue, 16 Dec 2025 20:52:47 -0500 Subject: [PATCH 4/4] Adds counter UI and Corral Functionality. --- soh/soh/Enhancements/Holiday/Fredomato.cpp | 83 ++++++++++++++++++- soh/soh/Enhancements/Holiday/Fredomato.h | 1 + .../Enhancements/TimeDisplay/TimeDisplay.cpp | 8 +- .../Enhancements/TimeDisplay/TimeDisplay.h | 1 + 4 files changed, 88 insertions(+), 5 deletions(-) diff --git a/soh/soh/Enhancements/Holiday/Fredomato.cpp b/soh/soh/Enhancements/Holiday/Fredomato.cpp index b63185212ee..e24e908a40a 100644 --- a/soh/soh/Enhancements/Holiday/Fredomato.cpp +++ b/soh/soh/Enhancements/Holiday/Fredomato.cpp @@ -11,6 +11,8 @@ #include "soh/Enhancements/custom-item/CustomItem.h" #include "soh/Enhancements/nametag.h" +#include "Fredomato.h" + #include "objects/gameplay_field_keep/gameplay_field_keep.h" #include "objects/gameplay_keep/gameplay_keep.h" #include "objects/object_md/object_md.h" @@ -22,9 +24,14 @@ extern "C" { #include "functions.h" #include "variables.h" + + extern PlayState* gPlayState; void DoorAna_SetupAction(DoorAna* doorAna, DoorAnaActionFunc actionFunc); void DoorAna_GrabPlayer(DoorAna* doorAna, PlayState* play); + +#include "overlays/actors/ovl_Bg_Mjin/z_bg_mjin.h" +s32 Object_Spawn(ObjectContext* objectCtx, s16 objectId); } extern GetItemEntry vanillaQueuedItemEntry; @@ -410,7 +417,35 @@ void OnSceneInit() { } float distanceToTree = 500.0f; -float treeMoveSpeed = 1.0f; +float treeMoveSpeed = 6.0f; +uint32_t corralledTrees = 0; +std::vector> treeSlots; + +std::vector> CreatePadGrid38(const Vec3f& center) { + std::vector> positions; + positions.reserve(38); + + const int cols = 6; + const int rows = 7; + const float spacing = 80.0f; // adjust as needed + + // Offset so grid is centered on the pad + const float startX = center.x - ((cols - 1) * spacing * 0.5f); + const float startZ = center.z - ((rows - 1) * spacing * 0.5f); + + for (int r = 0; r < rows && positions.size() < 38; ++r) { + for (int c = 0; c < cols && positions.size() < 38; ++c) { + Vec3f pos; + pos.x = startX + (c * spacing); + pos.y = center.y; + pos.z = startZ + (r * spacing); + + positions.push_back({ NULL, pos }); + } + } + + return positions; +} void MoveTreeActors(void* treeActor) { Actor* tree = (Actor*)treeActor; @@ -505,10 +540,49 @@ void MoveTreeActors(void* treeActor) { static void OnConfigurationChanged() { // New Fred Ketchmas - COND_ID_HOOK(OnActorUpdate, ACTOR_EN_WOOD02, CVarGetInteger(CVAR("FredTest.Enabled"), 0), - [](void* actorRef) { MoveTreeActors(actorRef); }); + COND_ID_HOOK(OnActorUpdate, ACTOR_EN_WOOD02, CVarGetInteger(CVAR("FredTest.Enabled"), 0), [](void* actorRef) { + Actor* treeActor = (Actor*)actorRef; + bool treeSlotted = false; + for (auto& tree : treeSlots) { + if (tree.first == treeActor) { + treeSlotted = true; + treeActor->world.pos.x = tree.second.x; + treeActor->world.pos.y = tree.second.y; + treeActor->world.pos.z = tree.second.z; + break; + } + } - COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("FredsQuest.Enabled"), 0), OnSceneInit); + if (!treeSlotted) { + MoveTreeActors(actorRef); + + Actor* warpActor = Actor_FindNearby(gPlayState, (Actor*)actorRef, ACTOR_BG_MJIN, ACTORCAT_BG, 45.0f); + if (warpActor != NULL) { + corralledTrees++; + + for (auto& slot : treeSlots) { + if (slot.first == NULL) { + slot.first = (Actor*)actorRef; + break; + } + } + + Audio_PlaySoundGeneral(NA_SE_SY_ATTENTION_ON, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + } + } + }); + COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("FredTest.Enabled"), 0), []() { + if (gPlayState->sceneNum == SCENE_HYRULE_FIELD) { + treeSlots = CreatePadGrid38({ 335.571f, -0.0f, 2677.854f }); + corralledTrees = 0; + Object_Spawn(&gPlayState->objectCtx, OBJECT_MJIN); + Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_BG_MJIN, 335.571f, -0.0f, 2677.854f, 0, 0, 0, 1, + false); + } + }); + + //COND_HOOK(OnSceneSpawnActors, CVarGetInteger(CVAR("FredsQuest.Enabled"), 0), OnSceneInit); COND_HOOK(OnPlayerUpdate, CVarGetInteger(CVAR("RandomTraps.Enabled"), 0), []() { if (rand() % CVarGetInteger(CVAR("RandomTraps.SpawnChance"), 400) == 0) { @@ -543,6 +617,7 @@ static void RegisterMenu() { SohGui::mSohMenu->AddWidget(path, "FredTest", WIDGET_CVAR_CHECKBOX) .CVar(CVAR("FredTest.Enabled")) + .Callback([](WidgetInfo& info) { OnConfigurationChanged(); }) .Options(UIWidgets::CheckboxOptions().Tooltip("Aaaaaaaaah!")); SohGui::mSohMenu->AddWidget(path, "Trap Lifetime (Seconds)", WIDGET_CVAR_SLIDER_INT) diff --git a/soh/soh/Enhancements/Holiday/Fredomato.h b/soh/soh/Enhancements/Holiday/Fredomato.h index 3479b3f1ecc..952bf6c1fcf 100644 --- a/soh/soh/Enhancements/Holiday/Fredomato.h +++ b/soh/soh/Enhancements/Holiday/Fredomato.h @@ -10,6 +10,7 @@ extern "C" { #endif bool HandleTreeBonk(Actor* actor); +extern uint32_t corralledTrees; #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp index 77ca5823880..c5cecdc3093 100644 --- a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp +++ b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.cpp @@ -42,7 +42,8 @@ const std::vector timeDisplayList = { { DISPLAY_TIME_OF_DAY, "Display Time of Day", CVAR_TIME_DISPLAY("Timers.TimeofDay") }, { DISPLAY_CONDITIONAL_TIMER, "Display Conditional Timer", CVAR_TIME_DISPLAY("Timers.HotWater") }, { DISPLAY_NAVI_TIMER, "Display Navi Timer", CVAR_TIME_DISPLAY("Timers.NaviTimer") }, - { DISPLAY_FRED_QUEST, "Display Fred's Quest", CVAR_TIME_DISPLAY("Timers.FredsQuest") } + { DISPLAY_FRED_QUEST, "Display Fred's Quest", CVAR_TIME_DISPLAY("Timers.FredsQuest") }, + { DISPLAY_TREE_CORRAL, "Display Tree Corral", CVAR_TIME_DISPLAY("Timers.TreeCorral") }, }; static std::vector activeTimers; @@ -140,6 +141,11 @@ static void TimeDisplayGetTimer(uint32_t timeID) { timeDisplayTime = std::to_string(FredsQuestWoodOnHand) + "/" + std::to_string(FredsQuestWoodCollected) + "/" + std::to_string(CVarGetInteger("gHoliday.Fredomato.FredsQuest.WoodNeeded", 300)); textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("ITEM_STICK"); + break; + case DISPLAY_TREE_CORRAL: + timeDisplayTime = std::to_string(corralledTrees); + textureDisplay = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName("ITEM_STICK"); + break; default: break; } diff --git a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h index cf1c71c1591..d979bad362f 100644 --- a/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h +++ b/soh/soh/Enhancements/TimeDisplay/TimeDisplay.h @@ -19,6 +19,7 @@ typedef enum TimerDisplay { DISPLAY_CONDITIONAL_TIMER, DISPLAY_NAVI_TIMER, DISPLAY_FRED_QUEST, + DISPLAY_TREE_CORRAL, } TimerDisplay; typedef enum NaviTimerValues {