Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 223 additions & 0 deletions mm/2s2h/Rando/Logic/EntranceShuffle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
#include "2s2h/GameInteractor/GameInteractor.h"
#include "2s2h/ShipInit.hpp"
#include "EntranceShuffle.h"
#include "Logic.h"

extern "C" {
#include "z64scene.h"
}

namespace Rando {

namespace EntranceShuffle {

// Entrance mapping storage
std::map<s32, s32> sEntranceMap;

// Optimally, these lists are dynamically built from the maps we already have built for logic...

std::set<s32> interiorEntrances = {
// Clock Town
ENTRANCE(ASTRAL_OBSERVATORY, 0),
ENTRANCE(TREASURE_CHEST_SHOP, 0),
ENTRANCE(HONEY_AND_DARLINGS_SHOP, 0),
ENTRANCE(MAYORS_RESIDENCE, 0),
ENTRANCE(TOWN_SHOOTING_GALLERY, 0),
ENTRANCE(STOCK_POT_INN, 0),
ENTRANCE(STOCK_POT_INN, 1),
ENTRANCE(MILK_BAR, 0),
ENTRANCE(CURIOSITY_SHOP, 1),
ENTRANCE(FAIRY_FOUNTAIN, 0),
ENTRANCE(CLOCK_TOWER_INTERIOR, 1),
ENTRANCE(SWORDMANS_SCHOOL, 0),
ENTRANCE(CURIOSITY_SHOP, 0),
ENTRANCE(TRADING_POST, 0),
ENTRANCE(BOMB_SHOP, 0),
ENTRANCE(POST_OFFICE, 0),
ENTRANCE(LOTTERY_SHOP, 0),
// Termina Field & Roads
ENTRANCE(SWAMP_SHOOTING_GALLERY, 0),
ENTRANCE(TOURIST_INFORMATION, 0),
ENTRANCE(MAGIC_HAGS_POTION_SHOP, 0),
// Milk Road
ENTRANCE(CUCCO_SHACK, 0),
ENTRANCE(DOGGY_RACETRACK, 0),
ENTRANCE(RANCH_HOUSE, 0),
ENTRANCE(RANCH_HOUSE, 1),
// Mountain Village
ENTRANCE(GORON_SHOP, 0),
ENTRANCE(MOUNTAIN_SMITHY, 0),
// Great Bay
ENTRANCE(FISHERMANS_HUT, 0),
ENTRANCE(MARINE_RESEARCH_LAB, 0),
ENTRANCE(ZORA_HALL_ROOMS, 0),
ENTRANCE(ZORA_HALL_ROOMS, 1),
ENTRANCE(ZORA_HALL_ROOMS, 2),
ENTRANCE(ZORA_HALL_ROOMS, 3),
ENTRANCE(ZORA_HALL_ROOMS, 5),
ENTRANCE(OCEANSIDE_SPIDER_HOUSE, 0),
// Ikana
ENTRANCE(GHOST_HUT, 0),
ENTRANCE(MUSIC_BOX_HOUSE, 0),
// Great Fairy Fountains
ENTRANCE(FAIRY_FOUNTAIN, 1),
ENTRANCE(FAIRY_FOUNTAIN, 2),
ENTRANCE(FAIRY_FOUNTAIN, 3),
ENTRANCE(FAIRY_FOUNTAIN, 4),
// Other
ENTRANCE(SWAMP_SPIDER_HOUSE, 0),
};

std::set<s32> dungeonEntrances = {
ENTRANCE(WOODFALL_TEMPLE, 0),
ENTRANCE(SNOWHEAD_TEMPLE, 0),
ENTRANCE(GREAT_BAY_TEMPLE, 0),
};

std::vector<EntrancePair> ConvertSetToEntrancePairs(const std::set<s32>& entranceSet) {
std::vector<EntrancePair> entrancePairs;
for (s32 entrance : entranceSet) {
auto randoRegionId = Rando::Logic::GetRegionIdFromEntrance(entrance);
for (const auto& [exitId, regionExit] : Rando::Logic::Regions[randoRegionId].exits) {
if (regionExit.returnEntrance == entrance) {
entrancePairs.push_back({ entrance, exitId });
break;
}
}
}

return entrancePairs;
}

std::vector<EntrancePair> GetInteriorEntrances() {
return ConvertSetToEntrancePairs(interiorEntrances);
}

std::vector<EntrancePair> GetGrottoEntrances() {
return {};
}

std::vector<EntrancePair> GetDungeonEntrances() {
return ConvertSetToEntrancePairs(dungeonEntrances);
}

std::vector<EntrancePair> GetOverworldEntrances() {
return {
{ ENTRANCE(EAST_CLOCK_TOWN, 3), ENTRANCE(SOUTH_CLOCK_TOWN, 2) },
{ ENTRANCE(WEST_CLOCK_TOWN, 2), ENTRANCE(SOUTH_CLOCK_TOWN, 3) },
{ ENTRANCE(NORTH_CLOCK_TOWN, 2), ENTRANCE(SOUTH_CLOCK_TOWN, 4) },
{ ENTRANCE(WEST_CLOCK_TOWN, 1), ENTRANCE(SOUTH_CLOCK_TOWN, 5) },
{ ENTRANCE(LAUNDRY_POOL, 0), ENTRANCE(SOUTH_CLOCK_TOWN, 6) },
{ ENTRANCE(EAST_CLOCK_TOWN, 1), ENTRANCE(SOUTH_CLOCK_TOWN, 7) },
{ ENTRANCE(NORTH_CLOCK_TOWN, 1), ENTRANCE(EAST_CLOCK_TOWN, 5) },
};
}

std::vector<EntrancePair> GetEntrancePool(EntrancePoolType poolType) {
switch (poolType) {
case POOL_INTERIOR:
return GetInteriorEntrances();
case POOL_GROTTO:
return GetGrottoEntrances();
case POOL_DUNGEON:
return GetDungeonEntrances();
case POOL_OVERWORLD:
return GetOverworldEntrances();
default:
return {};
}
}

bool IsEntranceShuffleEnabled() {
return IS_RANDO && RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENTRANCES] != RO_ENTRANCE_SHUFFLE_OFF;
}

// Called on file load and in file creation prior to logic calculation
void ShuffleEntrances() {
sEntranceMap.clear();

if (!IsEntranceShuffleEnabled()) {
return;
}

Ship_Random_Seed(gSaveContext.save.shipSaveInfo.rando.finalSeed);

auto shuffleType = RANDO_SAVE_OPTIONS[RO_SHUFFLE_ENTRANCES];

std::vector<EntrancePoolType> poolsToShuffle;

switch (shuffleType) {
case RO_ENTRANCE_SHUFFLE_INTERIORS_ONLY:
poolsToShuffle = { POOL_INTERIOR };
break;
case RO_ENTRANCE_SHUFFLE_DUNGEONS_ONLY:
poolsToShuffle = { POOL_DUNGEON };
break;
case RO_ENTRANCE_SHUFFLE_FULL:
poolsToShuffle = { POOL_INTERIOR, POOL_DUNGEON, POOL_OVERWORLD };
break;
default:
return;
}

// Shuffle each pool independently
for (auto poolType : poolsToShuffle) {
std::vector<EntrancePair> originalPool = GetEntrancePool(poolType);
auto pool = originalPool; // Make a copy to shuffle

if (pool.size() < 2) {
continue;
}

// Shuffle pool
for (size_t i = 0; i < pool.size(); i++) {
size_t j = Ship_Random(0, pool.size() - 1);
std::swap(pool[i], pool[j]);
}

for (size_t i = 0; i < pool.size(); ++i) {
const auto& from = originalPool[i];
const auto& to = pool[i];

if (from.entrance != to.entrance) {
sEntranceMap[from.entrance] = to.entrance;
}

// if (from.isReversible && to.isReversible) {
if (to.exit != from.exit) {
sEntranceMap[to.exit] = from.exit;
}
// }
}
}
}

s32 GetShuffledEntrance(s32 originalEntrance) {
if (!IsEntranceShuffleEnabled()) {
return originalEntrance;
}

if (sEntranceMap.count(originalEntrance) > 0) {
return sEntranceMap[originalEntrance];
}

return originalEntrance;
}

s32 GetOriginalEntrance(s32 shuffledEntrance) {
if (!IsEntranceShuffleEnabled()) {
return shuffledEntrance;
}

for (const auto& [orig, mapped] : sEntranceMap) {
if (mapped == shuffledEntrance) {
return orig;
}
}

return shuffledEntrance;
}

} // namespace EntranceShuffle

} // namespace Rando
50 changes: 50 additions & 0 deletions mm/2s2h/Rando/Logic/EntranceShuffle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#ifndef RANDO_ENTRANCE_SHUFFLE_H
#define RANDO_ENTRANCE_SHUFFLE_H

