From 721be0eb3950a9d439894034daf4b79d284fa024 Mon Sep 17 00:00:00 2001 From: HylianFreddy <82058772+HylianFreddy@users.noreply.github.com> Date: Sat, 2 Mar 2024 12:20:54 +0100 Subject: [PATCH 1/7] Add Triforce Hunt squash head: 31c3e2fd --- code/include/z3D/z3D.h | 4 ++ code/include/z3D/z3Dactor.h | 4 +- code/include/z3D/z3Ditem.h | 2 +- code/include/z3D/z3Dmath.h | 10 ++++ code/object_and_gi_usage.txt | 2 + code/oot.ld | 28 +++++------ code/oot_e.ld | 28 +++++------ code/src/actor.c | 4 ++ code/src/actors/shops.c | 13 +++++ code/src/custom_models.c | 35 +++++++++++++ code/src/custom_models.h | 2 + code/src/gfx.c | 10 ++++ code/src/hooks.s | 98 ++++++++++++++---------------------- code/src/item_effect.c | 39 ++++++-------- code/src/item_effect.h | 1 + code/src/item_override.c | 31 +++++++++++- code/src/item_override.h | 1 + code/src/item_table.c | 2 +- code/src/message.c | 51 +++++++++++++++++++ code/src/message.h | 73 +++++++++++++++++++++++++++ code/src/models.c | 33 ++++++------ code/src/models.h | 1 + code/src/patches.s | 48 +++++++++--------- code/src/savefile.c | 14 ++++++ code/src/savefile.h | 1 + code/src/settings.h | 4 ++ source/custom_messages.cpp | 14 ++++++ source/custom_messages.hpp | 1 + source/descriptions.cpp | 14 ++++++ source/descriptions.hpp | 4 ++ source/hints.cpp | 3 ++ source/item_list.cpp | 4 +- source/item_pool.cpp | 7 ++- source/location_access.cpp | 13 +++-- source/logic.cpp | 2 + source/logic.hpp | 1 + source/main.cpp | 2 +- source/menu.cpp | 18 ++----- source/menu.hpp | 4 +- source/settings.cpp | 54 +++++++++++++++++++- source/settings.hpp | 27 +++++----- source/shops.cpp | 5 ++ 42 files changed, 520 insertions(+), 192 deletions(-) diff --git a/code/include/z3D/z3D.h b/code/include/z3D/z3D.h index b57cb3ffb..c6e9c6a4e 100644 --- a/code/include/z3D/z3D.h +++ b/code/include/z3D/z3D.h @@ -863,4 +863,8 @@ typedef void (*Animation_Change_proc)(SkelAnime* anime, s32 animation_index, f32 #define Animation_Change_addr 0x375C08 #define Animation_Change ((Animation_Change_proc)Animation_Change_addr) +typedef void (*SaveGame_proc)(GlobalContext* globalCtx, u8 isSaveFileCreation); +#define SaveGame_addr 0x2FDAC8 +#define SaveGame ((SaveGame_proc)SaveGame_addr) + #endif //_Z3D_H_ diff --git a/code/include/z3D/z3Dactor.h b/code/include/z3D/z3Dactor.h index 5c02c859d..a3c8b9f07 100644 --- a/code/include/z3D/z3Dactor.h +++ b/code/include/z3D/z3Dactor.h @@ -269,7 +269,9 @@ typedef struct { /* 0x01C0 */ void* leftHandDLists; /* 0x01C4 */ void* sheathDLists; /* 0x01C8 */ void* waistDLists; - /* 0x01CC */ char unk_1CC[0x80]; + /* 0x01CC */ char unk_1CC[0x78]; + /* 0x0244 */ SkeletonAnimationModel* giModel1; + /* 0x0248 */ SkeletonAnimationModel* giModel2; /* 0x024C */ void* giDrawSpace; /* 0x0250 */ char unk_250[0x0004]; /* 0x0254 */ struct SkelAnime skelAnime; diff --git a/code/include/z3D/z3Ditem.h b/code/include/z3D/z3Ditem.h index 62aefb612..0dc5f4f79 100644 --- a/code/include/z3D/z3Ditem.h +++ b/code/include/z3D/z3Ditem.h @@ -410,7 +410,7 @@ typedef enum { /* 0xC7 */ GI_TYCOON_WALLET, /* 0xC8 */ GI_LETTER_RUTO_2, /* 0xC9 */ GI_MAGIC_BEAN_PACK, - /* 0xCA */ GI_TRIFORCE_PIECE, // unused + /* 0xCA */ GI_TRIFORCE_PIECE, /* 0xCB */ GI_KOKIRI_EMERALD, /* 0xCC */ GI_GORON_RUBY, diff --git a/code/include/z3D/z3Dmath.h b/code/include/z3D/z3Dmath.h index 58fc0c66c..0b78e67e0 100644 --- a/code/include/z3D/z3Dmath.h +++ b/code/include/z3D/z3Dmath.h @@ -7,4 +7,14 @@ typedef f32 (*Math_SinS_proc)(s16 angle) __attribute__((pcs("aapcs-vfp"))); typedef f32 (*Math_CosS_proc)(s16 angle) __attribute__((pcs("aapcs-vfp"))); #define Math_CosS ((Math_CosS_proc)0x338F60) +typedef void (*Matrix_Multiply_proc)(nn_math_MTX34* dst, nn_math_MTX34* lhs, nn_math_MTX44* rhs) + __attribute__((pcs("aapcs-vfp"))); +#define Matrix_Multiply_addr 0x36C174 +#define Matrix_Multiply ((Matrix_Multiply_proc)Matrix_Multiply_addr) + +typedef void (*Matrix_UpdatePosition_proc)(nn_math_MTX34* dst, nn_math_MTX34* src, Vec3f* vec) + __attribute__((pcs("aapcs-vfp"))); +#define Matrix_UpdatePosition_addr 0x372070 +#define Matrix_UpdatePosition ((Matrix_UpdatePosition_proc)Matrix_UpdatePosition_addr) + #endif diff --git a/code/object_and_gi_usage.txt b/code/object_and_gi_usage.txt index 313437cf7..c9f811f7c 100644 --- a/code/object_and_gi_usage.txt +++ b/code/object_and_gi_usage.txt @@ -12,6 +12,7 @@ The following custom objectIds are currently being used: 126: GTG Small Key 127: Ganon Small Key 128: Boss Keys +366: Triforce Piece To use a custom asset, currently: - choose an unused objectId to be repurposed for the custom item @@ -20,4 +21,5 @@ To use a custom asset, currently: - if you need to apply a custom texture to the model: - add a CMAB file with the new texture in the custom archive (romfs/zelda_gi_melody.zar) - in CustomModels_ApplyItemCMAB, add a case for the objectId + - if you need to edit the scale or position of the model, add a case in Model_Create (for overworld models) and in CustomModels_UpdateMatrix (for shop and GetItem models) - add the objectId to this txt file diff --git a/code/oot.ld b/code/oot.ld index 058470552..573a4aefb 100644 --- a/code/oot.ld +++ b/code/oot.ld @@ -708,20 +708,12 @@ SECTIONS *(.patch_CamRoll) } - .patch_InstantTextFirstLine 0x2E049C : { - *(.patch_InstantTextFirstLine) + .patch_CheckForTextControlCode 0x2E0490 : { + * (.patch_CheckForTextControlCode) } - .patch_InstantTextBoxBreak 0x2E0664 : { - *(.patch_InstantTextBoxBreak) - } - - .patch_InstantTextRemoveOff 0x2E06C8 : { - *(.patch_InstantTextRemoveOff) - } - - .patch_SkippableText 0x2E09BC : { - *(.patch_SkippableText) + .patch_HandleTextControlCode 0x2E057C : { + * (.patch_HandleTextControlCode) } .patch_SceneInitAfterCopyScenes 0x2EAFDC : { @@ -1244,8 +1236,12 @@ SECTIONS *(.patch_GearMenuEmptySlot) } - .patch_LoadGame 0x447380 : { - *(.patch_LoadGame) + .patch_BeforeLoadGame 0x447380 : { + *(.patch_BeforeLoadGame) + } + + .patch_AfterLoadGame 0x449F00 : { + *(.patch_AfterLoadGame) } .patch_DontSetMotionSetting 0x447410 : { @@ -1568,6 +1564,10 @@ SECTIONS *(.patch_HookshotRotation) } + .patch_EditDrawGetItemAfterMatrixUpdate 0x4C4D14 : { + *(.patch_EditDrawGetItemAfterMatrixUpdate) + } + .patch_EditDrawGetItemAfterModelSpawn 0x4C61A4 : { *(.patch_EditDrawGetItemAfterModelSpawn) } diff --git a/code/oot_e.ld b/code/oot_e.ld index 9aea1c489..5d3b4fe69 100644 --- a/code/oot_e.ld +++ b/code/oot_e.ld @@ -708,20 +708,12 @@ SECTIONS *(.patch_CamRoll) } - .patch_InstantTextFirstLine 0x2E049C : { - *(.patch_InstantTextFirstLine) + .patch_CheckForTextControlCode 0x2E0490 : { + * (.patch_CheckForTextControlCode) } - .patch_InstantTextBoxBreak 0x2E0664 : { - *(.patch_InstantTextBoxBreak) - } - - .patch_InstantTextRemoveOff 0x2E06C8 : { - *(.patch_InstantTextRemoveOff) - } - - .patch_SkippableText 0x2E09BC : { - *(.patch_SkippableText) + .patch_HandleTextControlCode 0x2E057C : { + * (.patch_HandleTextControlCode) } .patch_SceneInitAfterCopyScenes 0x2EAFDC : { @@ -1244,8 +1236,12 @@ SECTIONS *(.patch_GearMenuEmptySlot) } - .patch_LoadGame 0x4473A0 : { - *(.patch_LoadGame) + .patch_BeforeLoadGame 0x4473A0 : { + *(.patch_BeforeLoadGame) + } + + .patch_AfterLoadGame 0x449F20 : { + *(.patch_AfterLoadGame) } .patch_DontSetMotionSetting 0x447430 : { @@ -1568,6 +1564,10 @@ SECTIONS *(.patch_HookshotRotation) } + .patch_EditDrawGetItemAfterMatrixUpdate 0x4C4D14 : { + *(.patch_EditDrawGetItemAfterMatrixUpdate) + } + .patch_EditDrawGetItemAfterModelSpawn 0x4C61A4 : { *(.patch_EditDrawGetItemAfterModelSpawn) } diff --git a/code/src/actor.c b/code/src/actor.c index 970923a1b..e0e08f5f5 100644 --- a/code/src/actor.c +++ b/code/src/actor.c @@ -61,6 +61,7 @@ #define OBJECT_GI_HEARTS 189 #define OBJECT_GI_OCARINA 222 #define OBJECT_GI_OCARINA_0 270 +#define OBJECT_TRIFORCE 149 typedef void (*TitleCard_Update_proc)(GlobalContext* globalCtx, TitleCardContext* titleCtx); #ifdef Version_EUR @@ -253,6 +254,9 @@ void Actor_Init() { // Define object 128 to be by default the same as object 185 strncpy(gObjectTable[OBJECT_CUSTOM_BOSS_KEYS].filename, gObjectTable[OBJECT_GI_BOSSKEY].filename, 0x40); + + // Define object 366 to be by default the same as object 149 + strncpy(gObjectTable[OBJECT_CUSTOM_TRIFORCE_PIECE].filename, gObjectTable[OBJECT_TRIFORCE].filename, 0x40); } void ActorSetup_Extra() { diff --git a/code/src/actors/shops.c b/code/src/actors/shops.c index ef48ba258..6bd5ab99c 100644 --- a/code/src/actors/shops.c +++ b/code/src/actors/shops.c @@ -37,6 +37,8 @@ s32 numShopItemsLoaded = 0; // Used to determine params. Reset this to 0 in ossa #define EnGirlA_InitializeItemAction ((EnGirlAActionFunc)0x14D5C8) +void ShopsanityItem_Draw(Actor* itemx, GlobalContext* globalCtx); + // Checks for if item is of a certain type u8 ShopsanityItem_IsBombs(u8 id) { @@ -223,6 +225,7 @@ void ShopsanityItem_InitializeItem(EnGirlA* item, GlobalContext* globalCtx) { u16 index = ShopsanityItem_GetIndex(shopItem); item->actor.textId = 0x9200 + index * 2; item->itemBuyPromptTextId = 0x9200 + index * 2 + 1; + item->actor.draw = ShopsanityItem_Draw; } } @@ -318,6 +321,16 @@ void ShopsanityItem_Init(Actor* itemx, GlobalContext* globalCtx) { } } +void ShopsanityItem_Draw(Actor* itemx, GlobalContext* globalCtx) { + ShopsanityItem* item = (ShopsanityItem*)itemx; + ItemOverride override = ItemOverride_Lookup(&item->super.actor, globalCtx->sceneNum, item->getItemId); + + u16 itemId = override.value.looksLikeItemId ? override.value.looksLikeItemId : override.value.itemId; + CustomModels_UpdateMatrix(&item->super.actor.modelMtx, ItemTable_GetItemRow(itemId)->objectId); + + EnGirlA_Draw(itemx, globalCtx); +} + void ShopsanityItem_SellOut(Actor* itemx, u16 index) { ShopsanityItem* item = (ShopsanityItem*)itemx; diff --git a/code/src/custom_models.c b/code/src/custom_models.c index f37de0496..a91f587c2 100644 --- a/code/src/custom_models.c +++ b/code/src/custom_models.c @@ -227,6 +227,14 @@ static void CustomModel_SetBossKeyToRGBA565(void* bossKeyCMB) { EDIT_BYTE(0x44B, 0x00); } +static void CustomModel_EditTriforce(void* triforceCMB) { + char* BASE_ = (char*)triforceCMB; + + // Set number of vertices from 0x120 to 0x60 so only one triangle will be drawn. + EDIT_BYTE(0x3FC, 0x60); + EDIT_BYTE(0x3FD, 0x00); +} + void CustomModel_Update(void) { // Make sure custom_assets is loaded if (ExtendedObject_GetIndex(&gGlobalContext->objectCtx, OBJECT_CUSTOM_GENERAL_ASSETS) < 0) { @@ -266,6 +274,10 @@ void CustomModels_EditItemCMB(void* ZARBuf, u16 objectId, s8 special) { cmb = ((char*)ZARBuf) + 0x78; CustomModel_SetBossKeyToRGBA565(cmb); break; + case OBJECT_CUSTOM_TRIFORCE_PIECE: + cmb = ((char*)ZARBuf) + 0xF0; + CustomModel_EditTriforce(cmb); + break; } } @@ -296,3 +308,26 @@ void CustomModels_ApplyItemCMAB(SkeletonAnimationModel* model, u16 objectId, s8 break; } } + +void CustomModels_UpdateMatrix(nn_math_MTX34* modelMtx, u16 objectId) { + f32 scale; + Vec3f posOffset; + + switch (objectId) { + case OBJECT_CUSTOM_TRIFORCE_PIECE: + scale = 0.05f; + posOffset = (Vec3f){ 0.0f, -800.0f, 0.0f }; + break; + default: + return; + } + + nn_math_MTX44 scaleMtx = { 0 }; + scaleMtx.data[0][0] = scale; + scaleMtx.data[1][1] = scale; + scaleMtx.data[2][2] = scale; + scaleMtx.data[3][3] = 1.0f; + + Matrix_Multiply(modelMtx, modelMtx, &scaleMtx); + Matrix_UpdatePosition(modelMtx, modelMtx, &posOffset); +} diff --git a/code/src/custom_models.h b/code/src/custom_models.h index 213cbd337..79486cc18 100644 --- a/code/src/custom_models.h +++ b/code/src/custom_models.h @@ -7,6 +7,7 @@ void CustomModel_EditTitleScreenLogo(void* titleScreenZAR); void CustomModel_Update(void); void CustomModels_EditItemCMB(void* ZARBuf, u16 objectId, s8 special); void CustomModels_ApplyItemCMAB(SkeletonAnimationModel* model, u16 objectId, s8 special); +void CustomModels_UpdateMatrix(nn_math_MTX34* modelMtx, u16 objectId); #define OBJECT_CUSTOM_DOUBLE_DEFENSE 4 #define OBJECT_CUSTOM_CHILD_SONGS 5 @@ -22,6 +23,7 @@ void CustomModels_ApplyItemCMAB(SkeletonAnimationModel* model, u16 objectId, s8 #define OBJECT_CUSTOM_SMALL_KEY_GANON 127 #define OBJECT_CUSTOM_BOSS_KEYS 128 #define OBJECT_CUSTOM_GENERAL_ASSETS 182 +#define OBJECT_CUSTOM_TRIFORCE_PIECE 366 typedef enum { TEXANIM_COPY_NINTENDO, diff --git a/code/src/gfx.c b/code/src/gfx.c index caa2ebdf1..585b16e39 100644 --- a/code/src/gfx.c +++ b/code/src/gfx.c @@ -368,6 +368,16 @@ static void Gfx_DrawSeedHash(void) { minutes, seconds); offsetY++; + if (gSettingsContext.triforceHunt) { + Draw_DrawString(10, 16 + (SPACING_Y * offsetY++), COLOR_TITLE, "Triforce Pieces:"); + u8 triforceDone = gExtSaveData.extInf[EXTINF_TRIFORCE_PIECES] >= gSettingsContext.triforcePiecesRequired; + Draw_DrawFormattedString( + 10 + (SPACING_X * 4), 16 + (SPACING_Y * offsetY++), triforceDone ? COLOR_YELLOW : COLOR_WHITE, "%d / %d", + gExtSaveData.extInf[EXTINF_TRIFORCE_PIECES], + triforceDone ? gSettingsContext.triforcePiecesTotal : gSettingsContext.triforcePiecesRequired); + offsetY++; + } + if (gSettingsContext.mp_Enabled) { Draw_DrawFormattedString(10, 16 + (SPACING_Y * offsetY++), COLOR_TITLE, "Multiplayer:"); s16 playerCount = Multiplayer_PlayerCount(); diff --git a/code/src/hooks.s b/code/src/hooks.s index 7696f7db8..10eb62b71 100644 --- a/code/src/hooks.s +++ b/code/src/hooks.s @@ -146,6 +146,14 @@ hook_EditDrawDetItemAfterModelSpawn: str r0,[r6,#0x78] bx lr +.global hook_EditDrawGetItemAfterMatrixUpdate +hook_EditDrawGetItemAfterMatrixUpdate: + push {r0-r12, lr} + cpy r0,r1 @ SkeletonAnimationModel + bl ItemOverride_EditDrawGetItemAfterMatrixUpdate + pop {r0-r12, lr} + b 0x330B98 + # TODO: Text ID in game gets messed up, # Gives the "What's that?" text instead of # the text about moving the statue @@ -847,62 +855,6 @@ hook_GKSetDurability: pop {r0-r12, lr} b 0x376BE0 -.global hook_SkippableText -hook_SkippableText: - push {r0-r12, lr} - bl Settings_GetQuickTextOption - cmp r0,#0x1 - pop {r0-r12, lr} - beq 0x2E0ED4 - ldr r0,[r5,#0x0] - b 0x2E09C0 - -.global hook_InstantTextFirstLine -hook_InstantTextFirstLine: - cmp r9,#0x0 - bgt NoInstantText - push {r0-r12, lr} - bl Settings_GetQuickTextOption - cmp r0,#0x2 - pop {r0-r12, lr} - blt NoInstantText - push {r0-r12, lr} - ldr r0,[r5,#0x0] - ldr r1,[r0,#0x20] - cpy r0,r5 - blx r1 - strb r11,[r4,#0x24] - pop {r0-r12, lr} -NoInstantText: - cmp r10,#0xFF - bx lr - -.global hook_InstantTextBoxBreak -hook_InstantTextBoxBreak: - push {r0-r12, lr} - bl Settings_GetQuickTextOption - cmp r0,#0x2 - pop {r0-r12, lr} - blt 0x2E0EE0 - push {r0-r12, lr} - ldr r0,[r5,#0x0] - ldr r1,[r0,#0x20] - cpy r0,r5 - blx r1 - strb r11,[r4,#0x24] - pop {r0-r12, lr} - b 0x2E0EE0 - -.global hook_InstantTextRemoveOff -hook_InstantTextRemoveOff: - push {r0-r12, lr} - bl Settings_GetQuickTextOption - cmp r0,#0x2 - pop {r0-r12, lr} - bge 0x2E0ED4 - ldr r0,[r5,#0x0] - b 0x2E06CC - .global hook_TurboTextAdvance hook_TurboTextAdvance: push {r0-r12, lr} @@ -1076,11 +1028,11 @@ hook_LostWoodsBridgeMusic: pop {r0-r12, lr} bx lr -.global hook_LoadGame -hook_LoadGame: +.global hook_BeforeLoadGame +hook_BeforeLoadGame: add r0, r4, r5 push {r0-r12, lr} - bl SaveFile_LoadExtSaveData + bl SaveFile_BeforeLoadGame pop {r0-r12, lr} .if _EUR_==1 b 0x4473A4 @@ -1088,6 +1040,13 @@ hook_LoadGame: b 0x447384 .endif +.global hook_AfterLoadGame +hook_AfterLoadGame: + push {r0-r12, lr} + bl SaveFile_AfterLoadGame + pop {r0-r12, lr} + pop {r4-r6, pc} + .global hook_SaveGame hook_SaveGame: cmp r5, #0 @@ -2058,6 +2017,27 @@ hook_RandomGsLoc_SkipSoilJingle: ldrsh r0,[r0,#0x1C] bx lr +.global hook_HandleTextControlCode +hook_HandleTextControlCode: + ldrb r0,[r6,#0x4] @ Control Code identifier + push {r0-r12, lr} + cpy r1,r5 @ Text Object + cpy r2,r3 @ Unk pointer + bl Message_HandleTextControlCode + cmp r0,#0x0 + pop {r0-r12, lr} + bxeq lr @ Not a custom control char, resume base game code + b 0x2E0ED4 @ Handled custom control char, skip base game code + +.global hook_CheckForTextControlCode +hook_CheckForTextControlCode: + push {r1-r12, lr} + cpy r2,r5 @ Text Object + cpy r3,r9 @ Char Index (loop counter) + bl Message_rCheckForControlCodes + pop {r1-r12, lr} + bx lr + @ ---------------------------------- @ ---------------------------------- diff --git a/code/src/item_effect.c b/code/src/item_effect.c index 02b5330cc..582a41cd0 100644 --- a/code/src/item_effect.c +++ b/code/src/item_effect.c @@ -20,29 +20,6 @@ void ItemEffect_FullHeal(SaveContext* saveCtx, s16 arg1, s16 arg2) { } } -// void give_triforce_piece(SaveContext* saveCtx, s16 arg1, s16 arg2) { -// save->scene_flags[0x48].unk_00_ += 1; //Unused word in scene x48. -// set_triforce_render(); - -// // Trigger win when the target is hit -// if (save->scene_flags[0x48].unk_00_ == triforce_pieces_requied) { -// // Give GC boss key to allow beating the game again afterwards -// give_dungeon_item(save, 0x01, 10); - -// // Save Game -// save->entrance_index = z64_game.entrance_index; -// save->scene_index = z64_game.scene_index; -// commit_scene_flags(&z64_game); -// save_game(&z64_game + 0x1F74); - -// // warp to start of credits sequence -// z64_file.cutscene_next = 0xFFF8; -// z64_game.entrance_index = 0x00A0; -// z64_game.scene_load_flag = 0x14; -// z64_game.fadeout_transition = 0x01; -// } -// } - void ItemEffect_GiveTycoonWallet(SaveContext* saveCtx, s16 arg1, s16 arg2) { saveCtx->upgrades |= 3 << 12; if (gSettingsContext.startingMaxRupees) @@ -170,6 +147,22 @@ void ItemEffect_BeanPack(SaveContext* saveCtx, s16 arg1, s16 arg2) { saveCtx->ammo[SLOT_BEAN] += 10; // 10 Magic Beans } +void ItemEffect_TriforcePiece(SaveContext* saveCtx, s16 arg1, s16 arg2) { + gExtSaveData.extInf[EXTINF_TRIFORCE_PIECES]++; + if (gSettingsContext.triforceHunt && + gExtSaveData.extInf[EXTINF_TRIFORCE_PIECES] == gSettingsContext.triforcePiecesRequired) { + // Save progress + SaveGame(gGlobalContext, FALSE); + // Warp to Ganon sealing cutscene + gGlobalContext->nextEntranceIndex = 0x00A0; + gSaveContext.nextCutsceneIndex = 0xFFF8; + gEntranceTable[0x00A0].field |= 0x8000; // continue playing background music (namely get item fanfare) + gGlobalContext->fadeOutTransition = 0x2F; + gGlobalContext->sceneLoadFlag = 0x14; + PLAYER->stateFlags1 |= 1; // Loading area + } +} + // With the No Ammo Drops option on, when the player gets an ammo upgrade, // the ammo count increases by 10 instead of being set to the maximum typedef void (*Inventory_ChangeUpgrade_proc)(u32 upgrade, u32 value); diff --git a/code/src/item_effect.h b/code/src/item_effect.h index cf22d7106..093d456f1 100644 --- a/code/src/item_effect.h +++ b/code/src/item_effect.h @@ -23,6 +23,7 @@ void ItemEffect_GiveUpgrade(SaveContext* saveCtx, s16 arg1, s16 arg2); void ItemEffect_IceTrap(SaveContext* saveCtx, s16 arg1, s16 arg2); void ItemEffect_GiveMasterSword(SaveContext* saveCtx, s16 arg1, s16 arg2); void ItemEffect_BeanPack(SaveContext* saveCtx, s16 arg1, s16 arg2); +void ItemEffect_TriforcePiece(SaveContext* saveCtx, s16 arg1, s16 arg2); void ItemEffect_RupeeAmmo(SaveContext* saveCtx); void ItemEffect_FillWalletUpgrade(SaveContext* saveCtx, s16 arg1, s16 arg2); void ItemEffect_OpenMaskShop(SaveContext* saveCtx, s16 arg1, s16 arg2); diff --git a/code/src/item_override.c b/code/src/item_override.c index 42e2614d9..4021a8fa0 100644 --- a/code/src/item_override.c +++ b/code/src/item_override.c @@ -322,7 +322,6 @@ void ItemOverride_AfterItemReceived(void) { return; } ItemOverride_AfterKeyReceived(key); - ItemOverride_Clear(); } static u32 ItemOverride_PlayerIsReadyOnLand(void) { @@ -419,6 +418,14 @@ void ItemOverride_Update(void) { } } } + + // Clear the active override once the GetItem process has finished, + // or when walking away from a chest without opening it or a collectible without collecting it. + // This will also keep the override if the player is doing a GIM-like glitch. + if (rActiveItemRow != NULL && + (PLAYER->getItemId == 0 || (PLAYER->interactRangeActor == NULL && (PLAYER->stateFlags1 & 0x400) == 0))) { + ItemOverride_Clear(); + } } void ItemOverride_GetItem(Actor* fromActor, Player* player, s8 incomingItemId) { @@ -534,6 +541,15 @@ void ItemOverride_EditDrawGetItemAfterModelSpawn(SkeletonAnimationModel* model) CustomModels_ApplyItemCMAB(model, rActiveItemObjectId, rActiveItemRow->special); } +// Called every frame while the GetItem is drawn +void ItemOverride_EditDrawGetItemAfterMatrixUpdate(SkeletonAnimationModel* model) { + if (ItemOverride_IsDrawItemVanilla()) { + return; + } + + CustomModels_UpdateMatrix(&model->mtx, rActiveItemObjectId); +} + s32 ItemOverride_GiveSariasGift(void) { u32 receivedGift = EventCheck(0xC1); if (receivedGift == 0 && @@ -597,3 +613,16 @@ s16 ItemOverride_OverrideGiDrawIdPlusOne(s16 originalDrawItemID) { return 1; // Default value that won't change the mesh. } + +void ItemOverride_PushHardcodedItem(s16 getItemId) { + ItemOverride override = { + .key = { + .type = OVR_DELAYED, // random value to have non-zero key + }, + .value = { + .itemId = getItemId, + .player = 0xFF, + } + }; + ItemOverride_PushPendingOverride(override); +} diff --git a/code/src/item_override.h b/code/src/item_override.h index 6b27ae375..075d0133e 100644 --- a/code/src/item_override.h +++ b/code/src/item_override.h @@ -50,5 +50,6 @@ void ItemOverride_PushDelayedOverride(u8 flag); void ItemOverride_CheckZeldasLetter(); void ItemOverride_PushDungeonReward(u8 dungeon); void ItemOverride_CheckStartingItem(); +void ItemOverride_PushHardcodedItem(s16 getItemId); #endif //_ITEM_OVERRIDES_H_ diff --git a/code/src/item_table.c b/code/src/item_table.c index 70b142335..d91170e94 100644 --- a/code/src/item_table.c +++ b/code/src/item_table.c @@ -247,7 +247,7 @@ static ItemRow rItemTable[] = { [GI_TYCOON_WALLET] = ITEM_ROW(0x53, CHEST_MAJOR, 0x41, 0x09F7, 0x00D1, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, ItemUpgrade_None, ItemEffect_GiveTycoonWallet, 3, -1), // Tycoon's Wallet [GI_LETTER_RUTO_2] = ITEM_ROW(0x53, CHEST_MAJOR, 0x14, 0x9099, 0x010B, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ItemUpgrade_None, ItemEffect_None, -1, -1), // Redundant Letter Bottle [GI_MAGIC_BEAN_PACK] = ITEM_ROW(0x53, CHEST_MAJOR, 0x41, 0x0048, 0x00F3, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ItemUpgrade_None, ItemEffect_BeanPack, -1, -1), // Magic Bean Pack -// [GI_TRIFORCE_PIECE] = ITEM_ROW(0x53, CHEST_MAJOR, 0x41, 0x9003, 0x0193, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ItemUpgrade_None, give_triforce_piece, -1, -1), // Triforce piece + [GI_TRIFORCE_PIECE] = ITEM_ROW(0x53, CHEST_MAJOR, 0x41, 0x9003, 0x016E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ItemUpgrade_None, ItemEffect_TriforcePiece, -1, -1), // Triforce piece [GI_KOKIRI_EMERALD] = ITEM_ROW(0x53, CHEST_MAJOR, 0x41, 0x0080, 0x019C, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ItemUpgrade_None, ItemEffect_GiveStone, 0x0004, -1), // Kokiri Emerald [GI_GORON_RUBY] = ITEM_ROW(0x53, CHEST_MAJOR, 0x41, 0x0081, 0x019D, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, ItemUpgrade_None, ItemEffect_GiveStone, 0x0008, -1), // Goron Ruby diff --git a/code/src/message.c b/code/src/message.c index e692f04e8..78f08372e 100644 --- a/code/src/message.c +++ b/code/src/message.c @@ -1,6 +1,11 @@ #include "z3D/z3D.h" #include "message.h" +#include "savefile.h" +#include "settings.h" +#include "3ds/util/utf.h" +#include "lib/printf.h" #include +#include // These consts are filled in by the app volatile const u32 numCustomMessageEntries; @@ -38,3 +43,49 @@ const MessageEntry* Message_GetCustomEntry(void* param_1, u32 textId) { const char* Message_GetCustomText(void* param_1, u32 offset) { return (offset > 0x500000) ? (char*)offset : Message_GetText(param_1, offset); } + +#define MESSAGE_MAX_CUSTOM_STRING_SIZE 10 +u32 Message_HandleTextControlCode(TextControlCode ctrl, void* textObj, UnkTextControlData* data) { + static u16 utf16Str[MESSAGE_MAX_CUSTOM_STRING_SIZE] = { 0 }; + char str[MESSAGE_MAX_CUSTOM_STRING_SIZE]; + + // Make text skippable or instant depending on setting. + if ((ctrl == TEXT_CTRL_UNSKIPPABLE && gSettingsContext.quickText >= QUICKTEXT_SKIPPABLE) || + (ctrl == TEXT_CTRL_INSTANT_TEXT_OFF && gSettingsContext.quickText >= QUICKTEXT_INSTANT)) { + return 1; // Bypass control code + } + + // When a custom control code is found, copy what FUN_0040b7d8 does to insert the player name, + // but instead insert a custom string. + if (ctrl == TEXT_CTRL_TRIFORCE_PIECE_COUNT) { + snprintf_(str, MESSAGE_MAX_CUSTOM_STRING_SIZE, "%d", gExtSaveData.extInf[EXTINF_TRIFORCE_PIECES]); + utf8_to_utf16(utf16Str, (u8*)str, strlen(str)); + Message_UnkControlCodeHandler(textObj, &data); + data->unk_05 = 0; + data->stringToInsert = utf16Str; + data->stringLength = strlen(str); + return 1; + } + + return 0; // Vanilla control char, resume base game function. +} + +char* Message_rCheckForControlCodes(void* unkStruct, char* nextChars, void* textObj, u32 charIdx) { + // Set instant text when parsing the first character of the text or immediately after a box break. + if (gSettingsContext.quickText >= QUICKTEXT_INSTANT && + (charIdx == 0 || (charIdx >= 2 && nextChars[-2] == '\x7F' && nextChars[-1] == TEXT_CTRL_WAIT_FOR_INPUT))) { + Message_SetInstantText(textObj); + } + + // If the next characters are either not a control code or a vanilla one, + // use base game function. + if (nextChars[0] != '\x7F' || nextChars[1] <= TEXT_CTRL_0x2F) { + return Message_CheckForControlCodes(unkStruct, nextChars); + } + + // Store control code identifier + *(char*)(unkStruct + 4) = nextChars[1]; + // Return pointer to next char after this control code, + // assuming all custom control codes will be 2 characters long. + return nextChars + 2; +} diff --git a/code/src/message.h b/code/src/message.h index 7ec14d46c..abf2db55d 100644 --- a/code/src/message.h +++ b/code/src/message.h @@ -40,4 +40,77 @@ typedef struct { u32 unk_0C; } MessageFileHeader; +typedef enum TextControlCode { + /* 0x00 */ TEXT_CTRL_MESSAGE_END, + /* 0x01 */ TEXT_CTRL_WAIT_FOR_INPUT, + /* 0x02 */ TEXT_CTRL_HORIZONTAL_SPACE, + /* 0x03 */ TEXT_CTRL_GO_TO, + /* 0x04 */ TEXT_CTRL_INSTANT_TEXT_ON, + /* 0x05 */ TEXT_CTRL_INSTANT_TEXT_OFF, + /* 0x06 */ TEXT_CTRL_SHOP_MESSAGE_BOX, + /* 0x07 */ TEXT_CTRL_EVENT_TRIGGER, + /* 0x08 */ TEXT_CTRL_DELAY_FRAMES, + /* 0x09 */ TEXT_CTRL_0x9, + /* 0x0A */ TEXT_CTRL_CLOSE_AFTER, + /* 0x0B */ TEXT_CTRL_PLAYER_NAME, + /* 0x0C */ TEXT_CTRL_PLAY_OCARINA, + /* 0x0D */ TEXT_CTRL_0xD, + /* 0x0E */ TEXT_CTRL_PLAY_SFX, + /* 0x0F */ TEXT_CTRL_ITEM_OBTAINED, + /* 0x10 */ TEXT_CTRL_SET_SPEED, + /* 0x11 */ TEXT_CTRL_WRONG_SONG, + /* 0x12 */ TEXT_CTRL_MARATHON_TIME, + /* 0x13 */ TEXT_CTRL_HORSE_RACE_TIME, + /* 0x14 */ TEXT_CTRL_GERUDO_ARCHERY_POINTS, + /* 0x15 */ TEXT_CTRL_SKULLTULAS_DESTROYED, + /* 0x16 */ TEXT_CTRL_FISH_INFO, + /* 0x17 */ TEXT_CTRL_CURRENT_TIME, + /* 0x18 */ TEXT_CTRL_HIGH_SCORES, + /* 0x19 */ TEXT_CTRL_UNSKIPPABLE, + /* 0x1A */ TEXT_CTRL_TWO_WAY_CHOICE, + /* 0x1B */ TEXT_CTRL_THREE_WAY_CHOICE, + /* 0x1C */ TEXT_CTRL_NEWLINE, + /* 0x1D */ TEXT_CTRL_COLOR, + /* 0x1E */ TEXT_CTRL_CENTER_TEXT, + /* 0x1F */ TEXT_CTRL_0x1F, + /* 0x20 */ TEXT_CTRL_0x20, + /* 0x21 */ TEXT_CTRL_0x21, + /* 0x22 */ TEXT_CTRL_0x22, + /* 0x23 */ TEXT_CTRL_BOSS_CHALLENGE_RECORDS, + /* 0x24 */ TEXT_CTRL_BUTTON_ICON, + /* 0x25 */ TEXT_CTRL_CREDITS_UNK, + /* 0x26 */ TEXT_CTRL_SINGULAR_FORM_START, + /* 0x27 */ TEXT_CTRL_PLURAL_FORM_START, + /* 0x28 */ TEXT_CTRL_PLURAL_FORM_END, + /* 0x29 */ TEXT_CTRL_IF_NOT_MQ, + /* 0x2A */ TEXT_CTRL_MQ_ELSE, + /* 0x2B */ TEXT_CTRL_MQ_END, + /* 0x2C */ TEXT_CTRL_0x2C, + /* 0x2D */ TEXT_CTRL_0x2D, + /* 0x2E */ TEXT_CTRL_0x2E, + /* 0x2F */ TEXT_CTRL_0x2F, + + // The following values are custom control codes added by the randomizer + + /* 0x30 */ TEXT_CTRL_TRIFORCE_PIECE_COUNT, +} TextControlCode; + +typedef struct UnkTextControlData { + /* 0x00 */ char unk_00[0x5]; + /* 0x05 */ u8 unk_05; + /* 0x06 */ char unk_06[0x2]; + /* 0x08 */ u16* stringToInsert; + /* 0x0C */ u32 stringLength; + // ... size unknown +} UnkTextControlData; + +typedef void (*Message_UnkControlCodeHandler_proc)(void* textObj, UnkTextControlData** data); +#define Message_UnkControlCodeHandler ((Message_UnkControlCodeHandler_proc)0x306318) + +typedef char* (*Message_CheckForControlCodes_proc)(void* data, char* nextChars); +#define Message_CheckForControlCodes ((Message_CheckForControlCodes_proc)0x4C08C0) + +typedef void (*Message_SetInstantText_proc)(void* textObj); +#define Message_SetInstantText ((Message_SetInstantText_proc)0x40B608) + #endif //_MESSAGE_H_ diff --git a/code/src/models.c b/code/src/models.c index 9dc267a70..0b9ea5fa0 100644 --- a/code/src/models.c +++ b/code/src/models.c @@ -26,11 +26,6 @@ typedef void (*Actor_SetModelMatrix_proc)(f32 x, f32 y, f32 z, nn_math_MTX34* mt #define Actor_SetModelMatrix_addr 0x3679D0 #define Actor_SetModelMatrix ((Actor_SetModelMatrix_proc)Actor_SetModelMatrix_addr) -typedef void (*Matrix_Multiply_proc)(nn_math_MTX34* dst, nn_math_MTX34* lhs, nn_math_MTX44* rhs) - __attribute__((pcs("aapcs-vfp"))); -#define Matrix_Multiply_addr 0x36C174 -#define Matrix_Multiply ((Matrix_Multiply_proc)Matrix_Multiply_addr) - #define LOADEDMODELS_MAX 16 Model ModelContext[LOADEDMODELS_MAX] = { 0 }; @@ -136,7 +131,7 @@ void Actor_SetModelMatrixWrapper(Actor* actor, nn_math_MTX34* mtx) { } void Model_UpdateMatrix(Model* model) { - nn_math_MTX44 scaleMtx; + nn_math_MTX44 scaleMtx = { 0 }; Actor_SetModelMatrixWrapper(model->actor, &model->saModel->mtx); if (model->saModel2 != NULL) { f32 tempRotY = model->actor->shape.rot.y; @@ -148,18 +143,16 @@ void Model_UpdateMatrix(Model* model) { model->actor->shape.rot.y = tempRotY; } - for (s32 i = 0; i < 4; ++i) { - for (s32 j = 0; j < 4; ++j) { - if (i == j) { - scaleMtx.data[i][j] = (i == 3) ? 1.0f : model->scale; - } else { - scaleMtx.data[i][j] = 0.0f; - } - } - } + scaleMtx.data[0][0] = model->scale; + scaleMtx.data[1][1] = model->scale; + scaleMtx.data[2][2] = model->scale; + scaleMtx.data[3][3] = 1.0f; + Matrix_Multiply(&model->saModel->mtx, &model->saModel->mtx, &scaleMtx); + Matrix_UpdatePosition(&model->saModel->mtx, &model->saModel->mtx, &model->posOffset); if (model->saModel2 != NULL) { Matrix_Multiply(&model->saModel2->mtx, &model->saModel2->mtx, &scaleMtx); + Matrix_UpdatePosition(&model->saModel->mtx, &model->saModel->mtx, &model->posOffset); } } @@ -247,10 +240,16 @@ void Model_Create(Model* model, GlobalContext* globalCtx) { case 0x019C: // Kokiri Emerald case 0x019D: // Goron Ruby case 0x019E: // Zora Sapphire - newModel->scale = 0.2f; + newModel->scale = 0.2f; + newModel->posOffset = (Vec3f){ 0 }; + break; + case OBJECT_CUSTOM_TRIFORCE_PIECE: + newModel->scale = 0.025f; + newModel->posOffset = (Vec3f){ 0.0f, -800.0f, 0.0f }; break; default: - newModel->scale = 0.3f; + newModel->scale = 0.3f; + newModel->posOffset = (Vec3f){ 0 }; break; } } diff --git a/code/src/models.h b/code/src/models.h index 9bdc77b57..2ca3faed9 100644 --- a/code/src/models.h +++ b/code/src/models.h @@ -12,6 +12,7 @@ typedef struct { SkeletonAnimationModel* saModel; SkeletonAnimationModel* saModel2; f32 scale; + Vec3f posOffset; } Model; void Model_UpdateAll(GlobalContext* globalCtx); diff --git a/code/src/patches.s b/code/src/patches.s index 5ae0fed48..adb3bffbf 100644 --- a/code/src/patches.s +++ b/code/src/patches.s @@ -71,6 +71,11 @@ EditDrawGetItemBeforeModelSpawn_patch: EditDrawGetItemAfterModelSpawn_patch: bl hook_EditDrawDetItemAfterModelSpawn +.section .patch_EditDrawGetItemAfterMatrixUpdate +.global EditDrawGetItemAfterMatrixUpdate_patch +EditDrawGetItemAfterMatrixUpdate_patch: + bl hook_EditDrawGetItemAfterMatrixUpdate + .section .patch_NoLensOfTruthNaviText nop @@ -1335,26 +1340,6 @@ SongOfTimeJingle_patch: GKSetDurability_patch: b hook_GKSetDurability -.section .patch_SkippableText -.global SkippableText_patch -SkippableText_patch: - b hook_SkippableText - -.section .patch_InstantTextFirstLine -.global InstantTextFirstLine_patch -InstantTextFirstLine_patch: - bl hook_InstantTextFirstLine - -.section .patch_InstantTextBoxBreak -.global InstantTextBoxBreak_patch -InstantTextBoxBreak_patch: - b hook_InstantTextBoxBreak - -.section .patch_InstantTextRemoveOff -.global InstantTextRemoveOff_patch -InstantTextRemoveOff_patch: - b hook_InstantTextRemoveOff - .section .patch_TurboTextAdvance .global TurboTextAdvance_patch TurboTextAdvance_patch: @@ -1582,10 +1567,15 @@ EnteredLocation_patch: LostWoodsBridgeMusic_patch: bl hook_LostWoodsBridgeMusic -.section .patch_LoadGame -.global .LoadGame_patch -LoadGame_patch: - b hook_LoadGame +.section .patch_BeforeLoadGame +.global BeforeLoadGame_patch +BeforeLoadGame_patch: + b hook_BeforeLoadGame + +.section .patch_AfterLoadGame +.global AfterLoadGame_patch +AfterLoadGame_patch: + b hook_AfterLoadGame .section .patch_SaveGame .global .SaveGame_patch @@ -2200,6 +2190,16 @@ RandomGsLoc_BlockSpawn_Soil_patch: RandomGsLoc_SkipSoilJingle_patch: bl hook_RandomGsLoc_SkipSoilJingle +.section .patch_HandleTextControlCode +.global HandleTextControlCode_patch +HandleTextControlCode_patch: + bl hook_HandleTextControlCode + +.section .patch_CheckForTextControlCode +.global CheckForTextControlCode_patch +CheckForTextControlCode_patch: + bl hook_CheckForTextControlCode + @ ---------------------------------- @ ---------------------------------- diff --git a/code/src/savefile.c b/code/src/savefile.c index 1b816530f..e8ed124ea 100644 --- a/code/src/savefile.c +++ b/code/src/savefile.c @@ -828,3 +828,17 @@ void SaveFile_LoadFileSwordless(void) { gExtSaveData.extInf[EXTINF_MASTERSWORDFLAGS] |= 2; } } + +void SaveFile_BeforeLoadGame(u32 saveNumber) { + SaveFile_LoadExtSaveData(saveNumber); +} + +void SaveFile_AfterLoadGame(void) { + // Give Ganon BK if Triforce Hunt has been completed + if (gSettingsContext.triforceHunt == ON && + gExtSaveData.extInf[EXTINF_TRIFORCE_PIECES] >= gSettingsContext.triforcePiecesRequired && + (gSaveContext.dungeonItems[DUNGEON_GANONS_TOWER] & 1) == 0) { + + ItemOverride_PushHardcodedItem(GI_GANON_BOSS_KEY); + } +} diff --git a/code/src/savefile.h b/code/src/savefile.h index 3c3681859..b0667d499 100644 --- a/code/src/savefile.h +++ b/code/src/savefile.h @@ -39,6 +39,7 @@ typedef enum { EXTINF_HASTIMETRAVELED, EXTINF_MASTERSWORDFLAGS, EXTINF_TOTALTAR_FLAGS, + EXTINF_TRIFORCE_PIECES, EXTINF_SIZE, } ExtInf; diff --git a/code/src/settings.h b/code/src/settings.h index eab0182a8..f9e78a936 100644 --- a/code/src/settings.h +++ b/code/src/settings.h @@ -231,6 +231,7 @@ typedef enum { GANONSBOSSKEY_ANY_DUNGEON, GANONSBOSSKEY_OVERWORLD, GANONSBOSSKEY_ANYWHERE, + GANONSBOSSKEY_TRIFORCE, // Unselectable option, set automatically for Triforce Hunt GANONSBOSSKEY_LACS_VANILLA, GANONSBOSSKEY_LACS_MEDALLIONS, GANONSBOSSKEY_LACS_STONES, @@ -496,6 +497,9 @@ typedef struct { u8 randomMQDungeons; u8 mqDungeonCount; u8 dungeonModesKnown[12]; // 12 dungeons which can be set Vanilla or MQ + u8 triforceHunt; + u8 triforcePiecesTotal; + u8 triforcePiecesRequired; u8 shuffleRewards; u8 linksPocketItem; diff --git a/source/custom_messages.cpp b/source/custom_messages.cpp index 8434497ef..216d60d28 100644 --- a/source/custom_messages.cpp +++ b/source/custom_messages.cpp @@ -1055,6 +1055,17 @@ void CreateAlwaysIncludedMessages() { rutoDialog.Replace("$", ""); // Plural marker CreateMessageFromTextObject(0x4050, 0, 2, 3, AddColorsAndFormat(rutoDialog, { itemColor })); } + + // Triforce Piece + { + Text triforceMsg = + Text{ /*english*/ "You found a piece of the #Triforce#!&You have #" + TRIFORCE_PIECE_COUNT() + "#!", + /*french */ "", + /*spanish*/ "", + /*italian*/ "Hai ottenuto un frammento della #Triforza#!&Ne hai #" + TRIFORCE_PIECE_COUNT() + "#!", + /*german */ "" }; + CreateMessageFromTextObject(0x9003, 0, 2, 3, AddColorsAndFormat(triforceMsg, { QM_RED, QM_RED })); + } } std::vector CreateBaseCompassTexts() { @@ -1357,4 +1368,7 @@ std::string MQ_ELSE() { std::string MQ_END() { return "\x7F\x2B"s; } +std::string TRIFORCE_PIECE_COUNT() { + return "\x7F\x30"s; +} } // namespace CustomMessages diff --git a/source/custom_messages.hpp b/source/custom_messages.hpp index e05ddc55e..4b8cc462e 100644 --- a/source/custom_messages.hpp +++ b/source/custom_messages.hpp @@ -56,4 +56,5 @@ std::string CENTER_TEXT(); std::string IF_NOT_MQ(); std::string MQ_ELSE(); std::string MQ_END(); +std::string TRIFORCE_PIECE_COUNT(); } // namespace CustomMessages diff --git a/source/descriptions.cpp b/source/descriptions.cpp index 687c3fcb9..4486fa49e 100644 --- a/source/descriptions.cpp +++ b/source/descriptions.cpp @@ -342,6 +342,20 @@ string_view mqDungeonCountDesc = "Specify the number of Master Quest dung string_view setDungeonTypesDesc = "If set, you can choose specific dungeons to be\n" // "vanilla, MQ, or random"; // /*------------------------------ // +| TRIFORCE HUNT | // +------------------------------*/ // +string_view triforceHuntDesc = "Pieces of the Triforce have been scattered around\n" + "the world. Find some of them to beat the game.\n" // + "\n" // + "Game is saved on completion, and Ganon's Castle\n"// + "key is given if beating the game again is desired."; +string_view triforcePiecesTotalDesc = "Set the total number of pieces that will appear\n"// + "in the world.\n\n" // + "Hold A to scroll faster."; // +string_view triforcePiecesRequiredDesc= "Set the number of pieces required to beat the\n" // + "game.\n\n" // + "Hold A to scroll faster."; // +/*------------------------------ // | SHUFFLE DUNGEON REWARDS | // ------------------------------*/ // string_view shuffleRewardsEndOfDungeon= "Medallions and Spiritual Stones will be given as\n" diff --git a/source/descriptions.hpp b/source/descriptions.hpp index 1c97483a5..65e539ac3 100644 --- a/source/descriptions.hpp +++ b/source/descriptions.hpp @@ -112,6 +112,10 @@ extern string_view scarceHeartsDesc; extern string_view mqDungeonCountDesc; extern string_view setDungeonTypesDesc; +extern string_view triforceHuntDesc; +extern string_view triforcePiecesTotalDesc; +extern string_view triforcePiecesRequiredDesc; + extern string_view shuffleRewardsEndOfDungeon; extern string_view shuffleRewardsAnyDungeon; extern string_view shuffleRewardsOverworld; diff --git a/source/hints.cpp b/source/hints.cpp index 8f9a336c3..27b194b01 100644 --- a/source/hints.cpp +++ b/source/hints.cpp @@ -727,6 +727,9 @@ static Text BuildGanonBossKeyText() { } else if (GanonsBossKey.Is(GANONSBOSSKEY_ANYWHERE)) { ganonBossKeyText = Hint(GANON_BK_ANYWHERE_HINT).GetText(); + } else if (GanonsBossKey.Is(GANONSBOSSKEY_TRIFORCE)) { + ganonBossKeyText = Hint(GANON_BK_TRIFORCE_HINT).GetText(); + } else if (GanonsBossKey.Is(GANONSBOSSKEY_LACS_VANILLA)) { ganonBossKeyText = Hint(LACS_VANILLA_HINT).GetText(); diff --git a/source/item_list.cpp b/source/item_list.cpp index 1de7637c1..3a339d3b7 100644 --- a/source/item_list.cpp +++ b/source/item_list.cpp @@ -242,6 +242,8 @@ void ItemTable_Init() { //Item Type itemTable[BUY_RED_POTION_40] = Item(ITEMTYPE_SHOP, 0x30, false, &noVariable, BOTTLE_WITH_RED_POTION, 40, Text{"Buy Red Potion [40]", "Acheter: Potion rouge [40]", "Comprar poción roja [40]", "Pozione vita in vendita [40]", "Rotes Elixier [40]"}); itemTable[BUY_RED_POTION_50] = Item(ITEMTYPE_SHOP, 0x31, false, &noVariable, BOTTLE_WITH_RED_POTION, 50, Text{"Buy Red Potion [50]", "Acheter: Potion rouge [50]", "Comprar poción roja [50]", "Pozione vita in vendita [50]", "Rotes Elixier [50]"}); + itemTable[TRIFORCE_PIECE] = Item(ITEMTYPE_ITEM, GI_TRIFORCE_PIECE, true, &TriforcePieces, TRIFORCE_PIECE, Text{"Triforce Piece", "Fragment de la Triforce", "Fragmento de la Trifuerza", "Frammento di Triforza", "Triforce-Splitter"}); + itemTable[TRIFORCE] = Item(ITEMTYPE_EVENT, GI_RUPEE_RED_LOSE, false, &noVariable, NONE, Text{"Triforce", "Triforce", "Trifuerza", "Triforza", "Triforce"}); itemTable[HINT] = Item(ITEMTYPE_EVENT, GI_RUPEE_BLUE_LOSE, false, &noVariable, NONE, Text{"Hint", "Indice", "Pista", "Indizio", "Hinweis"}); @@ -288,4 +290,4 @@ void NewItem(const ItemKey itemKey, const Item item) { } itemTable[itemKey] = item; -} \ No newline at end of file +} diff --git a/source/item_pool.cpp b/source/item_pool.cpp index 3cb3854ed..f1ad36437 100644 --- a/source/item_pool.cpp +++ b/source/item_pool.cpp @@ -740,6 +740,11 @@ void GenerateItemPool() { } } + if (TriforceHunt) { + AddItemToMainPool(TRIFORCE_PIECE, TriforcePiecesTotal.Value() + 1); + IceTrapModels.push_back(GI_TRIFORCE_PIECE); + } + if (ItemPoolValue.Is(ITEMPOOL_PLENTIFUL)) { if (ShuffleGerudoToken) { AddItemToPool(PendingJunkPool, GERUDO_TOKEN); @@ -984,7 +989,7 @@ void GenerateItemPool() { PlaceItemInLocation(TOT_LIGHT_ARROWS_CUTSCENE, GANONS_CASTLE_BOSS_KEY); } else if (GanonsBossKey.Is(GANONSBOSSKEY_VANILLA)) { PlaceItemInLocation(GANONS_TOWER_BOSS_KEY_CHEST, GANONS_CASTLE_BOSS_KEY); - } else { + } else if (GanonsBossKey.IsNot(GANONSBOSSKEY_TRIFORCE)) { AddItemToMainPool(GANONS_CASTLE_BOSS_KEY); } diff --git a/source/location_access.cpp b/source/location_access.cpp index 247369cf6..b162cf3ea 100644 --- a/source/location_access.cpp +++ b/source/location_access.cpp @@ -251,11 +251,14 @@ void AreaTable_Init() { areaTable.fill(Area("Invalid Area", "Invalid Area", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, {})); // name, scene, hint text, events, locations, exits - areaTable[ROOT] = Area("Root", "", LINKS_POCKET, NO_DAY_NIGHT_CYCLE, {}, - { // Locations - LocationAccess(LINKS_POCKET, { [] { return true; } }) }, - { // Exits - Entrance(ROOT_EXITS, { [] { return true; } }) }); + areaTable[ROOT] = + Area("Root", "", LINKS_POCKET, NO_DAY_NIGHT_CYCLE, {}, + { // Locations + LocationAccess(LINKS_POCKET, { [] { return true; } }), + LocationAccess( + GANON, { [] { return TriforceHunt && TriforcePieces >= TriforcePiecesTotal.Value() + 1; } }) }, + { // Exits + Entrance(ROOT_EXITS, { [] { return true; } }) }); areaTable[ROOT_EXITS] = Area("Root Exits", "", NONE, NO_DAY_NIGHT_CYCLE, {}, {}, diff --git a/source/logic.cpp b/source/logic.cpp index fc2e85f8b..5d106c625 100644 --- a/source/logic.cpp +++ b/source/logic.cpp @@ -203,6 +203,7 @@ bool MagicRefill = false; u8 PieceOfHeart = 0; u8 HeartContainer = 0; bool DoubleDefense = false; +u8 TriforcePieces = 0; /* --- HELPERS, EVENTS, AND LOCATION ACCESS --- */ /* These are used to simplify reading the logic, but need to be updated @@ -1109,6 +1110,7 @@ void LogicReset() { PieceOfHeart = 0; HeartContainer = 0; DoubleDefense = false; + TriforcePieces = 0; /* --- HELPERS, EVENTS, AND LOCATION ACCESS --- */ /* These are used to simplify reading the logic, but need to be updated diff --git a/source/logic.hpp b/source/logic.hpp index b39fa4497..fcd877998 100644 --- a/source/logic.hpp +++ b/source/logic.hpp @@ -197,6 +197,7 @@ extern bool MagicRefill; extern u8 PieceOfHeart; extern u8 HeartContainer; extern bool DoubleDefense; +extern u8 TriforcePieces; /* --- HELPERS --- */ /* These are used to simplify reading the logic, but need to be updated diff --git a/source/main.cpp b/source/main.cpp index b8cacb33f..9b0d31ba9 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -43,7 +43,7 @@ int main() { // send inputs off to the menu if (kDown) - MenuUpdate(kDown, updatedByHeld); + MenuUpdate(kDown, updatedByHeld, kHeld); // launch oot3d directly by holding L and R (cartridge only) if (kHeld & KEY_L && kHeld & KEY_R) { diff --git a/source/menu.cpp b/source/menu.cpp index a727708aa..4d01137f0 100644 --- a/source/menu.cpp +++ b/source/menu.cpp @@ -202,7 +202,7 @@ void MoveCursor(u32 kDown, bool updatedByHeld) { } } -void MenuUpdate(u32 kDown, bool updatedByHeld) { +void MenuUpdate(u32 kDown, bool updatedByHeld, u32 kHeld) { consoleSelect(&bottomScreen); if (currentMenu->mode != POST_GENERATE || (kDown & KEY_B)) { // Don't clear console if ValidateSettings printed error @@ -267,7 +267,7 @@ void MenuUpdate(u32 kDown, bool updatedByHeld) { if (currentMenu->mode == MAIN_MENU) { PrintMainMenu(); } else if (currentMenu->mode == OPTION_MENU) { - UpdateOptionSubMenu(kDown); + UpdateOptionSubMenu(kDown, kHeld); PrintOptionSubMenu(); } else if (currentMenu->mode == LOAD_PREMADE_PRESET) { UpdatePremadePresetsMenu(kDown); @@ -331,17 +331,9 @@ void UpdateCustomCosmeticColors(u32 kDown) { } } -void UpdateOptionSubMenu(u32 kDown) { - if ((kDown & KEY_RIGHT) != 0) { - currentSetting->NextOptionIndex(); - } - if ((kDown & KEY_LEFT) != 0) { - currentSetting->PrevOptionIndex(); - } - - // Bounds checking - currentSetting->SanitizeSelectedOptionIndex(); - +void UpdateOptionSubMenu(u32 kDown, u32 kHeld) { + bool fastScrolling = kHeld & KEY_A; + currentSetting->ScrollOptionIndex(kDown, fastScrolling); currentSetting->SetVariable(); Settings::ForceChange(kDown, currentSetting); UpdateCustomCosmeticColors(kDown); diff --git a/source/menu.hpp b/source/menu.hpp index 2c304f2d8..12ab94c23 100644 --- a/source/menu.hpp +++ b/source/menu.hpp @@ -33,7 +33,7 @@ #define WHITE "\x1b[37m" void ModeChangeInit(); -void UpdateOptionSubMenu(u32 kDown); +void UpdateOptionSubMenu(u32 kDown, u32 kHeld); void UpdatePremadePresetsMenu(u32 kDown); void UpdateCustomPresetsMenu(u32 kDown); void UpdateResetToDefaultsMenu(u32 kdown); @@ -51,4 +51,4 @@ void GenerateRandomizer(); std::string GetInput(const char* hintText); extern void MenuInit(); -extern void MenuUpdate(u32 kDown, bool updatedByHeld); +extern void MenuUpdate(u32 kDown, bool updatedByHeld, u32 kHeld); diff --git a/source/settings.cpp b/source/settings.cpp index 754e07c06..68a36cd66 100644 --- a/source/settings.cpp +++ b/source/settings.cpp @@ -128,6 +128,9 @@ Option MQBotW = Option::U8 (2, "Bottom of the Well", {"Vani Option MQIceCavern = Option::U8 (2, "Ice Cavern", {"Vanilla", "Master Quest", "Random"}, {setDungeonTypesDesc}); Option MQGTG = Option::U8 (2, "Training Grounds", {"Vanilla", "Master Quest", "Random"}, {setDungeonTypesDesc}); Option MQCastle = Option::U8 (2, "Ganon's Castle", {"Vanilla", "Master Quest", "Random"}, {setDungeonTypesDesc}); +Option TriforceHunt = Option::Bool("Triforce Hunt", {"Off", "On"}, {triforceHuntDesc}); +Option TriforcePiecesTotal = Option::U8 (2, "Total pieces", {NumOpts(1, 200)}, {triforcePiecesTotalDesc}, OptionCategory::Setting, 29); +Option TriforcePiecesRequired = Option::U8 (2, "Required pieces", {NumOpts(1, 100)}, {triforcePiecesRequiredDesc}, OptionCategory::Setting, 19); std::vector