Feature/split songs#6627
Conversation
This introduces song part items, logic/state tracking, and UI/runtime integration so two parts unlock each full song while preserving stable randomizer behavior in Anywhere mode.
Replaces the previous copyright wording in split_songs.h and split_songs.cpp with a simple Created by RaccoonCloud header line.
Made split songs to use progressive item flow similar to Bomb bags and Strengh Upgardes etc Replaces discrete song part pool entries with progressive song pickups while preserving part-flag progression, song completion behavior, and logic/obtainability handling for split song CURRENTLY ONLY 2 PARTS!!
Reworded the option in UI in rando menu to explain split songs now use two progressive pickups per song and keeps the Anywhere requirement messaging.
Updated get-item textbox icon handling so OTR paths load and draw correctly on pickup (including custom icons). Song icons use the correct texture format, sizing, and per-song colors. Songs are fully progressive in randomizer logic; seed/shuffle paths allow more than two song pieces in the pool so duplicate parts and edge cases do not softlock. Also: sync player get-item entry for message context, resolve gift row when entry and id disagree, custom icons for blue potion and key rings, and ice trap naming for split song models.
All pickup textbox icons now work correctly for progressive songs and split song parts, with the correct note icon and per-song color for each song.
Pepper0ni
left a comment
There was a problem hiding this comment.
Quick initial review to call out some of the more obvious code style problems, You don't need 2 parts anymore and there's a lot of cruft that could be cut down on as it should simply be a progressive that checks a RandInf, gives an item that sets it to true if it's not, and gives the song if it already is.
| itemTable[RG_BOTTLE_WITH_RED_POTION] = Item(RG_BOTTLE_WITH_RED_POTION, Text{ "Bottle with Red Potion", "Bouteille avec une Potion Rouge", "Flasche mit rotem Elixier" }, ITEMTYPE_ITEM, 0x8C, true, LOGIC_BOTTLES, RHT_BOTTLE_WITH_RED_POTION, RG_BOTTLE_WITH_RED_POTION, OBJECT_GI_LIQUID, GID_POTION_RED, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"a ", "une ", "eine "}); | ||
| itemTable[RG_BOTTLE_WITH_GREEN_POTION] = Item(RG_BOTTLE_WITH_GREEN_POTION, Text{ "Bottle with Green Potion", "Bouteille avec une Potion Verte", "Flasche mit grünem Elixier" }, ITEMTYPE_ITEM, 0x8D, true, LOGIC_BOTTLES, RHT_BOTTLE_WITH_GREEN_POTION, RG_BOTTLE_WITH_GREEN_POTION, OBJECT_GI_LIQUID, GID_POTION_GREEN, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"a ", "une ", "eine "}); | ||
| itemTable[RG_BOTTLE_WITH_BLUE_POTION] = Item(RG_BOTTLE_WITH_BLUE_POTION, Text{ "Bottle with Blue Potion", "Bouteille avec une Potion Bleue", "Flasche mit blauem Elixier" }, ITEMTYPE_ITEM, 0x8E, true, LOGIC_BOTTLES, RHT_BOTTLE_WITH_BLUE_POTION, RG_BOTTLE_WITH_BLUE_POTION, OBJECT_GI_LIQUID, GID_POTION_BLUE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"a ", "une ", "eine "}); | ||
| itemTable[RG_BOTTLE_WITH_BLUE_POTION] = Item(RG_BOTTLE_WITH_BLUE_POTION, Text{ "Bottle with Blue Potion", "Bouteille avec une Potion Bleue", "Flasche mit blauem Elixier" }, ITEMTYPE_ITEM, 0x8E, true, LOGIC_BOTTLES, RHT_BOTTLE_WITH_BLUE_POTION, RG_BOTTLE_WITH_BLUE_POTION, OBJECT_GI_LIQUID, GID_POTION_BLUE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"a ", "une ", "eine "}).CustomIcon(gItemIconBottlePotionBlueTex); |
There was a problem hiding this comment.
This seems unrelated to the rest of the PR, can you explain it?
There was a problem hiding this comment.
Thank you for taking the time to help and address some issues I will
Do my best to sort and correct here goes
This blue potion and forest temple boss key icon, I re added to help fix a custom icon issue I was having on Item Obtain for split song parts as some of the references for the icons were using different icons, will relook and sort
There was a problem hiding this comment.
This kind of fix could be it's own PR that would merge faster>
Also, no need to thank me every post 😅
| itemTable[RG_NOCTURNE_OF_SHADOW_PART1] = Item(RG_NOCTURNE_OF_SHADOW_PART1, Text{ "Nocturne of Shadow (Part 1)", "Nocturne de l'Ombre (Partie 1)", "Nocturne des Schattens (Teil 1)" }, ITEMTYPE_SONG, 0xF5, true, LOGIC_NONE, RHT_NOCTURNE_OF_SHADOW, RG_NOCTURNE_OF_SHADOW_PART1, OBJECT_GI_MELODY, GID_SONG_NOCTURNE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); | ||
| itemTable[RG_NOCTURNE_OF_SHADOW_PART2] = Item(RG_NOCTURNE_OF_SHADOW_PART2, Text{ "Nocturne of Shadow (Part 2)", "Nocturne de l'Ombre (Partie 2)", "Nocturne des Schattens (Teil 2)" }, ITEMTYPE_SONG, 0xF6, true, LOGIC_NONE, RHT_NOCTURNE_OF_SHADOW, RG_NOCTURNE_OF_SHADOW_PART2, OBJECT_GI_MELODY, GID_SONG_NOCTURNE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); | ||
| itemTable[RG_PRELUDE_OF_LIGHT_PART1] = Item(RG_PRELUDE_OF_LIGHT_PART1, Text{ "Prelude of Light (Part 1)", "Prélude de la Lumière (Partie 1)", "Kantate des Lichts (Teil 1)" }, ITEMTYPE_SONG, 0xF7, true, LOGIC_NONE, RHT_PRELUDE_OF_LIGHT, RG_PRELUDE_OF_LIGHT_PART1, OBJECT_GI_MELODY, GID_SONG_PRELUDE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); | ||
| itemTable[RG_PRELUDE_OF_LIGHT_PART2] = Item(RG_PRELUDE_OF_LIGHT_PART2, Text{ "Prelude of Light (Part 2)", "Prélude de la Lumière (Partie 2)", "Kantate des Lichts (Teil 2)" }, ITEMTYPE_SONG, 0xF8, true, LOGIC_NONE, RHT_PRELUDE_OF_LIGHT, RG_PRELUDE_OF_LIGHT_PART2, OBJECT_GI_MELODY, GID_SONG_PRELUDE, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER); |
There was a problem hiding this comment.
I don't see any reason to have 2 parts per song anymore, as there's 1 part, then the full song. this would make the code cleaner elsewhere.
There was a problem hiding this comment.
Thank you for addressing things.
This is old code from when I originally had the items in 2 parts, I will remove this and retest, I think I forgot to remove this.
| itemTable[RG_FISHING_HOLE_KEY].SetCustomDrawFunc(Randomizer_DrawOverworldKey); | ||
| // Key Rings | ||
| itemTable[RG_FOREST_TEMPLE_KEY_RING] = Item(RG_FOREST_TEMPLE_KEY_RING, Text{ "Forest Temple Key Ring", "Trousseau du Temple de la Forêt", "Schlüsselbund für den Waldtempel" }, ITEMTYPE_SMALLKEY, 0xD5, true, LOGIC_FOREST_TEMPLE_KEYS, RHT_FOREST_TEMPLE_KEY_RING, RG_FOREST_TEMPLE_KEY_RING, OBJECT_GI_KEY, GID_KEY_SMALL, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_SMALL_KEY,MOD_RANDOMIZER, {"the ", "le ", "den "}, "%g"); | ||
| itemTable[RG_FOREST_TEMPLE_KEY_RING] = Item(RG_FOREST_TEMPLE_KEY_RING, Text{ "Forest Temple Key Ring", "Trousseau du Temple de la Forêt", "Schlüsselbund für den Waldtempel" }, ITEMTYPE_SMALLKEY, 0xD5, true, LOGIC_FOREST_TEMPLE_KEYS, RHT_FOREST_TEMPLE_KEY_RING, RG_FOREST_TEMPLE_KEY_RING, OBJECT_GI_KEY, GID_KEY_SMALL, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_SHORT, ITEM_CATEGORY_SMALL_KEY,MOD_RANDOMIZER, {"the ", "le ", "den "}, "%g").CustomIcon(gQuestIconSmallKeyTex, ICON_SIZE_24); |
There was a problem hiding this comment.
More unrelated changes, please explain.
| "Requires Shuffle Songs: \"Anywhere\" — Song Locations / Dungeon Rewards only have 12 song slots, so split " | ||
| "parts are not generated there.\n" | ||
| "\n" | ||
| "When enabled with Anywhere, the item pool places two progressive song pickups per song (24 total song " |
There was a problem hiding this comment.
The explanation is a bit redundant as it only works when set to anywhere
There was a problem hiding this comment.
Thank you for addressing this
Will update this, possibly missed when sorting our parts to progressive
| // Gameplay stats: Update the time the item was obtained | ||
| Randomizer_GameplayStats_SetTimestamp(item); | ||
|
|
||
| if (Rando::SplitSongs::IsSongPart(item)) { |
There was a problem hiding this comment.
The handling of progressive songs is needlessly complex. PARTs could simply be implemented as RAND_INF's similar to child wallet or Roc's feather, and the progressive system will send the full song item when appropriate.
There was a problem hiding this comment.
Thank you for addressing this.
Ahh ok I went with similar upgrades like bombchu bags but I think I must still
Be referencing parts instead of making it progressive will sort and test.
| case RG_PROGRESSIVE_REQUIEM_OF_SPIRIT: | ||
| case RG_PROGRESSIVE_NOCTURNE_OF_SHADOW: | ||
| case RG_PROGRESSIVE_PRELUDE_OF_LIGHT: | ||
| actual = SplitSongs::ResolveProgressiveSongStage(randomizerGet); |
There was a problem hiding this comment.
if parts are implemented as RandInfs, and there's only need for 1 part, then much of these helper functions can be reduced to a Randinf check. Child wallet is a good example here.
There was a problem hiding this comment.
Thank you for addressing this
Ok will re write and test this, and get this cleaned up.
| 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); |
There was a problem hiding this comment.
Why are you running AddItemToPool twice? Given the other changes and cut downs you could simply say 3,2,2,2
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Drop Part 1/Part 2 as separate items. Each song uses progressive pool entries (still doubled for softlock) and one RandInf; first pickup sets the flag, second progressive grants the full song.
Use one AddItemToPool call per song (3,2,2,2) instead of doubling calls. Drop unrelated blue potion and key ring CustomIcon tweaks. Shorten option text now that Anywhere-only behavior is enforced in settings.
Sync with HarbourMasters develop (90823fa) before PR HarbourMasters#6627 review.
Restore GameInteractor hook includes removed during the develop merge. RegisterItemMessages still uses COND_ID_HOOK macros from GameInteractor_Hooks.h.
The develop merge updated SoH sources but left libultraship on an older commit missing Fast3dGui.h and Fast::WindowBackend, breaking all platforms.
DrawSplitSongProgress must cast Ship::Gui to Fast::Fast3dGui before GetTextureByName, matching DrawSong and the rest of the item tracker.
Clear the seed-generated flag before fill so the item tracker does not read spoiler locations while the generator thread resets context. Skip song-part spoiler lookups while RandoGenerating is set. Break infinite GetGIEntry recursion for first-stage progressive song items.
Progressive song pickups no longer resolve to full-song GI entries on the first piece; part tracking completes the song on the second pickup. Re-add key ring CustomIcon assignments dropped during the develop merge.
7e2b465 to
3e1aff7
Compare
Pepper0ni
left a comment
There was a problem hiding this comment.
This PR is massively overcomplicating the implementation and is including a fix/feature (the new icon code) that really should be in it's own PR, on top of other style issues.
Parts only need to be RandInf items that are checked before giving songs using the existing progressive system, with the rest of the implementation for supporting code using existing macros and frameworks to implement custom items.
While I have considered larger changes to support all items those changes will come later and should be a change to the progressive system far beyond the scope of this PR, so the focus should be on a simple implementation.
If you are unsure how to go about this I can try to find time for a simple re-implementation to put you on the right track.
| if (!ctx->GetOption(RSK_STARTING_PRELUDE_OF_LIGHT).Get()) { | ||
| AddItemToPool(RG_PRELUDE_OF_LIGHT, songAnywhere ? 2 : 1, 1, 1, 1, songAnywhere); | ||
| } | ||
| const bool split = ctx->GetOption(RSK_SPLIT_OCARINA_SONGS) && songAnywhere; |
There was a problem hiding this comment.
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.
| lesserPool.clear(); | ||
| int reservedSlots = 0; | ||
|
|
||
| if (ctx->GetOption(RSK_SHUFFLE_SONGS).Is(RO_SONG_SHUFFLE_ANYWHERE)) { |
There was a problem hiding this comment.
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
| #include "z64player.h" | ||
| #include "z64save.h" | ||
| extern PlayState* gPlayState; | ||
| void Message_LoadItemIcon(PlayState* play, u16 itemId, s16 y); |
There was a problem hiding this comment.
I assume this stuff is related to the blue potion and forest boss key icons? If so I would strongly prefer those were handled in a separate PR, as significant changes involving item messages need testing and reviews from people with different skill-sets to simply adding a new progressive item step that does nothing.
| f32 triforcePieceScale; | ||
|
|
||
| void RandomizerOnPlayerUpdateHandler() { | ||
| Rando::SplitSongs::ProcessPendingFullSongGrants(); |
There was a problem hiding this comment.
Why do we have a function in PlayerUpdate checking specifically if a song should be given and then seemingly rebuilding part of the item give queue(!?) to offer it separately?
The Progressive Item System we should be hooking into works by changing the given item at GetItem time. The first item given will be a part that does nothing but set the RandInf that we have the part, and the second will aware the song as normal. This all happens in the normal process of receiving an item, you do not need to build a separate pipeline to handle this. This only serves to lower performance and make the code less maintainable.
| randomizerQueuedChecks = std::queue<RandomizerCheck>(); | ||
| randomizerQueuedCheck = RC_UNKNOWN_CHECK; | ||
| randomizerQueuedItemEntry = GET_ITEM_NONE; | ||
| Rando::SplitSongs::ClearPendingFullSongGrants(); |
There was a problem hiding this comment.
Again, existing systems should manage the Progressive Song properly as they do a Bomb bag or Scale. no need for redundant checks.
| "Anywhere - Songs can appear at any location."; | ||
| mOptionDescriptions[RSK_SPLIT_OCARINA_SONGS] = | ||
| "Each ocarina song becomes a progressive song item (like bomb bag upgrades). The first pickup marks " | ||
| "progress; the second grants the full song. The pool includes extra copies per song to reduce softlocks."; |
There was a problem hiding this comment.
The pool includes extra copies per song to reduce softlocks. Is not part of the current implementation, but making this into a dropdown that has that as an option would be nice.
| SECTION_DISPLAY_MINIMAL_SEPARATE, | ||
| } ItemTrackerMinimalDisplayType; | ||
|
|
||
| // One icon per logical song; order matches Rando::SplitSongs::GetSongDef (split_songs.cpp kSplitSongs). |
There was a problem hiding this comment.
Why are we re-implementing everything? This should only need similar handling to Bronze Scale, with maybe new graphics. Please follow existing implementations where they exist, we have many structs designed to simplify the item adding procedure.
| initTrickNames = true; | ||
| } | ||
|
|
||
| RandomizerGet rg = static_cast<RandomizerGet>(id); |
There was a problem hiding this comment.
You do not need special handling here, just add trick names for your new items.
| 14.0f, // ? | ||
| }; | ||
|
|
||
| /** |
There was a problem hiding this comment.
Please do not add to src directly unless you have a very good reason, use hooks instead
| } else { | ||
| giEntry = this->getItemEntry; | ||
| } | ||
| // SOH [Randomizer]: Message_OpenText / custom text hooks read player->getItemEntry. Without syncing, |
There was a problem hiding this comment.
as before, do stuff like this in hooks.
Thank you for taking your time again to go over this. I'm over complicating it yes I'm sorry, I was thinking is works it could be used overall, my problem is overthinking and not simplifying as you have put. Thank you for your patience with me on this I will try and strip it right back it all works in practice and my build but again I'm forgetting it need it to slot with the whole build and follow the main tree and not my branch and build and tweaks. This feedback I will use and attempt but when you have time and stuff please by all means inject yourself to whatever you feel or needs doing! |
Revert z_message_PAL overhaul and unrelated icon/debug changes; keep minimal song pickup icons via vanilla ITEM_SONG_* in ItemMessages. Drop deferred grant queue — second pickup resolves to full song through GetGIEntry.
FinalizeSettings clears split songs when shuffle is not Anywhere. Drop manual ice trap model list. Add progressive song trick names. Song tracker shows 1/2 on the normal songs row. getItemEntry sync lives in ItemMessages, not z_player.
… text. Stop GetGIEntry recursing when the resolved stage is still the progressive item. Reword the option tooltip like Bombchu Bag, without bracketed asides.
Hook song textbox icons through ItemMessages and the existing custom icon VB hooks, with correct IA8 16x24 gSongNoteTex drawing and warp song colours from the pause quest screen. Small z_message guard so the note is not stretched into a 24x24 quad. Sanitize UTF-8 in spoiler log, hints, and save JSON to fix regen and new-game crashes. Mark seed ready after spoiler write. Second progressive pickup grants the full song through normal item give.
c8761e8 to
b78aac9
Compare
GCC rejects in-class template specializations that MSVC allows. Same behavior as before - strings are still passed through SanitizeUtf8 on save.
Sync with HarbourMasters develop and resolve Traps/item tracker conflicts.
5a1b237 to
1198703
Compare
Pepper0ni
left a comment
There was a problem hiding this comment.
Just want to hear more reasons for why you are doing certain things.
| Random_Init(seed); | ||
|
|
||
| auto ctx = Rando::Context::GetInstance(); | ||
| ctx->SetSeedGenerated(false); |
There was a problem hiding this comment.
Can you explain why you added these?
There was a problem hiding this comment.
Thank you again for your help and patience with me (first timer — I kept messaging Cal).
Every time I ran a new seed or generated one, SoH was crashing because other things (item tracker, spoiler lookups) could read placement data while Fill() was still mid-reset — like a “seed ready” race.
I SetSeedGenerated(false) at the top of Playthrough_Init and only flip to true after SpoilerLog_Write() finishes, so nothing treats the seed as valid until the spoiler JSON is fully written. Before that, things were trying to use spoiler data that wasn’t ready yet and it’d just crash on generate. If there’s a better way I’ll relook and sort.
This showed up most with split songs because the tracker hits song progress during seed generation, but the flag lifecycle fix is general — it just enforces generate → write → consume order.
There was a problem hiding this comment.
Rebuilt and tested split songs still works (first pickup = part / 1/2 on tracker, second = full song).
Pulled icon hooks and UTF-8 stuff into separate branches like you asked.
Also removed the logic scratch workaround progressive songs go through normal ApplyItemEffect now
| std::string placementtxt; | ||
| } // namespace | ||
|
|
||
| static std::string SafeJsonString(std::string value) { |
There was a problem hiding this comment.
This JSON stuff looks useful, but like with the icon fixes from before might be better in it's own PR unless it relates to the functionality of split songs.
There was a problem hiding this comment.
Not really anything to do with the split-songs logic it seemed to be a language spelling or non-english text (curly quotes, accents, etc.) was producing invalid JSON in the spoiler log, which then broke SoH on Seed generation so SafeJsonString is a thin wrapper: SohUtils::SanitizeUtf8 on every string before it hits jsonData.
I bundled it here because I kept hitting it while testing split songs with FR/DE UI, but happy to move this and the SaveManager string overload into a PR or if you’d rather it separately? I was scared of messing up alot of preset code and stuff you all have in place and it being other languages I panicked abit
| } | ||
|
|
||
| void BuildCustomItemMessage(Player* player, CustomMessage& msg) { | ||
| if (TryBuildSongItemMessage(player, msg)) { |
There was a problem hiding this comment.
Can you explain why you needed to cut your songs parts out from the normal custom text/icon flow and instead run custom code?
If it's related to the icon issues you had fixed here in the older iteration, please push those changes to a new PR and we can look at getting that bugfix in before merging this.
There was a problem hiding this comment.
Progressive song pickups use custom items, not vanilla ITEM_SONG_*. Vanilla always draws the textbox icon as 32×32 RGBA but the song note texture is IA8 16×24 which was a pain to find so without rerouting you get a garbled/static icon on pickup which was so annoying and frustrating.
TryBuildSongItemMessage routes through ITEM_CUSTOM and the existing icon hooks so the note draws right (including warp song colours from the quest screen). Also picks the right name and the progressive label on first pickup, full song name on second.
Happy to peel the icon hook stuff into a separate PR if you’d rather that first like you mentioned before.
There was a problem hiding this comment.
The fact it could mess with other things is precisely why it needs to be in a separate PR, it needs completely different testing from the rest of the PR and could sneak past people who handle this stuff if you put it in this PR
| auto ctx = Rando::Context::GetInstance(); | ||
| auto logic = ctx->GetLogic(); | ||
| if (!logic->CalculatingAvailableChecks) { | ||
| if (SplitSongs::IsProgressiveSong(randomizerGet) && UsingLogicSimulationBuffer(logic)) { |
There was a problem hiding this comment.
What reason do you have for breaking the normal ApplyItemEffect workflow here?
There was a problem hiding this comment.
This bit only runs during seed generation when the game is using a temporary save copy to figure out what you can get, not when you actually pick something up in-game.
If I let the normal ApplyItemEffect run there, it was giving you the full song right away, which skips the whole progressive/split idea of “first pickup = part flag, second pickup = real song”.
So ApplyProgressiveEffectToLogicScratch fakes that in the temp save: first time = set the RandInf part flag, second time = grant the song.
When you’re actually playing, it still goes through the normal progressive flow (GetGIEntry → item give). This is just so logic checks during fill don’t get it wrong.
First PR so still finding my feet — hope that makes sense.
There was a problem hiding this comment.
That sounds like an issue in ApplyItemEffect. Don't apply boilerplate to workaround the issue, that leaves a ton of mess and special cases for future coders to clean up, solve it properly. Double check what RG is hitting your checks in ApplyItemEffect, and figure out why you are getting the wrong song level.
Progressives are handled in ApplyItemEffect by checking the RG and processing what level to use based on settings and the current state, if you are applying the wrong state, something is not being checked properly.
There was a problem hiding this comment.
Done ok so workaround is out, using normal ApplyItemEffect path now.
Progressive songs now go through normal ApplyItemEffect (child wallet style) instead of the logic scratch workaround.
|
Tick the box to add this pull request to the merge queue (same as
|
Pepper0ni
left a comment
There was a problem hiding this comment.
This is certainly getting better, but I still think the code quality isn't there for merge. Rather than constantly play PR and fix tennis where I try to second hand debug the issues that caused you to implement other changes and possible get things wrong through (more) assumptions, I made a probably working demonstration of how this should be done in #6803 which can be used as a learning exercise.
I think the most important thing to say though is that if similar code is working for other items but not yours, you should maybe ask yourself or more experienced devs what you are doing wrong before attaching a new code-path for your items. If everyone did that, the code would quickly become an unmaintainable mess of special cases, so as reviewer I have to stop it here.
| "Putput-Kapazität (prog.)" }, // "Capacidad progresiva de pera" | ||
| Text{ "Progressive Nut Bag", "Sac de noix (prog.)", "Nußbeutel (prog.)" }, // "Bolsa de nueces progresiva" | ||
| Text{ "Progressive Husk Capacity", "Capacité de noisettes (prog.)", | ||
| "Schalen-Kapazität (prog.)€" }, // "Mayor capacidad de castañas" |
There was a problem hiding this comment.
These Euro symbols are control characters that signal that the text is plural, why were these removed?
| RAND_INF_SPLIT_REQUIEM_PART1, RAND_INF_SPLIT_NOCTURNE_PART1, RAND_INF_SPLIT_PRELUDE_PART1, | ||
| }; | ||
|
|
||
| static constexpr SplitSongDef kSplitSongs[SPLIT_SONG_MAX] = { |
There was a problem hiding this comment.
Is there a reason this isn't a map to save iterating. You can remove SplitSongId completely if you make SplitSongDef a map where the progressive RG is the key and and the value is a struct containing other data. The item tracker needs to work in QuestItem, which is unfortunate for us as it's in a bad order and nothing else wants it, but another conversion map solves that relatively efficiently.
| return &kSplitSongs[id]; | ||
| } | ||
|
|
||
| const SplitSongDef* SplitSongs::GetSongDefFromProgressive(RandomizerGet rg) { |
There was a problem hiding this comment.
you can save iterating by using rg - RG_PROGRESSIVE_ZELDAS_LULLABY instead, as RG_PROGRESSIVE_ZELDAS_LULLABY - RG_PROGRESSIVE_ZELDAS_LULLABY = 0, which is SPLIT_SONG_ZELDAS_LULLABY, and both the songs and defs share an order
You can do the same in GetSongDefFromFullSong too using RG_ZELDAS_LULLABY
| }; | ||
|
|
||
| static bool IsValidSplitSongId(SplitSongId id) { | ||
| return id >= 0 && id < SPLIT_SONG_MAX; |
There was a problem hiding this comment.
- You started counting SplitSongId from 0, checking from more than 0 with result in it not recognizing ZL
- the compiler should be doing this validation for you, because you have the enum's file type
| return id >= 0 && id < SPLIT_SONG_MAX; | ||
| } | ||
|
|
||
| static bool UsingLogicSimulationBuffer(Logic* logic) { |
There was a problem hiding this comment.
If this is useful, it shouldn't be here but somewhere more central, maybe in logic itself (depends on the scoping)
Really though, if you are following existing patterns, you shouldn't need to validate more than it already does.
| } | ||
|
|
||
| bool SplitSongs::IsProgressiveSong(RandomizerGet rg) { | ||
| return GetSongDefFromProgressive(rg) != nullptr; |
There was a problem hiding this comment.
We can just do a >= and <= check on the enum here. Remember than enums are just fancy names for numbers.
| ImGui::SetCursorScreenPos(ImVec2(p.x + 6, p.y)); | ||
| ImGui::Image(std::dynamic_pointer_cast<Fast::Fast3dGui>(Ship::Context::GetRawInstance()->GetWindow()->GetGui()) | ||
| ->GetTextureByName(hasSong && IsValidSaveFile() ? item.name : item.nameFaded), | ||
| ->GetTextureByName(showBright && IsValidSaveFile() ? item.name : item.nameFaded), |
There was a problem hiding this comment.
My gut feeling is that faded with 1/2 is more clear than shaded with 1/2 as it signals you still can't use the song better, but am willing to hear other opinions.
| if (giEntry != nullptr && actual == RG_NONE) { | ||
| return giEntry; | ||
| } | ||
| if (actual == randomizerGet && giEntry != nullptr) { |
There was a problem hiding this comment.
Editing a core function's logic like GetGIEntry is dangerous as it could break other things unexpectedly, if you think you need this, you're probably making a mistake elsewhere, if you can't find it, tell more senior devs your problem so we can give advice.
| SetQuestItem(RandoGetToQuestItem.find(item.GetRandomizerGet())->second, state); | ||
| break; | ||
| case ITEMTYPE_SONG: { | ||
| RandomizerGet rg = item.GetRandomizerGet(); |
There was a problem hiding this comment.
The handling here doesn't need to be this bespoke or nested. The PART items are just randInf items, so they can be handled with the other RandInfs, while the song item themselves are unchanged, we're only changing how we get them
| itemTable[RG_PROGRESSIVE_MAGIC_METER] = Item(RG_PROGRESSIVE_MAGIC_METER, Text{ "Progressive Magic Meter", "Jauge de Magie (prog.)", "Progressives Magisches Maß" }, ITEMTYPE_ITEM, 0x8A, true, LOGIC_PROGRESSIVE_MAGIC, RHT_PROGRESSIVE_MAGIC_METER, ITEM_CATEGORY_MAJOR, {"a ", "une ", "ein "}, "%g", true).CustomIcon(gQuestIconMagicJarBigTex, ICON_SIZE_24); | ||
| itemTable[RG_PROGRESSIVE_OCARINA] = Item(RG_PROGRESSIVE_OCARINA, Text{ "Progressive Ocarina", "Ocarina (prog.)", "Progressive Okarina" }, ITEMTYPE_ITEM, 0x8B, true, LOGIC_PROGRESSIVE_OCARINA, RHT_PROGRESSIVE_OCARINA, ITEM_CATEGORY_MAJOR, {"a ", "un ", "eine "}, "%g", true); | ||
| itemTable[RG_PROGRESSIVE_GORONSWORD] = Item(RG_PROGRESSIVE_GORONSWORD, Text{ "Progressive Goron Sword", "Épée Goron (prog.)", "Progressives Goronen-Schwert" }, ITEMTYPE_ITEM, 0xD4, true, LOGIC_PROGRESSIVE_GIANT_KNIFE, RHT_PROGRESSIVE_GORONSWORD, ITEM_CATEGORY_MAJOR, {"a ", "une ", "ein "}, "%g", true); | ||
| itemTable[RG_PROGRESSIVE_ZELDAS_LULLABY] = Item(RG_PROGRESSIVE_ZELDAS_LULLABY, Text{ "Progressive Zelda's Lullaby", "Berceuse de Zelda (prog.)", "Progressives Zeldas Wiegenlied" }, ITEMTYPE_SONG, 0xE1, true, LOGIC_NONE, RHT_ZELDAS_LULLABY, RG_PROGRESSIVE_ZELDAS_LULLABY, OBJECT_GI_MELODY, GID_SONG_ZELDA, TEXT_RANDOMIZER_CUSTOM_ITEM, 0x80, CHEST_ANIM_LONG, ITEM_CATEGORY_MAJOR, MOD_RANDOMIZER, {"a ", "une ", "ein "}, "%g", true); |
There was a problem hiding this comment.
OK this is probably my bad for miscommunication, but my point about removing PART items in the future meant "we do it in a future PR", and I forgot bombchu bags has funny handling for bombchus instead of using it's own RG. It would be cleanet to have 1 part per song as an RG for the time being.
Summary
Adds a randomizer option to split each ocarina song into two progressive pickups per song, using RandInf + the existing progressive item system (same pattern as child wallet / strength upgrades). Follow-up to #6561 with review feedback addressed.
RG_PROGRESSIVE_*pool entries (doubled per song for softlock safety)FinalizeSettings)developTest plan
Created by RaccoonCloud — inspired by MM Goron Lullaby split song.
Credit and thanks to the HM64 Team.
Special thank you for your friendship, support, and memories over the years, and for making SoH great:
Caladius, Proxysaw, aMannus, itsHeckinPat, Fredomato, Leggettc18, MoonlitxShadows, CardinalNerd, Kenix, Smiffic, SirMagicPenguin, alwayszchartergirl, Nordic Ryan, Grimey, Scorched11, AGreenSpoon, Mellar, PapChiefo, OneSaltyGinger, Pepper0ni