From 4aebdab43d90015a9e9da68d479ab43cfcfe4b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 19 Nov 2025 15:56:21 +0000 Subject: [PATCH 01/15] refactor gtg (#5662) --- .../dungeons/gerudo_training_ground.cpp | 188 +++++++++++++----- .../Enhancements/randomizer/randomizerTypes.h | 24 ++- 2 files changed, 154 insertions(+), 58 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/gerudo_training_ground.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/gerudo_training_ground.cpp index 6d75dffb6..04b9e5474 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/gerudo_training_ground.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/gerudo_training_ground.cpp @@ -20,19 +20,29 @@ void RegionTable_Init_GerudoTrainingGround() { //Locations LOCATION(RC_GERUDO_TRAINING_GROUND_LOBBY_LEFT_CHEST, logic->CanHitEyeTargets()), LOCATION(RC_GERUDO_TRAINING_GROUND_LOBBY_RIGHT_CHEST, logic->CanHitEyeTargets()), - LOCATION(RC_GERUDO_TRAINING_GROUND_STALFOS_CHEST, logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2, true)), - LOCATION(RC_GERUDO_TRAINING_GROUND_BEAMOS_CHEST, logic->CanKillEnemy(RE_BEAMOS) && logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2, true)), LOCATION(RC_GERUDO_TRAINING_GROUND_ENTRANCE_STORMS_FAIRY, logic->CanUse(RG_SONG_OF_STORMS)), - LOCATION(RC_GERUDO_TRAINING_GROUND_BEAMOS_SOUTH_HEART, true), - LOCATION(RC_GERUDO_TRAINING_GROUND_BEAMOS_EAST_HEART, true), }, { //Exits Entrance(RR_GERUDO_TRAINING_GROUND_ENTRYWAY, []{return true;}), - Entrance(RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM, []{return logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2, true) && (logic->CanUse(RG_HOOKSHOT) || ctx->GetTrickOption(RT_GTG_WITHOUT_HOOKSHOT));}), - Entrance(RR_GERUDO_TRAINING_GROUND_LAVA_ROOM, []{return Here(RR_GERUDO_TRAINING_GROUND_LOBBY, []{return logic->CanKillEnemy(RE_BEAMOS) && logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2, true);});}), + Entrance(RR_GERUDO_TRAINING_GROUND_SAND_ROOM, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_DINALFOS, []{return true;}), Entrance(RR_GERUDO_TRAINING_GROUND_CENTRAL_MAZE, []{return true;}), }); + areaTable[RR_GERUDO_TRAINING_GROUND_SAND_ROOM] = Region("Gerudo Training Ground Sand Room", SCENE_GERUDO_TRAINING_GROUND, {}, { + //Locations + LOCATION(RC_GERUDO_TRAINING_GROUND_STALFOS_CHEST, logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2, true)), + }, { + //Exits + Entrance(RR_GERUDO_TRAINING_GROUND_LOBBY, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_BOULDER_ROOM, []{return Here(RR_GERUDO_TRAINING_GROUND_SAND_ROOM, []{return logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2, true);});}), + }); + + areaTable[RR_GERUDO_TRAINING_GROUND_BOULDER_ROOM] = Region("Gerudo Training Ground Boulder Room", SCENE_GERUDO_TRAINING_GROUND, {}, {}, { + Entrance(RR_GERUDO_TRAINING_GROUND_SAND_ROOM, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM, []{return Here(RR_GERUDO_TRAINING_GROUND_BOULDER_ROOM, []{return logic->CanUse(logic->IsAdult ? RG_HOOKSHOT : RG_LONGSHOT) || ctx->GetTrickOption(RT_GTG_WITHOUT_HOOKSHOT);});}), + }); + areaTable[RR_GERUDO_TRAINING_GROUND_CENTRAL_MAZE] = Region("Gerudo Training Ground Central Maze", SCENE_GERUDO_TRAINING_GROUND, {}, { //Locations LOCATION(RC_GERUDO_TRAINING_GROUND_HIDDEN_CEILING_CHEST, logic->SmallKeys(SCENE_GERUDO_TRAINING_GROUND, 3) && (ctx->GetTrickOption(RT_LENS_GTG) || logic->CanUse(RG_LENS_OF_TRUTH))), @@ -42,6 +52,7 @@ void RegionTable_Init_GerudoTrainingGround() { LOCATION(RC_GERUDO_TRAINING_GROUND_MAZE_PATH_FINAL_CHEST, logic->SmallKeys(SCENE_GERUDO_TRAINING_GROUND, 9)), }, { //Exits + Entrance(RR_GERUDO_TRAINING_GROUND_LOBBY, []{return true;}), Entrance(RR_GERUDO_TRAINING_GROUND_CENTRAL_MAZE_RIGHT, []{return logic->SmallKeys(SCENE_GERUDO_TRAINING_GROUND, 9);}), }); @@ -52,17 +63,74 @@ void RegionTable_Init_GerudoTrainingGround() { LOCATION(RC_GERUDO_TRAINING_GROUND_FREESTANDING_KEY, true), }, { //Exits - Entrance(RR_GERUDO_TRAINING_GROUND_HAMMER_ROOM, []{return logic->CanUse(RG_HOOKSHOT);}), - Entrance(RR_GERUDO_TRAINING_GROUND_LAVA_ROOM, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_LAVA_ROOM, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_LAVA_ROOM_UPPER_LEDGE, []{return logic->CanUse(RG_HOOKSHOT);}), + Entrance(RR_GERUDO_TRAINING_GROUND_CENTRAL_MAZE, []{return logic->SmallKeys(SCENE_GERUDO_TRAINING_GROUND, 9);}), }); - areaTable[RR_GERUDO_TRAINING_GROUND_LAVA_ROOM] = Region("Gerudo Training Ground Lava Room", SCENE_GERUDO_TRAINING_GROUND, {}, { + areaTable[RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM] = Region("Gerudo Training Ground Heavy Block Room", SCENE_GERUDO_TRAINING_GROUND, { + //Events + EventAccess(LOGIC_GTG_PUSHED_HEAVY_BLOCK, []{return logic->CanUse(RG_SILVER_GAUNTLETS);}), + }, { //Locations - LOCATION(RC_GERUDO_TRAINING_GROUND_UNDERWATER_SILVER_RUPEE_CHEST, logic->CanUse(RG_HOOKSHOT) && logic->CanUse(RG_SONG_OF_TIME) && logic->CanUse(RG_IRON_BOOTS) && logic->WaterTimer() >= 24), + LOCATION(RC_GERUDO_TRAINING_GROUND_BEFORE_HEAVY_BLOCK_CHEST, logic->CanKillEnemy(RE_WOLFOS, ED_CLOSE, true, 4, true)), }, { //Exits - Entrance(RR_GERUDO_TRAINING_GROUND_CENTRAL_MAZE_RIGHT, []{return logic->CanUse(RG_SONG_OF_TIME) || logic->IsChild;}), - Entrance(RR_GERUDO_TRAINING_GROUND_HAMMER_ROOM, []{return logic->CanUse(RG_LONGSHOT) || (logic->CanUse(RG_HOVER_BOOTS) && logic->CanUse(RG_HOOKSHOT));}), + Entrance(RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM_UPPER, []{return (ctx->GetTrickOption(RT_LENS_GTG) || logic->CanUse(RG_LENS_OF_TRUTH)) && (logic->CanUse(RG_HOOKSHOT) || (ctx->GetTrickOption(RT_GTG_FAKE_WALL) && logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS)) || (logic->IsAdult && logic->CanGroundJump()));}), + Entrance(RR_GERUDO_TRAINING_GROUND_BEHIND_HEAVY_BLOCK, []{return logic->Get(LOGIC_GTG_PUSHED_HEAVY_BLOCK);}), + Entrance(RR_GERUDO_TRAINING_GROUND_BOULDER_ROOM, []{return true;}), + }); + + areaTable[RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM_UPPER] = Region("Gerudo Training Ground Heavy Block Room Upper", SCENE_GERUDO_TRAINING_GROUND, { + //Events + EventAccess(LOGIC_GTG_UNLOCKED_DOOR_BEHIND_HEAVY_BLOCK, []{return true;}), + }, {}, { + //Exits + Entrance(RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM, []{return ctx->GetTrickOption(RT_LENS_GTG) || logic->CanUse(RG_LENS_OF_TRUTH);}), + Entrance(RR_GERUDO_TRAINING_GROUND_EYE_STATUE_UPPER, []{return true;}), + }); + + areaTable[RR_GERUDO_TRAINING_GROUND_BEHIND_HEAVY_BLOCK] = Region("Gerudo Training Ground Behind Heavy Block", SCENE_GERUDO_TRAINING_GROUND, {}, {}, { + //Exits + Entrance(RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM, []{return logic->Get(LOGIC_GTG_PUSHED_HEAVY_BLOCK);}), + Entrance(RR_GERUDO_TRAINING_GROUND_LIKE_LIKE_ROOM, []{return logic->Get(LOGIC_GTG_UNLOCKED_DOOR_BEHIND_HEAVY_BLOCK);}), + }); + + areaTable[RR_GERUDO_TRAINING_GROUND_LIKE_LIKE_ROOM] = Region("Gerudo Training Ground Like Like Room", SCENE_GERUDO_TRAINING_GROUND, {}, { + //Locations + LOCATION(RC_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_FIRST_CHEST, logic->CanKillEnemy(RE_LIKE_LIKE)), + LOCATION(RC_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_SECOND_CHEST, logic->CanKillEnemy(RE_LIKE_LIKE)), + LOCATION(RC_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_THIRD_CHEST, (ctx->GetTrickOption(RT_LENS_GTG) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->CanPassEnemy(RE_LIKE_LIKE)), + LOCATION(RC_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_FOURTH_CHEST, true), + }, { + //Exits + Entrance(RR_GERUDO_TRAINING_GROUND_BEHIND_HEAVY_BLOCK, []{return true;}), + }); + + areaTable[RR_GERUDO_TRAINING_GROUND_EYE_STATUE_UPPER] = Region("Gerudo Training Ground Eye Statue Upper", SCENE_GERUDO_TRAINING_GROUND, {}, {}, { + //Exits + Entrance(RR_GERUDO_TRAINING_GROUND_EYE_STATUE_LOWER, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM_UPPER, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_ABOVE_MAZE, []{return logic->Get(LOGIC_GTG_CLEARED_EYE_STATUE);}), + }); + + areaTable[RR_GERUDO_TRAINING_GROUND_ABOVE_MAZE] = Region("Gerudo Training Ground Above Eye", SCENE_GERUDO_TRAINING_GROUND, {}, { + //Locations + LOCATION(RC_GERUDO_TRAINING_GROUND_NEAR_SCARECROW_CHEST, true), + }, { + //Exits + Entrance(RR_GERUDO_TRAINING_GROUND_EYE_STATUE_UPPER, []{return true;}), + }); + + areaTable[RR_GERUDO_TRAINING_GROUND_EYE_STATUE_LOWER] = Region("Gerudo Training Ground Eye Statue Lower", SCENE_GERUDO_TRAINING_GROUND, { + //Events + EventAccess(LOGIC_GTG_CLEARED_EYE_STATUE, []{return logic->CanUse(RG_FAIRY_BOW);}), + }, { + //Locations + LOCATION(RC_GERUDO_TRAINING_GROUND_EYE_STATUE_CHEST, logic->Get(LOGIC_GTG_CLEARED_EYE_STATUE)), + }, { + //Exits + Entrance(RR_GERUDO_TRAINING_GROUND_HAMMER_ROOM, []{return true;}), }); areaTable[RR_GERUDO_TRAINING_GROUND_HAMMER_ROOM] = Region("Gerudo Training Ground Hammer Room", SCENE_GERUDO_TRAINING_GROUND, {}, { @@ -75,39 +143,43 @@ void RegionTable_Init_GerudoTrainingGround() { Entrance(RR_GERUDO_TRAINING_GROUND_LAVA_ROOM, []{return true;}), }); - areaTable[RR_GERUDO_TRAINING_GROUND_EYE_STATUE_LOWER] = Region("Gerudo Training Ground Eye Statue Lower", SCENE_GERUDO_TRAINING_GROUND, {}, { - //Locations - LOCATION(RC_GERUDO_TRAINING_GROUND_EYE_STATUE_CHEST, logic->CanUse(RG_FAIRY_BOW)), - }, { + areaTable[RR_GERUDO_TRAINING_GROUND_LAVA_ROOM] = Region("Gerudo Training Ground Lava Room", SCENE_GERUDO_TRAINING_GROUND, { + EventAccess(LOGIC_GTG_PLATFORM_SILVER_RUPEES, []{return logic->CanUse(RG_HOOKSHOT) && (logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_SONG_OF_TIME));}), + }, {}, { //Exits + Entrance(RR_GERUDO_TRAINING_GROUND_DINALFOS, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_CENTRAL_MAZE_RIGHT, []{return logic->CanUse(RG_SONG_OF_TIME) || logic->IsChild;}), + // possible to make across with adult's rolling jump, only requiring hookshot + Entrance(RR_GERUDO_TRAINING_GROUND_LAVA_ROOM_UPPER_LEDGE, []{return logic->CanUse(RG_LONGSHOT) || (logic->CanUse(RG_HOOKSHOT) && (logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_SONG_OF_TIME)));}), + Entrance(RR_GERUDO_TRAINING_GROUND_UNDERWATER, []{return logic->Get(LOGIC_GTG_PLATFORM_SILVER_RUPEES);}), + }); + + areaTable[RR_GERUDO_TRAINING_GROUND_LAVA_ROOM_UPPER_LEDGE] = Region("Gerudo Training Ground Lava Room", SCENE_GERUDO_TRAINING_GROUND, {}, {}, { + //Exits + // possible to make across with as adult's rolling jump, no hookshot necessary + Entrance(RR_GERUDO_TRAINING_GROUND_LAVA_ROOM, []{return logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_HOVER_BOOTS) || logic->CanUse(RG_SONG_OF_TIME);}), Entrance(RR_GERUDO_TRAINING_GROUND_HAMMER_ROOM, []{return true;}), }); - areaTable[RR_GERUDO_TRAINING_GROUND_EYE_STATUE_UPPER] = Region("Gerudo Training Ground Eye Statue Upper", SCENE_GERUDO_TRAINING_GROUND, {}, { + areaTable[RR_GERUDO_TRAINING_GROUND_UNDERWATER] = Region("Gerudo Training Ground Underwater", SCENE_GERUDO_TRAINING_GROUND, {}, { //Locations - LOCATION(RC_GERUDO_TRAINING_GROUND_NEAR_SCARECROW_CHEST, logic->CanUse(RG_FAIRY_BOW)), + LOCATION(RC_GERUDO_TRAINING_GROUND_UNDERWATER_SILVER_RUPEE_CHEST, logic->CanUse(RG_SONG_OF_TIME) && logic->CanUse(RG_IRON_BOOTS) && logic->HasItem(RG_BRONZE_SCALE) && logic->WaterTimer() >= 24), }, { //Exits - Entrance(RR_GERUDO_TRAINING_GROUND_EYE_STATUE_LOWER, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_LAVA_ROOM, []{return true;}), }); - areaTable[RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM] = Region("Gerudo Training Ground Heavy Block Room", SCENE_GERUDO_TRAINING_GROUND, {}, { + areaTable[RR_GERUDO_TRAINING_GROUND_DINALFOS] = Region("Gerudo Training Dinalfos", SCENE_GERUDO_TRAINING_GROUND, {}, { //Locations - LOCATION(RC_GERUDO_TRAINING_GROUND_BEFORE_HEAVY_BLOCK_CHEST, logic->CanKillEnemy(RE_WOLFOS, ED_CLOSE, true, 4, true)), + LOCATION(RC_GERUDO_TRAINING_GROUND_BEAMOS_CHEST, logic->CanKillEnemy(RE_BEAMOS) && logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2, true)), + LOCATION(RC_GERUDO_TRAINING_GROUND_BEAMOS_SOUTH_HEART, true), + LOCATION(RC_GERUDO_TRAINING_GROUND_BEAMOS_EAST_HEART, true), }, { //Exits - Entrance(RR_GERUDO_TRAINING_GROUND_EYE_STATUE_UPPER, []{return (ctx->GetTrickOption(RT_LENS_GTG) || logic->CanUse(RG_LENS_OF_TRUTH)) && (logic->CanUse(RG_HOOKSHOT) || (logic->IsAdult && (ctx->GetTrickOption(RT_GTG_FAKE_WALL) && logic->CanUse(RG_HOVER_BOOTS)) || logic->CanGroundJump()));}), - Entrance(RR_GERUDO_TRAINING_GROUND_LIKE_LIKE_ROOM, []{return (ctx->GetTrickOption(RT_LENS_GTG) || logic->CanUse(RG_LENS_OF_TRUTH)) && (logic->CanUse(RG_HOOKSHOT) || (logic->IsAdult && (ctx->GetTrickOption(RT_GTG_FAKE_WALL) && logic->CanUse(RG_HOVER_BOOTS)) || logic->CanGroundJump())) && logic->CanUse(RG_SILVER_GAUNTLETS);}), + Entrance(RR_GERUDO_TRAINING_GROUND_LOBBY, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_LAVA_ROOM, []{return Here(RR_GERUDO_TRAINING_GROUND_DINALFOS, []{return logic->CanKillEnemy(RE_BEAMOS) && logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2, true);});}), }); - areaTable[RR_GERUDO_TRAINING_GROUND_LIKE_LIKE_ROOM] = Region("Gerudo Training Ground Like Like Room", SCENE_GERUDO_TRAINING_GROUND, {}, { - //Locations - LOCATION(RC_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_FIRST_CHEST, logic->CanJumpslashExceptHammer()), - LOCATION(RC_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_SECOND_CHEST, logic->CanJumpslashExceptHammer()), - LOCATION(RC_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_THIRD_CHEST, logic->CanJumpslashExceptHammer()), - LOCATION(RC_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_FOURTH_CHEST, logic->CanJumpslashExceptHammer()), - }, {}); - #pragma endregion #pragma region MQ @@ -116,8 +188,6 @@ void RegionTable_Init_GerudoTrainingGround() { //Locations LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_LOBBY_LEFT_CHEST, true), LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_LOBBY_RIGHT_CHEST, true), - LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_MAZE_PATH_FIRST_CHEST, true), - LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_MAZE_PATH_SECOND_CHEST, true), LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_LOBBY_LEFT_POT_1, logic->CanBreakPots()), LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_LOBBY_LEFT_POT_2, logic->CanBreakPots()), LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_LOBBY_RIGHT_POT_1, logic->CanBreakPots()), @@ -125,19 +195,21 @@ void RegionTable_Init_GerudoTrainingGround() { }, { //Exits Entrance(RR_GERUDO_TRAINING_GROUND_ENTRYWAY, []{return true;}), - Entrance(RR_GERUDO_TRAINING_GROUND_MQ_MAZE_HIDDEN_ROOM, []{return ctx->GetTrickOption(RT_LENS_GTG_MQ) || logic->CanUse(RG_LENS_OF_TRUTH);}), - Entrance(RR_GERUDO_TRAINING_GROUND_MQ_MAZE_FIRST_LOCK, []{return logic->SmallKeys(SCENE_GERUDO_TRAINING_GROUND, 1);}), - //It's possible to use the torch in RR_GERUDO_TRAINING_GROUND_MQ_MAZE_HIDDEN_ROOM with flame storage to light these + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_MAZE_BY_LOBBY, []{return true;}), + //It's possible to use the torch in hidden room of maze with flame storage to light these Entrance(RR_GERUDO_TRAINING_GROUND_MQ_SAND_ROOM, []{return Here(RR_GERUDO_TRAINING_GROUND_MQ_LOBBY, []{return logic->HasFireSource();});}), - Entrance(RR_GERUDO_TRAINING_GROUND_MQ_DINOLFOS_ROOM, []{return Here(RR_GERUDO_TRAINING_GROUND_MQ_LOBBY, []{return (logic->IsAdult && logic->CanUse(RG_FAIRY_BOW)) || (logic->IsChild && logic->CanUse(RG_FAIRY_SLINGSHOT));});}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_DINOLFOS_ROOM, []{return Here(RR_GERUDO_TRAINING_GROUND_MQ_LOBBY, []{return logic->CanHitEyeTargets();});}), }); - areaTable[RR_GERUDO_TRAINING_GROUND_MQ_MAZE_HIDDEN_ROOM] = Region("Gerudo Training Ground MQ Maze Hidden Room", SCENE_GERUDO_TRAINING_GROUND, {}, { + areaTable[RR_GERUDO_TRAINING_GROUND_MQ_MAZE_BY_LOBBY] = Region("Gerudo Training Ground MQ Maze By Lobby", SCENE_GERUDO_TRAINING_GROUND, {}, { //Locations - LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_HIDDEN_CEILING_CHEST, true), + LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_MAZE_PATH_FIRST_CHEST, true), + LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_MAZE_PATH_SECOND_CHEST, true), + LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_HIDDEN_CEILING_CHEST, ctx->GetTrickOption(RT_LENS_GTG_MQ) || logic->CanUse(RG_LENS_OF_TRUTH)), }, { //Exits - Entrance(RR_GERUDO_TRAINING_GROUND_MQ_LOBBY, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_LOBBY, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_MAZE_FIRST_LOCK, []{return logic->SmallKeys(SCENE_GERUDO_TRAINING_GROUND, 1);}), }); areaTable[RR_GERUDO_TRAINING_GROUND_MQ_MAZE_FIRST_LOCK] = Region("Gerudo Training Ground MQ Maze First Lock", SCENE_GERUDO_TRAINING_GROUND, {}, { @@ -166,34 +238,44 @@ void RegionTable_Init_GerudoTrainingGround() { LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_FIRST_IRON_KNUCKLE_CHEST, logic->CanKillEnemy(RE_IRON_KNUCKLE)), }, { //Exits - Entrance(RR_GERUDO_TRAINING_GROUND_MQ_LOBBY, []{return true;}), - Entrance(RR_GERUDO_TRAINING_GROUND_MQ_LEFT_SIDE, []{return Here(RR_GERUDO_TRAINING_GROUND_MQ_SAND_ROOM, []{return logic->CanKillEnemy(RE_IRON_KNUCKLE);});}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_LOBBY, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_BOULDER_ROOM, []{return Here(RR_GERUDO_TRAINING_GROUND_MQ_SAND_ROOM, []{return logic->CanKillEnemy(RE_IRON_KNUCKLE);});}), }); - areaTable[RR_GERUDO_TRAINING_GROUND_MQ_LEFT_SIDE] = Region("Gerudo Training Ground MQ Left Side", SCENE_GERUDO_TRAINING_GROUND, {}, {}, { + areaTable[RR_GERUDO_TRAINING_GROUND_MQ_BOULDER_ROOM] = Region("Gerudo Training Ground MQ Left Side", SCENE_GERUDO_TRAINING_GROUND, {}, {}, { //Exits Entrance(RR_GERUDO_TRAINING_GROUND_MQ_SAND_ROOM, []{return true;}), - Entrance(RR_GERUDO_TRAINING_GROUND_MQ_STALFOS_ROOM, []{return Here(RR_GERUDO_TRAINING_GROUND_MQ_LEFT_SIDE, []{return logic->CanUse(RG_LONGSHOT) || ctx->GetTrickOption(RT_GTG_MQ_WITHOUT_HOOKSHOT) || (ctx->GetTrickOption(RT_GTG_MQ_WITH_HOOKSHOT) && logic->IsAdult && logic->CanJumpslash() && logic->CanUse(RG_HOOKSHOT));});}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_STALFOS_ROOM, []{return Here(RR_GERUDO_TRAINING_GROUND_MQ_BOULDER_ROOM, []{return logic->CanUse(RG_LONGSHOT) || ctx->GetTrickOption(RT_GTG_MQ_WITHOUT_HOOKSHOT) || (ctx->GetTrickOption(RT_GTG_MQ_WITH_HOOKSHOT) && logic->IsAdult && logic->CanJumpslash() && logic->CanUse(RG_HOOKSHOT));});}), }); areaTable[RR_GERUDO_TRAINING_GROUND_MQ_STALFOS_ROOM] = Region("Gerudo Training Ground MQ Stalfos Room", SCENE_GERUDO_TRAINING_GROUND, { //Events - EventAccess(LOGIC_BLUE_FIRE_ACCESS, []{return true;}), + EventAccess(LOGIC_BLUE_FIRE_ACCESS, []{return true;}), + EventAccess(LOGIC_GTG_UNLOCKED_DOOR_BEHIND_HEAVY_BLOCK, []{return Here(RR_GERUDO_TRAINING_GROUND_MQ_STALFOS_ROOM, []{return logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2, true);});}), + EventAccess(LOGIC_GTG_PUSHED_HEAVY_BLOCK, []{return logic->CanUse(RG_SILVER_GAUNTLETS) && logic->CanAvoidEnemy(RE_STALFOS, true, 2);}), }, { //Locations //implies logic->CanKillEnemy(RE_BIG_SKULLTULA) LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_BEFORE_HEAVY_BLOCK_CHEST, logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2, true)), }, { //Exits - Entrance(RR_GERUDO_TRAINING_GROUND_MQ_BEHIND_BLOCK, []{return Here(RR_GERUDO_TRAINING_GROUND_MQ_STALFOS_ROOM, []{return logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2, true);}) && logic->CanUse(RG_SILVER_GAUNTLETS);}), - Entrance(RR_GERUDO_TRAINING_GROUND_MQ_STATUE_ROOM_LEDGE, []{return logic->IsAdult && Here(RR_GERUDO_TRAINING_GROUND_MQ_STALFOS_ROOM, []{return logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2, true);}) && (ctx->GetTrickOption(RT_LENS_GTG_MQ) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->BlueFire() && logic->IsAdult && (logic->CanUse(RG_SONG_OF_TIME) || (ctx->GetTrickOption(RT_GTG_FAKE_WALL) && logic->CanUse(RG_HOVER_BOOTS)) || logic->CanGroundJump());}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_BOULDER_ROOM, []{return true;}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_BEHIND_BLOCK, []{return logic->Get(LOGIC_GTG_PUSHED_HEAVY_BLOCK);}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_STATUE_ROOM_LEDGE, []{return logic->IsAdult && Here(RR_GERUDO_TRAINING_GROUND_MQ_STALFOS_ROOM, []{return logic->CanKillEnemy(RE_STALFOS, ED_CLOSE, true, 2, true);}) && (ctx->GetTrickOption(RT_LENS_GTG_MQ) || logic->CanUse(RG_LENS_OF_TRUTH)) && logic->BlueFire() && (logic->CanUse(RG_SONG_OF_TIME) || (ctx->GetTrickOption(RT_GTG_FAKE_WALL) && logic->IsAdult && logic->CanUse(RG_HOVER_BOOTS)) || (logic->IsAdult && logic->CanGroundJump()));}), }); - areaTable[RR_GERUDO_TRAINING_GROUND_MQ_BEHIND_BLOCK] = Region("Gerudo Training Ground MQ Behind Block", SCENE_GERUDO_TRAINING_GROUND, {}, { + areaTable[RR_GERUDO_TRAINING_GROUND_MQ_BEHIND_BLOCK] = Region("Gerudo Training Ground MQ Behind Block", SCENE_GERUDO_TRAINING_GROUND, {}, {}, { + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_STALFOS_ROOM, []{return logic->Get(LOGIC_GTG_PUSHED_HEAVY_BLOCK);}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_ROOM_BEHIND_BLOCK, []{return logic->Get(LOGIC_GTG_UNLOCKED_DOOR_BEHIND_HEAVY_BLOCK);}), + }); + + areaTable[RR_GERUDO_TRAINING_GROUND_MQ_ROOM_BEHIND_BLOCK] = Region("Gerudo Training Ground MQ Room Behind Block", SCENE_GERUDO_TRAINING_GROUND, {}, { //Locations //implies logic->CanKillEnemy(RE_SPIKE) LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_HEAVY_BLOCK_CHEST, logic->CanKillEnemy(RE_FREEZARD)), - }, {}); + }, { + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_ROOM_BEHIND_BLOCK, []{return true;}), + }); areaTable[RR_GERUDO_TRAINING_GROUND_MQ_STATUE_ROOM_LEDGE] = Region("Gerudo Training Ground MQ Statue Room Ledge", SCENE_GERUDO_TRAINING_GROUND, {}, {}, { //Exits @@ -211,7 +293,7 @@ void RegionTable_Init_GerudoTrainingGround() { Entrance(RR_GERUDO_TRAINING_GROUND_MQ_STATUE_ROOM_LEDGE, []{return true;}), }); - areaTable[RR_GERUDO_TRAINING_GROUND_MQ_STATUE_ROOM] = Region("Gerudo Training Ground MQ Statue ROom", SCENE_GERUDO_TRAINING_GROUND, {}, { + areaTable[RR_GERUDO_TRAINING_GROUND_MQ_STATUE_ROOM] = Region("Gerudo Training Ground MQ Statue Room", SCENE_GERUDO_TRAINING_GROUND, {}, { //Locations LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_EYE_STATUE_CHEST, logic->CanUse(RG_FAIRY_BOW)), }, { @@ -241,6 +323,7 @@ void RegionTable_Init_GerudoTrainingGround() { //the fire bubble here is a jerk if you are aiming for the nearest hook platform, you have to aim to the right hand side with hook to dodge it Entrance(RR_GERUDO_TRAINING_GROUND_MQ_PLATFORMS_UNLIT_TORCH, []{return logic->CanUse(RG_LONGSHOT) || (logic->Get(LOGIC_GTG_PLATFORM_SILVER_RUPEES) && logic->CanUse(RG_HOOKSHOT)) || ((logic->CanUse(RG_FIRE_ARROWS) && logic->Get(LOGIC_GTG_PLATFORM_SILVER_RUPEES)) && logic->CanUse(RG_HOVER_BOOTS));}), Entrance(RR_GERUDO_TRAINING_GROUND_MQ_MAZE_RIGHT, []{return logic->Get(LOGIC_GTG_MQ_RIGHT_SIDE_SWITCH) && logic->CanUse(RG_LONGSHOT);}), + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_TORCH_SLUG_ROOM, []{return true;}), }); //this region exists to place silver rupee items on later, normally it's all on fire and cannot be stood on without access from another area @@ -312,16 +395,17 @@ void RegionTable_Init_GerudoTrainingGround() { areaTable[RR_GERUDO_TRAINING_GROUND_MQ_DINOLFOS_ROOM] = Region("Gerudo Training Ground MQ Dinolfos Room", SCENE_GERUDO_TRAINING_GROUND, { //Events - //EventAccess(&WallFairy, []{return WallFairy || (logic->IsAdult && logic->CanUse(RG_FAIRY_BOW));}), + //EventAccess(&WallFairy, []{return logic->IsAdult && logic->CanUse(RG_FAIRY_BOW);}), }, { //Locations //implies logic->CanKillEnemy(RE_LIZALFOS and logic->CanKillEnemy(RE_DODONGO) - //is logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2, true) && logic->CanKillEnemy(RE_ARMOS, ED_CLOSE, true, 1, true) broken down to exclude sticks, as it take too many to clear the room + //is logic->CanKillEnemy(RE_DINOLFOS, ED_CLOSE, true, 2, true) && logic->CanKillEnemy(RE_ARMOS, ED_CLOSE, true, 1, true) broken down to exclude sticks, as it takes too many to clear the room //Proper enemy kill room ammo logic is needed to handle this room //some combinations may be impossible without taking damage, keep an eye out for issues here LOCATION(RC_GERUDO_TRAINING_GROUND_MQ_DINOLFOS_CHEST, logic->CanUse(RG_MASTER_SWORD) || logic->CanUse(RG_BIGGORON_SWORD) || logic->CanUse(RG_MEGATON_HAMMER) || logic->CanUse(RG_FAIRY_BOW) || ((logic->CanUse(RG_NUTS) || logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_BOOMERANG)) && (logic->CanUse(RG_KOKIRI_SWORD) || logic->CanUse(RG_FAIRY_SLINGSHOT)))), }, { //Exits + Entrance(RR_GERUDO_TRAINING_GROUND_MQ_LOBBY, []{return true;}), Entrance(RR_GERUDO_TRAINING_GROUND_MQ_TORCH_SIDE_PLATFORMS, []{return Here(RR_GERUDO_TRAINING_GROUND_MQ_DINOLFOS_ROOM, []{return logic->CanUse(RG_MASTER_SWORD) || logic->CanUse(RG_BIGGORON_SWORD) || logic->CanUse(RG_MEGATON_HAMMER) || logic->CanUse(RG_FAIRY_BOW) || ((logic->CanUse(RG_NUTS) || logic->CanUse(RG_HOOKSHOT) || logic->CanUse(RG_BOOMERANG)) && (logic->CanUse(RG_KOKIRI_SWORD) || logic->CanUse(RG_FAIRY_SLINGSHOT)));});}), }); diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 9884c60db..149404042 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -331,6 +331,9 @@ typedef enum { LOGIC_GTG_MQ_MAZE_SWITCH, LOGIC_GTG_MQ_RIGHT_SIDE_SWITCH, LOGIC_GTG_PLATFORM_SILVER_RUPEES, + LOGIC_GTG_UNLOCKED_DOOR_BEHIND_HEAVY_BLOCK, + LOGIC_GTG_PUSHED_HEAVY_BLOCK, + LOGIC_GTG_CLEARED_EYE_STATUE, LOGIC_SHADOW_TRIAL_FIRST_CHEST, LOGIC_MAX } LogicVal; @@ -1201,23 +1204,32 @@ typedef enum { RR_ICE_CAVERN_MQ_ABOVE_BEGINNING, RR_GERUDO_TRAINING_GROUND_LOBBY, + RR_GERUDO_TRAINING_GROUND_SAND_ROOM, + RR_GERUDO_TRAINING_GROUND_BOULDER_ROOM, RR_GERUDO_TRAINING_GROUND_CENTRAL_MAZE, RR_GERUDO_TRAINING_GROUND_CENTRAL_MAZE_RIGHT, - RR_GERUDO_TRAINING_GROUND_LAVA_ROOM, - RR_GERUDO_TRAINING_GROUND_HAMMER_ROOM, - RR_GERUDO_TRAINING_GROUND_EYE_STATUE_LOWER, - RR_GERUDO_TRAINING_GROUND_EYE_STATUE_UPPER, RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM, + RR_GERUDO_TRAINING_GROUND_HEAVY_BLOCK_ROOM_UPPER, + RR_GERUDO_TRAINING_GROUND_BEHIND_HEAVY_BLOCK, RR_GERUDO_TRAINING_GROUND_LIKE_LIKE_ROOM, + RR_GERUDO_TRAINING_GROUND_EYE_STATUE_UPPER, + RR_GERUDO_TRAINING_GROUND_ABOVE_MAZE, + RR_GERUDO_TRAINING_GROUND_EYE_STATUE_LOWER, + RR_GERUDO_TRAINING_GROUND_HAMMER_ROOM, + RR_GERUDO_TRAINING_GROUND_LAVA_ROOM, + RR_GERUDO_TRAINING_GROUND_LAVA_ROOM_UPPER_LEDGE, + RR_GERUDO_TRAINING_GROUND_UNDERWATER, + RR_GERUDO_TRAINING_GROUND_DINALFOS, RR_GERUDO_TRAINING_GROUND_MQ_LOBBY, - RR_GERUDO_TRAINING_GROUND_MQ_MAZE_HIDDEN_ROOM, + RR_GERUDO_TRAINING_GROUND_MQ_MAZE_BY_LOBBY, RR_GERUDO_TRAINING_GROUND_MQ_MAZE_FIRST_LOCK, RR_GERUDO_TRAINING_GROUND_MQ_MAZE_CENTER, RR_GERUDO_TRAINING_GROUND_MQ_SAND_ROOM, - RR_GERUDO_TRAINING_GROUND_MQ_LEFT_SIDE, + RR_GERUDO_TRAINING_GROUND_MQ_BOULDER_ROOM, RR_GERUDO_TRAINING_GROUND_MQ_STALFOS_ROOM, RR_GERUDO_TRAINING_GROUND_MQ_BEHIND_BLOCK, + RR_GERUDO_TRAINING_GROUND_MQ_ROOM_BEHIND_BLOCK, RR_GERUDO_TRAINING_GROUND_MQ_STATUE_ROOM_LEDGE, RR_GERUDO_TRAINING_GROUND_MQ_MAGENTA_FIRE_ROOM, RR_GERUDO_TRAINING_GROUND_MQ_STATUE_ROOM, From 3e6b590db47910e079ef2141485f704c02c0de87 Mon Sep 17 00:00:00 2001 From: Pierre-Alain BESSERO Date: Mon, 24 Nov 2025 14:50:56 +0100 Subject: [PATCH 02/15] Added new trick (#5972) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added new trick Skip Dodongo Cavern by using Bombchus to light the eyes * Update soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp Co-authored-by: Philip Dubé * Update soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp Co-authored-by: Philip Dubé --------- Co-authored-by: Philip Dubé --- .../randomizer/location_access/dungeons/dodongos_cavern.cpp | 6 +++++- soh/soh/Enhancements/randomizer/randomizerTypes.h | 1 + soh/soh/Enhancements/randomizer/settings.cpp | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp index c21a7a33e..8ec64078a 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/dodongos_cavern.cpp @@ -25,6 +25,7 @@ void RegionTable_Init_DodongosCavern() { areaTable[RR_DODONGOS_CAVERN_LOBBY] = Region("Dodongos Cavern Lobby", SCENE_DODONGOS_CAVERN, { //Events EventAccess(LOGIC_GOSSIP_STONE_FAIRY, []{return (Here(RR_DODONGOS_CAVERN_LOBBY, []{return logic->CanBreakMudWalls();}) || logic->HasItem(RG_GORONS_BRACELET)) && logic->CallGossipFairy();}), + EventAccess(LOGIC_DC_EYES_LIT, []{return ctx->GetTrickOption(RT_DC_EYES_CHU) && logic->CanUse(RG_BOMBCHU_5);}), }, { //Locations LOCATION(RC_DODONGOS_CAVERN_MAP_CHEST, logic->CanBreakMudWalls() || logic->HasItem(RG_GORONS_BRACELET);), @@ -282,7 +283,10 @@ void RegionTable_Init_DodongosCavern() { Entrance(RR_DODONGOS_CAVERN_MQ_LOBBY, []{return Here(RR_DODONGOS_CAVERN_MQ_BEGINNING, []{return logic->CanBreakMudWalls() || logic->HasItem(RG_GORONS_BRACELET);});}), }); - areaTable[RR_DODONGOS_CAVERN_MQ_LOBBY] = Region("Dodongos Cavern MQ Lobby", SCENE_DODONGOS_CAVERN, {}, { + areaTable[RR_DODONGOS_CAVERN_MQ_LOBBY] = Region("Dodongos Cavern MQ Lobby", SCENE_DODONGOS_CAVERN, { + //Events + EventAccess(LOGIC_DC_EYES_LIT, []{return ctx->GetTrickOption(RT_DC_EYES_CHU) && logic->CanUse(RG_BOMBCHU_5);}), + }, { //Locations LOCATION(RC_DODONGOS_CAVERN_MQ_MAP_CHEST, logic->CanBreakMudWalls() || logic->HasItem(RG_GORONS_BRACELET)), LOCATION(RC_DODONGOS_CAVERN_MQ_DEKU_SCRUB_LOBBY_REAR, logic->CanStunDeku()), diff --git a/soh/soh/Enhancements/randomizer/randomizerTypes.h b/soh/soh/Enhancements/randomizer/randomizerTypes.h index 149404042..ec8e05d05 100644 --- a/soh/soh/Enhancements/randomizer/randomizerTypes.h +++ b/soh/soh/Enhancements/randomizer/randomizerTypes.h @@ -3867,6 +3867,7 @@ typedef enum { RT_DC_MQ_CHILD_EYES, RT_DC_MQ_ADULT_EYES, RT_DC_DODONGO_CHU, + RT_DC_EYES_CHU, RT_JABU_ALCOVE_JUMP_DIVE, RT_JABU_BOSS_HOVER, RT_JABU_NEAR_BOSS_RANGED, diff --git a/soh/soh/Enhancements/randomizer/settings.cpp b/soh/soh/Enhancements/randomizer/settings.cpp index 6adccc5e5..68bed88c8 100644 --- a/soh/soh/Enhancements/randomizer/settings.cpp +++ b/soh/soh/Enhancements/randomizer/settings.cpp @@ -706,6 +706,11 @@ void Settings::CreateOptions() { RT_DC_MQ_ADULT_EYES, RCQUEST_MQ, RA_DODONGOS_CAVERN, { Tricks::Tag::ADVANCED }, "Dodongo\'s Cavern MQ Light the Eyes with Strength as Adult", "If you move very quickly, it is possible to use the bomb flower at the top of the room to light the eyes."); + OPT_TRICK( + RT_DC_EYES_CHU, RCQUEST_BOTH, RA_DODONGOS_CAVERN, { Tricks::Tag::ADVANCED }, + "Dodongo\'s Cavern Light the Eyes with Bombchus", + "You can light the dodongo head's eyes with bombchus from the main room, allowing instant access to the end " + "of the dungeon."); OPT_TRICK(RT_JABU_ALCOVE_JUMP_DIVE, RCQUEST_BOTH, RA_JABU_JABUS_BELLY, { Tricks::Tag::NOVICE }, "Jabu Underwater Alcove as Adult with Jump Dive", "Standing above the underwater tunnel leading to the scrub, jump down and swim through the tunnel. This " From 0f41ecb145a688f66ac311db31483a41908ad4d0 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Mon, 24 Nov 2025 12:30:34 -0500 Subject: [PATCH 03/15] Modularize Hurt Container mode hook (#5874) * Modularize Hurt Container mode hook * Hook condition was wrong - fixed it * Change type of hurtEnabled for clarity * Change type back to bool * Add VB hook * Don't duplicate health capacity modifier calculation * Add constants, replace magic numbers * Clang format * Publicize more health unit macros * Make mod file self-contained --- soh/include/z64save.h | 4 ++ .../Enhancements/ExtraModes/HurtContainer.cpp | 42 +++++++++++++++++++ .../game-interactor/GameInteractionEffect.cpp | 6 ++- .../GameInteractor_RawAction.cpp | 8 ++-- .../vanilla-behavior/GIVanillaBehavior.h | 8 ++++ soh/soh/Enhancements/mods.cpp | 23 ---------- soh/soh/Enhancements/mods.h | 1 - .../Enhancements/randomizer/hook_handlers.cpp | 6 +-- .../Enhancements/randomizer/randomizer.cpp | 2 +- .../Enhancements/timesaver_hook_handlers.cpp | 2 +- soh/soh/SaveManager.cpp | 12 +++--- soh/soh/SohGui/SohMenuEnhancements.cpp | 3 -- soh/src/code/z_en_item00.c | 2 +- soh/src/code/z_lifemeter.c | 8 ++-- soh/src/code/z_message_PAL.c | 11 ++--- soh/src/code/z_parameter.c | 15 +++---- soh/src/code/z_sram.c | 4 +- .../ovl_Bg_Dy_Yoseizo/z_bg_dy_yoseizo.c | 6 +-- .../actors/ovl_Boss_Ganon/z_boss_ganon.c | 4 +- .../ovl_En_Bom_Bowl_Pit/z_en_bom_bowl_pit.c | 2 +- .../actors/ovl_player_actor/z_player.c | 12 +++--- .../ovl_file_choose/z_file_choose.c | 2 +- .../misc/ovl_kaleido_scope/z_kaleido_debug.c | 22 +++++----- .../ovl_kaleido_scope/z_kaleido_scope_PAL.c | 5 ++- 24 files changed, 117 insertions(+), 93 deletions(-) create mode 100644 soh/soh/Enhancements/ExtraModes/HurtContainer.cpp diff --git a/soh/include/z64save.h b/soh/include/z64save.h index bf1342360..c20bf2034 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -9,6 +9,10 @@ #include "soh/Enhancements/randomizer/randomizer_entrance.h" #include "soh/Enhancements/boss-rush/BossRush.h" +#define FULL_HEART_HEALTH 0x10 +#define STARTING_HEALTH (3 * FULL_HEART_HEALTH) +#define MAX_HEALTH (20 * FULL_HEART_HEALTH) + typedef enum { /* 0x0 */ MAGIC_STATE_IDLE, // Regular gameplay /* 0x1 */ MAGIC_STATE_CONSUME_SETUP, // Sets the speed at which magic border flashes diff --git a/soh/soh/Enhancements/ExtraModes/HurtContainer.cpp b/soh/soh/Enhancements/ExtraModes/HurtContainer.cpp new file mode 100644 index 000000000..ecf39ef31 --- /dev/null +++ b/soh/soh/Enhancements/ExtraModes/HurtContainer.cpp @@ -0,0 +1,42 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "variables.h" +extern SaveContext gSaveContext; +} + +static constexpr int32_t CVAR_HURT_CONTAINER_DEFAULT = 0; +#define CVAR_HURT_CONTAINER_NAME CVAR_ENHANCEMENT("HurtContainer") +#define CVAR_HURT_CONTAINER_VALUE CVarGetInteger(CVAR_HURT_CONTAINER_NAME, CVAR_HURT_CONTAINER_DEFAULT) + +static bool hurtEnabled = false; + +static void UpdateHurtContainerModeState() { + hurtEnabled = CVAR_HURT_CONTAINER_VALUE; + uint16_t heartPieceContainers = gSaveContext.ship.stats.heartPieces / 4; + uint16_t heartContainers = gSaveContext.ship.stats.heartContainers; + uint16_t healthCapacityMod = (heartPieceContainers + heartContainers) * FULL_HEART_HEALTH; + + if (hurtEnabled != CVAR_HURT_CONTAINER_DEFAULT) { + gSaveContext.healthCapacity = MAX_HEALTH - healthCapacityMod; + } else { + gSaveContext.healthCapacity = STARTING_HEALTH + healthCapacityMod; + } +} + +static void RegisterHurtContainer() { + if (GameInteractor::IsSaveLoaded(false)) { + UpdateHurtContainerModeState(); + } + + COND_HOOK(OnLoadGame, hurtEnabled != CVAR_HURT_CONTAINER_VALUE, [](int32_t) { UpdateHurtContainerModeState(); }); + + COND_VB_SHOULD(VB_HEARTS_INCREASE_WITH_CONTAINERS, CVAR_HURT_CONTAINER_VALUE, { + *should = false; + gSaveContext.healthCapacity -= FULL_HEART_HEALTH; + gSaveContext.health -= FULL_HEART_HEALTH; + }); +} + +static RegisterShipInitFunc initFunc(RegisterHurtContainer, { CVAR_HURT_CONTAINER_NAME }); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp index 682a1271e..7c762ca66 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractionEffect.cpp @@ -101,8 +101,10 @@ void UnsetFlag::_Apply() { GameInteractionEffectQueryResult ModifyHeartContainers::CanBeApplied() { if (!GameInteractor::IsSaveLoaded(true)) { return GameInteractionEffectQueryResult::TemporarilyNotPossible; - } else if ((parameters[0] > 0 && (gSaveContext.healthCapacity + (parameters[0] * 0x10) > 0x140)) || - (parameters[0] < 0 && (gSaveContext.healthCapacity + (parameters[0] * 0x10) < 0x10))) { + } else if ((parameters[0] > 0 && + (gSaveContext.healthCapacity + (parameters[0] * FULL_HEART_HEALTH) > MAX_HEALTH)) || + (parameters[0] < 0 && + (gSaveContext.healthCapacity + (parameters[0] * FULL_HEART_HEALTH) < FULL_HEART_HEALTH))) { return GameInteractionEffectQueryResult::NotPossible; } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp index 793466c19..0e82ac7c3 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_RawAction.cpp @@ -17,7 +17,7 @@ extern PlayState* gPlayState; #include "overlays/actors/ovl_En_Bom/z_en_bom.h" void GameInteractor::RawAction::AddOrRemoveHealthContainers(int16_t amount) { - gSaveContext.healthCapacity += amount * 0x10; + gSaveContext.healthCapacity += amount * FULL_HEART_HEALTH; } void GameInteractor::RawAction::AddOrRemoveMagic(int8_t amount) { @@ -46,17 +46,17 @@ void GameInteractor::RawAction::AddOrRemoveMagic(int8_t amount) { void GameInteractor::RawAction::HealOrDamagePlayer(int16_t hearts) { if (hearts > 0) { - Health_ChangeBy(gPlayState, hearts * 0x10); + Health_ChangeBy(gPlayState, hearts * FULL_HEART_HEALTH); } else if (hearts < 0) { Player* player = GET_PLAYER(gPlayState); - Health_ChangeBy(gPlayState, hearts * 0x10); + Health_ChangeBy(gPlayState, hearts * FULL_HEART_HEALTH); func_80837C0C(gPlayState, player, 0, 0, 0, 0, 0); player->invincibilityTimer = 28; } } void GameInteractor::RawAction::SetPlayerHealth(int16_t hearts) { - gSaveContext.health = hearts * 0x10; + gSaveContext.health = hearts * FULL_HEART_HEALTH; } void GameInteractor::RawAction::SetLinkInvisibility(bool active) { diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index fe437907a..8806b0a63 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -1175,6 +1175,14 @@ typedef enum { // - None VB_HEALTH_METER_BE_CRITICAL, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - None + VB_HEARTS_INCREASE_WITH_CONTAINERS, + // #### `result` // ```c // (respawnFlag == 1) || (respawnFlag == -1) diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 93b4ce79e..f4ad7ee47 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -486,28 +486,6 @@ void RegisterEnemyDefeatCounts() { }); } -void UpdateHurtContainerModeState(bool newState) { - static bool hurtEnabled = false; - if (hurtEnabled == newState) { - return; - } - - hurtEnabled = newState; - uint16_t getHeartPieces = gSaveContext.ship.stats.heartPieces / 4; - uint16_t getHeartContainers = gSaveContext.ship.stats.heartContainers; - - if (hurtEnabled) { - gSaveContext.healthCapacity = 320 - ((getHeartPieces + getHeartContainers) * 16); - } else { - gSaveContext.healthCapacity = 48 + ((getHeartPieces + getHeartContainers) * 16); - } -} - -void RegisterHurtContainerModeHandler() { - GameInteractor::Instance->RegisterGameHook( - [](int32_t fileNum) { UpdateHurtContainerModeState(CVarGetInteger(CVAR_ENHANCEMENT("HurtContainer"), 0)); }); -} - void RegisterRandomizedEnemySizes() { GameInteractor::Instance->RegisterGameHook([](void* refActor) { // Randomized Enemy Sizes @@ -572,6 +550,5 @@ void InitMods() { RegisterEnemyDefeatCounts(); RegisterRandomizedEnemySizes(); RegisterPatchHandHandler(); - RegisterHurtContainerModeHandler(); RandoKaleido_RegisterHooks(); } diff --git a/soh/soh/Enhancements/mods.h b/soh/soh/Enhancements/mods.h index 24710d5b2..7ba012737 100644 --- a/soh/soh/Enhancements/mods.h +++ b/soh/soh/Enhancements/mods.h @@ -9,7 +9,6 @@ extern "C" { void DirtPathFix_UpdateZFightingMode(int32_t sceneNum); void UpdateMirrorModeState(int32_t sceneNum); -void UpdateHurtContainerModeState(bool newState); void UpdateToTMedallions(); void UpdatePermanentHeartLossState(); void UpdateHyperEnemiesState(); diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 82a9336fa..276270bad 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -391,11 +391,11 @@ void RandomizerOnItemReceiveHandler(GetItemEntry receivedItemEntry) { if (receivedItemEntry.modIndex == MOD_NONE && (receivedItemEntry.itemId == ITEM_HEART_PIECE || receivedItemEntry.itemId == ITEM_HEART_PIECE_2 || receivedItemEntry.itemId == ITEM_HEART_CONTAINER)) { - gSaveContext.healthAccumulator = 0x140; // Refill 20 hearts + gSaveContext.healthAccumulator = MAX_HEALTH; // Refill 20 hearts if ((s32)(gSaveContext.inventory.questItems & 0xF0000000) == 0x40000000) { gSaveContext.inventory.questItems ^= 0x40000000; - gSaveContext.healthCapacity += 0x10; - gSaveContext.health += 0x10; + gSaveContext.healthCapacity += FULL_HEART_HEALTH; + gSaveContext.health += FULL_HEART_HEALTH; } } diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index cac735121..4ad550164 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -6221,7 +6221,7 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { case RG_DOUBLE_DEFENSE: gSaveContext.isDoubleDefenseAcquired = true; gSaveContext.inventory.defenseHearts = 20; - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; break; case RG_TYCOON_WALLET: Inventory_ChangeUpgrade(UPG_WALLET, 3); diff --git a/soh/soh/Enhancements/timesaver_hook_handlers.cpp b/soh/soh/Enhancements/timesaver_hook_handlers.cpp index 89bef13c5..ff1e18354 100644 --- a/soh/soh/Enhancements/timesaver_hook_handlers.cpp +++ b/soh/soh/Enhancements/timesaver_hook_handlers.cpp @@ -773,7 +773,7 @@ void TimeSaverOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_li (IS_RANDO || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO))) { if (IS_RANDO || *should) { Flags_SetRandomizerInf(flag); - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; Magic_Fill(gPlayState); } *should = false; diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index ab6350758..b05770c54 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -655,10 +655,10 @@ void SaveManager::InitFileNormal() { gSaveContext.ship.filenameLanguage = (gSaveContext.language == LANGUAGE_JPN) ? NAME_LANGUAGE_NTSC_JPN : NAME_LANGUAGE_NTSC_ENG; } - gSaveContext.healthCapacity = 0x30; - gSaveContext.health = 0x30; + gSaveContext.healthCapacity = STARTING_HEALTH; + gSaveContext.health = STARTING_HEALTH; gSaveContext.magicLevel = 0; - gSaveContext.magic = 0x30; + gSaveContext.magic = MAGIC_NORMAL_METER; gSaveContext.rupees = 0; gSaveContext.swordHealth = 0; gSaveContext.naviTimer = 0; @@ -950,10 +950,10 @@ void SaveManager::InitFileMaxed() { gSaveContext.ship.filenameLanguage = (gSaveContext.language == LANGUAGE_JPN) ? NAME_LANGUAGE_NTSC_JPN : NAME_LANGUAGE_NTSC_ENG; } - gSaveContext.healthCapacity = 0x140; - gSaveContext.health = 0x140; + gSaveContext.healthCapacity = MAX_HEALTH; + gSaveContext.health = MAX_HEALTH; gSaveContext.magicLevel = 2; - gSaveContext.magic = 0x60; + gSaveContext.magic = MAGIC_DOUBLE_METER; gSaveContext.rupees = 500; gSaveContext.swordHealth = 8; gSaveContext.naviTimer = 0; diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 149eb6577..b528c6f45 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1568,9 +1568,6 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip("A Wallmaster follows Link everywhere, don't get caught!")); AddWidget(path, "Hurt Container Mode", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("HurtContainer")) - .Callback([](WidgetInfo& info) { - UpdateHurtContainerModeState(CVarGetInteger(CVAR_ENHANCEMENT("HurtContainer"), 0)); - }) .Options(CheckboxOptions().Tooltip("Changes Heart Piece and Heart Container functionality.\n\n" " - Each Heart Container or full Heart Piece reduces Link's Hearts by 1.\n" " - Can be enabled retroactively after a File has already started.")); diff --git a/soh/src/code/z_en_item00.c b/soh/src/code/z_en_item00.c index b6bf5ecdf..28459ba5e 100644 --- a/soh/src/code/z_en_item00.c +++ b/soh/src/code/z_en_item00.c @@ -1707,7 +1707,7 @@ void Item_DropCollectibleRandom(PlayState* play, Actor* fromActor, Vec3f* spawnP } if (dropId == ITEM00_FLEXIBLE) { - if (gSaveContext.health <= 0x10) { // 1 heart or less + if (gSaveContext.health <= FULL_HEART_HEALTH) { // 1 heart or less Actor_Spawn(&play->actorCtx, play, ACTOR_EN_ELF, spawnPos->x, spawnPos->y + 40.0f, spawnPos->z, 0, 0, 0, FAIRY_HEAL_TIMED, true); EffectSsDeadSound_SpawnStationary(play, spawnPos, NA_SE_EV_BUTTERFRY_TO_FAIRY, true, diff --git a/soh/src/code/z_lifemeter.c b/soh/src/code/z_lifemeter.c index 451bdf9ab..cd6de967e 100644 --- a/soh/src/code/z_lifemeter.c +++ b/soh/src/code/z_lifemeter.c @@ -393,9 +393,9 @@ void HealthMeter_Draw(PlayState* play) { InterfaceContext* interfaceCtx = &play->interfaceCtx; GraphicsContext* gfxCtx = play->state.gfxCtx; Vtx* sp154 = interfaceCtx->beatingHeartVtx; - s32 curHeartFraction = gSaveContext.health % 0x10; - s16 totalHeartCount = gSaveContext.healthCapacity / 0x10; - s16 fullHeartCount = gSaveContext.health / 0x10; + s32 curHeartFraction = gSaveContext.health % FULL_HEART_HEALTH; + s16 totalHeartCount = gSaveContext.healthCapacity / FULL_HEART_HEALTH; + s16 fullHeartCount = gSaveContext.health / FULL_HEART_HEALTH; s32 pad2; f32 sp144 = interfaceCtx->unk_22A * 0.1f; s32 curCombineModeSet = 0; @@ -410,7 +410,7 @@ void HealthMeter_Draw(PlayState* play) { OPEN_DISPS(gfxCtx); - if (!(gSaveContext.health % 0x10)) { + if (!(gSaveContext.health % FULL_HEART_HEALTH)) { fullHeartCount--; } diff --git a/soh/src/code/z_message_PAL.c b/soh/src/code/z_message_PAL.c index 6c357c612..25efbaadd 100644 --- a/soh/src/code/z_message_PAL.c +++ b/soh/src/code/z_message_PAL.c @@ -4653,7 +4653,7 @@ void Message_Update(PlayState* play) { } if ((msgCtx->textId >= 0xC2 && msgCtx->textId < 0xC7) || (msgCtx->textId >= 0xFA && msgCtx->textId < 0xFE)) { - gSaveContext.healthAccumulator = 0x140; // Refill 20 hearts + gSaveContext.healthAccumulator = MAX_HEALTH; // Refill 20 hearts } if (msgCtx->textId == 0x301F || msgCtx->textId == 0xA || msgCtx->textId == 0xC || msgCtx->textId == 0xCF || msgCtx->textId == 0x21C || msgCtx->textId == 9 || msgCtx->textId == 0x4078 || @@ -4691,12 +4691,9 @@ void Message_Update(PlayState* play) { } if ((s32)(gSaveContext.inventory.questItems & 0xF0000000) == 0x40000000) { gSaveContext.inventory.questItems ^= 0x40000000; - if (!CVarGetInteger(CVAR_ENHANCEMENT("HurtContainer"), 0)) { - gSaveContext.healthCapacity += 0x10; - gSaveContext.health += 0x10; - } else { - gSaveContext.healthCapacity -= 0x10; - gSaveContext.health -= 0x10; + if (GameInteractor_Should(VB_HEARTS_INCREASE_WITH_CONTAINERS, true)) { + gSaveContext.healthCapacity += FULL_HEART_HEALTH; + gSaveContext.health += FULL_HEART_HEALTH; } } if (msgCtx->ocarinaAction != OCARINA_ACTION_CHECK_NOWARP_DONE) { diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index f2713c8d6..84cea3264 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -2318,19 +2318,16 @@ u8 Item_Give(PlayState* play, u8 item) { gSaveContext.ship.stats.heartPieces++; return Return_Item(item, MOD_NONE, ITEM_NONE); } else if (item == ITEM_HEART_CONTAINER) { - if (!CVarGetInteger(CVAR_ENHANCEMENT("HurtContainer"), 0)) { - gSaveContext.healthCapacity += 0x10; - gSaveContext.health += 0x10; - } else { - gSaveContext.healthCapacity -= 0x10; - gSaveContext.health -= 0x10; + if (GameInteractor_Should(VB_HEARTS_INCREASE_WITH_CONTAINERS, true)) { + gSaveContext.healthCapacity += FULL_HEART_HEALTH; + gSaveContext.health += FULL_HEART_HEALTH; } gSaveContext.ship.stats.heartContainers++; return Return_Item(item, MOD_NONE, ITEM_NONE); } else if (item == ITEM_HEART) { osSyncPrintf("回復ハート回復ハート回復ハート\n"); // "Recovery Heart" if (play != NULL) { - Health_ChangeBy(play, 0x10); + Health_ChangeBy(play, FULL_HEART_HEALTH); } return Return_Item(item, MOD_NONE, item); } else if (item == ITEM_MAGIC_SMALL) { @@ -2905,7 +2902,7 @@ s32 Health_ChangeBy(PlayState* play, s16 healthChange) { gSaveContext.health = gSaveContext.healthCapacity; } - heartCount = gSaveContext.health % 0x10; + heartCount = gSaveContext.health % FULL_HEART_HEALTH; healthLevel = heartCount; if (heartCount != 0) { @@ -3516,7 +3513,7 @@ void Interface_DrawMagicBar(PlayState* play) { R_MAGIC_FILL_X - 1; } } else { - if ((gSaveContext.healthCapacity - 1) / 0x10 >= lineLength && lineLength != 0) { + if ((gSaveContext.healthCapacity - 1) / FULL_HEART_HEALTH >= lineLength && lineLength != 0) { magicBarY = magicBarY_original_l + magicDrop * (lineLength == 0 ? 0 : ((gSaveContext.healthCapacity - 1) / (0x10 * lineLength) - 1)); diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index 8fc6d1dde..7abc221a4 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -150,9 +150,9 @@ void Sram_OpenSave() { osSyncPrintf("scene_no = %d\n", gSaveContext.entranceIndex); osSyncPrintf(VT_RST); - if (gSaveContext.health < 0x30) { + if (gSaveContext.health < STARTING_HEALTH) { gSaveContext.health = - CVarGetInteger(CVAR_ENHANCEMENT("FullHealthSpawn"), 0) ? gSaveContext.healthCapacity : 0x30; + CVarGetInteger(CVAR_ENHANCEMENT("FullHealthSpawn"), 0) ? gSaveContext.healthCapacity : STARTING_HEALTH; } if (gSaveContext.scarecrowLongSongSet) { diff --git a/soh/src/overlays/actors/ovl_Bg_Dy_Yoseizo/z_bg_dy_yoseizo.c b/soh/src/overlays/actors/ovl_Bg_Dy_Yoseizo/z_bg_dy_yoseizo.c index 2e3ad7888..a1fe242c6 100644 --- a/soh/src/overlays/actors/ovl_Bg_Dy_Yoseizo/z_bg_dy_yoseizo.c +++ b/soh/src/overlays/actors/ovl_Bg_Dy_Yoseizo/z_bg_dy_yoseizo.c @@ -475,7 +475,7 @@ void BgDyYoseizo_HealPlayer_NoReward(BgDyYoseizo* this, PlayState* play) { } if (this->healingTimer == 110) { - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; Magic_Fill(play); this->refillTimer = 200; } @@ -743,7 +743,7 @@ void BgDyYoseizo_Give_Reward(BgDyYoseizo* this, PlayState* play) { } if (!this->healing) { - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; this->healing = true; if (actionIndex == 2) { Magic_Fill(play); @@ -771,7 +771,7 @@ void BgDyYoseizo_Give_Reward(BgDyYoseizo* this, PlayState* play) { } this->itemSpawned = true; - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; Interface_ChangeAlpha(9); gSaveContext.itemGetInf[1] |= sItemGetFlags[actionIndex]; Item_Give(play, sItemIds[actionIndex]); diff --git a/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c b/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c index f845f323a..534988630 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c @@ -584,7 +584,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) { this->unk_198 = 2; this->timers[2] = 110; if (GameInteractor_Should(VB_GANON_HEAL_BEFORE_FIGHT, true)) { - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; } Audio_QueueSeqCmd(NA_BGM_STOP); } else { @@ -800,7 +800,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) { } if (this->csTimer == 25) { - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; } if (this->csTimer == 100) { diff --git a/soh/src/overlays/actors/ovl_En_Bom_Bowl_Pit/z_en_bom_bowl_pit.c b/soh/src/overlays/actors/ovl_En_Bom_Bowl_Pit/z_en_bom_bowl_pit.c index 712a9008d..129d94879 100644 --- a/soh/src/overlays/actors/ovl_En_Bom_Bowl_Pit/z_en_bom_bowl_pit.c +++ b/soh/src/overlays/actors/ovl_En_Bom_Bowl_Pit/z_en_bom_bowl_pit.c @@ -206,7 +206,7 @@ void EnBomBowlPit_Reset(EnBomBowlPit* this, PlayState* play) { // "Normal termination"/"completion" osSyncPrintf(VT_FGCOL(GREEN) "☆☆☆☆☆ 正常終了 ☆☆☆☆☆ \n" VT_RST); if (this->getItemId == GI_HEART_PIECE) { - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; // "Ah recovery!" (?) osSyncPrintf(VT_FGCOL(GREEN) "☆☆☆☆☆ あぁ回復! ☆☆☆☆☆ \n" VT_RST); } diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 7da8447c0..2de7113d2 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -9499,7 +9499,7 @@ void func_80843AE8(PlayState* play, Player* this) { LinkAnimation_Change(play, &this->skelAnime, &gPlayerAnim_link_derth_rebirth, 1.0f, 99.0f, Animation_GetLastFrame(&gPlayerAnim_link_derth_rebirth), ANIMMODE_ONCE, 0.0f); } - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; this->av2.actionVar2 = -1; } } else if (gSaveContext.healthAccumulator == 0) { @@ -14581,20 +14581,20 @@ void Player_Action_8084EAC0(Player* this, PlayState* play) { rand = 3; } - if ((rand < 0) && (gSaveContext.health <= 0x10)) { + if ((rand < 0) && (gSaveContext.health <= FULL_HEART_HEALTH)) { rand = 3; } if (rand < 0) { - Health_ChangeBy(play, -0x10); + Health_ChangeBy(play, -FULL_HEART_HEALTH); } else { - gSaveContext.healthAccumulator = rand * 0x10; + gSaveContext.healthAccumulator = rand * FULL_HEART_HEALTH; } } else { s32 sp28 = D_808549FC[this->itemAction - PLAYER_IA_BOTTLE_POTION_RED]; if (sp28 & 1) { - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; } if (sp28 & 2) { @@ -14738,7 +14738,7 @@ void Player_Action_8084EED8(Player* this, PlayState* play) { Player_PlaySfx(this, NA_SE_EV_BOTTLE_CAP_OPEN); Player_PlaySfx(this, NA_SE_EV_FIATY_HEAL - SFX_FLAG); } else if (LinkAnimation_OnFrame(&this->skelAnime, 47.0f)) { - gSaveContext.healthAccumulator = 0x140; + gSaveContext.healthAccumulator = MAX_HEALTH; } } diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index 9e58f5e31..1870e760d 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -2209,7 +2209,7 @@ void FileChoose_DrawFileInfo(GameState* thisx, s16 fileIndex, s16 isActive) { gDPSetEnvColor(POLY_OPA_DISP++, heartBorder.r, heartBorder.g, heartBorder.b, 255); } - i = Save_GetSaveMetaInfo(fileIndex)->healthCapacity / 0x10; + i = Save_GetSaveMetaInfo(fileIndex)->healthCapacity / FULL_HEART_HEALTH; if (CVarGetInteger(CVAR_ENHANCEMENT("FileSelectMoreInfo"), 0) == 0 || this->menuMode != FS_MENU_MODE_SELECT) { // draw hearts diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_debug.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_debug.c index 2eb5e15bb..b79da892f 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_debug.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_debug.c @@ -140,7 +140,7 @@ void KaleidoScope_DrawDebugEditor(PlayState* play) { gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 0); // Current Health Quarter (X / 4) - KaleidoScope_DrawDigit(play, (gSaveContext.health % 0x10) / 4, 194, 15); + KaleidoScope_DrawDigit(play, (gSaveContext.health % FULL_HEART_HEALTH) / 4, 194, 15); gDPPipeSync(POLY_OPA_DISP++); gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, 255); @@ -169,7 +169,7 @@ void KaleidoScope_DrawDebugEditor(PlayState* play) { // Health capacity spD8[2] = 0; - spD8[3] = gSaveContext.healthCapacity / 0x10; + spD8[3] = gSaveContext.healthCapacity / FULL_HEART_HEALTH; while (spD8[3] >= 10) { spD8[2]++; spD8[3] -= 10; @@ -180,7 +180,7 @@ void KaleidoScope_DrawDebugEditor(PlayState* play) { // Health spD8[2] = 0; - spD8[3] = gSaveContext.health / 0x10; + spD8[3] = gSaveContext.health / FULL_HEART_HEALTH; while (spD8[3] >= 10) { spD8[2]++; spD8[3] -= 10; @@ -368,15 +368,15 @@ void KaleidoScope_DrawDebugEditor(PlayState* play) { case 1: if (CHECK_BTN_ALL(input->press.button, BTN_CUP) || CHECK_BTN_ALL(input->press.button, BTN_CLEFT)) { - gSaveContext.healthCapacity -= 0x10; - if (gSaveContext.healthCapacity < 0x30) { - gSaveContext.healthCapacity = 0x30; + gSaveContext.healthCapacity -= FULL_HEART_HEALTH; + if (gSaveContext.healthCapacity < STARTING_HEALTH) { + gSaveContext.healthCapacity = STARTING_HEALTH; } } else if (CHECK_BTN_ALL(input->press.button, BTN_CDOWN) || CHECK_BTN_ALL(input->press.button, BTN_CRIGHT)) { - gSaveContext.healthCapacity += 0x10; - if (gSaveContext.healthCapacity >= 0x140) { - gSaveContext.healthCapacity = 0x140; + gSaveContext.healthCapacity += FULL_HEART_HEALTH; + if (gSaveContext.healthCapacity >= MAX_HEALTH) { + gSaveContext.healthCapacity = MAX_HEALTH; } } break; @@ -387,9 +387,9 @@ void KaleidoScope_DrawDebugEditor(PlayState* play) { } else if (CHECK_BTN_ALL(input->press.button, BTN_CRIGHT)) { Health_ChangeBy(play, 4); } else if (CHECK_BTN_ALL(input->press.button, BTN_CUP)) { - Health_ChangeBy(play, -0x10); + Health_ChangeBy(play, -FULL_HEART_HEALTH); } else if (CHECK_BTN_ALL(input->press.button, BTN_CDOWN)) { - Health_ChangeBy(play, 0x10); + Health_ChangeBy(play, FULL_HEART_HEALTH); } break; diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c index 14f749d43..627300036 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c @@ -4773,8 +4773,9 @@ void KaleidoScope_Update(PlayState* play) { // Reset frame counter to prevent autosave on respawn play->gameplayFrames = 0; gSaveContext.nextTransitionType = TRANS_TYPE_FADE_BLACK; - gSaveContext.health = - CVarGetInteger(CVAR_ENHANCEMENT("FullHealthSpawn"), 0) ? gSaveContext.healthCapacity : 0x30; + gSaveContext.health = CVarGetInteger(CVAR_ENHANCEMENT("FullHealthSpawn"), 0) + ? gSaveContext.healthCapacity + : STARTING_HEALTH; Audio_QueueSeqCmd(0xF << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0xA); gSaveContext.healthAccumulator = 0; gSaveContext.magicState = MAGIC_STATE_IDLE; From da6cf439d68bf9ab6a2e6bd4a527a9f62491edd1 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Mon, 24 Nov 2025 13:48:56 -0500 Subject: [PATCH 04/15] Modularize equipment hand patch hooks (#5876) * Modularize equipment hand hooks * Remove unnecessary include * More efficient hammer hand hook * More efficient equipment visible hook * Add declarations of patching/resetting functions up front * Remove forward declarations * Make mod file self-contained --- soh/soh/Enhancements/Fixes/HammerHandFix.cpp | 38 ++++++++ .../Graphics/AgeDependentEquipment.cpp | 86 +++++++++++++++++++ soh/soh/Enhancements/mods.cpp | 71 --------------- soh/soh/Enhancements/mods.h | 1 - soh/soh/SohGui/SohMenuEnhancements.cpp | 2 - 5 files changed, 124 insertions(+), 74 deletions(-) create mode 100644 soh/soh/Enhancements/Fixes/HammerHandFix.cpp create mode 100644 soh/soh/Enhancements/Graphics/AgeDependentEquipment.cpp diff --git a/soh/soh/Enhancements/Fixes/HammerHandFix.cpp b/soh/soh/Enhancements/Fixes/HammerHandFix.cpp new file mode 100644 index 000000000..5cee8c30d --- /dev/null +++ b/soh/soh/Enhancements/Fixes/HammerHandFix.cpp @@ -0,0 +1,38 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "macros.h" +#include "soh/ResourceManagerHelpers.h" +#include "objects/object_link_boy/object_link_boy.h" +extern SaveContext gSaveContext; +} + +static constexpr int32_t CVAR_HAMMER_HAND_DEFAULT = 0; +#define CVAR_HAMMER_HAND_NAME CVAR_ENHANCEMENT("FixHammerHand") +#define CVAR_HAMMER_HAND_VALUE CVarGetInteger(CVAR_HAMMER_HAND_NAME, CVAR_HAMMER_HAND_DEFAULT) + +static void FixHammerHand() { + if (LINK_IS_ADULT) { + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand1", 92, + gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand2", 93, gsSPEndDisplayList()); + } +} + +static void ResetHammerHand() { + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand2"); +} + +static void RegisterHammerHandFix() { + if (CVAR_HAMMER_HAND_VALUE) { + FixHammerHand(); + } else { + ResetHammerHand(); + } + + COND_HOOK(OnSceneInit, CVAR_HAMMER_HAND_VALUE, [](int32_t) { FixHammerHand(); }); +} + +static RegisterShipInitFunc initFunc(RegisterHammerHandFix, { CVAR_HAMMER_HAND_NAME }); diff --git a/soh/soh/Enhancements/Graphics/AgeDependentEquipment.cpp b/soh/soh/Enhancements/Graphics/AgeDependentEquipment.cpp new file mode 100644 index 000000000..e8b0d1270 --- /dev/null +++ b/soh/soh/Enhancements/Graphics/AgeDependentEquipment.cpp @@ -0,0 +1,86 @@ +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "macros.h" +#include "soh/ResourceManagerHelpers.h" +#include "objects/object_link_boy/object_link_boy.h" +#include "objects/object_link_child/object_link_child.h" +extern SaveContext gSaveContext; +} + +static constexpr int32_t CVAR_AGE_EQUIPMENT_DEFAULT = 0; +#define CVAR_AGE_EQUIPMENT_NAME CVAR_ENHANCEMENT("EquipmentAlwaysVisible") +#define CVAR_AGE_EQUIPMENT_VALUE CVarGetInteger(CVAR_AGE_EQUIPMENT_NAME, CVAR_AGE_EQUIPMENT_DEFAULT) + +static void ResetAdultHands() { + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword2"); + ResourceMgr_UnpatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife1"); + ResourceMgr_UnpatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife2"); +} + +static void ResetChildHands() { + ResourceMgr_UnpatchGfxByName(gLinkChildLeftFistAndKokiriSwordNearDL, "adultKokiriSword"); + ResourceMgr_UnpatchGfxByName(gLinkChildRightHandHoldingSlingshotNearDL, "adultSlingshot"); + ResourceMgr_UnpatchGfxByName(gLinkChildLeftFistAndBoomerangNearDL, "adultBoomerang"); + ResourceMgr_UnpatchGfxByName(gLinkChildRightFistAndDekuShieldNearDL, "adultDekuShield"); +} + +static void MakeEquipmentAlwaysVisible() { + if (LINK_IS_CHILD) { + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer1", 92, + gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer2", 93, gsSPEndDisplayList()); + ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot1", 84, + gsSPDisplayListOTRFilePath(gLinkChildRightHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot2", 85, + gsSPEndDisplayList()); + ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow1", 51, + gsSPDisplayListOTRFilePath(gLinkChildRightHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow2", 52, gsSPEndDisplayList()); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword1", 104, + gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword2", 105, + gsSPEndDisplayList()); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword1", 79, + gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword2", 80, gsSPEndDisplayList()); + ResourceMgr_PatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife1", 76, + gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); + ResourceMgr_PatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife2", 77, + gsSPEndDisplayList()); + ResetChildHands(); + } else { + ResourceMgr_PatchGfxByName(gLinkChildLeftFistAndKokiriSwordNearDL, "adultKokiriSword", 13, + gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkChildRightHandHoldingSlingshotNearDL, "adultSlingshot", 13, + gsSPDisplayListOTRFilePath(gLinkAdultRightHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkChildLeftFistAndBoomerangNearDL, "adultBoomerang", 50, + gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL)); + ResourceMgr_PatchGfxByName(gLinkChildRightFistAndDekuShieldNearDL, "adultDekuShield", 49, + gsSPDisplayListOTRFilePath(gLinkAdultRightHandClosedNearDL)); + ResetAdultHands(); + } +} + +static void RegisterAgeDependentEquipmentHook() { + if (CVAR_AGE_EQUIPMENT_VALUE) { + MakeEquipmentAlwaysVisible(); + } else { + ResetAdultHands(); + ResetChildHands(); + } + + COND_HOOK(OnSceneInit, CVAR_AGE_EQUIPMENT_VALUE, [](int32_t) { MakeEquipmentAlwaysVisible(); }); +} + +static RegisterShipInitFunc initFunc(RegisterAgeDependentEquipmentHook, { CVAR_AGE_EQUIPMENT_NAME }); diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index f4ad7ee47..205c35ae6 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -25,8 +25,6 @@ #include "src/overlays/actors/ovl_Door_Shutter/z_door_shutter.h" #include "src/overlays/actors/ovl_Door_Gerudo/z_door_gerudo.h" #include "src/overlays/actors/ovl_En_Elf/z_en_elf.h" -#include "objects/object_link_boy/object_link_boy.h" -#include "objects/object_link_child/object_link_child.h" #include "soh_assets.h" #include "kaleido.h" @@ -228,74 +226,6 @@ void UpdateHyperEnemiesState() { } } -void UpdatePatchHand() { - if ((CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) && LINK_IS_CHILD) { - ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer1", 92, - gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); - ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer2", 93, gsSPEndDisplayList()); - ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot1", 84, - gsSPDisplayListOTRFilePath(gLinkChildRightHandClosedNearDL)); - ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot2", 85, - gsSPEndDisplayList()); - ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow1", 51, - gsSPDisplayListOTRFilePath(gLinkChildRightHandClosedNearDL)); - ResourceMgr_PatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow2", 52, gsSPEndDisplayList()); - ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword1", 104, - gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); - ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword2", 105, - gsSPEndDisplayList()); - ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword1", 79, - gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); - ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword2", 80, gsSPEndDisplayList()); - ResourceMgr_PatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife1", 76, - gsSPDisplayListOTRFilePath(gLinkChildLeftFistNearDL)); - ResourceMgr_PatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife2", 77, - gsSPEndDisplayList()); - - } else { - ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer1"); - ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "childHammer2"); - ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot1"); - ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingHookshotNearDL, "childHookshot2"); - ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow1"); - ResourceMgr_UnpatchGfxByName(gLinkAdultRightHandHoldingBowNearDL, "childBow2"); - ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword1"); - ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingMasterSwordNearDL, "childMasterSword2"); - ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword1"); - ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingBgsNearDL, "childBiggoronSword2"); - ResourceMgr_UnpatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife1"); - ResourceMgr_UnpatchGfxByName(gLinkAdultHandHoldingBrokenGiantsKnifeDL, "childBrokenGiantsKnife2"); - } - if ((CVarGetInteger(CVAR_ENHANCEMENT("EquipmentAlwaysVisible"), 0)) && LINK_IS_ADULT) { - ResourceMgr_PatchGfxByName(gLinkChildLeftFistAndKokiriSwordNearDL, "adultKokiriSword", 13, - gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL)); - ResourceMgr_PatchGfxByName(gLinkChildRightHandHoldingSlingshotNearDL, "adultSlingshot", 13, - gsSPDisplayListOTRFilePath(gLinkAdultRightHandClosedNearDL)); - ResourceMgr_PatchGfxByName(gLinkChildLeftFistAndBoomerangNearDL, "adultBoomerang", 50, - gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL)); - ResourceMgr_PatchGfxByName(gLinkChildRightFistAndDekuShieldNearDL, "adultDekuShield", 49, - gsSPDisplayListOTRFilePath(gLinkAdultRightHandClosedNearDL)); - } else { - ResourceMgr_UnpatchGfxByName(gLinkChildLeftFistAndKokiriSwordNearDL, "adultKokiriSword"); - ResourceMgr_UnpatchGfxByName(gLinkChildRightHandHoldingSlingshotNearDL, "adultSlingshot"); - ResourceMgr_UnpatchGfxByName(gLinkChildLeftFistAndBoomerangNearDL, "adultBoomerang"); - ResourceMgr_UnpatchGfxByName(gLinkChildRightFistAndDekuShieldNearDL, "adultDekuShield"); - } - if (CVarGetInteger("gEnhancements.FixHammerHand", 0) && LINK_IS_ADULT) { - ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand1", 92, - gsSPDisplayListOTRFilePath(gLinkAdultLeftHandClosedNearDL)); - ResourceMgr_PatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand2", 93, gsSPEndDisplayList()); - } else { - ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand1"); - ResourceMgr_UnpatchGfxByName(gLinkAdultLeftHandHoldingHammerNearDL, "hammerHand2"); - } -} - -void RegisterPatchHandHandler() { - GameInteractor::Instance->RegisterGameHook( - [](int32_t sceneNum) { UpdatePatchHand(); }); -} - // this map is used for enemies that can be uniquely identified by their id // and that are always counted // enemies that can't be uniquely identified by their id @@ -549,6 +479,5 @@ void InitMods() { UpdateHyperEnemiesState(); RegisterEnemyDefeatCounts(); RegisterRandomizedEnemySizes(); - RegisterPatchHandHandler(); RandoKaleido_RegisterHooks(); } diff --git a/soh/soh/Enhancements/mods.h b/soh/soh/Enhancements/mods.h index 7ba012737..2aa9c63be 100644 --- a/soh/soh/Enhancements/mods.h +++ b/soh/soh/Enhancements/mods.h @@ -14,7 +14,6 @@ void UpdatePermanentHeartLossState(); void UpdateHyperEnemiesState(); void UpdateHyperBossesState(); void InitMods(); -void UpdatePatchHand(); void SwitchAge(); #ifdef __cplusplus diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index b528c6f45..bfbc9bab3 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -581,7 +581,6 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Show Age-Dependent Equipment", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("EquipmentAlwaysVisible")) .RaceDisable(false) - .Callback([](WidgetInfo& info) { UpdatePatchHand(); }) .Options(CheckboxOptions().Tooltip("Makes all equipment visible, regardless of age.")); AddWidget(path, "Scale Adult Equipment as Child", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ScaleAdultEquipmentAsChild")) @@ -1074,7 +1073,6 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Fix Hand Holding Hammer", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("FixHammerHand")) .RaceDisable(false) - .Callback([](WidgetInfo& info) { UpdatePatchHand(); }) .Options(CheckboxOptions().Tooltip( "Fixes Adult Link having a backwards Left hand when holding the Megaton Hammer.")); AddWidget(path, "Fix Vanishing Paths", WIDGET_CVAR_COMBOBOX) From bc48fa84fd0f3980171a52f61082549e4fd39439 Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Tue, 25 Nov 2025 12:00:09 -0500 Subject: [PATCH 05/15] A bit of cleanup on multiple hooks (#5879) * A bit of cleanup on BGS hook * Cleanup on a few more hooks, fix itemId ref * Use direct pointer to params * Revert item receive ID hook setup * Remove callbacks from menu GUI * Add comments explaining conditions of permanent loss methods * Move custom skeletons hook to subfolder * Clang format * Shorten comment * Remove unnecessary re-register function call --- .../Difficulty/PermanentLosses.cpp | 18 ++++++++++----- .../Enhancements/ExtraModes/MirroredWorld.cpp | 22 ++++++++++--------- soh/soh/Enhancements/ExtraModes/RupeeDash.cpp | 4 ++-- soh/soh/Enhancements/ExtraModes/ShadowTag.cpp | 6 ++--- .../Enhancements/Fixes/BrokenGiantsKnife.cpp | 4 ++-- .../Enhancements/Fixes/DekuNutUpgradeFix.cpp | 6 ++--- soh/soh/Enhancements/Fixes/DirtPathFix.cpp | 9 ++++++-- .../Enhancements/Graphics/ToTMedallions.cpp | 15 +++++-------- soh/soh/Enhancements/QoL/DaytimeGS.cpp | 4 ++-- soh/soh/Enhancements/QoL/OpenAllHours.cpp | 13 +++++------ .../{ => cosmetics}/CustomSkeletons.cpp | 0 soh/soh/Enhancements/mods.h | 4 ---- soh/soh/SohGui/SohMenuEnhancements.cpp | 12 ---------- 13 files changed, 55 insertions(+), 62 deletions(-) rename soh/soh/Enhancements/{ => cosmetics}/CustomSkeletons.cpp (100%) diff --git a/soh/soh/Enhancements/Difficulty/PermanentLosses.cpp b/soh/soh/Enhancements/Difficulty/PermanentLosses.cpp index 32b00057e..cab0aa7d4 100644 --- a/soh/soh/Enhancements/Difficulty/PermanentLosses.cpp +++ b/soh/soh/Enhancements/Difficulty/PermanentLosses.cpp @@ -1,5 +1,4 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/Enhancements/mods.h" #include "soh/OTRGlobals.h" #include "soh/SaveManager.h" #include "soh/ShipInit.hpp" @@ -23,9 +22,14 @@ static constexpr int32_t CVAR_DELETE_FILE_DEFAULT = 0; static bool hasAffectedHealth = false; -void UpdatePermanentHeartLossState() { - if (!GameInteractor::IsSaveLoaded() || !hasAffectedHealth || CVAR_PERM_HEART_LOSS_VALUE) +static void UpdatePermanentHeartLossState() { + // Reset Link's hearts to the normal value without permanent losses. Only applies if all of the following are true: + // - A saved game is playing + // - The "Permanent Heart Loss" setting is turned off + // - The player has lost at least one Heart Container + if (!GameInteractor::IsSaveLoaded() || !hasAffectedHealth || CVAR_PERM_HEART_LOSS_VALUE) { return; + } uint8_t heartContainers = gSaveContext.ship.stats.heartContainers; // each worth 16 health uint8_t heartPieces = gSaveContext.ship.stats.heartPieces; // each worth 4 health, but only in groups of 4 @@ -39,8 +43,10 @@ void UpdatePermanentHeartLossState() { } static void UpdateHealthCapacity() { - if (!GameInteractor::IsSaveLoaded()) + // Applies permanent losses of Heart Containers to Link's health. Only applies when a saved game is playing. + if (!GameInteractor::IsSaveLoaded()) { return; + } if (gSaveContext.healthCapacity > 16 && gSaveContext.healthCapacity - gSaveContext.health >= 16) { gSaveContext.healthCapacity -= 16; @@ -50,8 +56,9 @@ static void UpdateHealthCapacity() { } static void DeleteFileOnDeath() { - if (!GameInteractor::IsSaveLoaded() || gPlayState == NULL) + if (!GameInteractor::IsSaveLoaded() || gPlayState == NULL) { return; + } if (gPlayState->gameOverCtx.state == GAMEOVER_DEATH_MENU && gPlayState->pauseCtx.state == 9) { SaveManager::Instance->DeleteZeldaFile(gSaveContext.fileNum); @@ -63,6 +70,7 @@ static void DeleteFileOnDeath() { } static void RegisterPermanentHeartLoss() { + UpdatePermanentHeartLossState(); COND_HOOK(OnPlayerUpdate, CVAR_PERM_HEART_LOSS_VALUE, UpdateHealthCapacity); } diff --git a/soh/soh/Enhancements/ExtraModes/MirroredWorld.cpp b/soh/soh/Enhancements/ExtraModes/MirroredWorld.cpp index 2f4f9e4d0..723a5f45e 100644 --- a/soh/soh/Enhancements/ExtraModes/MirroredWorld.cpp +++ b/soh/soh/Enhancements/ExtraModes/MirroredWorld.cpp @@ -3,13 +3,12 @@ #include "soh/Enhancements/randomizer/3drando/random.hpp" #include "soh/Enhancements/randomizer/context.h" #include "soh/Enhancements/enhancementTypes.h" -#include "soh/Enhancements/mods.h" #include "soh/ResourceManagerHelpers.h" #include "soh/ShipInit.hpp" extern "C" { -#include "variables.h" extern SaveContext gSaveContext; +extern PlayState* gPlayState; } static constexpr MirroredWorldMode CVAR_MIRRORED_WORLD_DEFAULT = MIRRORED_WORLD_OFF; @@ -55,13 +54,7 @@ static bool MirroredWorld_ShouldApply(int32_t sceneNum) { } } -static void RegisterMirroredWorld() { - COND_HOOK(OnSceneInit, CVAR_MIRRORED_WORLD_MODE_VALUE, UpdateMirrorModeState); -} - -static RegisterShipInitFunc initFunc(RegisterMirroredWorld, { CVAR_MIRRORED_WORLD_MODE_NAME }); - -void UpdateMirrorModeState(int32_t sceneNum) { +static void UpdateMirrorModeState(int32_t sceneNum) { bool nextMirroredWorld = MirroredWorld_ShouldApply(sceneNum); if (prevMirroredWorld == nextMirroredWorld) { @@ -73,8 +66,17 @@ void UpdateMirrorModeState(int32_t sceneNum) { CVarSetInteger(CVAR_MIRRORED_WORLD_NAME, 1); } else { CVarClear(CVAR_MIRRORED_WORLD_NAME); - RegisterMirroredWorld(); } ApplyMirrorWorldGfxPatches(); } + +static void RegisterMirroredWorld() { + if (gPlayState != NULL) { + UpdateMirrorModeState(gPlayState->sceneNum); + } + + COND_HOOK(OnSceneInit, CVAR_MIRRORED_WORLD_MODE_VALUE, UpdateMirrorModeState); +} + +static RegisterShipInitFunc initFunc(RegisterMirroredWorld, { CVAR_MIRRORED_WORLD_MODE_NAME }); diff --git a/soh/soh/Enhancements/ExtraModes/RupeeDash.cpp b/soh/soh/Enhancements/ExtraModes/RupeeDash.cpp index 20bdc16bc..2fc224152 100644 --- a/soh/soh/Enhancements/ExtraModes/RupeeDash.cpp +++ b/soh/soh/Enhancements/ExtraModes/RupeeDash.cpp @@ -17,7 +17,7 @@ static constexpr int32_t CVAR_RUPEE_DASH_INTERVAL_DEFAULT = 5; #define CVAR_RUPEE_DASH_INTERVAL_TIME \ CVarGetInteger(CVAR_RUPEE_DASH_INTERVAL_NAME, CVAR_RUPEE_DASH_INTERVAL_DEFAULT) * 20 -void UpdateRupeeDash() { +static void UpdateRupeeDash() { // Initialize Timer static uint16_t rupeeDashTimer = 0; @@ -36,7 +36,7 @@ void UpdateRupeeDash() { } } -void RegisterRupeeDash() { +static void RegisterRupeeDash() { COND_HOOK(OnPlayerUpdate, CVAR_RUPEE_DASH_VALUE, UpdateRupeeDash); } diff --git a/soh/soh/Enhancements/ExtraModes/ShadowTag.cpp b/soh/soh/Enhancements/ExtraModes/ShadowTag.cpp index 1b03b6c77..6dc93bfe1 100644 --- a/soh/soh/Enhancements/ExtraModes/ShadowTag.cpp +++ b/soh/soh/Enhancements/ExtraModes/ShadowTag.cpp @@ -16,7 +16,7 @@ static constexpr s8 ROOM_GREEN_POE = 16; static constexpr s8 ROOM_BLUE_POE = 13; static constexpr s8 ROOM_RED_POE = 12; -void OnPlayerUpdateShadowTag() { +static void OnPlayerUpdateShadowTag() { if (gPlayState->sceneNum == SCENE_FOREST_TEMPLE) { switch (gPlayState->roomCtx.curRoom.num) { case ROOM_GREEN_POE: @@ -36,12 +36,12 @@ void OnPlayerUpdateShadowTag() { } } -void ResetShadowTagSpawnTimer() { +static void ResetShadowTagSpawnTimer() { shouldSpawn = true; delayTimer = 60; } -void RegisterShadowTag() { +static void RegisterShadowTag() { COND_HOOK(OnPlayerUpdate, CVAR_SHADOW_TAG_VALUE, OnPlayerUpdateShadowTag); COND_HOOK(OnSceneSpawnActors, true, []() { ResetShadowTagSpawnTimer(); }); COND_HOOK(OnSceneInit, true, [](int16_t) { ResetShadowTagSpawnTimer(); }); diff --git a/soh/soh/Enhancements/Fixes/BrokenGiantsKnife.cpp b/soh/soh/Enhancements/Fixes/BrokenGiantsKnife.cpp index 1bde4e50c..1d4a55f5d 100644 --- a/soh/soh/Enhancements/Fixes/BrokenGiantsKnife.cpp +++ b/soh/soh/Enhancements/Fixes/BrokenGiantsKnife.cpp @@ -13,7 +13,7 @@ static constexpr int32_t CVAR_BGS_FIX_DEFAULT = 0; #define CVAR_BGS_FIX_NAME CVAR_ENHANCEMENT("FixBrokenGiantsKnife") #define CVAR_BGS_FIX_VALUE CVarGetInteger(CVAR_BGS_FIX_NAME, CVAR_BGS_FIX_DEFAULT) -void OnReceiveBrokenGiantsKnife(GetItemEntry itemEntry) { +static void OnReceiveBrokenGiantsKnife(GetItemEntry itemEntry) { if (itemEntry.itemId != ITEM_SWORD_BGS) { return; } @@ -39,7 +39,7 @@ void OnReceiveBrokenGiantsKnife(GetItemEntry itemEntry) { } } -void RegisterBrokenGiantsKnifeFix() { +static void RegisterBrokenGiantsKnifeFix() { // If enhancement is off, flag should be handled exclusively by vanilla behaviour COND_HOOK(OnItemReceive, CVAR_BGS_FIX_VALUE || IS_RANDO, OnReceiveBrokenGiantsKnife); } diff --git a/soh/soh/Enhancements/Fixes/DekuNutUpgradeFix.cpp b/soh/soh/Enhancements/Fixes/DekuNutUpgradeFix.cpp index 8096f2075..23e0a1fd6 100644 --- a/soh/soh/Enhancements/Fixes/DekuNutUpgradeFix.cpp +++ b/soh/soh/Enhancements/Fixes/DekuNutUpgradeFix.cpp @@ -13,7 +13,7 @@ static constexpr int32_t CVAR_NUT_UPGRADE_FIX_DEFAULT = 0; #define CVAR_NUT_UPGRADE_FIX_NAME CVAR_ENHANCEMENT("DekuNutUpgradeFix") #define CVAR_NUT_UPGRADE_FIX_VALUE CVarGetInteger(CVAR_NUT_UPGRADE_FIX_NAME, CVAR_NUT_UPGRADE_FIX_DEFAULT) -void DekuNutUpgradeFixAtForestStage(bool* should) { +static void DekuNutUpgradeFixAtForestStage(bool* should) { // This check is needed because of an intentional fallthrough at the source if (Player_GetMask(gPlayState) == PLAYER_MASK_SKULL) { return; @@ -30,11 +30,11 @@ void DekuNutUpgradeFixAtForestStage(bool* should) { } } -void DekuNutUpgradeSetByPoachersSaw(bool* should) { +static void DekuNutUpgradeSetByPoachersSaw(bool* should) { *should = false; } -void RegisterDekuNutUpgradeFix() { +static void RegisterDekuNutUpgradeFix() { COND_VB_SHOULD(VB_POACHERS_SAW_SET_DEKU_NUT_UPGRADE_FLAG, CVAR_NUT_UPGRADE_FIX_VALUE || IS_RANDO, { DekuNutUpgradeSetByPoachersSaw(should); }); COND_VB_SHOULD(VB_DEKU_SCRUBS_REACT_TO_MASK_OF_TRUTH, CVAR_NUT_UPGRADE_FIX_VALUE && !IS_RANDO, diff --git a/soh/soh/Enhancements/Fixes/DirtPathFix.cpp b/soh/soh/Enhancements/Fixes/DirtPathFix.cpp index 0ceafc9c1..ae49f45bf 100644 --- a/soh/soh/Enhancements/Fixes/DirtPathFix.cpp +++ b/soh/soh/Enhancements/Fixes/DirtPathFix.cpp @@ -1,13 +1,14 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/Enhancements/enhancementTypes.h" -#include "soh/Enhancements/mods.h" #include "soh/ShipInit.hpp" +extern "C" PlayState* gPlayState; + static constexpr ZFightingFixType CVAR_DIRT_PATH_DEFAULT = ZFIGHT_FIX_DISABLED; #define CVAR_DIRT_PATH_NAME CVAR_ENHANCEMENT("SceneSpecificDirtPathFix") #define CVAR_DIRT_PATH_VALUE CVarGetInteger(CVAR_DIRT_PATH_NAME, CVAR_DIRT_PATH_DEFAULT) -void DirtPathFix_UpdateZFightingMode(int32_t sceneNum) { +static void DirtPathFix_UpdateZFightingMode(int32_t sceneNum) { switch (sceneNum) { case SCENE_HYRULE_FIELD: case SCENE_KOKIRI_FOREST: @@ -20,6 +21,10 @@ void DirtPathFix_UpdateZFightingMode(int32_t sceneNum) { } static void RegisterDirtPathFix() { + if (gPlayState != NULL) { + DirtPathFix_UpdateZFightingMode(gPlayState->sceneNum); + } + COND_HOOK(OnTransitionEnd, CVAR_DIRT_PATH_VALUE, DirtPathFix_UpdateZFightingMode); } diff --git a/soh/soh/Enhancements/Graphics/ToTMedallions.cpp b/soh/soh/Enhancements/Graphics/ToTMedallions.cpp index 13c752a62..5a7809836 100644 --- a/soh/soh/Enhancements/Graphics/ToTMedallions.cpp +++ b/soh/soh/Enhancements/Graphics/ToTMedallions.cpp @@ -1,5 +1,4 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/Enhancements/mods.h" #include "soh/ShipInit.hpp" extern "C" { @@ -102,14 +101,6 @@ static void ResetToTMedallions() { endGrayscale.RevertPatch(); } -void UpdateToTMedallions() { - if (CVAR_TOT_MEDALLION_COLORS_VALUE) { - PatchToTMedallions(); - } else { - ResetToTMedallions(); - } -} - static void CheckTempleOfTime(int16_t sceneNum) { if (sceneNum != SCENE_TEMPLE_OF_TIME) { return; @@ -118,6 +109,12 @@ static void CheckTempleOfTime(int16_t sceneNum) { } static void RegisterToTMedallions() { + if (CVAR_TOT_MEDALLION_COLORS_VALUE) { + PatchToTMedallions(); + } else { + ResetToTMedallions(); + } + COND_HOOK(OnItemReceive, CVAR_TOT_MEDALLION_COLORS_VALUE, [](GetItemEntry) { if (gPlayState) { CheckTempleOfTime(gPlayState->sceneNum); diff --git a/soh/soh/Enhancements/QoL/DaytimeGS.cpp b/soh/soh/Enhancements/QoL/DaytimeGS.cpp index 6d4474274..f1aa322ce 100644 --- a/soh/soh/Enhancements/QoL/DaytimeGS.cpp +++ b/soh/soh/Enhancements/QoL/DaytimeGS.cpp @@ -21,7 +21,7 @@ struct DayTimeGoldSkulltulas { using DayTimeGoldSkulltulasList = std::vector; -void OnSpawnNighttimeGoldSkulltula() { +static void OnSpawnNighttimeGoldSkulltula() { // Gold Skulltulas that are not part of the scene actor list during the day // Actor values copied from the night time scene actor list static const DayTimeGoldSkulltulasList dayTimeGoldSkulltulas = { @@ -62,7 +62,7 @@ void OnSpawnNighttimeGoldSkulltula() { } } -void RegisterDaytimeGoldSkultullas() { +static void RegisterDaytimeGoldSkultullas() { COND_HOOK(OnSceneSpawnActors, CVAR_DAYTIME_GS_VALUE, OnSpawnNighttimeGoldSkulltula); } diff --git a/soh/soh/Enhancements/QoL/OpenAllHours.cpp b/soh/soh/Enhancements/QoL/OpenAllHours.cpp index 5b6d39ad9..b46db53c3 100644 --- a/soh/soh/Enhancements/QoL/OpenAllHours.cpp +++ b/soh/soh/Enhancements/QoL/OpenAllHours.cpp @@ -24,12 +24,10 @@ static constexpr int32_t DOOR_NIGHT_KAK_POTION_SHOP = 7822; static constexpr int32_t DOOR_NIGHT_KAK_POTION_SHOP_BACK = 8846; static void OpenAllHours(void* refActor) { - Actor* actor = static_cast(refActor); - if (actor->id != ACTOR_EN_DOOR) { - return; - } + EnDoor* enDoor = static_cast(refActor); + s16* params = &enDoor->actor.params; - switch (actor->params) { + switch (*params) { case DOOR_DAY_CHEST_GAME: case DOOR_DAY_BOMBCHU_SHOP: case DOOR_NIGHT_POTION_SHOP: @@ -40,8 +38,7 @@ static void OpenAllHours(void* refActor) { case DOOR_NIGHT_KAK_BAZAAR: case DOOR_NIGHT_KAK_POTION_SHOP: case DOOR_NIGHT_KAK_POTION_SHOP_BACK: { - actor->params = (actor->params & 0xFC00) | (DOOR_SCENEEXIT << 7) | 0x3F; - EnDoor* enDoor = static_cast(refActor); + *params = (*params & 0xFC00) | (DOOR_SCENEEXIT << 7) | 0x3F; EnDoor_SetupType(enDoor, gPlayState); break; } @@ -54,7 +51,7 @@ static void RegisterOpenAllHours() { bool overworldDoorsOpen = !IS_RANDO || !OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LOCK_OVERWORLD_DOORS); - COND_HOOK(OnActorInit, CVAR_OPEN_ALL_HOURS_VALUE && overworldDoorsOpen, OpenAllHours); + COND_ID_HOOK(OnActorInit, ACTOR_EN_DOOR, CVAR_OPEN_ALL_HOURS_VALUE && overworldDoorsOpen, OpenAllHours); } static RegisterShipInitFunc initFunc(RegisterOpenAllHours, { CVAR_OPEN_ALL_HOURS_NAME, "IS_RANDO" }); diff --git a/soh/soh/Enhancements/CustomSkeletons.cpp b/soh/soh/Enhancements/cosmetics/CustomSkeletons.cpp similarity index 100% rename from soh/soh/Enhancements/CustomSkeletons.cpp rename to soh/soh/Enhancements/cosmetics/CustomSkeletons.cpp diff --git a/soh/soh/Enhancements/mods.h b/soh/soh/Enhancements/mods.h index 2aa9c63be..fe5083ce2 100644 --- a/soh/soh/Enhancements/mods.h +++ b/soh/soh/Enhancements/mods.h @@ -7,10 +7,6 @@ extern "C" { #endif -void DirtPathFix_UpdateZFightingMode(int32_t sceneNum); -void UpdateMirrorModeState(int32_t sceneNum); -void UpdateToTMedallions(); -void UpdatePermanentHeartLossState(); void UpdateHyperEnemiesState(); void UpdateHyperBossesState(); void InitMods(); diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index bfbc9bab3..e73aa59cc 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -601,7 +601,6 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Color Temple of Time's Medallions", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("ToTMedallionsColors")) .RaceDisable(false) - .Callback([](WidgetInfo& info) { UpdateToTMedallions(); }) .Options(CheckboxOptions().Tooltip( "When Medallions are collected, the Medallion imprints around the Master Sword Pedestal in the Temple " "of Time will become colored-in.")); @@ -1078,11 +1077,6 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Fix Vanishing Paths", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_ENHANCEMENT("SceneSpecificDirtPathFix")) .RaceDisable(false) - .Callback([](WidgetInfo& info) { - if (gPlayState != NULL) { - DirtPathFix_UpdateZFightingMode(gPlayState->sceneNum); - } - }) .Options( ComboboxOptions() .ComboMap(zFightingOptions) @@ -1179,7 +1173,6 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Health", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Permanent Heart Loss", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("PermanentHeartLoss")) - .Callback([](WidgetInfo& info) { UpdatePermanentHeartLossState(); }) .Options(CheckboxOptions().Tooltip( "When you lose 4 quarters of a heart you will permanently lose that Heart Container.\n\n" "Disabling this after the fact will restore your Heart Containers.")); @@ -1522,11 +1515,6 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "Mirrored World", WIDGET_CVAR_COMBOBOX) .CVar(CVAR_ENHANCEMENT("MirroredWorldMode")) - .Callback([](WidgetInfo& info) { - if (gPlayState != NULL) { - UpdateMirrorModeState(gPlayState->sceneNum); - } - }) .Options( ComboboxOptions() .DefaultIndex(MIRRORED_WORLD_OFF) From 9cd31099e29589b1afb3d60f2b14a9d11116c3b5 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Sun, 30 Nov 2025 19:17:00 -0600 Subject: [PATCH 06/15] Additional Anchor functionality: (#5999) - Returned support for custom tunic colors - Ocarina playback now audible - Fixed movement translation issue when climbing or going through crawlspaces - Fixed issue preventing some items from being visible in Dummy hands (namely ocarina) - Fixed stick length not correctly syncing --- .../GameInteractor_HookTable.h | 1 + .../game-interactor/GameInteractor_Hooks.cpp | 4 ++ .../game-interactor/GameInteractor_Hooks.h | 1 + .../vanilla-behavior/GIVanillaBehavior.h | 9 +++ soh/soh/Network/Anchor/Anchor.cpp | 2 + soh/soh/Network/Anchor/Anchor.h | 9 +++ soh/soh/Network/Anchor/DummyPlayer.cpp | 29 ++++++++- soh/soh/Network/Anchor/HookHandlers.cpp | 27 ++++++++ soh/soh/Network/Anchor/Menu.cpp | 5 +- soh/soh/Network/Anchor/Packets/OcarinaSfx.cpp | 65 +++++++++++++++++++ .../Network/Anchor/Packets/PlayerUpdate.cpp | 6 ++ .../Anchor/Packets/UpdateClientState.cpp | 2 +- soh/src/code/code_800EC960.c | 1 + soh/src/code/z_player_lib.c | 5 +- 14 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 soh/soh/Network/Anchor/Packets/OcarinaSfx.cpp diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index e8953835d..5da302fe8 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -26,6 +26,7 @@ DEFINE_HOOK(OnPlayerUpdate, ()); DEFINE_HOOK(OnSetDoAction, (uint16_t action)); DEFINE_HOOK(OnPlayerSfx, (u16 sfxId)); DEFINE_HOOK(OnOcarinaSongAction, ()); +DEFINE_HOOK(OnOcarinaNote, (uint8_t note, float modulator, int8_t bend)); DEFINE_HOOK(OnCuccoOrChickenHatch, ()); DEFINE_HOOK(OnShopSlotChange, (uint8_t cursorIndex, int16_t price)); DEFINE_HOOK(OnDungeonKeyUsed, (uint16_t mapIndex)); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 575b8a5ca..2ffb7099e 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -102,6 +102,10 @@ void GameInteractor_ExecuteOnOcarinaSongAction() { GameInteractor::Instance->ExecuteHooks(); } +void GameInteractor_ExecuteOnOcarinaNote(uint8_t note, float modulator, int8_t bend) { + GameInteractor::Instance->ExecuteHooks(note, modulator, bend); +} + void GameInteractor_ExecuteOnCuccoOrChickenHatch() { GameInteractor::Instance->ExecuteHooks(); } diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index 472453135..fe3533f73 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -29,6 +29,7 @@ void GameInteractor_ExecuteOnPlayerUpdate(); void GameInteractor_ExecuteOnSetDoAction(uint16_t action); void GameInteractor_ExecuteOnPlayerSfx(u16 sfxId); void GameInteractor_ExecuteOnOcarinaSongAction(); +void GameInteractor_ExecuteOnOcarinaNote(uint8_t note, float modulator, int8_t bend); void GameInteractor_ExecuteOnCuccoOrChickenHatch(); bool GameInteractor_ShouldActorInit(void* actor); void GameInteractor_ExecuteOnActorInit(void* actor); diff --git a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h index 8806b0a63..d0b017d96 100644 --- a/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h +++ b/soh/soh/Enhancements/game-interactor/vanilla-behavior/GIVanillaBehavior.h @@ -2343,6 +2343,15 @@ typedef enum { // - `*BgHidanKowarerukabe` VB_FIRE_TEMPLE_BOMBABLE_WALL_BREAK, + // #### `result` + // ```c + // true + // ``` + // #### `args` + // - `*Player` + // - `*Color_RGB8` + VB_APPLY_TUNIC_COLOR, + } GIVanillaBehavior; #endif diff --git a/soh/soh/Network/Anchor/Anchor.cpp b/soh/soh/Network/Anchor/Anchor.cpp index e996ac6a0..5852e62e0 100644 --- a/soh/soh/Network/Anchor/Anchor.cpp +++ b/soh/soh/Network/Anchor/Anchor.cpp @@ -110,6 +110,8 @@ void Anchor::ProcessIncomingPacketQueue() { HandlePacket_GameComplete(payload); else if (packetType == GIVE_ITEM) HandlePacket_GiveItem(payload); + else if (packetType == OCARINA_SFX) + HandlePacket_OcarinaSfx(payload); else if (packetType == PLAYER_SFX) HandlePacket_PlayerSfx(payload); else if (packetType == UPDATE_TEAM_STATE) diff --git a/soh/soh/Network/Anchor/Anchor.h b/soh/soh/Network/Anchor/Anchor.h index 93c404617..9eb103230 100644 --- a/soh/soh/Network/Anchor/Anchor.h +++ b/soh/soh/Network/Anchor/Anchor.h @@ -35,6 +35,8 @@ typedef struct { s32 linkAge; PosRot posRot; Vec3s jointTable[24]; + u8 movementFlags; + Vec3s prevTransl; Vec3s upperLimbRot; s8 currentBoots; s8 currentShield; @@ -46,8 +48,12 @@ typedef struct { s8 heldItemAction; u8 modelGroup; s8 invincibilityTimer; + f32 unk_85C; s16 unk_862; s8 actionVar1; + u8 ocarinaNote; + f32 ocarinaModulator; + s8 ocarinaBend; // Ptr to the dummy player Player* player; @@ -84,6 +90,7 @@ class Anchor : public Network { void HandlePacket_EntranceDiscovered(nlohmann::json payload); void HandlePacket_GameComplete(nlohmann::json payload); void HandlePacket_GiveItem(nlohmann::json payload); + void HandlePacket_OcarinaSfx(nlohmann::json payload); void HandlePacket_PlayerSfx(nlohmann::json payload); void HandlePacket_PlayerUpdate(nlohmann::json payload); void HandlePacket_RequestTeamState(nlohmann::json payload); @@ -111,6 +118,7 @@ class Anchor : public Network { inline static const std::string GAME_COMPLETE = "GAME_COMPLETE"; inline static const std::string GIVE_ITEM = "GIVE_ITEM"; inline static const std::string HANDSHAKE = "HANDSHAKE"; + inline static const std::string OCARINA_SFX = "OCARINA_SFX"; inline static const std::string PLAYER_SFX = "PLAYER_SFX"; inline static const std::string PLAYER_UPDATE = "PLAYER_UPDATE"; inline static const std::string REQUEST_TEAM_STATE = "REQUEST_TEAM_STATE"; @@ -148,6 +156,7 @@ class Anchor : public Network { void SendPacket_GameComplete(); void SendPacket_GiveItem(u16 modId, s16 getItemId); void SendPacket_Handshake(); + void SendPacket_OcarinaSfx(uint8_t note, float modulator, int8_t bend); void SendPacket_PlayerSfx(u16 sfxId); void SendPacket_PlayerUpdate(); void SendPacket_RequestTeamState(); diff --git a/soh/soh/Network/Anchor/DummyPlayer.cpp b/soh/soh/Network/Anchor/DummyPlayer.cpp index 3bc085413..8e5df3c8e 100644 --- a/soh/soh/Network/Anchor/DummyPlayer.cpp +++ b/soh/soh/Network/Anchor/DummyPlayer.cpp @@ -122,6 +122,8 @@ void DummyPlayer_Update(Actor* actor, PlayState* play) { Math_Vec3s_Copy(&actor->shape.rot, &client.posRot.rot); Math_Vec3f_Copy(&actor->world.pos, &client.posRot.pos); player->skelAnime.jointTable = client.jointTable; + player->skelAnime.movementFlags = client.movementFlags; + Math_Vec3s_Copy(&player->skelAnime.prevTransl, &client.prevTransl); player->currentBoots = client.currentBoots; player->currentShield = client.currentShield; player->currentTunic = client.currentTunic; @@ -131,15 +133,38 @@ void DummyPlayer_Update(Actor* actor, PlayState* play) { player->heldItemAction = client.heldItemAction; player->invincibilityTimer = client.invincibilityTimer; player->unk_862 = client.unk_862; + player->unk_85C = client.unk_85C; player->av1.actionVar1 = client.actionVar1; - if (player->modelGroup != client.modelGroup) { + // Apply animation movement (Copied from Player_ApplyAnimMovementScaledByAge) + Vec3f diff; + SkelAnime_UpdateTranslation(&player->skelAnime, &diff, player->actor.shape.rot.y); + + if (player->skelAnime.movementFlags & 1) { + if (!LINK_IS_ADULT) { + diff.x *= 0.64f; + diff.z *= 0.64f; + } + + player->actor.world.pos.x += diff.x * player->actor.scale.x; + player->actor.world.pos.z += diff.z * player->actor.scale.z; + } + + if (player->skelAnime.movementFlags & 2) { + if (!(player->skelAnime.movementFlags & 4)) { + diff.y *= player->ageProperties->unk_08; + } + + player->actor.world.pos.y += diff.y * player->actor.scale.y; + } + + if (player->modelGroup != Player_ActionToModelGroup(player, player->itemAction)) { // Hack to account for usage of gSaveContext s32 originalAge = gSaveContext.linkAge; gSaveContext.linkAge = client.linkAge; u8 originalButtonItem0 = gSaveContext.equips.buttonItems[0]; gSaveContext.equips.buttonItems[0] = client.buttonItem0; - Player_SetModelGroup(player, client.modelGroup); + Player_SetModelGroup(player, Player_ActionToModelGroup(player, player->itemAction)); gSaveContext.linkAge = originalAge; gSaveContext.equips.buttonItems[0] = originalButtonItem0; } diff --git a/soh/soh/Network/Anchor/HookHandlers.cpp b/soh/soh/Network/Anchor/HookHandlers.cpp index 9a4622c29..c620ee30e 100644 --- a/soh/soh/Network/Anchor/HookHandlers.cpp +++ b/soh/soh/Network/Anchor/HookHandlers.cpp @@ -98,6 +98,8 @@ void Anchor::RegisterHooks() { COND_HOOK(OnGameFrameUpdate, isConnected, [&]() { ProcessIncomingPacketQueue(); }); COND_HOOK(OnPlayerSfx, isConnected, [&](u16 sfxId) { SendPacket_PlayerSfx(sfxId); }); + COND_HOOK(OnOcarinaNote, isConnected, + [&](uint8_t note, float modulator, int8_t bend) { SendPacket_OcarinaSfx(note, modulator, bend); }); COND_HOOK(OnLoadGame, isConnected, [&](s16 fileNum) { justLoadedSave = true; }); @@ -152,6 +154,31 @@ void Anchor::RegisterHooks() { SendPacket_UpdateDungeonItems(); }); + COND_VB_SHOULD(VB_APPLY_TUNIC_COLOR, isConnected, { + Actor* myPlayer = (Actor*)GET_PLAYER(gPlayState); + Actor* actor = va_arg(args, Actor*); + Color_RGB8* color = va_arg(args, Color_RGB8*); + + if (actor == myPlayer) { + Color_RGBA8 ownColor = CVarGetColor(CVAR_REMOTE_ANCHOR("Color.Value"), { 100, 255, 100 }); + color->r = ownColor.r; + color->g = ownColor.g; + color->b = ownColor.b; + return; + } + + uint32_t clientId = Anchor::Instance->GetDummyPlayerClientId(actor); + + if (!Anchor::Instance->clients.contains(clientId)) { + return; + } + + AnchorClient& client = Anchor::Instance->clients[clientId]; + color->r = client.color.r; + color->g = client.color.g; + color->b = client.color.b; + }); + // #endregion // #region Hooks that are purely to sync actor states across the clients, not super essential diff --git a/soh/soh/Network/Anchor/Menu.cpp b/soh/soh/Network/Anchor/Menu.cpp index 9c4530431..77b544fb3 100644 --- a/soh/soh/Network/Anchor/Menu.cpp +++ b/soh/soh/Network/Anchor/Menu.cpp @@ -46,7 +46,10 @@ void AnchorMainMenu(WidgetInfo& info) { } UIWidgets::PopStyleInput(); - ImGui::Text("Name"); + ImGui::Text("Name & Color"); + static Color_RGBA8 defaultColor = { 100, 255, 100, 255 }; + UIWidgets::CVarColorPicker("##Color", CVAR_REMOTE_ANCHOR("Color"), defaultColor); + ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (UIWidgets::InputString("##Name", &anchorName, UIWidgets::InputOptions().Color(THEME_COLOR))) { CVarSetString(CVAR_REMOTE_ANCHOR("Name"), anchorName.c_str()); diff --git a/soh/soh/Network/Anchor/Packets/OcarinaSfx.cpp b/soh/soh/Network/Anchor/Packets/OcarinaSfx.cpp new file mode 100644 index 000000000..72eee3c5c --- /dev/null +++ b/soh/soh/Network/Anchor/Packets/OcarinaSfx.cpp @@ -0,0 +1,65 @@ +#include "soh/Network/Anchor/Anchor.h" +#include "soh/Network/Anchor/JsonConversions.hpp" +#include +#include + +extern "C" { +#include "macros.h" +#include "functions.h" +#include "variables.h" +extern PlayState* gPlayState; +extern f32 D_80130F28; +} + +/** + * OCARINA_SFX + * + * Ocarina effects, only sent to other clients in the same scene as the player + */ + +void Anchor::SendPacket_OcarinaSfx(uint8_t note, float modulator, int8_t bend) { + if (!IsSaveLoaded()) { + return; + } + + nlohmann::json payload; + + payload["type"] = OCARINA_SFX; + payload["note"] = note; + payload["modulator"] = modulator; + payload["bend"] = bend; + payload["quiet"] = true; + + for (auto& [clientId, client] : clients) { + if (client.sceneNum == gPlayState->sceneNum && client.online && client.isSaveLoaded && !client.self) { + payload["targetClientId"] = clientId; + SendJsonToRemote(payload); + } + } +} + +void Anchor::HandlePacket_OcarinaSfx(nlohmann::json payload) { + uint32_t clientId = payload["clientId"].get(); + uint8_t note = payload["note"].get(); + float modulator = payload["modulator"].get(); + int8_t bend = payload["bend"].get(); + + if (!clients.contains(clientId) || !clients[clientId].player) { + return; + } + + auto& client = clients[clientId]; + client.ocarinaModulator = modulator; + client.ocarinaBend = bend; + + if ((note != 0xFF) && (client.ocarinaNote != note)) { + Audio_QueueCmdS8(0x6 << 24 | SEQ_PLAYER_SFX << 16 | 0xD07, client.ocarinaBend - 1); + Audio_QueueCmdS8(0x6 << 24 | SEQ_PLAYER_SFX << 16 | 0xD05, note); + Audio_PlaySoundGeneral(NA_SE_OC_OCARINA, &client.player->actor.projectedPos, 4, &client.ocarinaModulator, + &D_80130F28, &gSfxDefaultReverb); + } else if ((client.ocarinaNote != 0xFF) && (note == 0xFF)) { + Audio_StopSfxById(NA_SE_OC_OCARINA); + } + + client.ocarinaNote = note; +} diff --git a/soh/soh/Network/Anchor/Packets/PlayerUpdate.cpp b/soh/soh/Network/Anchor/Packets/PlayerUpdate.cpp index a4ee17b76..c05de0a23 100644 --- a/soh/soh/Network/Anchor/Packets/PlayerUpdate.cpp +++ b/soh/soh/Network/Anchor/Packets/PlayerUpdate.cpp @@ -50,6 +50,8 @@ void Anchor::SendPacket_PlayerUpdate() { jointArray.push_back(joint.y); jointArray.push_back(joint.z); } + payload["prevTransl"] = player->skelAnime.prevTransl; + payload["movementFlags"] = player->skelAnime.movementFlags; payload["jointTable"] = jointArray; payload["upperLimbRot"] = player->upperLimbRot; payload["currentBoots"] = player->currentBoots; @@ -63,6 +65,7 @@ void Anchor::SendPacket_PlayerUpdate() { payload["modelGroup"] = player->modelGroup; payload["invincibilityTimer"] = player->invincibilityTimer; payload["unk_862"] = player->unk_862; + payload["unk_85C"] = player->unk_85C; payload["actionVar1"] = player->av1.actionVar1; payload["quiet"] = true; @@ -94,6 +97,8 @@ void Anchor::HandlePacket_PlayerUpdate(nlohmann::json payload) { client.jointTable[i].y = jointArray[i * 3 + 1]; client.jointTable[i].z = jointArray[i * 3 + 2]; } + client.movementFlags = payload["movementFlags"].get(); + client.prevTransl = payload["prevTransl"].get(); client.upperLimbRot = payload["upperLimbRot"].get(); client.currentBoots = payload["currentBoots"].get(); client.currentShield = payload["currentShield"].get(); @@ -106,6 +111,7 @@ void Anchor::HandlePacket_PlayerUpdate(nlohmann::json payload) { client.modelGroup = payload["modelGroup"].get(); client.invincibilityTimer = payload["invincibilityTimer"].get(); client.unk_862 = payload["unk_862"].get(); + client.unk_85C = payload["unk_85C"].get(); client.actionVar1 = payload["actionVar1"].get(); } } diff --git a/soh/soh/Network/Anchor/Packets/UpdateClientState.cpp b/soh/soh/Network/Anchor/Packets/UpdateClientState.cpp index 7b3c180bb..3099da84a 100644 --- a/soh/soh/Network/Anchor/Packets/UpdateClientState.cpp +++ b/soh/soh/Network/Anchor/Packets/UpdateClientState.cpp @@ -23,7 +23,7 @@ extern PlayState* gPlayState; nlohmann::json Anchor::PrepClientState() { nlohmann::json payload; payload["name"] = CVarGetString(CVAR_REMOTE_ANCHOR("Name"), ""); - payload["color"] = CVarGetColor24(CVAR_REMOTE_ANCHOR("Color"), { 100, 255, 100 }); + payload["color"] = CVarGetColor24(CVAR_REMOTE_ANCHOR("Color.Value"), { 100, 255, 100 }); payload["clientVersion"] = clientVersion; payload["teamId"] = CVarGetString(CVAR_REMOTE_ANCHOR("TeamId"), "default"); payload["online"] = true; diff --git a/soh/src/code/code_800EC960.c b/soh/src/code/code_800EC960.c index 2da5b4d2b..0cc9debe8 100644 --- a/soh/src/code/code_800EC960.c +++ b/soh/src/code/code_800EC960.c @@ -1677,6 +1677,7 @@ void func_800ED458(s32 arg0) { } else if ((sPrevOcarinaNoteVal != 0xFF) && (sCurOcarinaBtnVal == 0xFF)) { Audio_StopSfxById(NA_SE_OC_OCARINA); } + GameInteractor_ExecuteOnOcarinaNote(sCurOcarinaBtnVal, D_80130F24, D_80130F10); } } diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 58829aee8..49d9380bf 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -7,6 +7,7 @@ #include "overlays/actors/ovl_Demo_Effect/z_demo_effect.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/Enhancements/randomizer/draw.h" #include "soh/ResourceManagerHelpers.h" @@ -1076,7 +1077,9 @@ void Player_DrawImpl(PlayState* play, void** skeleton, Vec3s* jointTable, s32 dL color = &sTemp; } - gDPSetEnvColor(POLY_OPA_DISP++, color->r, color->g, color->b, 0); + if (GameInteractor_Should(VB_APPLY_TUNIC_COLOR, true, data, color)) { + gDPSetEnvColor(POLY_OPA_DISP++, color->r, color->g, color->b, 0); + } // If we have a custom link model, always use the most detailed LOD if (Player_IsCustomLinkModel()) { From 4366da631b4b8131cd136a7e9b05618414ab2f6b Mon Sep 17 00:00:00 2001 From: TheLynk <44308308+TheLynk@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:56:58 +0100 Subject: [PATCH 07/15] Fix logic gf (#6003) * Create AutoSyncFork.yml * Off * Delete .github/workflows/AutoSyncFork.yml * Fix Logic In GF --- .../randomizer/location_access/overworld/gerudo_fortress.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp b/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp index 15f75ab8d..bad0027bb 100644 --- a/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/overworld/gerudo_fortress.cpp @@ -13,7 +13,7 @@ void RegionTable_Init_GerudoFortress() { }, { //Locations LOCATION(RC_GF_OUTSKIRTS_NE_CRATE, (logic->IsChild || logic->CanPassEnemy(RE_GERUDO_GUARD)) && logic->CanBreakCrates()), - LOCATION(RC_GF_OUTSKIRTS_NW_CRATE, logic->IsChild || logic->CanPassEnemy(RE_GERUDO_GUARD)), + LOCATION(RC_GF_OUTSKIRTS_NW_CRATE, (logic->IsChild || logic->CanPassEnemy(RE_GERUDO_GUARD)) && logic->CanBreakCrates()), }, { //Exits Entrance(RR_GV_FORTRESS_SIDE, []{return true;}), @@ -260,4 +260,4 @@ void RegionTable_Init_GerudoFortress() { Entrance(RR_GF_NEAR_GROTTO, []{return true;}), }); } -// clang-format on \ No newline at end of file +// clang-format on From a2e4e4d4176dbf8690fec86f5bce920ec5a96bd7 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Wed, 3 Dec 2025 09:57:15 -0600 Subject: [PATCH 08/15] Check tracker improvements (#6000) --- .../randomizer/randomizer_check_tracker.cpp | 118 +++++++++++------- 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp index 022303aa3..9363ace39 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_check_tracker.cpp @@ -1042,16 +1042,17 @@ void CheckTrackerWindow::DrawElement() { #else float headerHeight = 20.0f; #endif - ImVec2 size = ImGui::GetContentRegionMax(); - size.y -= headerHeight; - if (!ImGui::BeginTable("Check Tracker", 1, 0, size)) { + if (!ImGui::BeginTable("Check Tracker", 1, 0)) { EndFloatWindows(); return; } - ImGui::TableNextRow(0, headerHeight); + ImGui::SetWindowFontScale(CVarGetFloat(CVAR_TRACKER_CHECK("FontSize"), 1.0f)); + + ImGui::TableNextRow(0, 0); ImGui::TableNextColumn(); - if (UIWidgets::CVarCheckbox( + if (CVarGetInteger(CVAR_TRACKER_CHECK("HiddenItemsToggleVisible"), 1) && + UIWidgets::CVarCheckbox( "Show Hidden Items", CVAR_TRACKER_CHECK("ShowHidden"), UIWidgets::CheckboxOptions( { { .tooltip = "When active, items will show hidden checks by default when updated to this state." } }) @@ -1060,7 +1061,7 @@ void CheckTrackerWindow::DrawElement() { showHidden = CVarGetInteger(CVAR_TRACKER_CHECK("ShowHidden"), 0); RecalculateAllAreaTotals(); } - if (enableAvailableChecks) { + if (enableAvailableChecks && CVarGetInteger(CVAR_TRACKER_CHECK("AvailableChecksToggleVisible"), 1)) { if (UIWidgets::CVarCheckbox( "Only Show Available Checks", CVAR_TRACKER_CHECK("OnlyShowAvailable"), UIWidgets::CheckboxOptions({ { .tooltip = "When active, unavailable checks will be hidden." } }) @@ -1069,49 +1070,61 @@ void CheckTrackerWindow::DrawElement() { RecalculateAllAreaTotals(); } } - UIWidgets::PaddedSeparator(); - if (UIWidgets::Button("Expand All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { - optCollapseAll = false; - optExpandAll = true; - doAreaScroll = true; - } - ImGui::SameLine(); - if (UIWidgets::Button("Collapse All", - UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { - optExpandAll = false; - optCollapseAll = true; - } - ImGui::SameLine(); - if (UIWidgets::Button("Clear", UIWidgets::ButtonOptions({ { .tooltip = "Clear the search field" } }) - .Color(THEME_COLOR) - .Size(UIWidgets::Sizes::Inline))) { - checkSearch.Clear(); - UpdateFilters(); - doAreaScroll = true; + if (CVarGetInteger(CVAR_TRACKER_CHECK("ExpandCollapseButtonsVisible"), 0)) { + if (UIWidgets::Button( + "Expand All", + UIWidgets::ButtonOptions().Color(THEME_COLOR).Size({ ImGui::GetContentRegionAvail().x / 2 - 6, 0 }))) { + optCollapseAll = false; + optExpandAll = true; + doAreaScroll = true; + } + ImGui::SameLine(); + if (UIWidgets::Button( + "Collapse All", + UIWidgets::ButtonOptions().Color(THEME_COLOR).Size({ ImGui::GetContentRegionAvail().x - 6, 0 }))) { + optExpandAll = false; + optCollapseAll = true; + } } UIWidgets::PushStyleCombobox(THEME_COLOR); - if (checkSearch.Draw()) { - UpdateFilters(); + if (CVarGetInteger(CVAR_TRACKER_CHECK("SearchInputVisible"), 1)) { + if (checkSearch.Draw("", ImGui::GetContentRegionAvail().x - 6)) { + UpdateFilters(); + } + std::string checkSearchText = ""; + checkSearchText = checkSearch.InputBuf; + checkSearchText.erase(std::remove(checkSearchText.begin(), checkSearchText.end(), ' '), checkSearchText.end()); + if (checkSearchText.length() < 1) { + ImGui::SameLine(20.0f); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Search..."); + } } UIWidgets::PopStyleCombobox(); - ImGui::Separator(); - - std::ostringstream totalChecksSS; - totalChecksSS << "Total Checks: "; - if (enableAvailableChecks) { - totalChecksSS << totalChecksAvailable << " Available / "; + if (CVarGetInteger(CVAR_TRACKER_CHECK("CheckTotalsVisible"), 1)) { + std::ostringstream totalChecksSS; + totalChecksSS << ""; + if (enableAvailableChecks) { + totalChecksSS << totalChecksAvailable << " Available / "; + } + totalChecksSS << totalChecksGotten << " Checked / " << totalChecks << " Total"; + ImGui::Text("%s", totalChecksSS.str().c_str()); } - totalChecksSS << totalChecksGotten << " Checked / " << totalChecks << " Total"; - ImGui::Text("%s", totalChecksSS.str().c_str()); - UIWidgets::PaddedSeparator(); + bool headerPresent = + CVarGetInteger(CVAR_TRACKER_CHECK("HiddenItemsToggleVisible"), 1) || + (enableAvailableChecks && CVarGetInteger(CVAR_TRACKER_CHECK("AvailableChecksToggleVisible"), 1)) || + CVarGetInteger(CVAR_TRACKER_CHECK("ExpandCollapseButtonsVisible"), 0) || + CVarGetInteger(CVAR_TRACKER_CHECK("SearchInputVisible"), 1) || + CVarGetInteger(CVAR_TRACKER_CHECK("CheckTotalsVisible"), 1); + if (headerPresent) { + ImGui::Separator(); + } // Checks Section Lead-in ImGui::TableNextRow(); ImGui::TableNextColumn(); - size = ImGui::GetContentRegionAvail(); - if (!ImGui::BeginTable("CheckTracker##Checks", 1, ImGuiTableFlags_ScrollY, size)) { + if (!ImGui::BeginTable("CheckTracker##Checks", 1, ImGuiTableFlags_ScrollY)) { ImGui::EndTable(); EndFloatWindows(); return; @@ -1181,7 +1194,7 @@ void CheckTrackerWindow::DrawElement() { } else { ImGui::SetNextItemOpen(!thisAreaFullyChecked, ImGuiCond_Once); } - doDraw = ImGui::TreeNode(stemp.c_str()); + doDraw = ImGui::TreeNodeEx(stemp.c_str(), ImGuiTreeNodeFlags_NoTreePushOnOpen); ImGui::PopStyleColor(); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(extraColor.r / 255.0f, extraColor.g / 255.0f, @@ -1230,10 +1243,6 @@ void CheckTrackerWindow::DrawElement() { DrawLocation(rc); } } - - if (doDraw) { - ImGui::TreePop(); - } } } ImGui::PopStyleVar(); @@ -2130,6 +2139,16 @@ void CheckTrackerSettingsWindow::DrawElement() { SohGui::mSohMenu->MenuDrawItem(windowTypeWidget, ImGui::GetContentRegionAvail().x, THEME_COLOR); + UIWidgets::CVarSliderFloat("Font Size", CVAR_TRACKER_CHECK("FontSize"), + UIWidgets::FloatSliderOptions() + .Tooltip("Sets the font size used in the check tracker.") + .Format("%.1f") + .Step(0.1f) + .Min(0.3f) + .Max(2.0f) + .Color(THEME_COLOR) + .DefaultValue(1.0f)); + if (CVarGetInteger(CVAR_TRACKER_CHECK("WindowType"), TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) { UIWidgets::CVarCheckbox("Enable Dragging", CVAR_TRACKER_CHECK("Draggable"), UIWidgets::CheckboxOptions().Color(THEME_COLOR)); @@ -2172,7 +2191,6 @@ void CheckTrackerSettingsWindow::DrawElement() { ImGui::EndDisabled(); // Filtering settings - UIWidgets::PaddedSeparator(); UIWidgets::CVarCheckbox( "Filter Empty Areas", CVAR_TRACKER_CHECK("HideFilteredAreas"), UIWidgets::CheckboxOptions() @@ -2180,6 +2198,18 @@ void CheckTrackerSettingsWindow::DrawElement() { .Color(THEME_COLOR) .DefaultValue(true)); + ImGui::SeparatorText("Tracker Header Visibility"); + UIWidgets::CVarCheckbox("Hidden Items Toggle", CVAR_TRACKER_CHECK("HiddenItemsToggleVisible"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); + UIWidgets::CVarCheckbox("Available Checks Toggle", CVAR_TRACKER_CHECK("AvailableChecksToggleVisible"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); + UIWidgets::CVarCheckbox("Expand/Collapse Buttons", CVAR_TRACKER_CHECK("ExpandCollapseButtonsVisible"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).DefaultValue(false)); + UIWidgets::CVarCheckbox("Search Input", CVAR_TRACKER_CHECK("SearchInputVisible"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); + UIWidgets::CVarCheckbox("Check Totals", CVAR_TRACKER_CHECK("CheckTotalsVisible"), + UIWidgets::CheckboxOptions().Color(THEME_COLOR).DefaultValue(true)); + ImGui::TableNextColumn(); CheckTracker::ImGuiDrawTwoColorPickerSection("Area Incomplete", CVAR_TRACKER_CHECK("AreaIncomplete.MainColor"), From 8aa7b2fc716cf40251bc9cb97db598d1d055636c Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Wed, 3 Dec 2025 10:58:06 -0500 Subject: [PATCH 09/15] Modularize Hyper Enemies hook (#5968) * Modularize Hyper Enemies hook * Use extern "C" * Make mod file self-contained --- .../Enhancements/Difficulty/HyperEnemies.cpp | 33 +++++++++++++++++++ soh/soh/Enhancements/mods.cpp | 29 ---------------- soh/soh/Enhancements/mods.h | 1 - soh/soh/SohGui/SohMenuEnhancements.cpp | 1 - 4 files changed, 33 insertions(+), 31 deletions(-) create mode 100644 soh/soh/Enhancements/Difficulty/HyperEnemies.cpp diff --git a/soh/soh/Enhancements/Difficulty/HyperEnemies.cpp b/soh/soh/Enhancements/Difficulty/HyperEnemies.cpp new file mode 100644 index 000000000..b2e0c933b --- /dev/null +++ b/soh/soh/Enhancements/Difficulty/HyperEnemies.cpp @@ -0,0 +1,33 @@ +#include +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/ShipInit.hpp" +#include "functions.h" +#include "macros.h" + +extern "C" PlayState* gPlayState; + +static constexpr int32_t CVAR_HYPER_ENEMIES_DEFAULT = 0; +#define CVAR_HYPER_ENEMIES_NAME CVAR_ENHANCEMENT("HyperEnemies") +#define CVAR_HYPER_ENEMIES_VALUE CVarGetInteger(CVAR_HYPER_ENEMIES_NAME, CVAR_HYPER_ENEMIES_DEFAULT) + +static void MakeHyperEnemies(void* refActor) { + // Run the update function a second time to make enemies and minibosses move and act twice as fast. + + Player* player = GET_PLAYER(gPlayState); + Actor* actor = static_cast(refActor); + + // Some enemies are not in the ACTORCAT_ENEMY category, and some are that aren't really enemies. + bool isEnemy = actor->category == ACTORCAT_ENEMY || actor->id == ACTOR_EN_TORCH2; + bool isExcludedEnemy = actor->id == ACTOR_EN_FIRE_ROCK || actor->id == ACTOR_EN_ENCOUNT2; + + // Don't apply during cutscenes because it causes weird behaviour and/or crashes on some cutscenes. + if (isEnemy && !isExcludedEnemy && !Player_InBlockingCsMode(gPlayState, player)) { + GameInteractor::RawAction::UpdateActor(actor); + } +} + +static void UpdateHyperEnemiesState() { + COND_HOOK(OnActorUpdate, CVAR_HYPER_ENEMIES_VALUE, MakeHyperEnemies); +} + +static RegisterShipInitFunc initFunc(UpdateHyperEnemiesState, { CVAR_HYPER_ENEMIES_NAME }); diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 205c35ae6..3e724c54f 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -198,34 +198,6 @@ void RegisterHyperBosses() { [](int16_t fileNum) { UpdateHyperBossesState(); }); } -void UpdateHyperEnemiesState() { - static uint32_t actorUpdateHookId = 0; - if (actorUpdateHookId != 0) { - GameInteractor::Instance->UnregisterGameHook(actorUpdateHookId); - actorUpdateHookId = 0; - } - - if (CVarGetInteger(CVAR_ENHANCEMENT("HyperEnemies"), 0)) { - actorUpdateHookId = - GameInteractor::Instance->RegisterGameHook([](void* refActor) { - // Run the update function a second time to make enemies and minibosses move and act twice as fast. - - Player* player = GET_PLAYER(gPlayState); - Actor* actor = static_cast(refActor); - - // Some enemies are not in the ACTORCAT_ENEMY category, and some are that aren't really enemies. - bool isEnemy = actor->category == ACTORCAT_ENEMY || actor->id == ACTOR_EN_TORCH2; - bool isExcludedEnemy = actor->id == ACTOR_EN_FIRE_ROCK || actor->id == ACTOR_EN_ENCOUNT2; - - // Don't apply during cutscenes because it causes weird behaviour and/or crashes on some cutscenes. - if (CVarGetInteger(CVAR_ENHANCEMENT("HyperEnemies"), 0) && isEnemy && !isExcludedEnemy && - !Player_InBlockingCsMode(gPlayState, player)) { - GameInteractor::RawAction::UpdateActor(actor); - } - }); - } -} - // this map is used for enemies that can be uniquely identified by their id // and that are always counted // enemies that can't be uniquely identified by their id @@ -476,7 +448,6 @@ void InitMods() { RegisterTTS(); RegisterOcarinaTimeTravel(); RegisterHyperBosses(); - UpdateHyperEnemiesState(); RegisterEnemyDefeatCounts(); RegisterRandomizedEnemySizes(); RandoKaleido_RegisterHooks(); diff --git a/soh/soh/Enhancements/mods.h b/soh/soh/Enhancements/mods.h index fe5083ce2..9a0c6794b 100644 --- a/soh/soh/Enhancements/mods.h +++ b/soh/soh/Enhancements/mods.h @@ -7,7 +7,6 @@ extern "C" { #endif -void UpdateHyperEnemiesState(); void UpdateHyperBossesState(); void InitMods(); void SwitchAge(); diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index e73aa59cc..c32b11755 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1291,7 +1291,6 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip("All Major Bosses move and act twice as fast.")); AddWidget(path, "Hyper Enemies", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("HyperEnemies")) - .Callback([](WidgetInfo& info) { UpdateHyperEnemiesState(); }) .Options(CheckboxOptions().Tooltip("All Regular Enemies and Mini-Bosses move and act twice as fast.")); AddWidget(path, "Enable Visual Guard Vision", WIDGET_CVAR_CHECKBOX).CVar(CVAR_ENHANCEMENT("GuardVision")); AddWidget(path, "Leever Spawn Rate: %d seconds", WIDGET_CVAR_SLIDER_INT) From c24b2d74d5c5ae3cb0ba7e5670fade5349290bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Wed, 3 Dec 2025 15:58:16 +0000 Subject: [PATCH 10/15] hookify cosmetics (#5900) also make goron neck searchable --- soh/soh/Enhancements/audio/AudioEditor.cpp | 1 - .../cosmetics/CosmeticsEditor.cpp | 71 ++++++++++--------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/soh/soh/Enhancements/audio/AudioEditor.cpp b/soh/soh/Enhancements/audio/AudioEditor.cpp index be6acae73..fa0727218 100644 --- a/soh/soh/Enhancements/audio/AudioEditor.cpp +++ b/soh/soh/Enhancements/audio/AudioEditor.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include "../randomizer/3drando/random.hpp" diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp index 314b92b40..202fef039 100644 --- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp +++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp @@ -5,12 +5,11 @@ #include #include -#include #include -#include #include #include "soh/SohGui/UIWidgets.hpp" +#include "soh/SohGui/SohMenu.h" #include "soh/SohGui/SohGui.hpp" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" @@ -22,7 +21,6 @@ extern "C" { #include "objects/object_link_boy/object_link_boy.h" #include "objects/object_link_child/object_link_child.h" #include "objects/object_gi_shield_3/object_gi_shield_3.h" -#include "objects/object_gi_heart/object_gi_heart.h" #include "objects/object_gi_bow/object_gi_bow.h" #include "objects/object_gi_bracelet/object_gi_bracelet.h" #include "objects/object_gi_rupy/object_gi_rupy.h" @@ -57,6 +55,12 @@ void ResourceMgr_UnpatchGfxByName(const char* path, const char* patchName); u8 Randomizer_GetSettingValue(RandomizerSettingKey randoSettingKey); } +static WidgetInfo goronNeck; + +namespace SohGui { +extern std::shared_ptr mSohMenu; +} + #define PATCH_GFX(path, name, cvar, index, instruction) \ if (CVarGetInteger(cvar, 0)) { \ ResourceMgr_PatchGfxByName(path, name, index, instruction); \ @@ -1964,15 +1968,7 @@ void DrawSillyTab() { UIWidgets::Separator(true, true, 2.0f, 2.0f); - UIWidgets::CVarSliderFloat("Goron Neck Length", CVAR_COSMETIC("Goron.NeckLength"), - UIWidgets::FloatSliderOptions() - .Format("%.0f") - .Min(0.0f) - .Max(5000.0f) - .DefaultValue(0.0f) - .Step(10.0f) - .Size(ImVec2(300.0f, 0.0f)) - .Color(THEME_COLOR)); + SohGui::mSohMenu->MenuDrawItem(goronNeck, ImGui::GetContentRegionAvail().x, THEME_COLOR); Reset_Option_Single("Reset##Goron_NeckLength", CVAR_COSMETIC("Goron.NeckLength")); UIWidgets::Separator(true, true, 2.0f, 2.0f); @@ -2586,22 +2582,6 @@ void RegisterOnGameFrameUpdateHook() { GameInteractor::Instance->RegisterGameHook([]() { CosmeticsUpdateTick(); }); } -void Cosmetics_RegisterOnSceneInitHook() { - GameInteractor::Instance->RegisterGameHook([](int16_t sceneNum) { - if (CVarGetInteger(CVAR_COSMETIC("RandomizeAllOnNewScene"), 0)) { - CosmeticsEditor_RandomizeAll(); - } - }); -} - -void CosmeticsEditorRegisterOnGenerationCompletionHook() { - GameInteractor::Instance->RegisterGameHook([]() { - if (CVarGetInteger(CVAR_COSMETIC("RandomizeAllOnRandoGen"), 0)) { - CosmeticsEditor_RandomizeAll(); - } - }); -} - void CosmeticsEditorWindow::InitElement() { // Convert the `current color` into the format that the ImGui color picker expects for (auto& [id, cosmeticOption] : cosmeticOptions) { @@ -2617,11 +2597,6 @@ void CosmeticsEditorWindow::InitElement() { Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); ApplyOrResetCustomGfxPatches(); ApplyAuthenticGfxPatches(); - - RegisterOnLoadGameHook(); - RegisterOnGameFrameUpdateHook(); - Cosmetics_RegisterOnSceneInitHook(); - CosmeticsEditorRegisterOnGenerationCompletionHook(); } void CosmeticsEditor_RandomizeAll() { @@ -2670,3 +2645,33 @@ void CosmeticsEditor_ResetGroup(CosmeticGroup group) { Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); ApplyOrResetCustomGfxPatches(); } + +void RegisterCosmeticHooks() { + COND_HOOK(OnSceneInit, CVarGetInteger(CVAR_COSMETIC("RandomizeAllOnNewScene"), 0), + [](s16 sceneNum) { CosmeticsEditor_RandomizeAll(); }); + + COND_HOOK(OnGenerationCompletion, CVarGetInteger(CVAR_COSMETIC("RandomizeAllOnRandoGen"), 0), + []() { CosmeticsEditor_RandomizeAll(); }); + + COND_HOOK(OnGameFrameUpdate, true, CosmeticsUpdateTick); +} + +void RegisterCosmeticWidgets() { + goronNeck = { .name = "Goron Neck Length", .type = WidgetType::WIDGET_CVAR_SLIDER_FLOAT }; + goronNeck.CVar(CVAR_COSMETIC("Goron.NeckLength")) + .Options(UIWidgets::FloatSliderOptions() + .Format("%.0f") + .Min(0.0f) + .Max(5000.0f) + .DefaultValue(0.0f) + .Step(10.0f) + .Size(ImVec2(300.0f, 0.0f)) + .Color(THEME_COLOR)); + SohGui::mSohMenu->AddSearchWidget({ goronNeck, "Enhancements", "Cosmetics Editor", "Silly" }); +} + +static RegisterShipInitFunc initFunc(RegisterCosmeticHooks, { + CVAR_COSMETIC("RandomizeAllOnNewScene"), + CVAR_COSMETIC("RandomizeAllOnRandoGen"), + }); +static RegisterMenuInitFunc menuInitFunc(RegisterCosmeticWidgets); From 42282c804e8b30f95f1b83848bf88f2fe584dd61 Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Thu, 4 Dec 2025 00:58:24 +0900 Subject: [PATCH 11/15] Better Debug Warp: Remember Link's Age and Day/Night Settings (#5981) --- soh/src/overlays/gamestates/ovl_select/z_select.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/soh/src/overlays/gamestates/ovl_select/z_select.c b/soh/src/overlays/gamestates/ovl_select/z_select.c index a057ebbd5..7f15d3971 100644 --- a/soh/src/overlays/gamestates/ovl_select/z_select.c +++ b/soh/src/overlays/gamestates/ovl_select/z_select.c @@ -50,6 +50,8 @@ void Select_LoadGame(SelectContext* this, s32 entranceIndex) { CVarSetInteger(CVAR_GENERAL("BetterDebugWarpScreenCurrentScene"), this->currentScene); CVarSetInteger(CVAR_GENERAL("BetterDebugWarpScreenTopDisplayedScene"), this->topDisplayedScene); CVarSetInteger(CVAR_GENERAL("BetterDebugWarpScreenPageDownIndex"), this->pageDownIndex); + CVarSetInteger(CVAR_GENERAL("BetterDebugWarpScreenLinkAge"), gSaveContext.linkAge); + CVarSetInteger(CVAR_GENERAL("BetterDebugWarpScreenNightFlag"), gSaveContext.nightFlag); CVarSave(); if (ResourceMgr_GameHasMasterQuest() && ResourceMgr_GameHasOriginal()) { @@ -118,6 +120,8 @@ void Select_Grotto_LoadGame(SelectContext* this, s32 grottoIndex) { CVarSetInteger(CVAR_GENERAL("BetterDebugWarpScreenCurrentScene"), this->currentScene); CVarSetInteger(CVAR_GENERAL("BetterDebugWarpScreenTopDisplayedScene"), this->topDisplayedScene); CVarSetInteger(CVAR_GENERAL("BetterDebugWarpScreenPageDownIndex"), this->pageDownIndex); + CVarSetInteger(CVAR_GENERAL("BetterDebugWarpScreenLinkAge"), gSaveContext.linkAge); + CVarSetInteger(CVAR_GENERAL("BetterDebugWarpScreenNightFlag"), gSaveContext.nightFlag); CVarSave(); } @@ -1833,6 +1837,10 @@ void Select_SwitchBetterWarpMode(SelectContext* this, u8 isBetterWarpMode) { this->opt = 1; } } + + gSaveContext.linkAge = CVarGetInteger(CVAR_GENERAL("BetterDebugWarpScreenLinkAge"), 1); + gSaveContext.nightFlag = CVarGetInteger(CVAR_GENERAL("BetterDebugWarpScreenNightFlag"), 0); + gSaveContext.dayTime = gSaveContext.nightFlag ? 0x0000 : 0x8000; } else { this->count = ARRAY_COUNT(sScenes); From 17f7c3e8f5b1cc664efdd67d52e28326e607d0af Mon Sep 17 00:00:00 2001 From: nclok1405 <155463060+nclok1405@users.noreply.github.com> Date: Thu, 4 Dec 2025 00:58:32 +0900 Subject: [PATCH 12/15] Add Expand All/Collapse All buttons to Hook Debugger (#6002) --- .../Enhancements/debugger/hookDebugger.cpp | 35 +++++++++++++++++++ soh/soh/Enhancements/debugger/hookDebugger.h | 5 +++ 2 files changed, 40 insertions(+) diff --git a/soh/soh/Enhancements/debugger/hookDebugger.cpp b/soh/soh/Enhancements/debugger/hookDebugger.cpp index 146db2ddc..709bbb1d4 100644 --- a/soh/soh/Enhancements/debugger/hookDebugger.cpp +++ b/soh/soh/Enhancements/debugger/hookDebugger.cpp @@ -1,4 +1,5 @@ #include "hookDebugger.h" +#include "soh/SohGui/SohGui.hpp" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/SohGui/UIWidgets.hpp" #include "soh/OTRGlobals.h" @@ -7,6 +8,9 @@ static std::map*> hookData; +static bool hookOptCollapseAll; // A bool that will collapse all hook group once +static bool hookOptExpandAll; // A bool that will expand all hook group once + const ImVec4 grey = ImVec4(0.75, 0.75, 0.75, 1); const ImVec4 yellow = ImVec4(1, 1, 0, 1); const ImVec4 red = ImVec4(1, 0, 0, 1); @@ -77,6 +81,9 @@ void DrawHookRegisteringInfos(const char* hookName) { } void HookDebuggerWindow::DrawElement() { + bool collapseLogic = false; + bool doingCollapseOrExpand = hookOptExpandAll || hookOptCollapseAll; + ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); #ifndef __cpp_lib_source_location ImGui::TextColored(yellow, "Some features of the Hook Debugger are unavailable because SoH was compiled " @@ -84,9 +91,29 @@ void HookDebuggerWindow::DrawElement() { "(\"__cpp_lib_source_location\" not defined in \"\")."); #endif + if (UIWidgets::Button("Expand All", UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { + hookOptCollapseAll = false; + hookOptExpandAll = true; + } + ImGui::SameLine(); + if (UIWidgets::Button("Collapse All", + UIWidgets::ButtonOptions().Color(THEME_COLOR).Size(UIWidgets::Sizes::Inline))) { + hookOptExpandAll = false; + hookOptCollapseAll = true; + } + ImGui::PushFont(OTRGlobals::Instance->fontMonoLarger); for (auto& [hookName, _] : hookData) { + if (doingCollapseOrExpand) { + if (hookOptExpandAll) { + collapseLogic = true; + } else if (hookOptCollapseAll) { + collapseLogic = false; + } + ImGui::SetNextItemOpen(collapseLogic, ImGuiCond_Always); + } + if (ImGui::TreeNode(hookName)) { DrawHookRegisteringInfos(hookName); ImGui::TreePop(); @@ -95,9 +122,17 @@ void HookDebuggerWindow::DrawElement() { ImGui::PopFont(); ImGui::EndDisabled(); + + if (doingCollapseOrExpand) { + hookOptExpandAll = false; + hookOptCollapseAll = false; + } } void HookDebuggerWindow::InitElement() { + hookOptExpandAll = false; + hookOptCollapseAll = false; + #define DEFINE_HOOK(name, _) hookData.insert({ #name, GameInteractor::Instance->GetHookData() }); #include "../game-interactor/GameInteractor_HookTable.h" diff --git a/soh/soh/Enhancements/debugger/hookDebugger.h b/soh/soh/Enhancements/debugger/hookDebugger.h index 1a586a09c..c1f439f30 100644 --- a/soh/soh/Enhancements/debugger/hookDebugger.h +++ b/soh/soh/Enhancements/debugger/hookDebugger.h @@ -1,3 +1,6 @@ +#ifndef hookDebugger_h +#define hookDebugger_h + #include class HookDebuggerWindow final : public Ship::GuiWindow { @@ -8,3 +11,5 @@ class HookDebuggerWindow final : public Ship::GuiWindow { void DrawElement() override; void UpdateElement() override{}; }; + +#endif // hookDebugger_h From b649f5ed52ea4e824af2f05076b5d9ca697183af Mon Sep 17 00:00:00 2001 From: Jordan Longstaff Date: Wed, 3 Dec 2025 10:58:43 -0500 Subject: [PATCH 13/15] Apply ImGui scaling when using presets (#5991) * Apply ImGui scaling when using presets * Make ImGuiScale function do nothing if scale setting is unchanged --- soh/soh/Enhancements/Presets/Presets.cpp | 1 + soh/soh/OTRGlobals.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/Presets/Presets.cpp b/soh/soh/Enhancements/Presets/Presets.cpp index 7671ee18e..c75303045 100644 --- a/soh/soh/Enhancements/Presets/Presets.cpp +++ b/soh/soh/Enhancements/Presets/Presets.cpp @@ -129,6 +129,7 @@ void applyPreset(std::string presetName, std::vector includeSecti } } ShipInit::InitAll(); + OTRGlobals::Instance->ScaleImGui(); } void DrawPresetSelector(std::vector includeSections, std::string presetLoc, bool disabled) { diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 97d8454b5..2aadd55d2 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -152,6 +152,7 @@ Color_RGB8 kokiriColor = { 0x1E, 0x69, 0x1B }; Color_RGB8 goronColor = { 0x64, 0x14, 0x00 }; Color_RGB8 zoraColor = { 0x00, 0xEC, 0x64 }; +int32_t previousImGuiScaleIndex; float previousImGuiScale; bool prevAltAssets = false; @@ -431,6 +432,7 @@ void OTRGlobals::Initialize() { hasMasterQuest = hasOriginal = false; + previousImGuiScaleIndex = -1; previousImGuiScale = defaultImGuiScale; fontMonoSmall = CreateFontWithSize(14.0f, "fonts/Inconsolata-Regular.ttf"); @@ -499,11 +501,17 @@ OTRGlobals::~OTRGlobals() { } void OTRGlobals::ScaleImGui() { - float scale = imguiScaleOptionToValue[CVarGetInteger(CVAR_SETTING("ImGuiScale"), defaultImGuiScale)]; + int32_t imGuiScaleIndex = CVarGetInteger(CVAR_SETTING("ImGuiScale"), defaultImGuiScale); + if (imGuiScaleIndex == previousImGuiScaleIndex) { + return; + } + + float scale = imguiScaleOptionToValue[imGuiScaleIndex]; float newScale = scale / previousImGuiScale; ImGui::GetStyle().ScaleAllSizes(newScale); ImGui::GetIO().FontGlobalScale = scale; previousImGuiScale = scale; + previousImGuiScaleIndex = imGuiScaleIndex; } ImFont* OTRGlobals::CreateDefaultFontWithSize(float size) { From 280455db4276f801b174bd51b95b000be058d374 Mon Sep 17 00:00:00 2001 From: Christopher Leggett Date: Wed, 3 Dec 2025 15:58:52 +0000 Subject: [PATCH 14/15] Improvements to Custom Kaleido Menu (#5997) * Use game over textures instead of save textures * Improves custom kaleido menu. Specifically: 1. Changes textures to the Game Over screen textures, which look the same but doesn't have "SAVE" at the top. 2. Adds a cursor on the left, doesn't currently do anything other than make it slightly clearer that you can move up and down and scroll, but opens the door for more menu-ing/toggling capabilities later. * Add fishing rod to kaleido menu * Adds skeleton key to kaleido menu with placeholder icon * More condensing of kaleido menu + add overworld keys * clang-format --- soh/soh/Enhancements/kaleido.cpp | 169 +++++++++++++----- soh/soh/Enhancements/kaleido.h | 21 +-- .../ovl_kaleido_scope/z_kaleido_scope_PAL.c | 6 +- 3 files changed, 135 insertions(+), 61 deletions(-) diff --git a/soh/soh/Enhancements/kaleido.cpp b/soh/soh/Enhancements/kaleido.cpp index 2b9552263..5edf050f3 100644 --- a/soh/soh/Enhancements/kaleido.cpp +++ b/soh/soh/Enhancements/kaleido.cpp @@ -1,5 +1,7 @@ #include "kaleido.h" +#include "objects/gameplay_keep/gameplay_keep.h" +#include "soh/Enhancements/randomizer/randomizerTypes.h" #include "soh/frame_interpolation.h" #include "soh/ShipUtils.h" @@ -45,7 +47,7 @@ void KaleidoEntryIcon::LoadIconTex(std::vector* mEntryDl) { } } -KaleidoEntry::KaleidoEntry(int16_t x, int16_t y, std::string text) : mX(x), mY(y), mText(std::move(text)) { +KaleidoEntry::KaleidoEntry(std::string text) : mText(std::move(text)) { mHeight = 0; mWidth = 0; vtx = nullptr; @@ -55,7 +57,12 @@ void KaleidoEntry::SetYOffset(int yOffset) { mY = yOffset; } +void KaleidoEntry::SetSelected(bool val) { + mSelected = val; +} + void KaleidoEntryIcon::Draw(PlayState* play, std::vector* mEntryDl) { + PauseContext* pauseCtx = &play->pauseCtx; if (vtx == nullptr) { return; } @@ -74,13 +81,24 @@ void KaleidoEntryIcon::Draw(PlayState* play, std::vector* mEntryDl) { mEntryDl->push_back(gsSPMatrix(Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), G_MTX_PUSH | G_MTX_LOAD | G_MTX_MODELVIEW)); + // cursor (if selected) + if (mSelected) { + mEntryDl->push_back(gsDPSetPrimColor(0, 0, 255, 255, 255, 255)); + mEntryDl->push_back(gsSPVertex(vtx, 4, 0)); + Gfx cursorIconTex[] = { gsDPLoadTextureBlock(gArrowCursorTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 24, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, + G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD) }; + mEntryDl->insert(mEntryDl->end(), std::begin(cursorIconTex), std::end(cursorIconTex)); + mEntryDl->push_back(gsSP1Quadrangle(0, 2, 3, 1, 0)); + } + // icon if (!mAchieved) { mEntryDl->push_back(gsDPSetGrayscaleColor(109, 109, 109, 255)); mEntryDl->push_back(gsSPGrayscale(true)); } mEntryDl->push_back(gsDPSetPrimColor(0, 0, mIconColor.r, mIconColor.g, mIconColor.b, mIconColor.a)); - mEntryDl->push_back(gsSPVertex(vtx, 4, 0)); + mEntryDl->push_back(gsSPVertex(&vtx[4], 4, 0)); LoadIconTex(mEntryDl); mEntryDl->push_back(gsSP1Quadrangle(0, 2, 3, 1, 0)); mEntryDl->push_back(gsSPGrayscale(false)); @@ -90,10 +108,10 @@ void KaleidoEntryIcon::Draw(PlayState* play, std::vector* mEntryDl) { for (size_t i = 0, vtxGroup = 0; i < numChar; i++) { // A maximum of 64 Vtx can be loaded at once by gSPVertex, or basically 16 characters // handle loading groups of 16 chars at a time until there are no more left to load. - // By this point 4 vertices have already been loaded for the preceding icon. + // By this point 8 vertices have already been loaded for the preceding icon and cursor. if (i % 16 == 0) { size_t numVtxToLoad = std::min(numChar - i, 16) * 4; - mEntryDl->push_back(gsSPVertex(&vtx[4 + (vtxGroup * 16 * 4)], numVtxToLoad, 0)); + mEntryDl->push_back(gsSPVertex(&vtx[8 + (vtxGroup * 16 * 4)], numVtxToLoad, 0)); vtxGroup++; } @@ -111,22 +129,29 @@ void KaleidoEntryIcon::Draw(PlayState* play, std::vector* mEntryDl) { Kaleido::Kaleido() { const auto ctx = Rando::Context::GetInstance(); - int yOffset = 2; + int yOffset = 0; mEntries.push_back(std::make_shared( gRupeeCounterIconTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16, Color_RGBA8{ 0xC8, 0xFF, 0x64, 255 }, - FlagType::FLAG_RANDOMIZER_INF, static_cast(RAND_INF_GREG_FOUND), 0, yOffset, "Greg")); - yOffset += 18; + FlagType::FLAG_RANDOMIZER_INF, static_cast(RAND_INF_GREG_FOUND), "Greg")); + if (ctx->GetOption(RSK_SHUFFLE_FISHING_POLE)) { + mEntries.push_back(std::make_shared( + gItemIconFishingPoleTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 }, + FlagType::FLAG_RANDOMIZER_INF, static_cast(RAND_INF_FISHING_POLE_FOUND), "Fishing Pole")); + } if (ctx->GetOption(RSK_TRIFORCE_HUNT)) { mEntries.push_back(std::make_shared( - gTriforcePieceTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 }, 0, yOffset, + gTriforcePieceTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 }, reinterpret_cast(&gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected), ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_REQUIRED).Get() + 1, ctx->GetOption(RSK_TRIFORCE_HUNT_PIECES_TOTAL).Get() + 1)); - yOffset += 18; + } + if (ctx->GetOption(RSK_SKELETON_KEY)) { + mEntries.push_back(std::make_shared( + gSmallKeyCounterIconTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16, Color_RGBA8{ 255, 255, 255, 255 }, + FlagType::FLAG_RANDOMIZER_INF, static_cast(RAND_INF_HAS_SKELETON_KEY), "Skeleton Key")); } if (ctx->GetOption(RSK_SHUFFLE_OCARINA_BUTTONS)) { - mEntries.push_back(std::make_shared(0, yOffset)); - yOffset += 18; + mEntries.push_back(std::make_shared()); } if (ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).IsNot(RO_BOSS_SOULS_OFF)) { static const char* bossSoulNames[] = { @@ -136,15 +161,22 @@ Kaleido::Kaleido() { for (int i = RAND_INF_GOHMA_SOUL; i < RAND_INF_GANON_SOUL; i++) { mEntries.push_back(std::make_shared( gBossSoulTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 }, - FlagType::FLAG_RANDOMIZER_INF, i, 0, yOffset, bossSoulNames[i - RAND_INF_GOHMA_SOUL])); - yOffset += 18; + FlagType::FLAG_RANDOMIZER_INF, i, bossSoulNames[i - RAND_INF_GOHMA_SOUL])); } } if (ctx->GetOption(RSK_SHUFFLE_BOSS_SOULS).Is(RO_BOSS_SOULS_ON_PLUS_GANON)) { mEntries.push_back(std::make_shared( gBossSoulTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, Color_RGBA8{ 255, 255, 255, 255 }, - FlagType::FLAG_RANDOMIZER_INF, RAND_INF_GANON_SOUL, 0, yOffset, "Ganon's Soul")); - yOffset += 18; + FlagType::FLAG_RANDOMIZER_INF, RAND_INF_GANON_SOUL, "Ganon's Soul")); + } + if (ctx->GetOption(RSK_LOCK_OVERWORLD_DOORS)) { + int rg = RG_GUARD_HOUSE_KEY; + for (int i = RAND_INF_GUARD_HOUSE_KEY_OBTAINED; i <= RAND_INF_FISHING_HOLE_KEY_OBTAINED; i += 2, rg++) { + mEntries.push_back(std::make_shared( + gSmallKeyCounterIconTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16, Color_RGBA8{ 255, 255, 255, 255 }, + FlagType::FLAG_RANDOMIZER_INF, i, + Rando::StaticData::RetrieveItem(static_cast(rg)).GetName().english)); + } } } @@ -162,6 +194,7 @@ void Kaleido::Draw(PlayState* play) { mEntryDl.clear(); OPEN_DISPS(play->state.gfxCtx); mEntryDl.push_back(gsDPPipeSync()); + Gfx_SetupDL_39Opa(play->state.gfxCtx); Gfx_SetupDL_42Opa(play->state.gfxCtx); mEntryDl.push_back(gsDPSetCombineMode(G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM)); @@ -179,13 +212,23 @@ void Kaleido::Draw(PlayState* play) { if (!((pauseCtx->state != 6) || ((pauseCtx->stickRelX == 0) && (pauseCtx->stickRelY == 0)))) { if (pauseCtx->cursorSpecialPos == 0) { if ((pauseCtx->stickRelY > 30) || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DUP))) { - if (mTopIndex > 0) { - mTopIndex--; + if (mCursorPos > 0) { + mCursorPos--; + Audio_PlaySoundGeneral(NA_SE_SY_CURSOR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + } + if (mCursorPos < mTopIndex) { + mTopIndex = mCursorPos; shouldScroll = true; } } else if ((pauseCtx->stickRelY < -30) || (dpad && CHECK_BTN_ALL(input->press.button, BTN_DDOWN))) { - if (mTopIndex + mNumVisible < mEntries.size()) { - mTopIndex++; + if (mCursorPos < mEntries.size() - 1) { + mCursorPos++; + Audio_PlaySoundGeneral(NA_SE_SY_CURSOR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + } + if (mCursorPos >= mTopIndex + mNumVisible && mTopIndex + mNumVisible < mEntries.size()) { + mTopIndex = mCursorPos - mNumVisible + 1; shouldScroll = true; } } @@ -213,16 +256,14 @@ void Kaleido::Draw(PlayState* play) { pauseCtx->cursorSpecialPos = 0; } } - int yOffset = 2; + int yOffset = 1; for (int i = mTopIndex; i < (mTopIndex + mNumVisible) && i < mEntries.size(); i++) { auto& entry = mEntries[i]; - if (shouldScroll) { - entry->SetYOffset(yOffset); - yOffset += 18; - Audio_PlaySoundGeneral(NA_SE_SY_CURSOR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, - &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); - } + entry->SetYOffset(yOffset); + yOffset += 9; Matrix_Push(); + entry->SetSelected((i == mCursorPos) && !(pauseCtx->cursorSpecialPos == PAUSE_CURSOR_PAGE_RIGHT || + pauseCtx->cursorSpecialPos == PAUSE_CURSOR_PAGE_LEFT)); entry->Draw(play, &mEntryDl); Matrix_Pop(); } @@ -252,9 +293,9 @@ extern "C" void RandoKaleido_UpdateMiscCollectibles(int16_t inDungeonScene) { KaleidoEntryIconFlag::KaleidoEntryIconFlag(const char* iconResourceName, int iconFormat, int iconSize, int iconWidth, int iconHeight, Color_RGBA8 iconColor, FlagType flagType, int flag, - int16_t x, int16_t y, std::string name) - : mFlagType(flagType), mFlag(flag), KaleidoEntryIcon(iconResourceName, iconFormat, iconSize, iconWidth, iconHeight, - iconColor, x, y, std::move(name)) { + std::string name) + : mFlagType(flagType), mFlag(flag), + KaleidoEntryIcon(iconResourceName, iconFormat, iconSize, iconWidth, iconHeight, iconColor, std::move(name)) { BuildVertices(); } @@ -264,9 +305,9 @@ void KaleidoEntryIconFlag::Update(PlayState* play) { KaleidoEntryIconCountRequired::KaleidoEntryIconCountRequired(const char* iconResourceName, int iconFormat, int iconSize, int iconWidth, int iconHeight, Color_RGBA8 iconColor, - int16_t x, int16_t y, int* watch, int required, int total) + int* watch, int required, int total) : mWatch(watch), mRequired(required), mTotal(total), - KaleidoEntryIcon(iconResourceName, iconFormat, iconSize, iconWidth, iconHeight, iconColor, x, y) { + KaleidoEntryIcon(iconResourceName, iconFormat, iconSize, iconWidth, iconHeight, iconColor) { mCount = *mWatch; BuildText(); BuildVertices(); @@ -287,31 +328,54 @@ void KaleidoEntryIconCountRequired::BuildText() { void KaleidoEntryIcon::BuildVertices() { int offsetY = 0; int offsetX = 0; - // 4 vertices per character, plus one for the preceding icon. - Vtx* vertices = (Vtx*)calloc(sizeof(Vtx[4]), mText.length() + 1); + // 4 vertices per character, plus one for the preceding icon, plus one for the cursor. + Vtx* vertices = (Vtx*)calloc(sizeof(Vtx[4]), mText.length() + 2); + // Vertex for the cursor. + Ship_CreateQuadVertexGroup(vertices, offsetX, offsetY, 16, 24, 0); + offsetX += 18; // Vertex for the preceding icon. - Ship_CreateQuadVertexGroup(vertices, offsetX, offsetY, mIconWidth, mIconHeight, 0); + Ship_CreateQuadVertexGroup(&vertices[4], offsetX, offsetY, mIconWidth, mIconHeight, 0); offsetX += 18; for (size_t i = 0; i < mText.length(); i++) { int charWidth = static_cast(Ship_GetCharFontWidth(mText[i])); - Ship_CreateQuadVertexGroup(&(vertices)[(i + 1) * 4], offsetX, offsetY, charWidth, 16, 0); + Ship_CreateQuadVertexGroup(&(vertices)[((i + 1) * 4) + 4], offsetX, offsetY, charWidth, 16, 0); offsetX += charWidth; } offsetY += FONT_CHAR_TEX_HEIGHT; - mWidth = static_cast(offsetX); - mHeight = static_cast(offsetY); + // mWidth = static_cast(offsetX); + // mHeight = static_cast(offsetY); + + vertices[1].v.ob[0] = 15; // top-right x + vertices[2].v.ob[1] = 15; // bottom-left y + vertices[3].v.ob[0] = 15; // bottom-right x + vertices[3].v.ob[1] = 15; // bottom-right y + vertices[5].v.ob[0] = 32; // top-right x + vertices[6].v.ob[1] = 16; // bottom-left-y + vertices[7].v.ob[0] = 32; // bottom-right x + vertices[7].v.ob[1] = 16; // bottom-right y + + for (size_t i = 0; i < mText.length() + 2; i++) { + size_t j = i * 4; + vertices[j].v.ob[0] = vertices[j].v.ob[0] / 2; + vertices[j].v.ob[1] = vertices[j].v.ob[1] / 2; + vertices[j + 1].v.ob[0] = vertices[j + 1].v.ob[0] / 2; + vertices[j + 1].v.ob[1] = vertices[j + 1].v.ob[1] / 2; + vertices[j + 2].v.ob[0] = vertices[j + 2].v.ob[0] / 2; + vertices[j + 2].v.ob[1] = vertices[j + 2].v.ob[1] / 2; + vertices[j + 3].v.ob[0] = vertices[j + 3].v.ob[0] / 2; + vertices[j + 3].v.ob[1] = vertices[j + 3].v.ob[1] / 2; + } + + mWidth = static_cast(offsetX / 2); + mHeight = static_cast(8); - vertices[1].v.ob[0] = 16; - vertices[2].v.ob[1] = 16; - vertices[3].v.ob[0] = 16; - vertices[3].v.ob[1] = 16; vtx = vertices; } KaleidoEntryIcon::KaleidoEntryIcon(const char* iconResourceName, int iconFormat, int iconSize, int iconWidth, - int iconHeight, Color_RGBA8 iconColor, int16_t x, int16_t y, std::string text) + int iconHeight, Color_RGBA8 iconColor, std::string text) : mIconResourceName(iconResourceName), mIconFormat(iconFormat), mIconSize(iconSize), mIconWidth(iconWidth), - mIconHeight(iconHeight), mIconColor(iconColor), KaleidoEntry(x, y, std::move(text)) { + mIconHeight(iconHeight), mIconColor(iconColor), KaleidoEntry(std::move(text)) { } void KaleidoEntryIcon::RebuildVertices() { @@ -329,9 +393,9 @@ void KaleidoEntryIconCountRequired::Update(PlayState* play) { } } -KaleidoEntryOcarinaButtons::KaleidoEntryOcarinaButtons(int16_t x, int16_t y) +KaleidoEntryOcarinaButtons::KaleidoEntryOcarinaButtons() : KaleidoEntryIcon(gItemIconOcarinaOfTimeTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, - Color_RGBA8{ 255, 255, 255, 255 }, x, y, "\x9F\xA5\xA6\xA7\xA8") { + Color_RGBA8{ 255, 255, 255, 255 }, "\x9F\xA5\xA6\xA7\xA8") { CalculateColors(); BuildVertices(); } @@ -405,13 +469,24 @@ void KaleidoEntryOcarinaButtons::Draw(PlayState* play, std::vector* mEntryD mEntryDl->push_back(gsSPMatrix(Matrix_NewMtx(play->state.gfxCtx, (char*)__FILE__, __LINE__), G_MTX_PUSH | G_MTX_LOAD | G_MTX_MODELVIEW)); + // cursor (if selected) + if (mSelected) { + mEntryDl->push_back(gsDPSetPrimColor(0, 0, 255, 255, 255, 255)); + mEntryDl->push_back(gsSPVertex(vtx, 4, 0)); + Gfx cursorIconTex[] = { gsDPLoadTextureBlock(gArrowCursorTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 24, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, + G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD) }; + mEntryDl->insert(mEntryDl->end(), std::begin(cursorIconTex), std::end(cursorIconTex)); + mEntryDl->push_back(gsSP1Quadrangle(0, 2, 3, 1, 0)); + } + // icon if (!mAchieved) { mEntryDl->push_back(gsDPSetGrayscaleColor(109, 109, 109, 255)); mEntryDl->push_back(gsSPGrayscale(true)); } mEntryDl->push_back(gsDPSetPrimColor(0, 0, mIconColor.r, mIconColor.g, mIconColor.b, mIconColor.a)); - mEntryDl->push_back(gsSPVertex(vtx, 4, 0)); + mEntryDl->push_back(gsSPVertex(&vtx[4], 4, 0)); LoadIconTex(mEntryDl); mEntryDl->push_back(gsSP1Quadrangle(0, 2, 3, 1, 0)); mEntryDl->push_back(gsSPGrayscale(false)); @@ -426,7 +501,7 @@ void KaleidoEntryOcarinaButtons::Draw(PlayState* play, std::vector* mEntryD // By this point 4 vertices have already been loaded for the preceding icon. if (i % 16 == 0) { size_t numVtxToLoad = std::min(numChar - i, 16) * 4; - mEntryDl->push_back(gsSPVertex(&vtx[4 + (vtxGroup * 16 * 4)], numVtxToLoad, 0)); + mEntryDl->push_back(gsSPVertex(&vtx[8 + (vtxGroup * 16 * 4)], numVtxToLoad, 0)); vtxGroup++; } diff --git a/soh/soh/Enhancements/kaleido.h b/soh/soh/Enhancements/kaleido.h index 7776afb66..605bae226 100644 --- a/soh/soh/Enhancements/kaleido.h +++ b/soh/soh/Enhancements/kaleido.h @@ -26,18 +26,20 @@ class KaleidoEntry { * @param text the initial value of the line of text. Can be omitted for an * empty string. */ - KaleidoEntry(int16_t x, int16_t y, std::string text = ""); + KaleidoEntry(std::string text = ""); virtual void Draw(PlayState* play, std::vector* mEntryDl) = 0; virtual void Update(PlayState* play) = 0; void SetYOffset(int yOffset); + void SetSelected(bool val); protected: - int16_t mX; - int16_t mY; + int16_t mX = 0; + int16_t mY = 0; int16_t mHeight; int16_t mWidth; Vtx* vtx; std::string mText; + bool mSelected = false; bool mAchieved = false; }; @@ -59,7 +61,7 @@ class KaleidoEntryIcon : public KaleidoEntry { * @param text text to draw to the right of the icon. */ KaleidoEntryIcon(const char* iconResourceName, int iconFormat, int iconSize, int iconWidth, int iconHeight, - Color_RGBA8 iconColor, int16_t x, int16_t y, std::string text = ""); + Color_RGBA8 iconColor, std::string text = ""); void Draw(PlayState* play, std::vector* mEntryDl) override; void RebuildVertices(); @@ -95,8 +97,7 @@ class KaleidoEntryIconFlag : public KaleidoEntryIcon { * @param mName name to draw to the right of the icon. Leave blank to omit. */ KaleidoEntryIconFlag(const char* iconResourceName, int iconFormat, int iconSize, int iconWidth, int iconHeight, - Color_RGBA8 iconColor, FlagType flagType, int flag, int16_t x, int16_t y, - std::string name = ""); + Color_RGBA8 iconColor, FlagType flagType, int flag, std::string name = ""); void Update(PlayState* play) override; private: @@ -128,8 +129,7 @@ class KaleidoEntryIconCountRequired : public KaleidoEntryIcon { * @param total The amount of this collectible available in the seed. Set to 0 to not render. */ KaleidoEntryIconCountRequired(const char* iconResourceName, int iconFormat, int iconSize, int iconWidth, - int iconHeight, Color_RGBA8 iconColor, int16_t x, int16_t y, int* watch, - int required = 0, int total = 0); + int iconHeight, Color_RGBA8 iconColor, int* watch, int required = 0, int total = 0); void Update(PlayState* play) override; private: @@ -143,7 +143,7 @@ class KaleidoEntryIconCountRequired : public KaleidoEntryIcon { class KaleidoEntryOcarinaButtons : public KaleidoEntryIcon { public: - KaleidoEntryOcarinaButtons(int16_t x, int16_t y); + KaleidoEntryOcarinaButtons(); void Update(PlayState* play) override; void Draw(PlayState* play, std::vector* mEntryDl) override; @@ -164,7 +164,8 @@ class Kaleido { std::vector> mEntries; std::vector mEntryDl; int mTopIndex = 0; - int mNumVisible = 7; + int mCursorPos = 0; + int mNumVisible = 14; }; } // namespace Rando diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c index 627300036..1e53d8180 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c @@ -1633,8 +1633,7 @@ void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) { gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); if (pauseCtx->randoQuestMode) { - POLY_OPA_DISP = - KaleidoScope_DrawPageSections(POLY_OPA_DISP, pauseCtx->saveVtx, sSaveTexs[gSaveContext.language]); + POLY_OPA_DISP = KaleidoScope_DrawPageSections(POLY_OPA_DISP, pauseCtx->saveVtx, sGameOverTexs); RandoKaleido_DrawMiscCollectibles(play); } else { POLY_OPA_DISP = KaleidoScope_DrawPageSections(POLY_OPA_DISP, pauseCtx->questPageVtx, @@ -1729,8 +1728,7 @@ void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) { gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); if (pauseCtx->randoQuestMode) { - POLY_OPA_DISP = KaleidoScope_DrawPageSections(POLY_OPA_DISP, pauseCtx->saveVtx, - sSaveTexs[gSaveContext.language]); + POLY_OPA_DISP = KaleidoScope_DrawPageSections(POLY_OPA_DISP, pauseCtx->saveVtx, sGameOverTexs); RandoKaleido_DrawMiscCollectibles(play); } else { POLY_OPA_DISP = KaleidoScope_DrawPageSections(POLY_OPA_DISP, pauseCtx->questPageVtx, From 9401d4fd712cc4b99537b37940881e9e1a7f61b6 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Wed, 3 Dec 2025 10:00:18 -0600 Subject: [PATCH 15/15] Stop hiding key counts with skeleton key, and grant all keys (#5932) --- .../Enhancements/randomizer/hook_handlers.cpp | 6 ------ soh/soh/Enhancements/randomizer/randomizer.cpp | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/hook_handlers.cpp b/soh/soh/Enhancements/randomizer/hook_handlers.cpp index 276270bad..95a20083a 100644 --- a/soh/soh/Enhancements/randomizer/hook_handlers.cpp +++ b/soh/soh/Enhancements/randomizer/hook_handlers.cpp @@ -1616,12 +1616,6 @@ void RandomizerOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_l } break; } - case VB_RENDER_KEY_COUNTER: { - if (Flags_GetRandomizerInf(RAND_INF_HAS_SKELETON_KEY)) { - *should = false; - } - break; - } case VB_RENDER_RUPEE_COUNTER: { if (!Flags_GetRandomizerInf(RAND_INF_HAS_WALLET) || Flags_GetRandomizerInf(RAND_INF_HAS_INFINITE_MONEY)) { *should = false; diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 4ad550164..cf30a53b3 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -5990,7 +5990,6 @@ std::map randomizerGetToRandInf = { { RG_MAGIC_INF, RAND_INF_HAS_INFINITE_MAGIC_METER }, { RG_BOMBCHU_INF, RAND_INF_HAS_INFINITE_BOMBCHUS }, { RG_WALLET_INF, RAND_INF_HAS_INFINITE_MONEY }, - { RG_SKELETON_KEY, RAND_INF_HAS_SKELETON_KEY }, { RG_OCARINA_A_BUTTON, RAND_INF_HAS_OCARINA_A }, { RG_OCARINA_C_UP_BUTTON, RAND_INF_HAS_OCARINA_C_UP }, { RG_OCARINA_C_DOWN_BUTTON, RAND_INF_HAS_OCARINA_C_DOWN }, @@ -6190,6 +6189,21 @@ extern "C" u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) { } gSaveContext.inventory.dungeonItems[mapIndex] |= bitmask; + return Return_Item_Entry(giEntry, RG_NONE); + } else if (item == RG_SKELETON_KEY) { + Flags_SetRandomizerInf(RAND_INF_HAS_SKELETON_KEY); + // This isn't technically necessary, because keys will no longer be consumed, + // but for the player's sanity we display that they _have_ keys. + gSaveContext.inventory.dungeonKeys[SCENE_FOREST_TEMPLE] = FOREST_TEMPLE_SMALL_KEY_MAX; + gSaveContext.inventory.dungeonKeys[SCENE_FIRE_TEMPLE] = FIRE_TEMPLE_SMALL_KEY_MAX; + gSaveContext.inventory.dungeonKeys[SCENE_WATER_TEMPLE] = WATER_TEMPLE_SMALL_KEY_MAX; + gSaveContext.inventory.dungeonKeys[SCENE_SPIRIT_TEMPLE] = SPIRIT_TEMPLE_SMALL_KEY_MAX; + gSaveContext.inventory.dungeonKeys[SCENE_SHADOW_TEMPLE] = SHADOW_TEMPLE_SMALL_KEY_MAX; + gSaveContext.inventory.dungeonKeys[SCENE_BOTTOM_OF_THE_WELL] = BOTTOM_OF_THE_WELL_SMALL_KEY_MAX; + gSaveContext.inventory.dungeonKeys[SCENE_GERUDO_TRAINING_GROUND] = GERUDO_TRAINING_GROUND_SMALL_KEY_MAX; + gSaveContext.inventory.dungeonKeys[SCENE_THIEVES_HIDEOUT] = GERUDO_FORTRESS_SMALL_KEY_MAX; + gSaveContext.inventory.dungeonKeys[SCENE_INSIDE_GANONS_CASTLE] = GANONS_CASTLE_SMALL_KEY_MAX; + return Return_Item_Entry(giEntry, RG_NONE); } else if (item >= RG_GUARD_HOUSE_KEY && item <= RG_FISHING_HOLE_KEY) { Flags_SetRandomizerInf(