#include "Rando/Rando.h"
#include <map>
#include <vector>

extern "C" {
#include "functions.h"
#include "variables.h"
}

namespace Rando {

namespace EntranceShuffle {

// Structure to represent an entrance pair
struct EntrancePair {
s32 entrance; // the entrance you take to enter Target area from source
s32 exit; // the exit you take to leave Target area to source
};

// Groups of entrances that can be shuffled together
enum EntrancePoolType {
POOL_INTERIOR, // Houses, shops, etc.
POOL_GROTTO, // Grottos and caves
POOL_DUNGEON, // Dungeon entrances
POOL_OVERWORLD, // Overworld area connections
};

// Generate entrance shuffle mappings for a new seed
void ShuffleEntrances();

// Get the shuffled destination for an entrance
s32 GetShuffledEntrance(s32 originalEntrance);

// Get the original entrance from a shuffled one (for reverse lookups)
s32 GetOriginalEntrance(s32 shuffledEntrance);

// Check if entrance shuffle is enabled
bool IsEntranceShuffleEnabled();

// Get all entrances in a specific pool
std::vector<EntrancePair> GetEntrancePool(EntrancePoolType poolType);

} // namespace EntranceShuffle

} // namespace Rando

#endif // RANDO_ENTRANCE_SHUFFLE_H
8 changes: 7 additions & 1 deletion mm/2s2h/Rando/Logic/Logic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "2s2h/ShipInit.hpp"

