diff --git a/code/include/z3D/z3D.h b/code/include/z3D/z3D.h index 3e89ff19..a261dd66 100644 --- a/code/include/z3D/z3D.h +++ b/code/include/z3D/z3D.h @@ -700,6 +700,7 @@ extern const char DungeonNames[][25]; #define gRestrictionFlags ((RestrictionFlags*)0x539DC4) #define PLAYER ((Player*)gGlobalContext->actorCtx.actorList[ACTORTYPE_PLAYER].first) #define gMainClass ((MainClass*)0x5BE5B8) +#define gIsBottomScreenDimmed (*(s32*)0x5043EC) #define GearSlot(X) (X - ITEM_SWORD_KOKIRI) @@ -898,4 +899,11 @@ typedef void (*EffectSsDeadDb_Spawn_proc)(GlobalContext* globalCtx, Vec3f* posit #define EffectSsDeadDb_Spawn_addr 0x3642F4 #define EffectSsDeadDb_Spawn ((EffectSsDeadDb_Spawn_proc)EffectSsDeadDb_Spawn_addr) +typedef void (*SaveGame_proc)(GlobalContext* globalCtx, u8 isSaveFileCreation); +#define SaveGame_addr 0x2FDAC8 +#define SaveGame ((SaveGame_proc)SaveGame_addr) + +typedef s32 (*Message_GetState_proc)(void); +#define Message_GetState ((Message_GetState_proc)0x3769d8) + #endif //_Z3D_H_ diff --git a/code/include/z3D/z3Dactor.h b/code/include/z3D/z3Dactor.h index 25f2d08b..e89f0fbc 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 c9ef059c..126caa83 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 58fc0c66..0b78e67e 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 4acc629a..cb81c6cc 100644 --- a/code/object_and_gi_usage.txt +++ b/code/object_and_gi_usage.txt @@ -14,6 +14,7 @@ The following custom objectIds are currently being used: 128: Boss Keys 228: Enemy Souls 291: Ocarina Note Buttons +366: Triforce Piece To use a custom asset, currently: - choose an unused objectId to be repurposed for the custom item @@ -22,4 +23,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 03b37dea..12eb6eab 100644 --- a/code/oot.ld +++ b/code/oot.ld @@ -728,20 +728,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 : { @@ -1276,8 +1268,12 @@ SECTIONS *(.patch_GearMenuEmptySlot) } - .patch_LoadGame 0x447380 : { - *(.patch_LoadGame) + .patch_BeforeLoadGame 0x447380 : { + *(.patch_BeforeLoadGame) + } + + .patch_AfterLoadGame 0x449F00 : { + *(.patch_AfterLoadGame) } .patch_DontSetMotionSetting 0x447410 : { @@ -1600,6 +1596,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 15a147fb..f9da2397 100644 --- a/code/oot_e.ld +++ b/code/oot_e.ld @@ -728,20 +728,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 : { @@ -1276,8 +1268,12 @@ SECTIONS *(.patch_GearMenuEmptySlot) } - .patch_LoadGame 0x4473A0 : { - *(.patch_LoadGame) + .patch_BeforeLoadGame 0x4473A0 : { + *(.patch_BeforeLoadGame) + } + + .patch_AfterLoadGame 0x449F20 : { + *(.patch_AfterLoadGame) } .patch_DontSetMotionSetting 0x447430 : { @@ -1600,6 +1596,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 16018c08..eca3ec83 100644 --- a/code/src/actor.c +++ b/code/src/actor.c @@ -69,6 +69,7 @@ #define OBJECT_GI_OCARINA_0 270 #define OBJECT_GI_SHOP_FAIRY 375 #define OBJECT_GI_SOLD_OUT 328 +#define OBJECT_TRIFORCE 149 typedef void (*TitleCard_Update_proc)(GlobalContext* globalCtx, TitleCardContext* titleCtx); #ifdef Version_EUR @@ -271,6 +272,9 @@ void Actor_Init() { // Define object 291 to be by default the same as object 328 strncpy(gObjectTable[OBJECT_CUSTOM_OCARINA_BUTTON].filename, gObjectTable[OBJECT_GI_SOLD_OUT].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/chest.c b/code/src/actors/chest.c index 30d21fec..66ceb23c 100644 --- a/code/src/actors/chest.c +++ b/code/src/actors/chest.c @@ -173,7 +173,7 @@ void EnBox_rUpdate(Actor* thisx, GlobalContext* globalCtx) { u8 Chest_OverrideAnimation() { if ((gSettingsContext.chestAnimations == CHESTANIMATIONS_ALWAYSFAST) || - (rActiveItemActionId == 0)) // The animation is always fast for unused chests that aren't randomized + (!isItemOverrideActive)) // The animation is always fast for unused chests that aren't randomized return FALSE; switch (rActiveItemChestType) { diff --git a/code/src/actors/shops.c b/code/src/actors/shops.c index ef48ba25..6bd5ab99 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 bce5de50..ade9dbd6 100644 --- a/code/src/custom_models.c +++ b/code/src/custom_models.c @@ -259,6 +259,14 @@ static void CustomModel_SetSoldOutToRGBA565(void* soldOutCMB) { EDIT_BYTE(0x274, 0x5B); // ImageFormat: 0x6758 -> 0x675B } +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) { @@ -306,6 +314,10 @@ void CustomModels_EditItemCMB(void* ZARBuf, u16 objectId, s8 special) { cmb = ((char*)ZARBuf) + 0xA4; CustomModel_SetSoldOutToRGBA565(cmb); break; + case OBJECT_CUSTOM_TRIFORCE_PIECE: + cmb = ((char*)ZARBuf) + 0xF0; + CustomModel_EditTriforce(cmb); + break; } } @@ -342,3 +354,26 @@ void CustomModels_ApplyItemCMAB(SkeletonAnimationModel* model, u16 objectId, s8 model->unk_0C->curFrame = special; } } + +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 2f6122d1..508247a1 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 @@ -24,6 +25,7 @@ void CustomModels_ApplyItemCMAB(SkeletonAnimationModel* model, u16 objectId, s8 #define OBJECT_CUSTOM_GENERAL_ASSETS 182 #define OBJECT_CUSTOM_ENEMY_SOUL 228 #define OBJECT_CUSTOM_OCARINA_BUTTON 291 +#define OBJECT_CUSTOM_TRIFORCE_PIECE 366 typedef enum { TEXANIM_COPY_NINTENDO, diff --git a/code/src/gfx.c b/code/src/gfx.c index 2816637e..0e6cb301 100644 --- a/code/src/gfx.c +++ b/code/src/gfx.c @@ -376,6 +376,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.triforcePieces >= gSettingsContext.triforcePiecesRequired; + Draw_DrawFormattedString(10 + (SPACING_X * 4), 16 + (SPACING_Y * offsetY++), + triforceDone ? COLOR_YELLOW : COLOR_WHITE, "%d / %d", gExtSaveData.triforcePieces, + 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 5a1167f8..23b53d94 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} @@ -991,6 +943,10 @@ hook_TurboTextSignalNPC: push {r0-r12, lr} bl Settings_IsTurboText cmp r0,#0x0 + @ If about to warp to credits, signal NPC so that + @ the collection flag is set before the game is saved + bleq Triforce_IsWaitingForText + cmp r0,#0x0 pop {r0-r12, lr} movne r4,#0x1 bx lr @@ -1076,11 +1032,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 +1044,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 @@ -2138,6 +2101,27 @@ hook_OcarinaNoteButtonsPress: pop {r1-r12, lr} 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 a8477c8b..16953744 100644 --- a/code/src/item_effect.c +++ b/code/src/item_effect.c @@ -8,6 +8,7 @@ #include "actors/chest.h" #include "enemy_souls.h" #include "ocarina_notes.h" +#include "triforce.h" void ItemEffect_None(SaveContext* saveCtx, s16 arg1, s16 arg2) { } @@ -22,29 +23,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) @@ -172,6 +150,13 @@ 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.triforcePieces++; + if (gSettingsContext.triforceHunt && gExtSaveData.triforcePieces == gSettingsContext.triforcePiecesRequired) { + TriforceWarpStatus = TRIFORCEWARP_WHEN_TEXT_COMPLETE; + } +} + // 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 84899a40..209399e0 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 42e2614d..dd6089db 100644 --- a/code/src/item_override.c +++ b/code/src/item_override.c @@ -8,6 +8,7 @@ #include "entrance.h" #include "savefile.h" #include "common.h" + #include #include "z3D/z3D.h" @@ -26,6 +27,7 @@ static Actor* rDummyActor = NULL; static ItemOverride rActiveItemOverride = { 0 }; DrawItemTableEntry rActiveDrawItem = { 0 }; ItemRow* rActiveItemRow = NULL; +u32 isItemOverrideActive = FALSE; // Split active_item_row into variables for convenience in ASM u32 rActiveItemActionId = 0; u32 rActiveItemTextId = 0; @@ -193,6 +195,7 @@ static void ItemOverride_Activate(ItemOverride override) { looksLikeItemId = 0; } + isItemOverrideActive = TRUE; rActiveItemOverride = override; rActiveItemRow = itemRow; rActiveItemActionId = itemRow->actionId; @@ -211,6 +214,7 @@ static void ItemOverride_Activate(ItemOverride override) { } static void ItemOverride_Clear(void) { + isItemOverrideActive = FALSE; rActiveItemOverride = (ItemOverride){ 0 }; rActiveDrawItem = (DrawItemTableEntry){ 0 }; rActiveItemRow = NULL; @@ -322,7 +326,6 @@ void ItemOverride_AfterItemReceived(void) { return; } ItemOverride_AfterKeyReceived(key); - ItemOverride_Clear(); } static u32 ItemOverride_PlayerIsReadyOnLand(void) { @@ -419,6 +422,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 +545,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 +617,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 6b27ae37..b3ae5e1f 100644 --- a/code/src/item_override.h +++ b/code/src/item_override.h @@ -3,7 +3,7 @@ #include "../include/z3D/z3D.h" -extern u32 rActiveItemActionId; +extern u32 isItemOverrideActive; extern u32 rActiveItemChestType; void ItemOverride_Init(void); @@ -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 8eed38b9..d674fd68 100644 --- a/code/src/item_table.c +++ b/code/src/item_table.c @@ -249,7 +249,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/main.c b/code/src/main.c index b01303b6..ff7181cc 100644 --- a/code/src/main.c +++ b/code/src/main.c @@ -12,6 +12,7 @@ #include "multiplayer.h" #include "grotto.h" #include "item_effect.h" +#include "triforce.h" #include "z3D/z3D.h" #include "3ds/extdata.h" @@ -57,6 +58,8 @@ void before_GlobalContext_Update(GlobalContext* globalCtx) { Multiplayer_Run(); ItemEffect_RupeeAmmo(&gSaveContext); + + Triforce_HandleCreditsWarp(); } void after_GlobalContext_Update() { diff --git a/code/src/message.c b/code/src/message.c index e692f04e..b2315e7b 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.triforcePieces); + 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 7ec14d46..abf2db55 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 9dc267a7..0b9ea5fa 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 9bdc77b5..2ca3faed 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/multiplayer.c b/code/src/multiplayer.c index efd2d816..96b53477 100644 --- a/code/src/multiplayer.c +++ b/code/src/multiplayer.c @@ -9,6 +9,7 @@ #include "savefile.h" #include "settings.h" #include "giants_knife.h" +#include "triforce.h" #include "web.h" #include "skulltula.h" @@ -79,6 +80,7 @@ typedef struct { u8 extInf[EXTINF_SIZE]; u32 scenesDiscovered[SAVEFILE_SCENES_DISCOVERED_IDX_COUNT]; u32 entrancesDiscovered[SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT]; + u8 triforcePieces; } MultiplayerSaveContext; static MultiplayerSaveContext mSaveContext; @@ -142,6 +144,7 @@ static void Multiplayer_Overwrite_mSaveContext(void) { for (size_t i = 0; i < SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT; i++) { mSaveContext.entrancesDiscovered[i] = gExtSaveData.entrancesDiscovered[i]; } + mSaveContext.triforcePieces = gExtSaveData.triforcePieces; } static void Multiplayer_Overwrite_gSaveContext(void) { @@ -203,6 +206,7 @@ static void Multiplayer_Overwrite_gSaveContext(void) { for (size_t i = 0; i < SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT; i++) { gExtSaveData.entrancesDiscovered[i] = mSaveContext.entrancesDiscovered[i]; } + gExtSaveData.triforcePieces = mSaveContext.triforcePieces; } void Multiplayer_OnFileLoad(void) { @@ -265,6 +269,7 @@ u8 prevFishingFlags; u32 prevWorldMapAreaData; u32 prevAdultTrade; u8 prevExtInf[EXTINF_SIZE]; +u8 prevTriforcePieces; s16 prevHealth; s16 prevRupees; @@ -305,6 +310,7 @@ typedef enum { PACKET_EXTINF, PACKET_DISCOVEREDSCENE, PACKET_DISCOVEREDENTRANCE, + PACKET_TRIFORCEPIECES, PACKET_UNLOCKEDDOOR, PACKET_ACTORUPDATE, PACKET_ACTORSPAWN, @@ -518,6 +524,9 @@ static void Multiplayer_Sync_Init(void) { prevExtInf[i] = gExtSaveData.extInf[i]; } + // Triforce Pieces + prevTriforcePieces = gExtSaveData.triforcePieces; + // Health prevHealth = gSaveContext.health; @@ -858,6 +867,12 @@ static void Multiplayer_Sync_SharedProgress(void) { } prevExtInf[index] = gExtSaveData.extInf[index]; } + + // Triforce Pieces + if (prevTriforcePieces != gExtSaveData.triforcePieces) { + Multiplayer_Send_TriforcePieces(gExtSaveData.triforcePieces - prevTriforcePieces); + } + prevTriforcePieces = gExtSaveData.triforcePieces; } void Multiplayer_Sync_UpdatePrevActorFlags(void) { @@ -1100,6 +1115,7 @@ void Multiplayer_Send_BaseSync(u16 targetID) { for (size_t i = 0; i < ARRAY_SIZE(mSaveContext.extInf); i++) { mBuffer[memSpacer++] = mSaveContext.extInf[i]; } + mBuffer[memSpacer++] = mSaveContext.triforcePieces; mBuffer[memSpacer++] = mSaveContext.health; mBuffer[memSpacer++] = mSaveContext.rupees; Multiplayer_SendPacket(memSpacer, targetID); @@ -1157,6 +1173,7 @@ void Multiplayer_Receive_BaseSync(u16 senderID) { for (size_t i = 0; i < ARRAY_SIZE(mSaveContext.extInf); i++) { mSaveContext.extInf[i] = mBuffer[memSpacer++]; } + mSaveContext.triforcePieces = mBuffer[memSpacer++]; if (gSettingsContext.mp_SharedHealth) { mSaveContext.health = mBuffer[memSpacer++]; } else { @@ -2159,6 +2176,35 @@ void Multiplayer_Receive_DiscoveredEntrance(u16 senderID) { mSaveContext.entrancesDiscovered[index] |= bit; } +void Multiplayer_Send_TriforcePieces(u32 piecesDiff) { + if (!IsSendReceiveReady() || gSettingsContext.mp_SharedProgress == OFF) { + return; + } + memset(mBuffer, 0, mBufSize); + u8 memSpacer = PrepareSharedProgressPacket(PACKET_TRIFORCEPIECES); + + mBuffer[memSpacer++] = piecesDiff; + Multiplayer_SendPacket(memSpacer, UDS_BROADCAST_NETWORKNODEID); +} + +void Multiplayer_Receive_TriforcePieces(u16 senderID) { + if (!IsInSameSyncGroup() || gSettingsContext.mp_SharedProgress == OFF) { + return; + } + u8 memSpacer = GetSharedProgressMemSpacerOffset(); + + u32 piecesDiff = mBuffer[memSpacer++]; + + mSaveContext.triforcePieces += piecesDiff; + + if ((prevTriforcePieces < gSettingsContext.triforcePiecesRequired) && + (mSaveContext.triforcePieces >= gSettingsContext.triforcePiecesRequired)) { + TriforceWarpStatus = TRIFORCEWARP_WHEN_PLAYER_READY; + } + + prevTriforcePieces += piecesDiff; +} + void Multiplayer_Send_UnlockedDoor(u32 flag) { if (!IsSendReceiveReady() || gSettingsContext.mp_SharedProgress == OFF) { return; @@ -2643,6 +2689,7 @@ static void Multiplayer_UnpackPacket(u16 senderID) { Multiplayer_Receive_ExtInfBit, Multiplayer_Receive_DiscoveredScene, Multiplayer_Receive_DiscoveredEntrance, + Multiplayer_Receive_TriforcePieces, Multiplayer_Receive_UnlockedDoor, Multiplayer_Receive_ActorUpdate, Multiplayer_Receive_ActorSpawn, diff --git a/code/src/multiplayer.h b/code/src/multiplayer.h index 2daaeecb..37c6e5b1 100644 --- a/code/src/multiplayer.h +++ b/code/src/multiplayer.h @@ -52,6 +52,7 @@ void Multiplayer_Send_BigPoePoints(u32 pointDiff); void Multiplayer_Send_FishingFlag(u8 bit, u8 setOrUnset); void Multiplayer_Send_WorldMapBit(u8 bit, u8 setOrUnset); void Multiplayer_Send_ExtInfBit(u8 index, u8 bit, u8 setOrUnset); +void Multiplayer_Send_TriforcePieces(u32 piecesDiff); void Multiplayer_Send_FullEntranceSync(u16 targetID); void Multiplayer_Send_DiscoveredScene(u32 index, u32 bit); void Multiplayer_Send_DiscoveredEntrance(u32 index, u32 bit); diff --git a/code/src/patches.s b/code/src/patches.s index 476b164a..2015835e 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 @@ -2242,6 +2232,16 @@ OcarinaNoteButtonsDraw_patch: OcarinaNoteButtonsPress_patch: bl hook_OcarinaNoteButtonsPress +.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 e7ca482b..b2328055 100644 --- a/code/src/savefile.c +++ b/code/src/savefile.c @@ -836,3 +836,16 @@ 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.triforcePieces >= 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 ad0ecd7a..83bc2f54 100644 --- a/code/src/savefile.h +++ b/code/src/savefile.h @@ -62,6 +62,7 @@ typedef struct { u32 scenesDiscovered[SAVEFILE_SCENES_DISCOVERED_IDX_COUNT]; u32 entrancesDiscovered[SAVEFILE_ENTRANCES_DISCOVERED_IDX_COUNT]; u8 permadeath; + u8 triforcePieces; // Ingame Options, all need to be s8 s8 option_EnableBGM; s8 option_EnableSFX; diff --git a/code/src/settings.h b/code/src/settings.h index e81c02ad..71d43bc5 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, @@ -490,6 +491,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/code/src/triforce.c b/code/src/triforce.c new file mode 100644 index 00000000..3b69174b --- /dev/null +++ b/code/src/triforce.c @@ -0,0 +1,33 @@ +#include "triforce.h" +#include "item_override.h" +#include "common.h" + +#define TEXT_STATE_DONE 6 + +u8 TriforceWarpStatus = TRIFORCEWARP_OFF; + +void Triforce_HandleCreditsWarp(void) { + static s32 sPreviousTextState = 0; // used to add a one-frame delay + + if ((TriforceWarpStatus == TRIFORCEWARP_WHEN_TEXT_COMPLETE && sPreviousTextState == TEXT_STATE_DONE) || + (TriforceWarpStatus == TRIFORCEWARP_WHEN_PLAYER_READY && IsInGame() && !gIsBottomScreenDimmed && + PauseContext_GetState() == 0 && gGlobalContext->sceneLoadFlag == 0 && !isItemOverrideActive && + gGlobalContext->csCtx.state == 0 && !ItemOverride_IsAPendingOverride())) { + // 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 + TriforceWarpStatus = TRIFORCEWARP_OFF; + } + sPreviousTextState = Message_GetState(); +} + +// Used in hook_TurboTextSignalNPC to make NPCs set their collection flag before the credits warp save. +s32 Triforce_IsWaitingForText(void) { + return TriforceWarpStatus == TRIFORCEWARP_WHEN_TEXT_COMPLETE; +} diff --git a/code/src/triforce.h b/code/src/triforce.h new file mode 100644 index 00000000..89576103 --- /dev/null +++ b/code/src/triforce.h @@ -0,0 +1,16 @@ +#ifndef _TRIFORCE_H_ +#define _TRIFORCE_H_ + +#include "z3D/z3D.h" + +enum TriforceWarp { + TRIFORCEWARP_OFF, + TRIFORCEWARP_WHEN_TEXT_COMPLETE, // wait for text completion, then signal NPCs from hook, then warp + TRIFORCEWARP_WHEN_PLAYER_READY, // wait for player is ready, then warp +}; + +extern u8 TriforceWarpStatus; + +void Triforce_HandleCreditsWarp(void); + +#endif //_TRIFORCE_H_ diff --git a/source/custom_messages.cpp b/source/custom_messages.cpp index cea1f32d..3f22c6e5 100644 --- a/source/custom_messages.cpp +++ b/source/custom_messages.cpp @@ -1070,6 +1070,18 @@ void CreateAlwaysIncludedMessages() { CreateMessageFromTextObject(0x93F0 + key - OCA_BUTTON_ITEM_L, 0, 2, 3, AddColorsAndFormat(text, { QM_RED })); } + + // Triforce Piece + { + Text triforceMsg = Text{ + /*english*/ "You found a piece of the #Triforce#!&You have #" + TRIFORCE_PIECE_COUNT() + "#!", + /*french */ "Vous obtenez un morceau de la #Triforce#!&Vous en avez #" + TRIFORCE_PIECE_COUNT() + "#!", + /*spanish*/ "¡Has encontrado un fragmento de la Trifuerza!&¡Tienes #" + TRIFORCE_PIECE_COUNT() + "#!", + /*italian*/ "Hai trovato un frammento della #Triforza#!&Ne hai #" + TRIFORCE_PIECE_COUNT() + "#!", + /*german */ "Du hast ein Teil des #Triforce# gefunden!&Du hast jetzt #" + TRIFORCE_PIECE_COUNT() + "#!" + }; + CreateMessageFromTextObject(0x9003, 0, 2, 3, AddColorsAndFormat(triforceMsg, { QM_RED, QM_RED })); + } } std::vector CreateBaseCompassTexts() { @@ -1372,4 +1384,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 e05ddc55..4b8cc462 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 10521da3..ac2a292d 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 c16f1285..3fac049e 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 8f9a336c..797e4918 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(); @@ -905,7 +908,9 @@ void CreateGossipStoneHints() { // Small Keys FOREST_TEMPLE_SMALL_KEY, FIRE_TEMPLE_SMALL_KEY, WATER_TEMPLE_SMALL_KEY, SPIRIT_TEMPLE_SMALL_KEY, SHADOW_TEMPLE_SMALL_KEY, BOTTOM_OF_THE_WELL_SMALL_KEY, GERUDO_TRAINING_GROUNDS_SMALL_KEY, - GERUDO_FORTRESS_SMALL_KEY, GANONS_CASTLE_SMALL_KEY + GERUDO_FORTRESS_SMALL_KEY, GANONS_CASTLE_SMALL_KEY, + // + TRIFORCE_PIECE }; const auto ignore = [&](std::vector v) { ignoredItems.insert(ignoredItems.end(), v.begin(), v.end()); diff --git a/source/item_list.cpp b/source/item_list.cpp index 6c9ee2a0..011c7219 100644 --- a/source/item_list.cpp +++ b/source/item_list.cpp @@ -298,6 +298,8 @@ void ItemTable_Init() { //Item Type itemTable[OCA_BUTTON_ITEM_Y] = Item(ITEMTYPE_ITEM, GI_OCARINA_BUTTON_Y, true, &OcarinaButtonY, OCA_BUTTON_ITEM_Y, Text{"Ocarina Y Button", "Bouton Y de l'ocarina", "Botón Y de la ocarina", "Pulsante Y dell'ocarina", "Ocarina Y-Knopf"}); itemTable[OCA_BUTTON_ITEM_A] = Item(ITEMTYPE_ITEM, GI_OCARINA_BUTTON_A, true, &OcarinaButtonA, OCA_BUTTON_ITEM_A, Text{"Ocarina A Button", "Bouton A de l'ocarina", "Botón A de la ocarina", "Pulsante A dell'ocarina", "Ocarina A-Knopf"}); + 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"}); diff --git a/source/item_pool.cpp b/source/item_pool.cpp index 33d1194c..b1a10c72 100644 --- a/source/item_pool.cpp +++ b/source/item_pool.cpp @@ -755,6 +755,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); @@ -999,7 +1004,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 247369cf..b162cf3e 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 6060273a..8fbf8c25 100644 --- a/source/logic.cpp +++ b/source/logic.cpp @@ -223,6 +223,7 @@ bool MagicRefill = false; u8 PieceOfHeart = 0; u8 HeartContainer = 0; bool DoubleDefense = false; +u8 TriforcePieces = 0; bool SoulPoe = false; bool SoulOctorok = false; @@ -1212,6 +1213,7 @@ void LogicReset() { PieceOfHeart = 0; HeartContainer = 0; DoubleDefense = false; + TriforcePieces = 0; SoulPoe = ShuffleEnemySouls.Is(OFF); SoulOctorok = ShuffleEnemySouls.Is(OFF); diff --git a/source/logic.hpp b/source/logic.hpp index 632a931b..f5019f72 100644 --- a/source/logic.hpp +++ b/source/logic.hpp @@ -217,6 +217,7 @@ extern bool MagicRefill; extern u8 PieceOfHeart; extern u8 HeartContainer; extern bool DoubleDefense; +extern u8 TriforcePieces; extern bool SoulPoe; extern bool SoulOctorok; diff --git a/source/main.cpp b/source/main.cpp index b0335391..ba88d168 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 1df2a1ba..6a313232 100644 --- a/source/menu.cpp +++ b/source/menu.cpp @@ -205,7 +205,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 @@ -270,7 +270,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); @@ -334,17 +334,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 2c304f2d..12ab94c2 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 350e3049..8f5d239a 100644 --- a/source/settings.cpp +++ b/source/settings.cpp @@ -131,6 +131,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