Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9397b10
iAdd split ocarina songs mode with part-based progression.
RaccoonCloud Apr 24, 2026
5feddec
iUpdate split songs file headers with creator attribution.
RaccoonCloud Apr 24, 2026
2bd2ea4
Merge develop into feature/split-songs and resolve conflicts
RaccoonCloud Apr 24, 2026
5417774
Apply clang-format to split songs changes
RaccoonCloud Apr 24, 2026
3d0a0f6

RaccoonCloud Apr 26, 2026
0386051
Update split songs option text for progressive behavior.
RaccoonCloud Apr 26, 2026
6d49e4d
Randomizer: pickup textbox icons and progressive split songs
RaccoonCloud May 10, 2026
042e035
Fix clang-format in ItemMessages.cpp
RaccoonCloud May 10, 2026
6a10537
Randomizer: all song textbox icons work for progressive pickup
RaccoonCloud May 17, 2026
bc2027e
Simplify split songs to progressive-only RandInf tracking
RaccoonCloud Jun 4, 2026
72c849f
Address split songs PR review feedback
RaccoonCloud Jun 4, 2026
bd13ce5
Merge upstream develop into feature/split-songs
RaccoonCloud Jun 4, 2026
11a4287
Fix ItemMessages build after develop merge
RaccoonCloud Jun 4, 2026
f8464be
Bump libultraship to match upstream develop after merge
RaccoonCloud Jun 8, 2026
10f462d
Fix split song tracker texture lookup for Fast3dGui API
RaccoonCloud Jun 8, 2026
2340e5a
Fix seed generation crash when regenerating with split songs
RaccoonCloud Jun 8, 2026
3e1aff7
Fix split song part-1 grant and restore key ring icons
RaccoonCloud Jun 8, 2026
2998034
Simplify split songs to RandInf + progressive-only pattern.
RaccoonCloud Jun 9, 2026
600b8d3
Address split songs review: settings, pool, tracker, traps.
RaccoonCloud Jun 9, 2026
39427cc
Fix seed gen crash on progressive songs and clean up split songs menu…
RaccoonCloud Jun 9, 2026
b78aac9
Fix split song pickup icons and stabilize seed/save generation
RaccoonCloud Jun 15, 2026
79b6882
Fix Linux CI: use SaveData string overload for UTF-8 sanitization.
RaccoonCloud Jun 15, 2026
1198703
Merge upstream develop into feature/split-songs
RaccoonCloud Jun 15, 2026
7d146a6
Split PR scope per review: drop icon and UTF-8 side fixes.
RaccoonCloud Jun 18, 2026
2edee8d
Refactor split songs to PART items and static songData maps per review.
RaccoonCloud Jun 23, 2026
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
22 changes: 22 additions & 0 deletions soh/soh/Enhancements/debugconsole.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "soh/Enhancements/cosmetics/CosmeticsEditor.h"
#include "soh/Enhancements/audio/AudioEditor.h"
#include "soh/Enhancements/randomizer/logic.h"
#include "soh/Enhancements/randomizer/static_data.h"
#include "soh/Enhancements/randomizer/split_songs.h"

#define Path _Path
#define PATH_HACK
Expand Down Expand Up @@ -403,6 +405,20 @@ static bool GiveItemHandler(std::shared_ptr<Ship::Console> Console, const std::v
return 0;
}

static bool RandoGiveAllSplitSongPartsHandler(std::shared_ptr<Ship::Console> Console,
const std::vector<std::string> args, std::string* output) {
if (gPlayState == nullptr) {
ERROR_MESSAGE("No active game; load into gameplay first.");
return 1;
}
if (!IS_RANDO) {
ERROR_MESSAGE("Only works in Randomizer (load a rando save).");
return 1;
}
Rando::SplitSongs::DebugGiveAllSongParts(gPlayState);
return 0;
}

static bool EntranceHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& args,
std::string* output) {
if (args.size() < 2) {
Expand Down Expand Up @@ -1605,6 +1621,12 @@ void DebugConsole_Init(void) {
{ "giveItemID", Ship::ArgumentType::NUMBER },
} });

CMD_REGISTER("rando_give_all_split_song_parts",
{ RandoGiveAllSplitSongPartsHandler,
"Randomizer: grants all 12 ocarina songs via both split parts (tests Randomizer_Item_Give + "
"completion). Rando save only.",
{} });