#include "Logic.h"
#include "EntranceShuffle.h"

namespace Rando {

Expand Down Expand Up @@ -44,7 +45,12 @@ void FindReachableRegions(RandoRegionId currentRegion, std::set<RandoRegionId>&
}

for (auto& [exitId, regionExit] : randoRegion.exits) {
RandoRegionId connectedRegionId = GetRegionIdFromEntrance(exitId);
s32 lookupExit = exitId;
if (Rando::EntranceShuffle::IsEntranceShuffleEnabled()) {
lookupExit = Rando::EntranceShuffle::GetShuffledEntrance(lookupExit);
}

RandoRegionId connectedRegionId = GetRegionIdFromEntrance(lookupExit);
// Check if the region is accessible and hasn’t been visited yet
if (reachableRegions.count(connectedRegionId) == 0 && regionExit.condition()) {
reachableRegions.insert(connectedRegionId); // Mark region as visited
Expand Down
8 changes: 8 additions & 0 deletions mm/2s2h/Rando/Menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ std::unordered_map<int32_t, const char*> accessTrialsOptions = {
{ RO_ACCESS_TRIALS_OPEN, "Open" },
};

std::unordered_map<int32_t, const char*> entranceShuffleOptions = {
{ RO_ENTRANCE_SHUFFLE_OFF, "Off" },
{ RO_ENTRANCE_SHUFFLE_INTERIORS_ONLY, "Interiors Only" },
{ RO_ENTRANCE_SHUFFLE_DUNGEONS_ONLY, "Dungeons Only" },
{ RO_ENTRANCE_SHUFFLE_FULL, "Full" },
};

std::vector<int32_t> incompatibleWithVanilla = {
RO_SHUFFLE_BOSS_SOULS,
RO_SHUFFLE_SWIM,
Expand Down Expand Up @@ -356,6 +363,7 @@ static void DrawShufflesTab() {
CVarCheckbox("Shuffle Freestanding Items", Rando::StaticData::Options[RO_SHUFFLE_FREESTANDING_ITEMS].cvar);
CVarCheckbox("Shuffle Wonder Items", "gPlaceholderBool",
CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
CVarCombobox("Shuffle Entrances", Rando::StaticData::Options[RO_SHUFFLE_ENTRANCES].cvar, &entranceShuffleOptions);
ImGui::EndChild();
ImGui::SameLine();
ImGui::BeginChild("randoLocationsColumn3", ImVec2(columnWidth, halfHeight));
Expand Down
39 changes: 39 additions & 0 deletions mm/2s2h/Rando/MiscBehavior/EntranceHooks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "2s2h/GameInteractor/GameInteractor.h"
#include "2s2h/ShipInit.hpp"
#include "2s2h/Rando/Logic/EntranceShuffle.h"

extern "C" {
#include "functions.h"
#include "variables.h"
#include "z64scene.h"
}

namespace Rando {

namespace EntranceShuffle {

// Hook into scene transitions to apply entrance shuffling
void OnPlayDestroy() {
if (!IsEntranceShuffleEnabled()) {
return;
}

// Get the shuffled destination entrance
s32 originalEntrance = gSaveContext.save.entrance;
s32 shuffledEntrance = GetShuffledEntrance(originalEntrance);

if (shuffledEntrance != originalEntrance) {
gSaveContext.save.entrance = shuffledEntrance;
}
}

static RegisterShipInitFunc registerHooks(
[]() {
// Hook into OnPlayDestroy which is called just before transitioning
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayDestroy>([]() { OnPlayDestroy(); });
},
{});

} // namespace EntranceShuffle

} // namespace Rando
2 changes: 2 additions & 0 deletions mm/2s2h/Rando/MiscBehavior/MiscBehavior.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "MiscBehavior.h"
#include "2s2h/Rando/Logic/Logic.h"
#include "2s2h/Rando/Logic/EntranceShuffle.h"

extern "C" {
#include "variables.h"
Expand All @@ -16,6 +17,7 @@ void Rando::MiscBehavior::OnFileLoad() {
Rando::MiscBehavior::CheckQueueReset();
Rando::MiscBehavior::InitKaleidoItemPage();
Rando::MiscBehavior::InitOfferGetItemBehavior();
Rando::EntranceShuffle::ShuffleEntrances();

COND_HOOK(OnFlagSet, IS_RANDO, Rando::MiscBehavior::OnFlagSet);
COND_HOOK(OnSceneFlagSet, IS_RANDO, Rando::MiscBehavior::OnSceneFlagSet);
Expand Down
4 changes: 4 additions & 0 deletions mm/2s2h/Rando/MiscBehavior/OnFileCreate.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "MiscBehavior.h"
#include "Rando/Spoiler/Spoiler.h"
#include "Rando/Logic/Logic.h"
#include "Rando/Logic/EntranceShuffle.h"
#include "2s2h/ShipUtils.h"
#include <libultraship/bridge/consolevariablebridge.h>
#include <spdlog/spdlog.h>
Expand Down Expand Up @@ -172,6 +173,9 @@ void Rando::MiscBehavior::OnFileCreate(s16 fileNum) {
// Grant the starting stuff
GrantStarters();

// Shuffle entrances if enabled
Rando::EntranceShuffle::ShuffleEntrances();

if (RANDO_SAVE_OPTIONS[RO_LOGIC] == RO_LOGIC_VANILLA) {
GiveItem(RI_SWORD_KOKIRI);
GiveItem(RI_SHIELD_HERO);
Expand Down
1 change: 1 addition & 0 deletions mm/2s2h/Rando/StaticData/Options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ std::map<RandoOptionId, RandoStaticOption> Options = {
RO(RO_SHUFFLE_COWS, RO_GENERIC_OFF),
RO(RO_SHUFFLE_CRATE_DROPS, RO_GENERIC_OFF),
RO(RO_SHUFFLE_ENEMY_DROPS, RO_GENERIC_OFF),
RO(RO_SHUFFLE_ENTRANCES, RO_ENTRANCE_SHUFFLE_OFF),
RO(RO_SHUFFLE_FREESTANDING_ITEMS, RO_GENERIC_OFF),
RO(RO_SHUFFLE_FROGS, RO_GENERIC_OFF),
RO(RO_SHUFFLE_GOLD_SKULLTULAS, RO_GENERIC_OFF),
Expand Down
Loading
Loading