CMD_REGISTER("item", { ItemHandler,
"Sets item ID in arg 1 into slot arg 2. No boundary checks. Use with caution.",
{
Expand Down
95 changes: 83 additions & 12 deletions soh/soh/Enhancements/randomizer/3drando/item_pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "random.hpp"
#include "spoiler_log.hpp"
#include "soh/Enhancements/randomizer/Traps.h"
#include "soh/Enhancements/randomizer/split_songs.h"
#include "z64item.h"
#include <spdlog/spdlog.h>

Expand Down Expand Up @@ -156,6 +157,15 @@ void GenerateItemPool() {
lesserPool.clear();
int reservedSlots = 0;

if (ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_ANYWHERE)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddItemToPool already adds items to possibleIceTrapModels if they are in the item pool, you should not need to add them manually.

You will however need to add trick names for the Progressive Songs in shop.cpp

const bool split = static_cast<bool>(ctx->GetOption(RSK_SPLIT_OCARINA_SONGS));
std::vector<RandomizerGet> songTrapModels;
Rando::SplitSongs::AppendSongIceTrapModels(songTrapModels, split);
for (RandomizerGet rg : songTrapModels) {
ctx->possibleIceTrapModels.insert(rg);
}
}

// clang-format off
AddItemToPool(RG_BOOMERANG, 2, 1, 1, 1);
AddItemToPool(RG_LENS_OF_TRUTH, 2, 1, 1, 1);
Expand Down Expand Up @@ -252,41 +262,102 @@ void GenerateItemPool() {
// add extra songs only if song shuffle is anywhere
if (ctx->GetOption(RSK_SHUFFLE_SONGS).IsNot(RO_SONG_SHUFFLE_OFF)) {
bool songAnywhere = ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_ANYWHERE);
const bool split = ctx->GetOption(RSK_SPLIT_OCARINA_SONGS) && songAnywhere;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having songs anywhere silently disable split songs here is not good practice for handling settings incompatibilities, instead check for and set 1 setting or the other in Context::FinalizeSettings and then trust it has been handled properly here.

This makes the setting easier to edit later.

if (!ctx->GetOption(RSK_STARTING_ZELDAS_LULLABY).Get()) {
AddItemToPool(RG_ZELDAS_LULLABY, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_ZELDAS_LULLABY, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_ZELDAS_LULLABY, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you running AddItemToPool twice? Given the other changes and cut downs you could simply say 3,2,2,2

@RaccoonCloud RaccoonCloud May 31, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for addressing this

This system was a potential setup for there to be additional progressive parts so you can set it to have say 6 split parts of a song, CURRENTLY A WIP but left in.

Also having an extra set of parts was to help say have a 3rd obtainable part to help avoid any soft locks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was considering future use of this, but I think that concept would be best done alongside a deeper change in progressives to support all items without bloating the item list.

I'm not sure if there's much demand for needing more than 1 "PART" however, and it would come with exponential code and flag bloat unless converted to it's own entry in save context like bombchu bags were

AddItemToPool(RG_PROGRESSIVE_ZELDAS_LULLABY, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_EPONAS_SONG).Get()) {
AddItemToPool(RG_EPONAS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_EPONAS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_EPONAS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_EPONAS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_SARIAS_SONG).Get()) {
AddItemToPool(RG_SARIAS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_SARIAS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_SARIAS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_SARIAS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_SUNS_SONG).Get()) {
AddItemToPool(RG_SUNS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_SUNS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_SUNS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_SUNS_SONG, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_SONG_OF_TIME).Get()) {
AddItemToPool(RG_SONG_OF_TIME, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_SONG_OF_TIME, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_SONG_OF_TIME, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_SONG_OF_TIME, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_SONG_OF_STORMS).Get()) {
AddItemToPool(RG_SONG_OF_STORMS, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_SONG_OF_STORMS, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_SONG_OF_STORMS, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_SONG_OF_STORMS, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_MINUET_OF_FOREST).Get()) {
AddItemToPool(RG_MINUET_OF_FOREST, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_MINUET_OF_FOREST, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_MINUET_OF_FOREST, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_MINUET_OF_FOREST, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_BOLERO_OF_FIRE).Get()) {
AddItemToPool(RG_BOLERO_OF_FIRE, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_BOLERO_OF_FIRE, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_BOLERO_OF_FIRE, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_BOLERO_OF_FIRE, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_SERENADE_OF_WATER).Get()) {
AddItemToPool(RG_SERENADE_OF_WATER, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_SERENADE_OF_WATER, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_SERENADE_OF_WATER, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_SERENADE_OF_WATER, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_REQUIEM_OF_SPIRIT).Get()) {
AddItemToPool(RG_REQUIEM_OF_SPIRIT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_REQUIEM_OF_SPIRIT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_REQUIEM_OF_SPIRIT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_REQUIEM_OF_SPIRIT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_NOCTURNE_OF_SHADOW).Get()) {
AddItemToPool(RG_NOCTURNE_OF_SHADOW, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_NOCTURNE_OF_SHADOW, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_NOCTURNE_OF_SHADOW, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_NOCTURNE_OF_SHADOW, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
if (!ctx->GetOption(RSK_STARTING_PRELUDE_OF_LIGHT).Get()) {
AddItemToPool(RG_PRELUDE_OF_LIGHT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
if (!split) {
AddItemToPool(RG_PRELUDE_OF_LIGHT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
} else {
AddItemToPool(RG_PROGRESSIVE_PRELUDE_OF_LIGHT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
AddItemToPool(RG_PROGRESSIVE_PRELUDE_OF_LIGHT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere);
}
}
} else {
ctx->PlaceItemInLocation(RC_SHEIK_IN_FOREST, RG_MINUET_OF_FOREST, false, true);
Expand Down
Loading
Loading