From a9922cb60c5e61d6c2fe2091701c30d865b4f1a5 Mon Sep 17 00:00:00 2001 From: PhlexPlexico Date: Sun, 19 Nov 2023 14:57:21 -0600 Subject: [PATCH] Spoiler Menu + Fixes (#77) * Spoiler Test (#72) Testing Spoiler Menu * Major update to menus. Put things where they're supposed to go. * More adjustments to gfx * Compiles! * Add awake callback. * Formatting fixes. * Update code info space and gfx hooks. * Inputs are now working as intended. * Menu now shows * Added debug statement to show menu is opening but not drawing. * Minor adjustments to input and newcodeinfo. * Update exheader to support larger section_data size. This should allow for a larger patch on the 3ds. * Formatting. Fix menu not showing with buffer issues. * Update gfx.cpp (#73) testing * Remove unused header file. * Fix improper memory allocation for custom messages (#74) * Spoiler-Menu Update #1 (#75) Reorganized in-game spoiler menu to make more sense. half implemented function to reveal checks on obtain * Fix warning in spoiler menu. Remove magic barrier from being used without magic. Currently the old zora swim system will be used until I can find a way to patch it back. * Basecode update. * Basecode change. * Change which IPS patch is actually brought in. * Update small keys for custom text. * Finish custom text for dungeon items. * Clang formatting. * Update asm for banker reward. * Include icons in spoiler menus. * Adjust item effects to hopefully resolve the small key issue. * Include fixes for tingle map overrides and custom text. Tingle will now show what he actually sells! * Use select as default for opening spoiler menu. Have checks on start/select if a user has the spoiler menu assigned to do no action. * Clang formatting. * Remove giving sword back on time reset. * Remove debug statement --------- Co-authored-by: Tacoman369 <90735287+Tacoman369@users.noreply.github.com> Co-authored-by: Nessy --- .gitignore | 2 +- code/{basecode1.1.ips => deltapatch.ips} | Bin 6134530 -> 5492080 bytes code/include/hid.h | 4 + code/include/{ => rnd}/draw.h | 58 +- code/include/rnd/dungeon.h | 56 ++ code/include/rnd/gfx.h | 67 ++ code/include/rnd/icons.h | 289 +++++++++ code/include/rnd/input.h | 26 +- code/include/rnd/savefile.h | 1 + code/include/rnd/settings.h | 15 + code/include/rnd/spoiler_data.h | 71 +- code/mm.ld | 31 +- code/source/asm/hooks.s | 23 + code/source/asm/patches.s | 39 +- code/source/asm/zora_hooks.s | 23 + code/source/asm/zora_patches.s | 10 + code/source/main.cpp | 6 +- code/source/rnd/custom_messages.cpp | 13 +- code/source/rnd/draw.cpp | 363 +++++++++++ code/source/rnd/dungeon.cpp | 125 ++++ code/source/rnd/gfx.cpp | 782 +++++++++++++++++++++++ code/source/rnd/input.cpp | 115 ++-- code/source/rnd/item_effect.cpp | 23 +- code/source/rnd/item_override.cpp | 2 + code/source/rnd/item_table.cpp | 126 ++-- code/source/rnd/link.cpp | 8 + code/source/rnd/savefile.cpp | 18 +- code/source/rnd/spoiler_data.cpp | 56 +- romfs/exheader.bin | Bin 2048 -> 2048 bytes 29 files changed, 2169 insertions(+), 183 deletions(-) rename code/{basecode1.1.ips => deltapatch.ips} (89%) rename code/include/{ => rnd}/draw.h (69%) create mode 100644 code/include/rnd/dungeon.h create mode 100644 code/include/rnd/gfx.h create mode 100644 code/include/rnd/icons.h create mode 100644 code/source/rnd/draw.cpp create mode 100644 code/source/rnd/dungeon.cpp create mode 100644 code/source/rnd/gfx.cpp diff --git a/.gitignore b/.gitignore index 9d261c2a..8a230e82 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,7 @@ oldcode/loader/build *.smdh *.pyc *.ips -!basecode1.1.ips +!deltapatch.ips *.zar source/patch_symbols.hpp .DS_Store \ No newline at end of file diff --git a/code/basecode1.1.ips b/code/deltapatch.ips similarity index 89% rename from code/basecode1.1.ips rename to code/deltapatch.ips index f266c623b1a5a97e3e682ba7f6f54f3d014f5854..64bf2b0e7d5f10b25ef51097c8fa22b5d724bf80 100644 GIT binary patch delta 181 zcmV~$X)=NV007YOl_<(CB(iT2LdlX!$=c!^?$DTb3g<9xVY-T6e)rzz&wudu)em}x z1VX}wMT{5~6*FdBT*8D&Q>M+BHD_MZf<;SGmZhy&m9b{shD}?x?bx+v-+`<{M{SAs<#>g-K9Uh delta 647591 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 diff --git a/code/include/hid.h b/code/include/hid.h index 9e6eb262..01c3ca13 100644 --- a/code/include/hid.h +++ b/code/include/hid.h @@ -111,5 +111,9 @@ typedef struct { #define BUTTON_L1 (1 << 9) #define BUTTON_X (1 << 10) #define BUTTON_Y (1 << 11) +#define CPAD_RIGHT (1 << 28) +#define CPAD_LEFT (1 << 29) +#define CPAD_UP (1 << 30) +#define CPAD_DOWN (1 << 31) #endif // HID_H diff --git a/code/include/draw.h b/code/include/rnd/draw.h similarity index 69% rename from code/include/draw.h rename to code/include/rnd/draw.h index b20ae0bf..bdf4d682 100644 --- a/code/include/draw.h +++ b/code/include/rnd/draw.h @@ -25,8 +25,10 @@ * or requiring that modified versions of such material be marked in * reasonable ways as different from the original version. */ +extern "C" { #include <3ds/gfx.h> #include <3ds/types.h> +} #define FB_BOTTOM_VRAM_ADDR ((void*)0x1F48F000) // cached #define FB_BOTTOM_VRAM_PA 0x1848F000 @@ -42,22 +44,73 @@ #define SPACING_Y 11 #define SPACING_X 6 +#define SPACING_SMALL_Y 9 +#define SPACING_SMALL_X 6 #define RGB8(r, g, b) (((b)&0xFF) | (((g)&0xFF) << 8) | (((r)&0xFF) << 16)) #define COLOR_TITLE RGB8(0x33, 0x33, 0xFF) #define COLOR_WHITE RGB8(0xFF, 0xFF, 0xFF) #define COLOR_RED RGB8(0xFF, 0x00, 0x00) #define COLOR_GREEN RGB8(0x00, 0xFF, 0x00) +#define COLOR_BLUE RGB8(0x56, 0xB4, 0xE9) +#define COLOR_ORANGE RGB8(0xE6, 0x9F, 0x00) +#define COLOR_YELLOW RGB8(0xF0, 0xE4, 0x42) +#define COLOR_PINK RGB8(0xCC, 0x79, 0xA7) #define COLOR_BLACK RGB8(0x00, 0x00, 0x00) +#define COLOR_DARK_GRAY RGB8(0x29, 0x29, 0x29) +#define COLOR_LIGHT_GRAY RGB8(0x71, 0x71, 0x71) #define DRAW_MAX_FORMATTED_STRING_SIZE 512 +#define ICON_WIDTH 8 +#define ICON_HEIGHT 8 + +typedef enum { + DISPLAY_0 = 0x400, + DISPLAY_1 = 0x401, + DISPLAY_BOTH = 0x402, + DISPLAY0_EXT = 0x410, +} Draw_Display; + +typedef enum { + ICON_SMALL_KEY, + ICON_BOSS_KEY, + ICON_TRIFORCE, + ICON_FOOL, + ICON_CHECK, + ICON_NO, + ICON_VANILLA, + ICON_MASTER_QUEST, + ICON_MAP, + ICON_COMPASS, + ICON_BUTTON_R, + ICON_BUTTON_R_WIDE_1, + ICON_BUTTON_R_WIDE_2, + ICON_BUTTON_L, + ICON_BUTTON_L_WIDE_1, + ICON_BUTTON_L_WIDE_2, + ICON_BUTTON_A, + ICON_BUTTON_B, + ICON_BUTTON_X, + ICON_BUTTON_Y, + ICON_BUTTON_DPAD, + ICON_BUTTON_FACE, + ICON_BUTTON_FACEH, + ICON_BUTTON_FACEV, + ICON_BUTTON_JOYSTICK, + ICONS_COUNT +} Draw_IconType; + void Draw_Lock(void); void Draw_Unlock(void); +void Draw_DrawIcon(u32 posX, u32 posY, u32 color, Draw_IconType icon); +void Draw_DrawRect(u32 posX, u32 posY, u32 width, u32 height, u32 color); void Draw_DrawCharacter(u32 posX, u32 posY, u32 color, char character); u32 Draw_DrawString(u32 posX, u32 posY, u32 color, const char* string); +u32 Draw_DrawString_Small(u32 posX, u32 posY, u32 color, const char* string); u32 Draw_DrawFormattedString(u32 posX, u32 posY, u32 color, const char* fmt, ...); +u32 Draw_DrawFormattedString_Small(u32 posX, u32 posY, u32 color, const char* fmt, ...); void Draw_DrawCharacterTop(u32 posX, u32 posY, u32 color, char character); u32 Draw_DrawStringTop(u32 posX, u32 posY, u32 color, const char* string); @@ -66,6 +119,9 @@ u32 Draw_DrawFormattedStringTop(u32 posX, u32 posY, u32 color, const char* fmt, void Draw_FillFramebuffer(u32 value); void Draw_ClearFramebuffer(void); void Draw_SetupFramebuffer(void); +void Draw_FillBackBuffer(void); +void Draw_ClearBackbuffer(void); +void Draw_CopyBackBuffer(void); void Draw_FlushFramebuffer(void); void Draw_FlushFramebufferTop(void); @@ -97,4 +153,4 @@ struct Graphics { Framebuffer top1; Framebuffer top2; Framebuffer bottom; -}; \ No newline at end of file +}; diff --git a/code/include/rnd/dungeon.h b/code/include/rnd/dungeon.h new file mode 100644 index 00000000..ecd59e5b --- /dev/null +++ b/code/include/rnd/dungeon.h @@ -0,0 +1,56 @@ +#ifndef _RND_DUNGEON_H +#define _RND_DUNGEON_H + +#include +#include "rnd/savefile.h" +#include "rnd/settings.h" +#include "rnd/spoiler_data.h" +//#include "rnd/draw.h" +extern "C" { +#include <3ds/svc.h> +} + +#define WOODFALL_KEY_COUNT 1 +#define SNOWHEAD_KEY_COUNT 3 +#define GREAT_KEY_COUNT 1 +#define STONE_KEY_COUNT 4 + +namespace rnd { + + typedef struct { + u16 spoilerIndex; + u8 keyAmount; + } KeyData; + + typedef enum { + DUNGEON_WOODFALL = 0, + DUNGEON_SNOWHEAD, + DUNGEON_GREAT_BAY, + DUNGEON_STONE_TOWER, + DUNGEON_PIRATE_FORTRESS, + DUNGEON_BENEATH_THE_WELL, + DUNGEON_IKANA_CASTLE, + DUNGEON_SECRET_SHRINE, + DUNGEON_THE_MOON, + DUNGEON_SWAMP_SKULLTULA_HOUSE, + DUNGEON_OCEAN_SKULLTULA_HOUSE, + } DungeonId; + + extern const char DungeonNames[][25]; + + static const char* const smallKeyStringWoodfall = "Woodfall Temple Small Key"; + static const char* const smallKeyStringSnowhead = "Snowhead Temple Small Key"; + static const char* const smallKeyStringGreatBay = "Great Bay Temple Small Key"; + static const char* const smallKeyStringStone = "Stone Tower Temple Small Key"; + + static const char* const keyRingStringWoodfall = "Woodfall Temple Key Ring"; + static const char* const keyRingStringSnowhead = "Snowhead Temple Key Ring"; + static const char* const keyRingStringGreatBay = "Great Bay Temple Key Ring"; + static const char* const keyRingStringStone = "Stone Tower Temple Key Ring"; + + u8 Dungeon_KeyAmount(u32); + u8 Dungeon_FoundSmallKeys(u32); + +} // namespace rnd + +#endif \ No newline at end of file diff --git a/code/include/rnd/gfx.h b/code/include/rnd/gfx.h new file mode 100644 index 00000000..66f0b1a6 --- /dev/null +++ b/code/include/rnd/gfx.h @@ -0,0 +1,67 @@ +#ifndef _RND_GFX_H_ +#define _RND_GFX_H_ + +#include "common/types.h" +#include "hid.h" +#include "rnd/custom_models.h" +#include "rnd/draw.h" +#include "rnd/dungeon.h" +#include "rnd/input.h" +#include "rnd/rheap.h" +#include "rnd/savefile.h" +#include "rnd/settings.h" +#include "rnd/spoiler_data.h" +#include "rnd/title_screen.h" +extern "C" { +#include <3ds/svc.h> +} + +namespace rnd { +#define TICKS_PER_SEC 268123480 +#define MAX_TICK_DELTA (TICKS_PER_SEC * 3) + +#define UP_ARROW_CHR 24 +#define DOWN_ARROW_CHR 25 +#define LEFT_ARROW_CHR 27 +#define RIGHT_ARROW_CHR 26 +#define H_DOUBLE_ARROW_CHR 29 +#define UP_SOLID_ARROW_CHR 30 +#define DOWN_SOLID_ARROW_CHR 31 + +#define MAX_ENTRY_LINES 9 +#define SCROLL_BAR_THICKNESS 2 +#define SCROLL_BAR_MIN_THUMB_SIZE 4 +#define COLOR_WARN RGB8(0xD1, 0xDF, 0x3C) +#define COLOR_SCROLL_BAR_BG RGB8(0x58, 0x58, 0x58) + +#define COLOR_ICON_BOSS_KEY RGB8(0x20, 0xF9, 0x25) +#define COLOR_ICON_MAP RGB8(0xF9, 0x97, 0xFF) +#define COLOR_ICON_COMPASS RGB8(0x20, 0x3A, 0xF9) +#define COLOR_ICON_WOTH RGB8(0xFF, 0xF8, 0x2D) +#define COLOR_ICON_FOOL RGB8(0xFF, 0x2D, 0x4B) + +#define COLOR_BUTTON_A RGB8(0xFF, 0x49, 0x3E) +#define COLOR_BUTTON_B RGB8(0xFD, 0xDD, 0x68) +#define COLOR_BUTTON_X RGB8(0x32, 0x7D, 0xFE) +#define COLOR_BUTTON_Y RGB8(0x00, 0xD0, 0x98) + + typedef enum { + PAGE_SEEDHASH, + PAGE_DUNGEONITEMS, + PAGE_SPHERES, + PAGE_ITEMTRACKER_ALL, + PAGE_ITEMTRACKER_GROUPS, + PAGE_ENTRANCETRACKER_ALL, + PAGE_ENTRANCETRACKER_GROUPS, + PAGE_OPTIONS, + } GfxPage; + + void Gfx_Init(void); + static u8 openingButton(); + extern "C" void Gfx_Update(); + extern "C" void Gfx_SleepQueryCallback(); + extern "C" void Gfx_AwakeCallback(); + +} // namespace rnd + +#endif //_RND_GFX_H_ \ No newline at end of file diff --git a/code/include/rnd/icons.h b/code/include/rnd/icons.h new file mode 100644 index 00000000..c10b236c --- /dev/null +++ b/code/include/rnd/icons.h @@ -0,0 +1,289 @@ +#ifndef _RND_ICONS_H_ +#define _RND_ICONS_H_ +/** + * @file icons.h + * @author Giometric (https://github.com/Giometric) + * @brief + * @date 2023-10-25 + * + * Brought in from the OoT3DR libraries. + */ +#define ICON_WIDTH 8 +#define ICON_HEIGHT 8 + +static const unsigned char rIcons[][ICON_HEIGHT] = {{ + /* Small Key */ + 0x18, /* 00011000 */ + 0x24, /* 00100100 */ + 0x24, /* 00100100 */ + 0x18, /* 00011000 */ + 0x08, /* 00001000 */ + 0x18, /* 00011000 */ + 0x08, /* 00001000 */ + 0x18, /* 00011000 */ + }, + { + /* Boss Key */ + 0xDB, /* 11011011 */ + 0x66, /* 01100110 */ + 0x66, /* 01100110 */ + 0x3C, /* 00111100 */ + 0x08, /* 00001000 */ + 0x38, /* 00111000 */ + 0x08, /* 00001000 */ + 0x38, /* 00111000 */ + }, + { + /* Triforce */ + 0x00, /* 00000000 */ + 0x08, /* 00001000 */ + 0x1C, /* 00011100 */ + 0x1C, /* 00011100 */ + 0x22, /* 00100010 */ + 0x77, /* 01110111 */ + 0x7F, /* 01111111 */ + 0x00, /* 00000000 */ + }, + { + /* Foolforce */ + 0x00, /* 00000000 */ + 0x7F, /* 01111111 */ + 0x77, /* 01110111 */ + 0x22, /* 00100010 */ + 0x1C, /* 00011100 */ + 0x1C, /* 00011100 */ + 0x08, /* 00001000 */ + 0x00, /* 00000000 */ + }, + { + /* Check */ + 0x01, /* 00000001 */ + 0x03, /* 00000011 */ + 0x06, /* 00000110 */ + 0x0C, /* 00001100 */ + 0x1B, /* 11011000 */ + 0x70, /* 01110000 */ + 0x20, /* 00100000 */ + 0x00, /* 00000000 */ + }, + { + /* No */ + 0x1C, /* 00011100 */ + 0x22, /* 00100010 */ + 0x45, /* 01000101 */ + 0x49, /* 01001001 */ + 0x51, /* 01010001 */ + 0x22, /* 00100010 */ + 0x1C, /* 00011100 */ + 0x00, /* 00000000 */ + }, + { + /* Vanilla */ + 0xC6, /* 11000110 */ + 0xC6, /* 11000110 */ + 0xC6, /* 11000110 */ + 0xC6, /* 11000110 */ + 0x6C, /* 01101100 */ + 0x38, /* 00111000 */ + 0x10, /* 00010000 */ + 0x00, /* 00000000 */ + }, + { + /* MasterQuest */ + 0x88, /* 10001000 */ + 0xD8, /* 11011000 */ + 0xA8, /* 10101000 */ + 0x88, /* 10001000 */ + 0x8C, /* 10001100 */ + 0x12, /* 00010010 */ + 0x16, /* 00010110 */ + 0x0F, /* 00001111 */ + }, + { + /* Map */ + 0x0E, /* 00001110 */ + 0x5F, /* 01011111 */ + 0xD3, /* 11010011 */ + 0xDF, /* 11011111 */ + 0xD9, /* 11011001 */ + 0xDF, /* 11011111 */ + 0xD1, /* 11010001 */ + 0x60, /* 01100000 */ + }, + { + /* Compass */ + 0x1C, /* 00011100 */ + 0x22, /* 00100010 */ + 0x49, /* 01001001 */ + 0x4D, /* 01001101 */ + 0x63, /* 01100011 */ + 0x3E, /* 00111110 */ + 0x1C, /* 00011100 */ + 0x00, /* 00000000 */ + }, + { + /* Button_R */ + 0x00, /* 00000000 */ + 0xfc, /* 11111100 */ + 0xce, /* 11001110 */ + 0xd7, /* 11010111 */ + 0xcf, /* 11001111 */ + 0xd7, /* 11010111 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + }, + { + /* Button_R_Wide_1 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xfc, /* 11111100 */ + 0xfd, /* 11111101 */ + 0xfc, /* 11111100 */ + 0xfd, /* 11111101 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + }, + { + /* Button_R_Wide_2 */ + 0xe0, /* 11100000 */ + 0xf8, /* 11111000 */ + 0xfc, /* 11111100 */ + 0x7e, /* 01111110 */ + 0xfe, /* 11111110 */ + 0x7f, /* 01111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + }, + { + /* Button_L */ + 0x00, /* 00000000 */ + 0x3f, /* 00111111 */ + 0x6f, /* 01101111 */ + 0xef, /* 11101111 */ + 0xef, /* 11101111 */ + 0xe3, /* 11100011 */ + 0xff, /* 11111111 */ + 0x00, /* 00000000 */ + }, + { + /* Button_L_Wide_1 */ + 0x07, /* 00000111 */ + 0x1f, /* 00011111 */ + 0x3e, /* 00111110 */ + 0x7e, /* 01111110 */ + 0x7e, /* 01111110 */ + 0xfe, /* 11111110 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + }, + { + /* Button_L_Wide_2 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + 0x3f, /* 00111111 */ + 0xff, /* 11111111 */ + 0xff, /* 11111111 */ + }, + { + /* Button_A */ + 0x38, /* 00111000 */ + 0x6c, /* 01101100 */ + 0xd6, /* 11010110 */ + 0xc6, /* 11000110 */ + 0xd6, /* 11010110 */ + 0x54, /* 01010100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + }, + { + /* Button_B */ + 0x38, /* 00111000 */ + 0x4c, /* 01001100 */ + 0xd6, /* 11010110 */ + 0xce, /* 11001110 */ + 0xd6, /* 11010110 */ + 0x4c, /* 01001100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + }, + { + /* Button_X */ + 0x38, /* 00111000 */ + 0x54, /* 01010100 */ + 0xd6, /* 11010110 */ + 0xee, /* 11101110 */ + 0xd6, /* 11010110 */ + 0x54, /* 01010100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + }, + { + /* Button_Y */ + 0x38, /* 00111000 */ + 0x54, /* 01010100 */ + 0xd6, /* 11010110 */ + 0xee, /* 11101110 */ + 0xee, /* 11101110 */ + 0x6c, /* 01101100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + }, + { + /* Button_DPad */ + 0x38, /* 00111000 */ + 0x38, /* 00111000 */ + 0xd6, /* 11010110 */ + 0xee, /* 11101110 */ + 0xd6, /* 11010110 */ + 0x38, /* 00111000 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + }, + { + /* Button_Face */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x06, /* 00000110 */ + 0xc6, /* 11000110 */ + 0xc0, /* 11000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + }, + { + /* Button_FaceH */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x06, /* 00000110 */ + 0xc6, /* 11000110 */ + 0xc0, /* 11000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + }, + { + /* Button_FaceV */ + 0x30, /* 00110000 */ + 0x30, /* 00110000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x00, /* 00000000 */ + 0x18, /* 00011000 */ + 0x18, /* 00011000 */ + 0x00, /* 00000000 */ + }, + { + /* Button_Joystick */ + 0x38, /* 00111000 */ + 0x44, /* 01000100 */ + 0xa2, /* 10100010 */ + 0xa2, /* 10100010 */ + 0xba, /* 10111010 */ + 0x44, /* 01000100 */ + 0x38, /* 00111000 */ + 0x00, /* 00000000 */ + }}; +#endif \ No newline at end of file diff --git a/code/include/rnd/input.h b/code/include/rnd/input.h index a3eed1ae..efe79fa3 100644 --- a/code/include/rnd/input.h +++ b/code/include/rnd/input.h @@ -4,21 +4,23 @@ #include "hid.h" #include "z3d/z3DVec.h" -typedef struct { - btn_t cur; - btn_t up; - btn_t pressed; - btn_t old; -} InputContext; +namespace rnd { + typedef struct { + btn_t cur; + btn_t up; + btn_t pressed; + btn_t old; + } InputContext; -void Input_Update(void); -u32 Input_WaitWithTimeout(u32 msec, u32 closingButton); -u32 Input_Wait(void); + void Input_Update(void); + u32 Input_WaitWithTimeout(u32 msec, u32 closingButton); + u32 Input_Wait(void); // Use the shared game HID. -#define real_hid_addr 0x007b2d34 -#define real_hid (*(hid_mem_t*)real_hid_addr) +#define real_hid_addr 0x007B2D34 +#define real_hid (*(hid_mem_t**)real_hid_addr) -extern InputContext rInputCtx; + extern "C" InputContext rInputCtx; +} // namespace rnd #endif \ No newline at end of file diff --git a/code/include/rnd/savefile.h b/code/include/rnd/savefile.h index b897ea08..106a1b6f 100644 --- a/code/include/rnd/savefile.h +++ b/code/include/rnd/savefile.h @@ -27,6 +27,7 @@ namespace rnd { bool SaveFile_IsValidSettingsHealth(void); void SaveFile_InitExtSaveData(u32 fileBaseIndex); void SaveFile_LoadExtSaveData(u32 saveNumber); + u8 SaveFile_GetIsSceneDiscovered(u8 sceneNum); extern "C" void SaveFile_SaveExtSaveData(); typedef struct { diff --git a/code/include/rnd/settings.h b/code/include/rnd/settings.h index 48bc8463..4403dc75 100644 --- a/code/include/rnd/settings.h +++ b/code/include/rnd/settings.h @@ -234,9 +234,23 @@ namespace rnd { SHUFFLEKOKIRISWORD_VANILLA, SHUFFLEKOKIRISWROD_ANYWHERE, }; + + typedef enum { + DUNGEON_NEITHER, + DUNGEON_BARREN, + DUNGEON_WOTH, + } DungeonInfo; + + typedef enum { + PLAY_ON_CONSOLE, + PLAY_ON_CITRA, + } PlayOption; + typedef struct { u8 hashIndexes[5]; + u8 playOption; + u8 logic; u8 locationsReachable; @@ -275,6 +289,7 @@ namespace rnd { u8 gossipStoneHints; u8 chestAnimations; u8 chestSize; + u8 compassesShowWotH; u8 generateSpoilerLog; u8 ingameSpoilers; u8 menuOpeningButton; diff --git a/code/include/rnd/spoiler_data.h b/code/include/rnd/spoiler_data.h index eb6caeec..546ebc85 100644 --- a/code/include/rnd/spoiler_data.h +++ b/code/include/rnd/spoiler_data.h @@ -1,8 +1,11 @@ #ifndef _RND_SPOILER_DATA_H_ #define _RND_SPOILER_DATA_H_ +#include "rnd/item_override.h" +#include "rnd/savefile.h" #include "z3d/z3DVec.h" +#define SPOILER_LOCDATS 2 #define SPOILER_SPHERES_MAX 50 #define SPOILER_ITEMS_MAX 512 #define SPOILER_STRING_DATA_SIZE 16384 @@ -29,36 +32,31 @@ namespace rnd { // Location groups for checks, used to group the checks by logical location typedef enum { GROUP_NO_GROUP, - GROUP_STARTING_ITEM, - GROUP_DEKU_PALACE, - GROUP_EAST_CLOCK_TOWN, - GROUP_DUNGEON_WOODFALL_TEMPLE, - GROUP_DUNGEON_SNOWHEAD_TEMPLE, - GROUP_E_CLOCK_TOWN, - GROUP_GORON_VILLAGE, - GROUP_GREAT_BAY_COAST, - GROUP_IKANA_CANYON, - GROUP_IKANA_GRAVEYARD, - GROUP_LAUNDRY_POOL, - GROUP_MILK_ROAD, - GROUP_MOUNTAIN_VILLAGE, - GROUP_N_CLOCK_TOWN, - GROUP_PATH_SNOWHEAD, - GROUP_PINNACLE_ROCK, - GROUP_ROAD_IKANA, - GROUP_ROAD_SWAMP, - GROUP_ROMANI_RANCH, GROUP_S_CLOCK_TOWN, - GROUP_SNOWHEAD, - GROUP_SOUTHERN_SWAMP, + GROUP_LAUNDRY_POOL, + GROUP_E_CLOCK_TOWN, GROUP_STOCKPOTINN, - GROUP_STONE_TOWER, - GROUP_TERMINA_FIELD, - GROUP_TWIN_ISLANDS, GROUP_W_CLOCK_TOWN, + GROUP_N_CLOCK_TOWN, + GROUP_TERMINA_FIELD, + GROUP_SOUTHERN_SWAMP, + GROUP_DEKU_PALACE, GROUP_WOODFALL, + GROUP_SNOWHEAD, + GROUP_MOUNTAIN_VILLAGE, + GROUP_TWIN_ISLANDS, + GROUP_GORON_VILLAGE, + GROUP_MILK_ROAD, + GROUP_ROMANI_RANCH, + GROUP_GREAT_BAY_COAST, + GROUP_PINNACLE_ROCK, GROUP_ZORA_CAPE, GROUP_ZORA_HALL, + GROUP_IKANA_CANYON, + GROUP_IKANA_GRAVEYARD, + GROUP_STONE_TOWER, + GROUP_DUNGEON_WOODFALL_TEMPLE, + GROUP_DUNGEON_SNOWHEAD_TEMPLE, GROUP_DUNGEON_GREAT_BAY, GROUP_DUNGEON_STONE_TOWER, GROUP_DUNGEON_PIRATE_FORTRESS, @@ -74,6 +72,18 @@ namespace rnd { // Grottos are all 0x3E } SpoilerCollectionCheckGroup; + typedef enum { + COLLECTTYPE_NORMAL, + COLLECTTYPE_REPEATABLE, + COLLECTTYPE_NEVER, + } SpoilerItemCollectType; + + typedef enum { + REVEALTYPE_NORMAL, + REVEALTYPE_SCENE, + REVEALTYPE_ALWAYS, + } SpoilerItemRevealType; + typedef struct { u16 LocationStrOffset; u16 ItemStrOffset; @@ -81,6 +91,9 @@ namespace rnd { u8 LocationScene; u8 LocationFlag; SpoilerCollectionCheckGroup Group; + SpoilerItemCollectType CollectType; + SpoilerItemRevealType RevealType; + ItemOverride_Type OverrideType; } SpoilerItemLocation; typedef struct { @@ -99,8 +112,16 @@ namespace rnd { u16 GroupOffsets[SPOILER_COLLECTION_GROUP_COUNT]; } SpoilerData; + typedef struct { + SpoilerItemLocation ItemLocations[SPOILER_ITEMS_MAX]; + char StringData[SPOILER_STRING_DATA_SIZE]; + } SpoilerDataLocs; + extern "C" SpoilerData gSpoilerData; + SpoilerItemLocation* SpoilerData_ItemLoc(u16 itemIndex); + char* SpoilerData_StringData(u16 itemIndex); + char* SpoilerData_GetItemLocationString(u16 itemIndex); char* SpoilerData_GetItemNameString(u16 itemIndex); SpoilerItemLocation GetSpoilerItemLocation(u8 sphere, u16 itemIndex); @@ -115,6 +136,8 @@ namespace rnd { u8 SpoilerData_ScrubCheck(SpoilerItemLocation itemLoc); u8 SpoilerData_ShopItemCheck(SpoilerItemLocation itemLoc); u8 SpoilerData_MagicBeansCheck(SpoilerItemLocation itemLoc); + u8 SpoilerData_GetIsItemLocationRevealed(u16 itemIndex); + u8 SpoilerLog_UpdateIngameLog(ItemOverride_Type type, u8 scene, u8 flag); } // namespace rnd #endif // _RND_SPOILER_DATA_H_ \ No newline at end of file diff --git a/code/mm.ld b/code/mm.ld index 30f93b94..3d115cd0 100644 --- a/code/mm.ld +++ b/code/mm.ld @@ -25,6 +25,18 @@ SECTIONS{ *(.patch_DecoupleStartSelect) } + .patch_AwakeCallback 0x124DE8 : { + *(.patch_AwakeCallback) + } + + .patch_SleepQueryCallback 0x124DF0 : { + *(.patch_SleepQueryCallback) + } + + .patch_Gfx_Update 0x1376DC : { + *(.patch_Gfx_Update) + } + .patch_RemoveSOHCutesceneAfterMessage 0x186810 : { *(.patch_RemoveSOHCutesceneAfterMessage) } @@ -55,6 +67,14 @@ SECTIONS{ *(.patch_DoNotRemoveKeys) } + .patch_DoNotGiveSwordBackOnReset 0x1C98FC : { + *(.patch_DoNotGiveSwordBackOnReset) + } + + .patch_RemoveItemBUsabilityOnReset 0x1C99F4 : { + *(.patch_RemoveItemBUsabilityOnReset) + } + .patch_RemoveDekuMaskCheckSoT 0x1D8008 : { *(.patch_RemoveDekuMaskCheckSoT) } @@ -119,6 +139,14 @@ SECTIONS{ *(.patch_ThirdZoraSwimCheck) } + .patch_CheckMagicForZoraFastSwim 0x1FFDE8 : { + *(.patch_CheckMagicForZoraFastSwim) + } + + .patch_ZoraBarrierCheckMagicAcquired 0x207230 : { + *(.patch_ZoraBarrierCheckMagicAcquired) + } + .patch_ChangeTriggerAandRToA 0x220EFC : { *(.patch_ChangeTriggerAandRToA) } @@ -316,7 +344,7 @@ SECTIONS{ } .patch_OverrideBankerWalletReward 0x459044 : { - *(.patch_OverrideProgessiveWallet) + *(.patch_OverrideProgessiveWalletTwo) } .patch_CouplesMaskGiveItem 0x46E228 : { @@ -396,7 +424,6 @@ SECTIONS{ *(.data) *(.bss) *(COMMON) - rCustomMessages = . ; __text_end = . ; } } \ No newline at end of file diff --git a/code/source/asm/hooks.s b/code/source/asm/hooks.s index 0e4d9892..e8c22dc6 100644 --- a/code/source/asm/hooks.s +++ b/code/source/asm/hooks.s @@ -32,6 +32,29 @@ hook_MainLoop: ldr r1, [r0,#0x138] b 0x0106770 +.global hook_Gfx_Update +hook_Gfx_Update: + push {r0-r12, lr} + bl Gfx_Update + pop {r0-r12, lr} + pop {r4-r8, pc} + +.global hook_Gfx_AwakeCallback +hook_Gfx_AwakeCallback: + push {r0-r12, lr} + bl Gfx_AwakeCallback + pop {r0-r12, lr} + add r0,r0,#0xC + b 0x124DEC + +.global hook_Gfx_SleepQueryCallback +hook_Gfx_SleepQueryCallback: + push {r0-r12, lr} + bl Gfx_SleepQueryCallback + pop {r0-r12, lr} + add r0,r0,#0xC + b 0x124DF4 + .global hook_OverrideCutsceneNextEntrance hook_OverrideCutsceneNextEntrance: push {r0-r12, lr} diff --git a/code/source/asm/patches.s b/code/source/asm/patches.s index 4cb67eab..aa224c6a 100644 --- a/code/source/asm/patches.s +++ b/code/source/asm/patches.s @@ -39,6 +39,21 @@ patch_MainLoop: patch_DecoupleStartSelect: nop +.section .patch_AwakeCallback +.global AwakeCallback_patch +AwakeCallback_patch: + b hook_Gfx_AwakeCallback + +.section .patch_SleepQueryCallback +.global SleepQueryCallback_patch +SleepQueryCallback_patch: + b hook_Gfx_SleepQueryCallback + +.section .patch_Gfx_Update +.global Gfx_Update_patch +Gfx_Update_patch: + b hook_Gfx_Update + @ This should remove the overwriting message for when the @ user receives the Zora Mask. @ Largely untested, need to check for any UB. @@ -52,6 +67,7 @@ patch_RemoveSOHCutesceneAfterMessage: patch_OverrideBombersNotebook: b hook_OverrideHMSBombers + .section .patch_OverrideCutsceneNextEntrance .global patch_OverrideCutsceneNextEntrance patch_OverrideCutsceneNextEntrance: @@ -93,6 +109,21 @@ patch_DoNotRemoveKeys: nop nop +@ NOP out the bit of code that checks your sword and gives it back if it +@ is not a razor sword. This should prevent us from ever getting Kokiri sword on +@ cycle reset. +.section .patch_DoNotGiveSwordBackOnReset +.global patch_DoNotGiveSwordBackOnReset +patch_DoNotGiveSwordBackOnReset: + nop + nop + nop + +.section .patch_RemoveItemBUsabilityOnReset +.global patch_RemoveItemBUsabilityOnReset +patch_RemoveItemBUsabilityOnReset: + nop + .section .patch_RemoveDekuMaskCheckSoT .global patch_RemoveDekuMaskCheckSoT patch_RemoveDekuMaskCheckSoT: @@ -291,6 +322,12 @@ patch_OverrideProgessiveWallet: @Override to use the progressive wallet instead. mov r2,#0x48 +.section .patch_OverrideProgessiveWalletTwo +.global patch_OverrideProgessiveWalletTwo +patch_OverrideProgessiveWalletTwo: +@Override to use the progressive wallet instead. + mov r2,#0x48 + .section .patch_CheckMoTRequirement .global patch_CheckMoTRequirement patch_CheckMoTRequirement: @@ -339,4 +376,4 @@ patch_RemoveCouplesMaskMessage: .section .patch_loader .global loader_patch loader_patch: - b hook_into_loader + b hook_into_loader \ No newline at end of file diff --git a/code/source/asm/zora_hooks.s b/code/source/asm/zora_hooks.s index 4048d601..01e712ca 100644 --- a/code/source/asm/zora_hooks.s +++ b/code/source/asm/zora_hooks.s @@ -2,6 +2,7 @@ @ https://github.com/leoetlino/project-restoration/blob/181ecbf6e806fc10c8d1f8b2d74489b0bd7f5e67/hooks/rst_zora_swim.hks .arm .text +.syntax unified .global hook_ZoraInWaterFastSwim hook_ZoraInWaterFastSwim: @@ -61,6 +62,28 @@ doThirdFastSwim: bl runZoraPatch b 0x1FFDC0 +.global hook_CheckMagicForZoraFastSwim +hook_CheckMagicForZoraFastSwim: + beq 0x200010 + push {r0} + bl CheckIfMagicAcquired + cmp r0, #0x0 + pop {r0} + beq 0x200010 + @cmp r0,#0x0 + b 0x1FFDEC + +.global hook_ZoraBarrierCheckMagicAcquired +hook_ZoraBarrierCheckMagicAcquired: + beq 0x2072B4 + push {r0} + bl CheckIfMagicAcquired + cmp r0, #0x0 + pop {r0} + beq 0x2072B4 + cmp r1, #0x0 + b 0x207234 + .global hook_ChangeTriggerAandRToA hook_ChangeTriggerAandRToA: push {r0-r12, lr} diff --git a/code/source/asm/zora_patches.s b/code/source/asm/zora_patches.s index 4de2fe36..79d96cc8 100644 --- a/code/source/asm/zora_patches.s +++ b/code/source/asm/zora_patches.s @@ -20,6 +20,16 @@ patch_UseZoraASwimSecond: patch_ThirdZoraSwimCheck: bl hook_ThirdZoraSwimCheck +.section .patch_CheckMagicForZoraFastSwim +.global patch_CheckMagicForZoraFastSwim +patch_CheckMagicForZoraFastSwim: + bl hook_CheckMagicForZoraFastSwim + +.section .patch_ZoraBarrierCheckMagicAcquired +.global patch_ZoraBarrierCheckMagicAcquired +patch_ZoraCheckMagicAcquired: + bl hook_ZoraBarrierCheckMagicAcquired + .section .patch_ChangeTriggerAandRToA .global patch_ChangeTriggerAandRToA patch_ChangeTriggerAandRToA: diff --git a/code/source/main.cpp b/code/source/main.cpp index a3139f43..17eba190 100644 --- a/code/source/main.cpp +++ b/code/source/main.cpp @@ -8,6 +8,7 @@ #include "game/ui.h" #include "rnd/extdata.h" #include "rnd/icetrap.h" +#include "rnd/input.h" #include "rnd/item_override.h" #include "rnd/link.h" #include "rnd/rheap.h" @@ -73,7 +74,7 @@ namespace rnd { #endif context.gctx = static_cast(state); - + Input_Update(); if (context.gctx->GetPlayerActor()) { ItemOverride_Update(); link::HandleFastOcarina(context.gctx); @@ -112,7 +113,8 @@ namespace rnd { game::ui::OpenScreen(game::ui::ScreenType::Map); gctx->pad_state.input.buttons.Clear(game::pad::Button::Select); gctx->pad_state.input.new_buttons.Clear(game::pad::Button::Select); - } else if (newButtons == (u32)game::pad::Button::Select || newButtons == (u32)game::pad::Button::Start) { + } else if ((gSettingsContext.customIngameSpoilerButton != 4 && newButtons == (u32)game::pad::Button::Select) || + (gSettingsContext.customIngameSpoilerButton != 8 && newButtons == (u32)game::pad::Button::Start)) { if (game::GetCommonData().save.inventory.collect_register.bombers_notebook != 0) game::ui::OpenScreen(game::ui::ScreenType::Schedule); else diff --git a/code/source/rnd/custom_messages.cpp b/code/source/rnd/custom_messages.cpp index e00a80e8..26ed0a9b 100644 --- a/code/source/rnd/custom_messages.cpp +++ b/code/source/rnd/custom_messages.cpp @@ -194,6 +194,10 @@ class MsgBuilder { if (msg.sfxAndFlags & INSTANT_FLAG) instant(); + // Tingle Map Choices. Add 3 choices, 2 for maps and 1 for no thanks. + if (msg.id >= 0x1D11 && msg.id <= 0x1D16) + addCom(0x2F, 3); + while (++idx < MAX_UNFORMATTED_SIZE && msg.text[idx]) { resolvedChar = msg.text[idx]; @@ -357,14 +361,17 @@ class MsgBuilder { if (inCol) rnd::util::Print("Warning formatting message, colour not closed: %s\n", msg.text); #endif - end(); + if (msg.id >= 0x1D11 && msg.id <= 0x1D16) + addCom(0x00); + else + end(); } }; MsgBuilder builder; CustomMessage customMsg; -volatile const UnformattedMessage* ptrCustomMessageEntries = {0}; +volatile const UnformattedMessage rCustomMessages[512] = {0}; volatile const u32 numCustomMessageEntries = {0}; bool SetCustomMessage(u16 id, game::MessageResEntry* msgResEntry) { @@ -381,7 +388,7 @@ bool SetCustomMessage(u16 id, game::MessageResEntry* msgResEntry) { while (start <= end) { current = (start + end) / 2; // Compiler isn't happy with assigning volatile const to not so reference/dereference to get data - customMsgData = *(UnformattedMessage*)&ptrCustomMessageEntries[current]; + customMsgData = *(UnformattedMessage*)&rCustomMessages[current]; if (customMsgData.id < id) start = current + 1; else if (customMsgData.id > id) diff --git a/code/source/rnd/draw.cpp b/code/source/rnd/draw.cpp new file mode 100644 index 00000000..5eb9c036 --- /dev/null +++ b/code/source/rnd/draw.cpp @@ -0,0 +1,363 @@ +/* + * This file is a modified part of Luma3DS + * Copyright (C) 2016-2019 Aurora Wright, TuxSH + * Modified 2020 Gamestabled + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional Terms 7.b and 7.c of GPLv3 apply to this file: + * * Requiring preservation of specified reasonable legal notices or + * author attributions in that material or in the Appropriate Legal + * Notices displayed by works containing it. + * * Prohibiting misrepresentation of the origin of that material, + * or requiring that modified versions of such material be marked in + * reasonable ways as different from the original version. + */ + +#include "rnd/draw.h" +#include "rnd/icons.h" +#include "utils.h" + +#include +#include "fonts/ascii_font.h" +#include "fonts/ascii_font_small.h" +#include "rnd/custom_messages.h" + +extern "C" { +#include <3ds/svc.h> +#include <3ds/synchronization.h> +} + +static u8* FRAMEBUFFER[6]; +static u8 backBufferBtm[FB_BOTTOM_SIZE]; +static u32 frontBufferIdx_btm = 0; + +void Draw_PreSwapBuffers(Draw_Display display) { + if (display == DISPLAY_1 || display == DISPLAY_BOTH) { + frontBufferIdx_btm++; + frontBufferIdx_btm %= 2; + } +} + +static RecursiveLock lock; + +void Draw_Lock(void) { + static bool lockInitialized = false; + if (!lockInitialized) { + RecursiveLock_Init(&lock); + lockInitialized = true; + } + + RecursiveLock_Lock(&lock); +} + +void Draw_Unlock(void) { + RecursiveLock_Unlock(&lock); +} + +void Draw_DrawIcon(u32 posX, u32 posY, u32 color, Draw_IconType icon) { + // Skip drawing entirely if we're off-screen + if (posX >= SCREEN_BOT_WIDTH) { + return; + } + if (posY >= SCREEN_BOT_HEIGHT) { + return; + } + + u8 sizeX = ICON_WIDTH; + u8 sizeY = ICON_HEIGHT; + + // Clamp size to screen bounds + if (posX + sizeX > SCREEN_BOT_WIDTH) { + sizeX = SCREEN_BOT_WIDTH - posX; + } + if (posY + sizeY > SCREEN_BOT_HEIGHT) { + sizeY = SCREEN_BOT_HEIGHT - posY; + } + + const u8 sizeXMinusOne = ICON_WIDTH - 1; + + const unsigned char* glyph = rIcons[icon]; + + for (s32 y = 0; y < sizeY; y++) { + const unsigned char glyphRow = glyph[y]; + const u32 screenPosY = (posX * SCREEN_BOT_HEIGHT) + (SCREEN_BOT_HEIGHT - y - posY - 1); + + for (s32 x = 0; x < sizeX; x++) { + const u32 shift = sizeXMinusOne - x; + const u32 screenPos = (screenPosY + x * SCREEN_BOT_HEIGHT) * 3; + const u32 pixelColor = ((glyphRow >> shift) & 1) ? color : COLOR_BLACK; + + backBufferBtm[screenPos] = (pixelColor)&0xFF; + backBufferBtm[screenPos + 1] = (pixelColor >> 8) & 0xFF; + backBufferBtm[screenPos + 2] = (pixelColor >> 16) & 0xFF; + } + } +} + +void Draw_DrawRect(u32 posX, u32 posY, u32 width, u32 height, u32 color) { + // Skip drawing entirely if we're off-screen + if (posX >= SCREEN_BOT_WIDTH) { + return; + } + if (posY >= SCREEN_BOT_HEIGHT) { + return; + } + + // Clamp size to screen bounds + if (posX + width > SCREEN_BOT_WIDTH) { + width = SCREEN_BOT_WIDTH - posX; + } + if (posY + height > SCREEN_BOT_HEIGHT) { + height = SCREEN_BOT_HEIGHT - posY; + } + + for (u32 y = 0; y < height; y++) { + const u32 screenPosY = (posX * SCREEN_BOT_HEIGHT) + (SCREEN_BOT_HEIGHT - y - posY - 1); + for (u32 x = 0; x < width; x++) { + const u32 screenPos = (screenPosY + x * SCREEN_BOT_HEIGHT) * 3; + + backBufferBtm[screenPos] = (color)&0xFF; + backBufferBtm[screenPos + 1] = (color >> 8) & 0xFF; + backBufferBtm[screenPos + 2] = (color >> 16) & 0xFF; + } + } +} + +void Draw_DrawCharacter_Impl(u32 posX, u32 posY, u32 color, char character, const unsigned char* font, u8 sizeX, + u8 sizeY) { + const u32 shiftBase = sizeX - 1 + (8 - sizeX); // If sizeX is smaller than 8, we must shift further left + + for (u32 y = 0; y < sizeY; y++) { + const unsigned char charPos = font[character * sizeY + y]; + const u32 screenPosY = (posX * SCREEN_BOT_HEIGHT) + (SCREEN_BOT_HEIGHT - y - posY - 1); + + for (u32 x = 0; x < sizeX; x++) { + const u32 shift = shiftBase - x; + const u32 screenPos = (screenPosY + x * SCREEN_BOT_HEIGHT) * 3; + const u32 pixelColor = ((charPos >> shift) & 1) ? color : COLOR_BLACK; + + backBufferBtm[screenPos] = (pixelColor)&0xFF; + backBufferBtm[screenPos + 1] = (pixelColor >> 8) & 0xFF; + backBufferBtm[screenPos + 2] = (pixelColor >> 16) & 0xFF; + } + } +} + +void Draw_DrawCharacter(u32 posX, u32 posY, u32 color, char character) { + Draw_DrawCharacter_Impl(posX, posY, color, character, ascii_font, FONT_WIDTH, FONT_HEIGHT); +} + +void Draw_DrawCharacterTop(u32 posX, u32 posY, u32 color, char character) { + volatile u8* const fb2 = (volatile u8* const)FRAMEBUFFER[2]; + volatile u8* const fb3 = (volatile u8* const)FRAMEBUFFER[3]; + volatile u8* const fb4 = (volatile u8* const)FRAMEBUFFER[4]; + volatile u8* const fb5 = (volatile u8* const)FRAMEBUFFER[5]; + + for (u32 y = 0; y < 10; y++) { + const char charPos = ascii_font[character * 10 + y]; + + for (u32 x = 6; x >= 1; x--) { + const u32 screenPos = + (posX * SCREEN_TOP_HEIGHT + (SCREEN_TOP_HEIGHT - y - posY - 1)) + (5 - x) * SCREEN_TOP_HEIGHT; + const u32 pixelColor = ((charPos >> x) & 1) ? color : COLOR_BLACK; + + fb2[screenPos * 3] = (pixelColor)&0xFF; + fb2[screenPos * 3 + 1] = (pixelColor >> 8) & 0xFF; + fb2[screenPos * 3 + 2] = (pixelColor >> 16) & 0xFF; + fb3[screenPos * 3] = (pixelColor)&0xFF; + fb3[screenPos * 3 + 1] = (pixelColor >> 8) & 0xFF; + fb3[screenPos * 3 + 2] = (pixelColor >> 16) & 0xFF; + fb4[screenPos * 3] = (pixelColor)&0xFF; + fb4[screenPos * 3 + 1] = (pixelColor >> 8) & 0xFF; + fb4[screenPos * 3 + 2] = (pixelColor >> 16) & 0xFF; + fb5[screenPos * 3] = (pixelColor)&0xFF; + fb5[screenPos * 3 + 1] = (pixelColor >> 8) & 0xFF; + fb5[screenPos * 3 + 2] = (pixelColor >> 16) & 0xFF; + } + } +} + +u32 Draw_DrawString(u32 posX, u32 posY, u32 color, const char* string) { + for (u32 i = 0, line_i = 0; i < strlen(string); i++) + switch (string[i]) { + case '\n': + posY += SPACING_Y; + line_i = 0; + break; + + case '\t': + line_i += 2; + break; + + default: + // Make sure we never get out of the screen + if (line_i >= ((SCREEN_BOT_WIDTH)-posX) / SPACING_X) { + posY += SPACING_Y; + line_i = 1; // Little offset so we know the same string continues + if (string[i] == ' ') + break; // Spaces at the start look weird + } + + Draw_DrawCharacter_Impl(posX + line_i * SPACING_X, posY, color, string[i], ascii_font, FONT_WIDTH, FONT_HEIGHT); + + line_i++; + break; + } + + return posY; +} + +u32 Draw_DrawString_Small(u32 posX, u32 posY, u32 color, const char* string) { + for (u32 i = 0, line_i = 0; i < strlen(string); i++) + switch (string[i]) { + case '\n': + posY += SPACING_SMALL_Y; + line_i = 0; + break; + + case '\t': + line_i += 2; + break; + + default: + // Make sure we never get out of the screen + if (line_i >= ((SCREEN_BOT_WIDTH)-posX) / SPACING_SMALL_X) { + posY += SPACING_SMALL_Y; + line_i = 1; // Little offset so we know the same string continues + if (string[i] == ' ') + break; // Spaces at the start look weird + } + + Draw_DrawCharacter_Impl(posX + line_i * SPACING_SMALL_X, posY, color, string[i], ascii_font_small, + FONT_WIDTH_SMALL, FONT_HEIGHT_SMALL); + + line_i++; + break; + } + + return posY; +} + +u32 Draw_DrawStringTop(u32 posX, u32 posY, u32 color, const char* string) { + for (u32 i = 0, line_i = 0; i < strlen(string); i++) + switch (string[i]) { + case '\n': + posY += SPACING_Y; + line_i = 0; + break; + + case '\t': + line_i += 2; + break; + + default: + // Make sure we never get out of the screen + if (line_i >= ((SCREEN_TOP_WIDTH)-posX) / SPACING_X) { + posY += SPACING_Y; + line_i = 1; // Little offset so we know the same string continues + if (string[i] == ' ') + break; // Spaces at the start look weird + } + + Draw_DrawCharacterTop(posX + line_i * SPACING_X, posY, color, string[i]); + + line_i++; + break; + } + + return posY; +} + +u32 Draw_DrawFormattedString(u32 posX, u32 posY, u32 color, const char* fmt, ...) { + char buf[DRAW_MAX_FORMATTED_STRING_SIZE + 1]; + va_list args; + va_start(args, fmt); + // vsprintf(buf, fmt, args); + vsnprintf_(buf, DRAW_MAX_FORMATTED_STRING_SIZE, fmt, args); + va_end(args); + + return Draw_DrawString(posX, posY, color, buf); +} + +u32 Draw_DrawFormattedString_Small(u32 posX, u32 posY, u32 color, const char* fmt, ...) { + char buf[DRAW_MAX_FORMATTED_STRING_SIZE + 1]; + va_list args; + va_start(args, fmt); + // vsprintf(buf, fmt, args); + vsnprintf_(buf, DRAW_MAX_FORMATTED_STRING_SIZE, fmt, args); + va_end(args); + + return Draw_DrawString_Small(posX, posY, color, buf); +} + +u32 Draw_DrawFormattedStringTop(u32 posX, u32 posY, u32 color, const char* fmt, ...) { + char buf[DRAW_MAX_FORMATTED_STRING_SIZE + 1]; + va_list args; + va_start(args, fmt); + // vsprintf(buf, fmt, args); + vsnprintf_(buf, DRAW_MAX_FORMATTED_STRING_SIZE, fmt, args); + va_end(args); + + return Draw_DrawStringTop(posX, posY, color, buf); +} + +void Draw_FillFramebuffer(u32 value) { + // Odd little thing - we need to set both bottom buffers or else + // we run into a small issue with the menu not actually drawing. + // memset(FRAMEBUFFER[frontBufferIdx_btm], value, FB_BOTTOM_SIZE); + memset(FRAMEBUFFER[0], value, FB_BOTTOM_SIZE); + memset(FRAMEBUFFER[1], value, FB_BOTTOM_SIZE); +} + +void Draw_ClearFramebuffer(void) { + Draw_FillFramebuffer(0); +} + +void Draw_SetupFramebuffer(void) { + Graphics* graphics = *rnd::util::GetPointer(0x6a3a4c); + FRAMEBUFFER[0] = graphics->bottom.display_buffers[0]; + FRAMEBUFFER[1] = graphics->bottom.display_buffers[1]; + FRAMEBUFFER[2] = graphics->top1.display_buffers[0]; + FRAMEBUFFER[3] = graphics->top1.display_buffers[1]; + FRAMEBUFFER[4] = graphics->top2.display_buffers[0]; + FRAMEBUFFER[5] = graphics->top2.display_buffers[1]; +} + +void Draw_FillBackbuffer(u32 value) { + memset(backBufferBtm, value, FB_BOTTOM_SIZE); +} + +void Draw_ClearBackbuffer(void) { + Draw_FillBackbuffer(0); +} + +void Draw_CopyBackBuffer(void) { + // Odd little thing - we need to set both bottom buffers or else + // we run into a small issue with the menu not actually drawing. + // memcpy(FRAMEBUFFER[frontBufferIdx_btm], backBufferBtm, FB_BOTTOM_SIZE); + memcpy(FRAMEBUFFER[1], backBufferBtm, FB_BOTTOM_SIZE); + memcpy(FRAMEBUFFER[0], backBufferBtm, FB_BOTTOM_SIZE); +} + +void Draw_FlushFramebuffer(void) { + svcFlushProcessDataCache(CUR_PROCESS_HANDLE, u32(FRAMEBUFFER[frontBufferIdx_btm]), FB_BOTTOM_SIZE); +} + +void Draw_FlushFramebufferTop(void) { + svcFlushProcessDataCache(CUR_PROCESS_HANDLE, u32(FRAMEBUFFER[2]), FB_TOP_SIZE); + svcFlushProcessDataCache(CUR_PROCESS_HANDLE, u32(FRAMEBUFFER[3]), FB_TOP_SIZE); + svcFlushProcessDataCache(CUR_PROCESS_HANDLE, u32(FRAMEBUFFER[4]), FB_TOP_SIZE); + svcFlushProcessDataCache(CUR_PROCESS_HANDLE, u32(FRAMEBUFFER[5]), FB_TOP_SIZE); +} \ No newline at end of file diff --git a/code/source/rnd/dungeon.cpp b/code/source/rnd/dungeon.cpp new file mode 100644 index 00000000..263cf24d --- /dev/null +++ b/code/source/rnd/dungeon.cpp @@ -0,0 +1,125 @@ +#include "rnd/dungeon.h" + +namespace rnd { + static u8 keyFinderInit = 0; + static KeyData keyData[DUNGEON_STONE_TOWER + 1][10]; + + const char* spoilerEntranceGroupNames[] = { + "Randomized Entrances", + "Spawns/Warps", + "Clock Town", + "Termina Field", + "Southern Swamp", + "Snowhead", + "Great Bay", + "Ikana", + "Milk Road", + "The Moon", + }; + + const char DungeonNames[][25] = { + "Woodfall Temple", "Snowhead Temple", "Great Bay Temple", "Stone Tower Temple", + "Pirate's Fortress", "Beneath The Well", "Ikana Castle", "Secret Shrine", + "The Moon", "Swamp Skulltula House", "Ocean Skulltula House", + }; + + // Used to compare key strings and item string of spoiler data. + // The regular strcmp won't be enough since items in shops have a colon and price added after. + static u8 strcmp_key(char* str, const char* keyStr) { + for (size_t i = 0; i <= strlen(keyStr); i++) { + if (keyStr[i] == '\0' && (str[i] == '\0' || str[i] == ':')) { + return 1; + } + if (keyStr[i] != str[i]) { + return 0; + } + } + return 0; + } + + static const char* GetKeyName(DungeonId id, u8 keyRing) { + static const char* noStr = ""; + + switch (id) { + case DUNGEON_WOODFALL: + return keyRing ? keyRingStringWoodfall : smallKeyStringWoodfall; + case DUNGEON_SNOWHEAD: + return keyRing ? keyRingStringSnowhead : smallKeyStringSnowhead; + case DUNGEON_GREAT_BAY: + return keyRing ? keyRingStringGreatBay : smallKeyStringGreatBay; + case DUNGEON_STONE_TOWER: + return keyRing ? keyRingStringStone : smallKeyStringStone; + default: + return noStr; + } + } + + u8 Dungeon_KeyAmount(u32 id) { + switch (id) { + case DUNGEON_WOODFALL: + return WOODFALL_KEY_COUNT; + case DUNGEON_SNOWHEAD: + return SNOWHEAD_KEY_COUNT; + case DUNGEON_GREAT_BAY: + return GREAT_KEY_COUNT; + case DUNGEON_STONE_TOWER: + return STONE_KEY_COUNT; + default: + return 0; + } + } + + // Stores the indexes of the gSpoilerData where small keys and key rings are for faster lookup. + static void InitKeyFinder(void) { + if (keyFinderInit) { + return; + } + keyFinderInit = 1; + + for (size_t i = 0; i < ARR_SIZE(keyData); i++) { + for (size_t j = 0; j < ARR_SIZE(keyData[0]); j++) { + keyData[i][j].spoilerIndex = -1; + } + } + + u8 keyDataIndex[DUNGEON_STONE_TOWER + 1] = {0}; + + for (size_t item = 0; item < gSpoilerData.ItemLocationsCount; item++) { + for (u32 dungeonId = DUNGEON_WOODFALL; dungeonId <= DUNGEON_STONE_TOWER; dungeonId++) { + if (dungeonId == DUNGEON_PIRATE_FORTRESS || dungeonId == DUNGEON_BENEATH_THE_WELL || + dungeonId == DUNGEON_IKANA_CASTLE || dungeonId == DUNGEON_SECRET_SHRINE || dungeonId == DUNGEON_THE_MOON || + dungeonId == DUNGEON_SWAMP_SKULLTULA_HOUSE || dungeonId == DUNGEON_OCEAN_SKULLTULA_HOUSE) { + continue; + } + if (strcmp_key(SpoilerData_GetItemNameString(item), GetKeyName(DungeonId(dungeonId), 0))) { + keyData[dungeonId][keyDataIndex[dungeonId]].spoilerIndex = item; + keyData[dungeonId][keyDataIndex[dungeonId]].keyAmount = 1; + keyDataIndex[dungeonId]++; + break; + } else if (strcmp_key(SpoilerData_GetItemNameString(item), GetKeyName(DungeonId(dungeonId), 1))) { + keyData[dungeonId][keyDataIndex[dungeonId]].spoilerIndex = item; + keyData[dungeonId][keyDataIndex[dungeonId]].keyAmount = Dungeon_KeyAmount(dungeonId); + keyDataIndex[dungeonId]++; + break; + } + } + } + } + + u8 Dungeon_FoundSmallKeys(u32 id) { + if (!keyFinderInit) { + InitKeyFinder(); + } + + u8 amount = 0; + for (size_t i = 0; i < ARR_SIZE(keyData[id]); i++) { + if (keyData[id][i].spoilerIndex == -1) { + break; + } + if (SpoilerData_GetIsItemLocationCollected(keyData[id][i].spoilerIndex)) { + amount += keyData[id][i].keyAmount; + } + } + return amount; + } +} // namespace rnd diff --git a/code/source/rnd/gfx.cpp b/code/source/rnd/gfx.cpp new file mode 100644 index 00000000..4e4841c0 --- /dev/null +++ b/code/source/rnd/gfx.cpp @@ -0,0 +1,782 @@ +#include "rnd/gfx.h" + +namespace rnd { + static u8 GfxInit = 0; + static u32 closingButton = 0; + static u8 currentSphere = 0; + static s16 spoilerScroll = 0; + + static s16 allItemsScroll = 0; + static s16 groupItemsScroll = 0; + static s8 currentItemGroup = 1; + + static s32 curMenuIdx = 0; + static bool showingLegend = false; + static u64 lastTick = 0; + static u64 ticksElapsed = 0; + static bool isAsleep = false; + DungeonInfo rDungeonInfoData[10]; + + u32 pressed; + bool handledInput; + const char* spoilerCollectionGroupNames[] = { + "All Item Locations", "South Clock Town", "Laundry Pool", "East Clock Town", "StockPotInn", + "West Clock Town", "North Clock Town", "Termina Field", "Southern Swamp", "Deku Palace", + "Woodfall", "Snowhead", "Mountain Village", "Twin Islands", "Goron Village", + "Milk Road", "Romani Ranch", "Great Bay Coast", "Pinnacle Rock", "Zora Cape", + "Zora Hall", "Ikana Canyon", "Ikana Graveyard", "Stone Tower", "Woodfall Temple", + "Snowhead Temple", "Greay Bay Temple", "Stone Tower Temple", "Pirate Fortress", "Beneath the Well", + "Ikana Castle", "Secret Shrine", "The Moon", "Swamp Skulltula House", "Ocean Skulltula House", + }; + + static s8 spoilerGroupDungeonIds[] = { + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + GROUP_DUNGEON_WOODFALL_TEMPLE, + GROUP_DUNGEON_SNOWHEAD_TEMPLE, + GROUP_DUNGEON_GREAT_BAY, + GROUP_DUNGEON_STONE_TOWER, + GROUP_DUNGEON_PIRATE_FORTRESS, + GROUP_DUNGEON_BENEATH_THE_WELL, + GROUP_DUNGEON_IKANA_CASTLE, + GROUP_DUNGEON_SECRET_SHRINE, + GROUP_DUNGEON_THE_MOON, + GROUP_SWAMP_SKULLTULA_HOUSE, + GROUP_OCEAN_SKULLTULA_HOUSE, + }; + + static bool IsDungeonDiscovered(s8 dungeonId) { + game::SaveData& saveData = game::GetCommonData().save; + if (dungeonId == DUNGEON_THE_MOON) { + return false; + } + + u8 idToModeKnown[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + if (idToModeKnown[dungeonId]) { + return true; + } + + // A dungeon is considered discovered if we've visited the dungeon, have the map, + // Or known bc of settings + bool hasMap = 0; + if (dungeonId == 0) { + hasMap = saveData.inventory.woodfall_dungeon_items.map.Value(); + } else if (dungeonId == 1) { + hasMap = saveData.inventory.snowhead_dungeon_items.map.Value(); + } else if (dungeonId == 2) { + hasMap = saveData.inventory.great_bay_dungeon_items.map.Value(); + } else if (dungeonId == 3) { + hasMap = saveData.inventory.stone_tower_dungeon_items.map.Value(); + } + return (hasMap = 1); // to-do: check overworld map for GB & Ikana areas for other dungeons. & scene check. + } + + static bool CanShowSpoilerGroup(SpoilerCollectionCheckGroup group) { + s8 dungeonId = spoilerGroupDungeonIds[group]; + return dungeonId == -1 || IsDungeonDiscovered(dungeonId); + } + + static void Gfx_DrawScrollBar(u16 barX, u16 barY, u16 barSize, u16 currentScroll, u16 maxScroll, u16 pageSize) { + Draw_DrawRect(barX, barY, SCROLL_BAR_THICKNESS, barSize, COLOR_SCROLL_BAR_BG); + + float thumbSizePercent = pageSize / (float)maxScroll; + if (thumbSizePercent > 1.0f) { + thumbSizePercent = 1.0f; + } + u16 thumbSize = (u16)(thumbSizePercent * barSize); + if (thumbSize < SCROLL_BAR_MIN_THUMB_SIZE) { + thumbSize = SCROLL_BAR_MIN_THUMB_SIZE; + } + float barThumbPosPercent = (float)currentScroll / (float)(maxScroll - pageSize); + u16 barThumbPosY = (u16)(barThumbPosPercent * (barSize - thumbSize)); + Draw_DrawRect(barX, barY + barThumbPosY, SCROLL_BAR_THICKNESS, thumbSize, COLOR_WHITE); + } + + static void NextItemGroup() { + groupItemsScroll = 0; + s8 prevGroup = currentItemGroup; + do { + ++currentItemGroup; + if (currentItemGroup >= SPOILER_COLLECTION_GROUP_COUNT) { + currentItemGroup = 1; + } + } while (gSpoilerData.GroupItemCounts[currentItemGroup] == 0 && currentItemGroup != prevGroup); + } + + static void PrevItemGroup() { + groupItemsScroll = 0; + s8 prevGroup = currentItemGroup; + do { + --currentItemGroup; + if (currentItemGroup < 1) { + currentItemGroup = SPOILER_COLLECTION_GROUP_COUNT - 1; + } + } while (gSpoilerData.GroupItemCounts[currentItemGroup] == 0 && currentItemGroup != prevGroup); + } + + static void Gfx_DrawButtonPrompts(void) { + u32 promptY = SCREEN_BOT_HEIGHT - 16; + u32 textY = promptY - 1; + // Close prompt, always shown + Draw_DrawIcon(SCREEN_BOT_WIDTH - 50, promptY, COLOR_BUTTON_B, ICON_BUTTON_B); + Draw_DrawString(SCREEN_BOT_WIDTH - 38, textY, COLOR_TITLE, "Close"); + + static const u8 buttonSpacing = 12; + u16 offsetX = 10; + const char* nextStr = NULL; + + if (curMenuIdx == PAGE_DUNGEONITEMS) { + Draw_DrawIcon(offsetX, promptY, COLOR_BUTTON_A, ICON_BUTTON_A); + offsetX += buttonSpacing; + Draw_DrawString(offsetX, textY, COLOR_TITLE, "Toggle Legend"); + } else if (curMenuIdx == PAGE_SPHERES) { + Draw_DrawIcon(offsetX, promptY, COLOR_WHITE, ICON_BUTTON_DPAD); + offsetX += buttonSpacing; + Draw_DrawString(offsetX, textY, COLOR_TITLE, "Browse spoiler log"); + } else if (curMenuIdx == PAGE_ITEMTRACKER_ALL || curMenuIdx == PAGE_ITEMTRACKER_GROUPS || + curMenuIdx == PAGE_ENTRANCETRACKER_ALL || curMenuIdx == PAGE_ENTRANCETRACKER_GROUPS) { + Draw_DrawIcon(offsetX, promptY, COLOR_WHITE, ICON_BUTTON_DPAD); + offsetX += buttonSpacing; + nextStr = "Browse entries"; + Draw_DrawString(offsetX, textY, COLOR_TITLE, nextStr); + offsetX += (strlen(nextStr) + 1) * SPACING_X; + if (curMenuIdx == PAGE_ITEMTRACKER_GROUPS || curMenuIdx == PAGE_ENTRANCETRACKER_GROUPS) { + Draw_DrawIcon(offsetX, promptY, COLOR_BUTTON_Y, ICON_BUTTON_Y); + offsetX += 8; + Draw_DrawString(offsetX, textY, COLOR_TITLE, "/"); + offsetX += 8; + Draw_DrawIcon(offsetX, promptY, COLOR_BUTTON_A, ICON_BUTTON_A); + offsetX += buttonSpacing; + nextStr = "Change group"; + Draw_DrawString(offsetX, textY, COLOR_TITLE, nextStr); + offsetX += (strlen(nextStr) + 1) * SPACING_X; + } else if (curMenuIdx == PAGE_ITEMTRACKER_ALL || curMenuIdx == PAGE_ENTRANCETRACKER_ALL) { + Draw_DrawIcon(offsetX, promptY, COLOR_BUTTON_A, ICON_BUTTON_A); + offsetX += buttonSpacing; + nextStr = "Toggle Legend"; + Draw_DrawString(offsetX, textY, COLOR_TITLE, nextStr); + offsetX += (strlen(nextStr) + 1) * SPACING_X; + } + } + } + + static void Gfx_UpdatePlayTime(void) { + u64 currentTick = svcGetSystemTick(); + if (!isAsleep) { + ticksElapsed += currentTick - lastTick; + if (ticksElapsed > MAX_TICK_DELTA) { + // Assume that if more ticks than MAX_TICK_DELTA have passed, it has been a long + // time since we last checked, which means the the system may have been asleep or the home button pressed. + // Reset the timer so we don't artificially inflate the play time. + ticksElapsed = 0; + } else { + while (ticksElapsed >= TICKS_PER_SEC) { + ticksElapsed -= TICKS_PER_SEC; + ++gExtSaveData.playtimeSeconds; + } + } + } + lastTick = currentTick; + } + + static void Gfx_DrawSeedHash(void) { + u8 offsetY = 0; + Draw_DrawFormattedString(10, 16 + (SPACING_Y * offsetY++), COLOR_TITLE, "Seed Hash:"); + for (u32 hashIndex = 0; hashIndex < ARR_SIZE(gSettingsContext.hashIndexes); ++hashIndex) { + Draw_DrawFormattedString(10 + (SPACING_X * 4), 16 + (SPACING_Y * offsetY++), COLOR_WHITE, "%s", + hashIconNames[gSettingsContext.hashIndexes[hashIndex]]); + } + offsetY++; + + Draw_DrawString(10, 16 + (SPACING_Y * offsetY++), COLOR_TITLE, "Play time:"); + u32 hours = gExtSaveData.playtimeSeconds / 3600; + u32 minutes = (gExtSaveData.playtimeSeconds / 60) % 60; + u32 seconds = gExtSaveData.playtimeSeconds % 60; + Draw_DrawFormattedString(10 + (SPACING_X * 4), 16 + (SPACING_Y * offsetY++), COLOR_WHITE, "%02u:%02u:%02u", hours, + minutes, seconds); + offsetY++; + } + + static void Gfx_DrawDungeonItems(void) { + static const u8 spacingY = 13; + game::SaveData& saveData = game::GetCommonData().save; + if (showingLegend) { + u8 offsetY = 0; + + Draw_DrawString(10, 16 + (spacingY * offsetY++), COLOR_TITLE, "Dungeon Items Legend"); + offsetY++; + Draw_DrawIcon(10, 16 + (spacingY * offsetY), COLOR_WHITE, ICON_SMALL_KEY); + Draw_DrawString(24, 16 + (spacingY * offsetY++), COLOR_WHITE, "Small Keys: Have / Found"); + Draw_DrawIcon(10, 16 + (spacingY * offsetY), COLOR_ICON_BOSS_KEY, ICON_BOSS_KEY); + Draw_DrawString(24, 16 + (spacingY * offsetY++), COLOR_WHITE, "Boss Key"); + Draw_DrawIcon(10, 16 + (spacingY * offsetY), COLOR_ICON_MAP, ICON_MAP); + Draw_DrawString(24, 16 + (spacingY * offsetY++), COLOR_WHITE, "Map"); + Draw_DrawIcon(10, 16 + (spacingY * offsetY), COLOR_ICON_COMPASS, ICON_COMPASS); + Draw_DrawString(24, 16 + (spacingY * offsetY++), COLOR_WHITE, "Compass"); + offsetY++; + Draw_DrawIcon(10, 16 + (spacingY * offsetY), COLOR_ICON_WOTH, ICON_TRIFORCE); + Draw_DrawString(24, 16 + (spacingY * offsetY++), COLOR_WHITE, "Way of the Hero"); + Draw_DrawIcon(10, 16 + (spacingY * offsetY), COLOR_ICON_FOOL, ICON_FOOL); + Draw_DrawString(24, 16 + (spacingY * offsetY++), COLOR_WHITE, "Barren Location"); + Draw_DrawString(10, 16 + (spacingY * offsetY), COLOR_WHITE, "-"); + Draw_DrawString(24, 16 + (spacingY * offsetY++), COLOR_WHITE, "Non-WotH / Non-Barren Location"); + return; + } + Draw_DrawString(10, 16, COLOR_TITLE, "Dungeon Items"); + // Draw header icons + Draw_DrawIcon(214, 16, COLOR_WHITE, ICON_SMALL_KEY); + Draw_DrawIcon(240, 16, COLOR_WHITE, ICON_BOSS_KEY); + Draw_DrawIcon(260, 16, COLOR_WHITE, ICON_MAP); + Draw_DrawIcon(280, 16, COLOR_WHITE, ICON_COMPASS); + if (gSettingsContext.compassesShowWotH) { + Draw_DrawIcon(300, 16, COLOR_WHITE, ICON_TRIFORCE); + } + + u8 yPos = 30; + for (u32 dungeonId = 0; dungeonId <= DUNGEON_STONE_TOWER; ++dungeonId) { + bool hasBossKey = 0; + if ((dungeonId = DUNGEON_WOODFALL)) { + hasBossKey = saveData.inventory.woodfall_dungeon_items.boss_key.Value(); + } + if ((dungeonId = DUNGEON_SNOWHEAD)) { + hasBossKey = saveData.inventory.snowhead_dungeon_items.boss_key.Value(); + } + if ((dungeonId = DUNGEON_GREAT_BAY)) { + hasBossKey = saveData.inventory.great_bay_dungeon_items.boss_key.Value(); + } + if ((dungeonId = DUNGEON_STONE_TOWER)) { + hasBossKey = saveData.inventory.stone_tower_dungeon_items.boss_key.Value(); + } + bool hasCompass = 0; + if ((dungeonId = DUNGEON_WOODFALL)) { + hasBossKey = saveData.inventory.woodfall_dungeon_items.compass.Value(); + } + if ((dungeonId = DUNGEON_SNOWHEAD)) { + hasBossKey = saveData.inventory.snowhead_dungeon_items.compass.Value(); + } + if ((dungeonId = DUNGEON_GREAT_BAY)) { + hasBossKey = saveData.inventory.great_bay_dungeon_items.compass.Value(); + } + if ((dungeonId = DUNGEON_STONE_TOWER)) { + hasBossKey = saveData.inventory.stone_tower_dungeon_items.compass.Value(); + } + bool hasMap = 0; + if ((dungeonId = DUNGEON_WOODFALL)) { + hasBossKey = saveData.inventory.woodfall_dungeon_items.map.Value(); + } + if ((dungeonId = DUNGEON_SNOWHEAD)) { + hasBossKey = saveData.inventory.snowhead_dungeon_items.map.Value(); + } + if ((dungeonId = DUNGEON_GREAT_BAY)) { + hasBossKey = saveData.inventory.great_bay_dungeon_items.map.Value(); + } + if ((dungeonId = DUNGEON_STONE_TOWER)) { + hasBossKey = saveData.inventory.stone_tower_dungeon_items.map.Value(); + } + + Draw_DrawString(24, yPos, COLOR_WHITE, DungeonNames[dungeonId]); + + // Small Keys + if (dungeonId <= DUNGEON_STONE_TOWER) { + u8 keysHave = 0; + if ((dungeonId = DUNGEON_WOODFALL)) { + keysHave = saveData.inventory.woodfall_temple_keys; + } + if ((dungeonId = DUNGEON_SNOWHEAD)) { + keysHave = saveData.inventory.snowhead_temple_keys; + } + if ((dungeonId = DUNGEON_GREAT_BAY)) { + keysHave = saveData.inventory.great_bay_temple_keys; + } + if ((dungeonId = DUNGEON_STONE_TOWER)) { + keysHave = saveData.inventory.stone_tower_temple_keys; + } + Draw_DrawFormattedString(208, yPos, keysHave > 0 ? COLOR_WHITE : COLOR_DARK_GRAY, "%d", keysHave); + Draw_DrawString(214, yPos, COLOR_WHITE, "/"); + + u8 keysFound = Dungeon_FoundSmallKeys(dungeonId); + if ((gSettingsContext.keysanity == u8(rnd::KeysanitySetting::KEYSANITY_START_WITH)) && + (dungeonId <= DUNGEON_STONE_TOWER)) { + keysFound += Dungeon_KeyAmount(dungeonId); + } + u32 keysFoundColor = COLOR_WHITE; + if (keysFound >= Dungeon_KeyAmount(dungeonId) || IsDungeonDiscovered(dungeonId)) { + keysFoundColor = COLOR_GREEN; + } else if (keysFound == 0) { + keysFoundColor = COLOR_DARK_GRAY; + } + Draw_DrawFormattedString(220, yPos, keysFoundColor, "%d", keysFound); + } + + // Boss Key + if ((dungeonId <= DUNGEON_STONE_TOWER)) { + Draw_DrawIcon(240, yPos, hasBossKey ? COLOR_ICON_BOSS_KEY : COLOR_DARK_GRAY, ICON_BOSS_KEY); + } + + if (dungeonId >= DUNGEON_PIRATE_FORTRESS) { + // Map and Compassz + Draw_DrawIcon(260, yPos, hasMap ? COLOR_ICON_MAP : COLOR_DARK_GRAY, ICON_MAP); + Draw_DrawIcon(280, yPos, hasCompass ? COLOR_ICON_COMPASS : COLOR_DARK_GRAY, ICON_COMPASS); + + // Way of the Hero + if (gSettingsContext.compassesShowWotH) { + if (hasCompass) { + if (rDungeonInfoData[dungeonId] == DUNGEON_WOTH) { + Draw_DrawIcon(300, yPos, COLOR_ICON_WOTH, ICON_TRIFORCE); + } else if (rDungeonInfoData[dungeonId] == DUNGEON_BARREN) { + Draw_DrawIcon(300, yPos, COLOR_ICON_FOOL, ICON_FOOL); + } else { + Draw_DrawCharacter(300, yPos, COLOR_WHITE, '-'); + } + } else { + Draw_DrawCharacter(300, yPos, COLOR_DARK_GRAY, '?'); + } + } + } + + yPos += spacingY; + } + } + + static void Gfx_DrawSpoilerData(void) { + if (gSpoilerData.SphereCount > 0) { + u16 itemCount = gSpoilerData.Spheres[currentSphere].ItemCount; + + Draw_DrawFormattedString(10, 16, COLOR_TITLE, "Spoiler Log - Sphere %i / %i", currentSphere + 1, + gSpoilerData.SphereCount); + + u16 sphereItemLocOffset = gSpoilerData.Spheres[currentSphere].ItemLocationsOffset; + u16 listTopY = 32; + for (u32 item = 0; item < MAX_ENTRY_LINES; ++item) { + u32 locIndex = item + spoilerScroll; + if (locIndex >= gSpoilerData.Spheres[currentSphere].ItemCount) { + break; + } + + u32 locPosY = listTopY + ((SPACING_SMALL_Y + 1) * item * 2); + u32 itemPosY = locPosY + SPACING_SMALL_Y; + u16 itemIndex = gSpoilerData.SphereItemLocations[sphereItemLocOffset + locIndex]; + u32 color = COLOR_WHITE; + if (SpoilerData_GetIsItemLocationCollected(itemIndex)) { + color = COLOR_GREEN; + } else if (SpoilerData_ItemLoc(itemIndex)->CollectType == COLLECTTYPE_REPEATABLE) { + color = COLOR_BLUE; + } else if (SpoilerData_ItemLoc(itemIndex)->CollectType == COLLECTTYPE_NEVER) { + color = COLOR_ORANGE; + } + Draw_DrawString_Small(10, locPosY, color, SpoilerData_GetItemLocationString(itemIndex)); + Draw_DrawString_Small(10 + (SPACING_SMALL_X * 2), itemPosY, color, SpoilerData_GetItemNameString(itemIndex)); + } + + Gfx_DrawScrollBar(SCREEN_BOT_WIDTH - 3, listTopY, SCREEN_BOT_HEIGHT - 40 - listTopY, spoilerScroll, itemCount, + MAX_ENTRY_LINES); + } else { + Draw_DrawString(10, 16, COLOR_TITLE, "Spoiler Log"); + Draw_DrawString(10, 46, COLOR_WHITE, "No spoiler log generated!"); + } + } + + static u8 ViewingGroups() { + return curMenuIdx == PAGE_ITEMTRACKER_GROUPS || curMenuIdx == PAGE_ENTRANCETRACKER_GROUPS; + } + + static void Gfx_DrawItemTracker(void) { + if (!ViewingGroups() && showingLegend) { + Draw_DrawString(10, 16, COLOR_TITLE, "Item Color Legend"); + + static const u8 squareWidth = 9; + u16 offsetY = 2; + Draw_DrawRect(10, 16 + SPACING_Y * offsetY, squareWidth, squareWidth, COLOR_GREEN); + Draw_DrawString(10 + SPACING_X * 2, 16 + SPACING_Y * offsetY++, COLOR_WHITE, "Collected"); + Draw_DrawRect(10, 16 + SPACING_Y * offsetY, squareWidth, squareWidth, COLOR_BLUE); + Draw_DrawString(10 + SPACING_X * 2, 16 + SPACING_Y * offsetY++, COLOR_WHITE, "Repeatable"); + Draw_DrawRect(10, 16 + SPACING_Y * offsetY, squareWidth, squareWidth, COLOR_ORANGE); + Draw_DrawString(10 + SPACING_X * 2, 16 + SPACING_Y * offsetY++, COLOR_WHITE, "Uncollectable"); + return; + } + if (ViewingGroups() && !CanShowSpoilerGroup(SpoilerCollectionCheckGroup(currentItemGroup))) { + Draw_DrawString(10, 16, COLOR_TITLE, spoilerCollectionGroupNames[currentItemGroup]); + Draw_DrawString(10, 46, COLOR_WHITE, "Reveal this dungeon to see the item list."); + Draw_DrawString(10, 57, COLOR_WHITE, " - Enter the dungeon at least once"); + + return; + } + + u16 itemCount = ViewingGroups() ? gSpoilerData.GroupItemCounts[currentItemGroup] : gSpoilerData.ItemLocationsCount; + u16 startIndex = ViewingGroups() ? gSpoilerData.GroupOffsets[currentItemGroup] : 0; + s16* itemScroll = ViewingGroups() ? &groupItemsScroll : &allItemsScroll; + + // Gather up completed items to calculate how far along this group is + u16 completeItems = 0; + u16 uncollectableItems = 0; + for (u32 i = 0; i < itemCount; ++i) { + u32 locIndex = i + startIndex; + if (SpoilerData_GetIsItemLocationCollected(locIndex)) { + completeItems++; + } else if (SpoilerData_ItemLoc(locIndex)->CollectType == COLLECTTYPE_NEVER || + (SpoilerData_ItemLoc(locIndex)->CollectType == COLLECTTYPE_REPEATABLE && + SpoilerData_GetIsItemLocationRevealed(locIndex))) { + uncollectableItems++; + } + } + u16 collectableItems = itemCount - uncollectableItems; + float groupPercent = ((float)completeItems / (float)collectableItems) * 100.0f; + Draw_DrawFormattedString(SCREEN_BOT_WIDTH - 10 - (SPACING_X * 6), 16, + completeItems == collectableItems ? COLOR_GREEN : COLOR_WHITE, "%5.1f%%", groupPercent); + + u16 firstItem = *itemScroll + 1; + u16 lastItem = *itemScroll + MAX_ENTRY_LINES; + if (lastItem > itemCount) { + lastItem = itemCount; + } + Draw_DrawFormattedString(10, 16, COLOR_TITLE, "%s - (%d - %d) / %d", + spoilerCollectionGroupNames[ViewingGroups() ? currentItemGroup : 0], firstItem, lastItem, + itemCount); + + u16 listTopY = 32; + u32 itemGroupIndex = 1; // Keep the last picked group index around to start the search from + for (u32 item = 0; item < MAX_ENTRY_LINES; ++item) { + u32 locIndex = item + startIndex + *itemScroll; + if (item >= itemCount) { + break; + } + + u32 locPosY = listTopY + ((SPACING_SMALL_Y + 1) * item * 2); + u32 itemPosY = locPosY + SPACING_SMALL_Y; + bool isCollected = SpoilerData_GetIsItemLocationCollected(locIndex); + + // Find this item's group index, so we can see if we should hide + // its name because it's located in an undiscovered dungeon + for (u32 group = itemGroupIndex; group < SPOILER_COLLECTION_GROUP_COUNT; ++group) { + u16 groupOffset = gSpoilerData.GroupOffsets[group]; + if (locIndex >= groupOffset && locIndex < groupOffset + gSpoilerData.GroupItemCounts[group]) { + itemGroupIndex = group; + break; + } + } + bool canShowGroup = isCollected || CanShowSpoilerGroup(SpoilerCollectionCheckGroup(itemGroupIndex)); + + u32 color = COLOR_WHITE; + if (isCollected) { + color = COLOR_GREEN; + } else if (canShowGroup) { + if (SpoilerData_ItemLoc(locIndex)->CollectType == COLLECTTYPE_REPEATABLE && + SpoilerData_GetIsItemLocationRevealed(locIndex)) { + color = COLOR_BLUE; + } else if (SpoilerData_ItemLoc(locIndex)->CollectType == COLLECTTYPE_NEVER) { + color = COLOR_ORANGE; + } + } + bool itemRevealed = canShowGroup && (isCollected || SpoilerData_GetIsItemLocationRevealed(locIndex)); + + if (canShowGroup) { + Draw_DrawString_Small(10, locPosY, color, SpoilerData_GetItemLocationString(locIndex)); + } else { + Draw_DrawFormattedString_Small(10, locPosY, color, "%s (Undiscovered)", + spoilerCollectionGroupNames[itemGroupIndex]); + } + const char* itemText = itemRevealed ? SpoilerData_GetItemNameString(locIndex) : "???"; + Draw_DrawString_Small(10 + (SPACING_SMALL_X * 2), itemPosY, color, itemText); + } + + Gfx_DrawScrollBar(SCREEN_BOT_WIDTH - 3, listTopY, SCREEN_BOT_HEIGHT - 40 - listTopY, *itemScroll, itemCount, + MAX_ENTRY_LINES); + } + + static void (*menu_draw_funcs[])(void) = { + // Make sure these line up with the GfxPage enum above + Gfx_DrawSeedHash, Gfx_DrawDungeonItems, Gfx_DrawSpoilerData, + Gfx_DrawItemTracker, // All + Gfx_DrawItemTracker, // Groups + // Gfx_DrawEntranceTracker, // All + // Gfx_DrawEntranceTracker, // Groups + // Gfx_DrawOptions, + }; + + static void Gfx_DrawHeader() { + const u32 totalTabsWidth = 280; + u32 tabsCount = ARR_SIZE(menu_draw_funcs); + u32 tabWidthPlusSpace = totalTabsWidth / tabsCount; + u32 tabXStart = 20; + u32 tabYStart = 3; + u32 tabHeightSmall = 4; + u32 tabHeightBig = 6; + + Draw_DrawIcon(3, 2, COLOR_WHITE, ICON_BUTTON_L_WIDE_1); + Draw_DrawIcon(11, 2, COLOR_WHITE, ICON_BUTTON_L_WIDE_2); + Draw_DrawIcon(SCREEN_BOT_WIDTH - 19, 2, COLOR_WHITE, ICON_BUTTON_R_WIDE_1); + Draw_DrawIcon(SCREEN_BOT_WIDTH - 11, 2, COLOR_WHITE, ICON_BUTTON_R_WIDE_2); + + for (u32 i = 0; i < tabsCount; i++) { + bool isAvailable = menu_draw_funcs[i] != NULL; + bool isCurrent = static_cast(curMenuIdx) == i; + u32 tabX = (u32)(i * tabWidthPlusSpace); + Draw_DrawRect(tabXStart + tabX, isCurrent ? tabYStart : tabYStart + 2, + i == tabsCount - 1 ? totalTabsWidth - tabX : (tabWidthPlusSpace - 1), + isCurrent ? tabHeightBig : tabHeightSmall, + isCurrent ? COLOR_WHITE : (isAvailable ? COLOR_LIGHT_GRAY : COLOR_DARK_GRAY)); + } + } + + static s16 Gfx_Scroll(s16 current, s16 scrollDelta, u16 itemCount) { + s16 maxScroll = itemCount > MAX_ENTRY_LINES ? itemCount - MAX_ENTRY_LINES : 0; + current += scrollDelta; + if (current < 0) { + current = 0; + } else if (current > maxScroll) { + current = maxScroll; + } + return current; + } + + static void Gfx_ShowMenu(void) { + pressed = 0; + Draw_ClearFramebuffer(); + if (gSettingsContext.playOption == PLAY_ON_CONSOLE) { + Draw_FlushFramebuffer(); + } + do { + // End the loop if the system has gone to sleep, so the game can properly respond + if (isAsleep) { + break; + } + + handledInput = false; + // Controls for spoiler log and all-items pages come first, as the user may have chosen + // one of the directional buttons as their menu open/close button and we need to use them + if (curMenuIdx == PAGE_DUNGEONITEMS) { + if (pressed & BUTTON_A) { + showingLegend = !showingLegend; + handledInput = true; + } + } else if (curMenuIdx == PAGE_SPHERES && gSpoilerData.SphereCount > 0) { + // Spoiler log + u16 itemCount = gSpoilerData.Spheres[currentSphere].ItemCount; + if (pressed & (BUTTON_LEFT | CPAD_LEFT)) { + if (currentSphere == 0) { + currentSphere = gSpoilerData.SphereCount - 1; + } else { + currentSphere--; + } + spoilerScroll = 0; + handledInput = true; + } else if (pressed & (BUTTON_RIGHT | CPAD_RIGHT)) { + if (currentSphere < gSpoilerData.SphereCount - 1) { + currentSphere++; + } else { + currentSphere = 0; + } + spoilerScroll = 0; + handledInput = true; + } else if (pressed & (BUTTON_UP | CPAD_UP)) { + spoilerScroll = Gfx_Scroll(spoilerScroll, -1, itemCount); + handledInput = true; + } else if (pressed & (BUTTON_DOWN | CPAD_DOWN)) { + spoilerScroll = Gfx_Scroll(spoilerScroll, 1, itemCount); + handledInput = true; + } + } else if (curMenuIdx == PAGE_ITEMTRACKER_ALL && gSpoilerData.ItemLocationsCount > 0) { + // All Items list + if (pressed & BUTTON_A) { + showingLegend = !showingLegend; + handledInput = true; + } else if (!showingLegend) { + u16 itemCount = gSpoilerData.ItemLocationsCount; + if (pressed & (BUTTON_LEFT | CPAD_LEFT)) { + allItemsScroll = Gfx_Scroll(allItemsScroll, -MAX_ENTRY_LINES * 10, itemCount); + handledInput = true; + } else if (pressed & (BUTTON_RIGHT | CPAD_RIGHT)) { + allItemsScroll = Gfx_Scroll(allItemsScroll, MAX_ENTRY_LINES * 10, itemCount); + handledInput = true; + } else if (pressed & (BUTTON_UP | CPAD_UP)) { + allItemsScroll = Gfx_Scroll(allItemsScroll, -MAX_ENTRY_LINES, itemCount); + handledInput = true; + } else if (pressed & (BUTTON_DOWN | CPAD_DOWN)) { + allItemsScroll = Gfx_Scroll(allItemsScroll, MAX_ENTRY_LINES, itemCount); + handledInput = true; + } + } + } else if (curMenuIdx == PAGE_ITEMTRACKER_GROUPS && gSpoilerData.ItemLocationsCount > 0) { + // Grouped Items list + u16 itemCount = gSpoilerData.GroupItemCounts[currentItemGroup]; + if (pressed & (BUTTON_LEFT | CPAD_LEFT)) { + groupItemsScroll = Gfx_Scroll(groupItemsScroll, -MAX_ENTRY_LINES, itemCount); + handledInput = true; + } else if (pressed & (BUTTON_RIGHT | CPAD_RIGHT)) { + groupItemsScroll = Gfx_Scroll(groupItemsScroll, MAX_ENTRY_LINES, itemCount); + handledInput = true; + } else if (pressed & (BUTTON_UP | CPAD_UP)) { + groupItemsScroll = Gfx_Scroll(groupItemsScroll, -1, itemCount); + handledInput = true; + } else if (pressed & (BUTTON_DOWN | CPAD_DOWN)) { + groupItemsScroll = Gfx_Scroll(groupItemsScroll, 1, itemCount); + handledInput = true; + } else if (pressed & BUTTON_A) { + NextItemGroup(); + handledInput = true; + } else if (pressed & BUTTON_Y) { + PrevItemGroup(); + handledInput = true; + } + } + + if (!handledInput) { + if (pressed & closingButton) { + showingLegend = false; + Draw_ClearBackbuffer(); + Draw_CopyBackBuffer(); + if (gSettingsContext.playOption == PLAY_ON_CONSOLE) { + Draw_FlushFramebuffer(); + } + break; + } else if (pressed & BUTTON_R1) { + showingLegend = false; + do { + curMenuIdx++; + if (static_cast(curMenuIdx) >= ARR_SIZE(menu_draw_funcs)) { + curMenuIdx = 0; + } + } while (menu_draw_funcs[curMenuIdx] == NULL); + handledInput = true; + } else if (pressed & BUTTON_L1) { + showingLegend = false; + do { + curMenuIdx--; + if (curMenuIdx < 0) { + curMenuIdx = (ARR_SIZE(menu_draw_funcs) - 1); + } + } while (menu_draw_funcs[curMenuIdx] == NULL); + handledInput = true; + } + } + + // Keep updating while in the in-game menu + Draw_ClearBackbuffer(); + Draw_ClearFramebuffer(); + + // Continue counting up play time while in the in-game menu + Gfx_UpdatePlayTime(); + + menu_draw_funcs[curMenuIdx](); + Gfx_DrawButtonPrompts(); + Gfx_DrawHeader(); + Draw_CopyBackBuffer(); + if (gSettingsContext.playOption == PLAY_ON_CONSOLE) { + Draw_FlushFramebuffer(); + } + pressed = Input_WaitWithTimeout(1000, closingButton); + + } while (true); + } + + void Gfx_Init(void) { + Draw_SetupFramebuffer(); + Draw_ClearBackbuffer(); + + // Setup the title screen logo edits + // gActorOverlayTable[0x171].initInfo->init = EnMag_rInit; + + if (gSettingsContext.menuOpeningButton == 0) + closingButton = BUTTON_B | BUTTON_SELECT; + else if (gSettingsContext.menuOpeningButton == 1) + closingButton = BUTTON_B | BUTTON_START; + else if (gSettingsContext.menuOpeningButton == 2) + closingButton = BUTTON_B | BUTTON_UP; + else if (gSettingsContext.menuOpeningButton == 3) + closingButton = BUTTON_B | BUTTON_DOWN; + else if (gSettingsContext.menuOpeningButton == 4) + closingButton = BUTTON_B | BUTTON_RIGHT; + else if (gSettingsContext.menuOpeningButton == 5) + closingButton = BUTTON_B | BUTTON_LEFT; + + if (!gSettingsContext.ingameSpoilers) { + menu_draw_funcs[PAGE_SPHERES] = NULL; + } + if (gSpoilerData.ItemLocationsCount == 0) { + menu_draw_funcs[PAGE_ITEMTRACKER_ALL] = NULL; + menu_draw_funcs[PAGE_ITEMTRACKER_GROUPS] = NULL; + } + + // Call these to go to the first non-empty group page + if (gSpoilerData.ItemLocationsCount > 0 && gSpoilerData.GroupItemCounts[currentItemGroup] == 0) { + NextItemGroup(); + } + + // InitOptions(); + + GfxInit = 1; + } + + static u8 openingButton() { + return (gSettingsContext.customIngameSpoilerButton != 0 && + rInputCtx.cur.val == gSettingsContext.customIngameSpoilerButton); + } + + extern "C" { + void Gfx_Update() { + if (!GfxInit) { + Gfx_Init(); + lastTick = svcGetSystemTick(); + } + // The update is called here so it works while in different game modes (title screen, file select, boss challenge, + // credits, MQ unlock) + static u64 lastTickM = 0; + static u64 elapsedTicksM = 0; + elapsedTicksM += svcGetSystemTick() - lastTickM; + if (elapsedTicksM >= TICKS_PER_SEC) { + elapsedTicksM = 0; + } + lastTickM = svcGetSystemTick(); + + Gfx_UpdatePlayTime(); + + if (!isAsleep && openingButton()) { //&& context.has_initialised +#if defined ENABLE_DEBUG || defined DEBUG_PRINT + rnd::util::Print("%s: Attempting to show menu. Are we asleep? %u openingButtons is %u.\n", __func__, isAsleep, + openingButton()); +#endif + Gfx_ShowMenu(); + // Check again as it's possible the system was put to sleep while the menu was open + if (!isAsleep) { + svcSleepThread(1000 * 1000 * 300LL); + // Update lastTick one more time so we don't count the added 0.3s sleep + lastTick = svcGetSystemTick(); + } + } + } + + void Gfx_SleepQueryCallback() { + ticksElapsed = 0; + isAsleep = true; + } + + void Gfx_AwakeCallback() { + ticksElapsed = 0; + lastTick = svcGetSystemTick(); + isAsleep = false; + } + } + +} // namespace rnd diff --git a/code/source/rnd/input.cpp b/code/source/rnd/input.cpp index 70e08c1a..c905954e 100644 --- a/code/source/rnd/input.cpp +++ b/code/source/rnd/input.cpp @@ -1,83 +1,84 @@ #include "rnd/input.h" +#include "common/debug.h" #include "hid.h" #include "utils.h" #include "z3d/z3DVec.h" - extern "C" { #include <3ds/svc.h> } - -u32 GetCurrentPadState(void) { - u32 hid_shared_mem = *(u32*)(0x007b2d34); - return *(volatile u32*)(hid_shared_mem + 0x1C); -} +namespace rnd { + u32 GetCurrentPadState(void) { + u32 hid_shared_mem = *(u32*)(0x007b2d34); + return *(volatile u32*)(hid_shared_mem + 0x1C); + } #define HID_PAD (GetCurrentPadState()) -InputContext rInputCtx; - -// Needed for menus external to the game for spoiler logs. -void Input_Update(void) { - rInputCtx.cur.val = real_hid.pad.pads[real_hid.pad.index].curr.val; - rInputCtx.pressed.val = (rInputCtx.cur.val) & (~rInputCtx.old.val); - rInputCtx.up.val = (~rInputCtx.cur.val) & (rInputCtx.old.val); - rInputCtx.old.val = rInputCtx.cur.val; -} + InputContext rInputCtx = {}; -u32 buttonCheck(u32 key) { - for (u32 i = 0x26000; i > 0; i--) { - if (key != real_hid.pad.pads[real_hid.pad.index].curr.val) - return 0; + // Needed for menus external to the game for spoiler logs. + void Input_Update() { + rInputCtx.cur.val = real_hid->pad.pads[real_hid->pad.index].curr.val; + rInputCtx.pressed.val = (rInputCtx.cur.val) & (~rInputCtx.old.val); + rInputCtx.up.val = (~rInputCtx.cur.val) & (rInputCtx.old.val); + rInputCtx.old.val = rInputCtx.cur.val; } - return 1; -} - -u32 Input_WaitWithTimeout(u32 msec, u32 closingButton) { - u32 pressedKey = 0; - u32 key = 0; - u32 n = 0; - u32 startingButtonState = HID_PAD; - // Wait for no keys to be pressed - while (HID_PAD && (msec == 0 || n <= msec)) { - svcSleepThread(reinterpret_cast(1 * 1000 * 1000LL)); - n++; - - // If the player presses the closing button while still holding other buttons, the menu closes - // (useful for buffering); - u32 tempButtons = HID_PAD; - if (tempButtons != startingButtonState && buttonCheck(tempButtons)) { - if (tempButtons & closingButton) { - break; - } else { - startingButtonState = tempButtons; - } + u32 buttonCheck(u32 key) { + for (u32 i = 0x26000; i > 0; i--) { + if (key != real_hid->pad.pads[real_hid->pad.index].curr.val) + return 0; } + return 1; } - if (msec != 0 && n >= msec) { - return 0; - } + u32 Input_WaitWithTimeout(u32 msec, u32 closingButton) { + u32 pressedKey = 0; + u32 key = 0; + u32 n = 0; + u32 startingButtonState = HID_PAD; - do { - // Wait for a key to be pressed - while (!HID_PAD && (msec == 0 || n < msec)) { - svcSleepThread(1 * 1000 * 1000LL); + // Wait for no keys to be pressed + while (HID_PAD && (msec == 0 || n <= msec)) { + svcSleepThread(reinterpret_cast(1 * 1000 * 1000LL)); n++; + + // If the player presses the closing button while still holding other buttons, the menu closes + // (useful for buffering); + u32 tempButtons = HID_PAD; + if (tempButtons != startingButtonState && buttonCheck(tempButtons)) { + if (tempButtons & closingButton) { + break; + } else { + startingButtonState = tempButtons; + } + } } if (msec != 0 && n >= msec) { return 0; } - key = HID_PAD; + do { + // Wait for a key to be pressed + while (!HID_PAD && (msec == 0 || n < msec)) { + svcSleepThread(1 * 1000 * 1000LL); + n++; + } + + if (msec != 0 && n >= msec) { + return 0; + } - // Make sure it's pressed - pressedKey = buttonCheck(key); - } while (!pressedKey); + key = HID_PAD; - return key; -} + // Make sure it's pressed + pressedKey = buttonCheck(key); + } while (!pressedKey); -u32 Input_Wait(void) { - return Input_WaitWithTimeout(0, 0); -} \ No newline at end of file + return key; + } + + u32 Input_Wait(void) { + return Input_WaitWithTimeout(0, 0); + } +} // namespace rnd diff --git a/code/source/rnd/item_effect.cpp b/code/source/rnd/item_effect.cpp index 404594da..da8c2b4e 100644 --- a/code/source/rnd/item_effect.cpp +++ b/code/source/rnd/item_effect.cpp @@ -61,23 +61,18 @@ namespace rnd { } void ItemEffect_GiveSmallKey(game::CommonData* comData, s16 dungeonId, s16 arg2) { - s8 keys; switch (dungeonId) { case 0: - keys = comData->save.inventory.woodfall_temple_keys < 0 ? 0 : comData->save.inventory.woodfall_temple_keys; - comData->save.inventory.woodfall_temple_keys = keys + 1; + comData->save.inventory.woodfall_temple_keys = comData->save.inventory.woodfall_temple_keys + 1; break; case 1: - keys = comData->save.inventory.snowhead_temple_keys < 0 ? 0 : comData->save.inventory.snowhead_temple_keys; - comData->save.inventory.snowhead_temple_keys = keys + 1; + comData->save.inventory.snowhead_temple_keys = comData->save.inventory.snowhead_temple_keys + 1; break; case 2: - keys = comData->save.inventory.great_bay_temple_keys < 0 ? 0 : comData->save.inventory.great_bay_temple_keys; - comData->save.inventory.great_bay_temple_keys = keys + 1; + comData->save.inventory.great_bay_temple_keys = comData->save.inventory.great_bay_temple_keys + 1; break; case 3: - keys = comData->save.inventory.stone_tower_temple_keys < 0 ? 0 : comData->save.inventory.stone_tower_temple_keys; - comData->save.inventory.stone_tower_temple_keys = keys + 1; + comData->save.inventory.stone_tower_temple_keys = comData->save.inventory.stone_tower_temple_keys + 1; break; default: break; @@ -209,6 +204,8 @@ namespace rnd { comData->save.inventory.items[(u32)game::ItemId::DekuNuts] = game::ItemId::DekuNuts; comData->save.inventory.item_counts[14] += 10; break; + default: + break; } } else { switch (arg2) { @@ -226,6 +223,8 @@ namespace rnd { comData->save.inventory.items[(u32)game::ItemId::DekuNuts] = game::ItemId::DekuNuts; comData->save.inventory.item_counts[14] = (10 + 10 * arg1); break; + default: + break; } } } @@ -260,6 +259,8 @@ namespace rnd { case 3: comData->save.inventory.collect_register.twinmolds_remains = 1; break; + default: + break; } } @@ -297,6 +298,8 @@ namespace rnd { else if (mask == 3) comData->save.inventory.stone_tower_dungeon_items.map = 1; break; + default: + break; } } @@ -327,6 +330,8 @@ namespace rnd { case 3: comData->save.inventory.collect_register.twinmolds_remains = 1; break; + default: + break; } // Make this call as we need to update field_11 in Sub1 CommonData. rnd::util::GetPointer(0x222BCC)(rnd::GetContext().gctx, 0); diff --git a/code/source/rnd/item_override.cpp b/code/source/rnd/item_override.cpp index 90ca8c34..cc823e02 100644 --- a/code/source/rnd/item_override.cpp +++ b/code/source/rnd/item_override.cpp @@ -5,6 +5,7 @@ #include "rnd/item_table.h" #include "rnd/rheap.h" #include "rnd/savefile.h" +#include "rnd/spoiler_data.h" #if defined ENABLE_DEBUG || defined DEBUG_PRINT #include "common/debug.h" @@ -278,6 +279,7 @@ namespace rnd { return; } ItemOverride_AfterKeyReceived(key); + SpoilerLog_UpdateIngameLog(key.type, key.scene, key.flag); ItemOverride_Clear(); } diff --git a/code/source/rnd/item_table.cpp b/code/source/rnd/item_table.cpp index 2c2cc6f6..2f1876b6 100644 --- a/code/source/rnd/item_table.cpp +++ b/code/source/rnd/item_table.cpp @@ -611,15 +611,15 @@ namespace rnd { (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_BeanPack, (s16)-1, (s16)-1), // Magic Beans - [0x76] = - ITEM_ROW((u32)GetItemID::GI_RUPEE_BLUE, ChestType::DECORATED_SMALL, (u8)game::ItemId::SmallKey, 0x003C, - 0x00086, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveSmallKey, (s16)0, (s16)-1), // Small Key (Woodfall) + [0x76] = ITEM_ROW((u32)GetItemID::GI_RUPEE_BLUE, ChestType::DECORATED_SMALL, (u8)game::ItemId::SmallKey, 0x6133, + 0x00086, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_SMALL_KEY, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveSmallKey, (s16)0, + (s16)-1), // Small Key (Woodfall) - [0x77] = - ITEM_ROW((u32)GetItemID::GI_RUPEE_BLUE, ChestType::DECORATED_SMALL, (u8)game::ItemId::SmallKey, 0x003C, - 0x00086, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveSmallKey, (s16)1, (s16)-1), // Small Key (Snowhead) + [0x77] = ITEM_ROW((u32)GetItemID::GI_RUPEE_BLUE, ChestType::DECORATED_SMALL, (u8)game::ItemId::SmallKey, 0x6134, + 0x00086, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_SMALL_KEY, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveSmallKey, (s16)1, + (s16)-1), // Small Key (Snowhead) [0x78] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::WOODEN_BIG, (u8)game::ItemId::DekuMask, 0x0078, 0x01BD, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_DEKU_MASK, @@ -729,10 +729,10 @@ namespace rnd { (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_KAFEI_MASK, (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_None, (s16)-1, (s16)-1), // Kafei Mask - [0x90] = - ITEM_ROW((u32)GetItemID::GI_RUPEE_BLUE, ChestType::DECORATED_SMALL, (u8)game::ItemId::SmallKey, 0x003C, - 0x00086, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveSmallKey, (s16)2, (s16)-1), // Small Key (Great Bay) + [0x90] = ITEM_ROW((u32)GetItemID::GI_RUPEE_BLUE, ChestType::DECORATED_SMALL, (u8)game::ItemId::SmallKey, 0x6135, + 0x00086, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_SMALL_KEY, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveSmallKey, (s16)2, + (s16)-1), // Small Key (Great Bay) // The following bottle items either give one bottle if you do not have one, or refills any // bottle if you do have an empty one. Essentially acts as if you caught something in a @@ -827,40 +827,40 @@ namespace rnd { (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_None, (s16)-1, (s16)-1), // Letter To Mama - [0xA2] = - ITEM_ROW((u32)GetItemID::GI_RUPEE_BLUE, ChestType::DECORATED_SMALL, (u8)game::ItemId::SmallKey, 0x003C, - 0x00086, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveSmallKey, (s16)3, (s16)-1), // Small Key (Stone Tower) + [0xA2] = ITEM_ROW((u32)GetItemID::GI_RUPEE_BLUE, ChestType::DECORATED_SMALL, (u8)game::ItemId::SmallKey, 0x6136, + 0x00086, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_SMALL_KEY, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveSmallKey, (s16)3, + (s16)-1), // Small Key (Stone Tower) - [0xA3] = - ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::BossKey, 0x003D, 0x00092, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)1, (s16)0), // Boss Key (Woodfall) + [0xA3] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::BossKey, 0x613F, + 0x00092, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_BOSS_KEY, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)1, + (s16)0), // Boss Key (Woodfall) - [0xA4] = - ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::BossKey, 0x003D, 0x00092, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)1, (s16)1), // Boss Key (Snowhead) + [0xA4] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::BossKey, 0x6140, + 0x00092, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_BOSS_KEY, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)1, + (s16)1), // Boss Key (Snowhead) - [0xA5] = - ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::BossKey, 0x003D, 0x00092, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)1, (s16)2), // Boss Key (Great Bay) + [0xA5] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::BossKey, 0x6141, + 0x00092, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_BOSS_KEY, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)1, + (s16)2), // Boss Key (Great Bay) - [0xA6] = - ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::BossKey, 0x003D, 0x00092, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)1, (s16)3), // Boss Key (Stone Tower) + [0xA6] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::BossKey, 0x6142, + 0x00092, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_BOSS_KEY, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)1, + (s16)3), // Boss Key (Stone Tower) - [0xA7] = - ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Compass, 0x003F, 0x00091, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)2, (s16)0), // Compass (Woodfall) + [0xA7] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Compass, 0x613B, + 0x00091, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_COMPASS, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)2, + (s16)0), // Compass (Woodfall) - [0xA8] = - ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Compass, 0x003F, 0x00091, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)2, (s16)1), // Compass (Snowhead) + [0xA8] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Compass, 0x613C, + 0x00091, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_COMPASS, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)2, + (s16)1), // Compass (Snowhead) [0xA9] = ITEM_ROW((u32)GetItemID::GI_RUPEE_BLUE, ChestType::WOODEN_SMALL, (u8)game::ItemId::Bottle, 0x0090, 0x0009E, @@ -878,34 +878,34 @@ namespace rnd { (s32)DrawGraphicItemID::DI_PENDANT_OF_MEMORIES, (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_None, (s16)-1, (s16)-1), // Pendant Of Memories - [0xAC] = - ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Compass, 0x003F, 0x00091, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)2, (s16)2), // Compass (Great Bay) + [0xAC] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Compass, 0x613D, + 0x00091, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_COMPASS, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)2, + (s16)2), // Compass (Great Bay) - [0xAD] = - ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Compass, 0x003F, 0x00091, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)2, (s16)3), // Compass (Stone Tower) + [0xAD] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Compass, 0x613E, + 0x00091, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_COMPASS, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)2, + (s16)3), // Compass (Stone Tower) - [0xAE] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Map, 0x003E, 0x000A0, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)3, - (s16)0), // Map (Woodfall) + [0xAE] = + ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Map, 0x6137, 0x000A0, + (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_DUNGEON_MAP_MAYBE, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)3, (s16)0), // Map (Woodfall) - [0xAF] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Map, 0x003E, 0x000A0, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)3, - (s16)1), // Map (Snowhead) + [0xAF] = + ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Map, 0x6138, 0x000A0, + (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_DUNGEON_MAP_MAYBE, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)3, (s16)1), // Map (Snowhead) - [0xB0] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Map, 0x003E, 0x000A0, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)3, - (s16)2), // Map (Great Bay) + [0xB0] = + ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Map, 0x6139, 0x000A0, + (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_DUNGEON_MAP_MAYBE, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)3, (s16)2), // Map (Great Bay) - [0xB1] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Map, 0x003E, 0x000A0, - (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, 0xFF, (rnd::upgradeFunc)ItemUpgrade_None, - ItemEffect_GiveDungeonItem, (s16)3, + [0xB1] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::DECORATED_BIG, (u8)game::ItemId::Map, 0x613A, 0x000A0, + (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s8)0xFF, (s32)DrawGraphicItemID::DI_DUNGEON_MAP_MAYBE, + (rnd::upgradeFunc)ItemUpgrade_None, ItemEffect_GiveDungeonItem, (s16)3, (s16)3), // Map (Stone Tower) [0xB2] = ITEM_ROW((u32)GetItemID::GI_NUTS_30, ChestType::WOODEN_BIG, (u8)game::ItemId::None, 0x000CB, 0x00096, diff --git a/code/source/rnd/link.cpp b/code/source/rnd/link.cpp index 6ea6c3c7..c50ed555 100644 --- a/code/source/rnd/link.cpp +++ b/code/source/rnd/link.cpp @@ -225,6 +225,14 @@ namespace rnd::link { do_switch(gctx, player, check_magic); } + bool CheckIfMagicAcquired() { + const game::CommonData& cdata = game::GetCommonData(); + if (cdata.save.player.magic_acquired == 0) + return 0; + else + return 1; + } + // This patch is all taken care of in ASM now. No need to loop into the main Calc function. void HandleFastTransform() { const game::GlobalContext* gctx = GetContext().gctx; diff --git a/code/source/rnd/savefile.cpp b/code/source/rnd/savefile.cpp index 10c2cf92..e7c38f5c 100644 --- a/code/source/rnd/savefile.cpp +++ b/code/source/rnd/savefile.cpp @@ -73,7 +73,7 @@ namespace rnd { saveData.inventory.stone_tower_dungeon_items.compass = 1; saveData.inventory.stone_tower_dungeon_items.boss_key = 1; saveData.inventory.woodfall_fairies = 14; - saveData.player.magic_acquired = 1; // Game does not check if value = 0, magic items still + // saveData.player.magic_acquired = 1; // Game does not check if value = 0, magic items still saveData.player.magic_size_type = 0; // saveData.player.magic = 10; saveData.player.magic_num_upgrades = 0; saveData.equipment.data[3].item_btns[0] = game::ItemId::DekuNuts; @@ -138,7 +138,7 @@ namespace rnd { saveData.player.owl_statue_flags.clock_town = 1; #ifdef ENABLE_DEBUG gSettingsContext.startingKokiriSword = 0; - gSettingsContext.startingShield = 1; + gSettingsContext.startingShield = 0; #endif SaveFile_SetStartingInventory(); @@ -345,6 +345,7 @@ namespace rnd { void SaveFile_SetStartingInventory(void) { game::PlayerData& playerData = game::GetCommonData().save.player; game::EquipmentData& equipmentData = game::GetCommonData().save.equipment; + game::SaveData& saveBackupData = game::GetCommonData().save_backup; game::SaveData& saveData = game::GetCommonData().save; // give maps and compasses if (gSettingsContext.mapsAndCompasses == (u8)MapsAndCompassesSetting::MAPSANDCOMPASSES_ANY_DUNGEON) { @@ -828,6 +829,19 @@ namespace rnd { extDataUnmount(fsa); extDataClose(fileHandle); } + + u8 SaveFile_GetIsSceneDiscovered(u8 sceneNum) { + // TODO: ENSURE THE SCENES ARE CHECKED WITH + // OUR BITFLAGS. NOT USING <<. + /*u32 numBits = sizeof(u32) * 8; + u32 idx = sceneNum / numBits; + if (idx < SAVEFILE_SCENES_DISCOVERED_IDX_COUNT) { + u32 bit = 1 << (sceneNum - (idx * numBits)); + return (gExtSaveData.scenesDiscovered[idx] & bit) != 0; + }*/ + return 0; + } + extern "C" void SaveFile_SaveExtSaveData() { #if defined ENABLE_DEBUG || defined DEBUG_PRINT rnd::util::Print("%s: Saving extdata.\n", __func__); diff --git a/code/source/rnd/spoiler_data.cpp b/code/source/rnd/spoiler_data.cpp index 4777d15a..ee47094a 100644 --- a/code/source/rnd/spoiler_data.cpp +++ b/code/source/rnd/spoiler_data.cpp @@ -1,10 +1,20 @@ #include "rnd/spoiler_data.h" #include "game/common_data.h" +#include "rnd/item_override.h" #include "rnd/settings.h" #include "z3d/z3DVec.h" namespace rnd { SpoilerData gSpoilerData = {0}; + SpoilerDataLocs gSpoilerDataLocs[SPOILER_LOCDATS] = {0}; + + SpoilerItemLocation* SpoilerData_ItemLoc(u16 itemIndex) { + return &gSpoilerDataLocs[itemIndex / SPOILER_ITEMS_MAX].ItemLocations[itemIndex % SPOILER_ITEMS_MAX]; + } + + char* SpoilerData_StringData(u16 itemIndex) { + return gSpoilerDataLocs[itemIndex / SPOILER_ITEMS_MAX].StringData; + } char* SpoilerData_GetItemLocationString(u16 itemIndex) { return &gSpoilerData.StringData[gSpoilerData.ItemLocations[itemIndex].LocationStrOffset]; @@ -20,11 +30,29 @@ namespace rnd { } u8 SpoilerData_ChestCheck(SpoilerItemLocation itemLoc) { - // TODO: Implement Chest Checking. No need to use bits as we have - // builtin BitField classes. - // Reference: - // https://github.com/gamestabled/OoT3D_Randomizer/blob/e53be23c14090b15c6c39e08933ca7af54f747f7/code/src/spoiler_data.c#L25-L32 - return 0; + return -1; + } + + u8 SpoilerLog_UpdateIngameLog(ItemOverride_Type type, u8 scene, u8 flag) { + // SpoilerData currentCheck = {0}; + for (int i = 0; i < gSpoilerData.ItemLocationsCount; i++) { + if (gSpoilerData.ItemLocations[i].LocationScene == scene) { + if (gSpoilerData.ItemLocations[i].OverrideType == type) { + if (gSpoilerData.ItemLocations[i].LocationFlag == flag) { + // reveal the check + return -1; + } + } + } + } + /* + for (gSpoilerData.ItemLocations->OverrideType == type && gSpoilerData.ItemLocations->LocationScene == scene && + gSpoilerData.ItemLocations->LocationFlag == flag) + { + //maybe do it this way? + return -1; + }*/ + return -1; } u8 SpoilerData_CollectableCheck(SpoilerItemLocation itemLoc) { @@ -32,7 +60,7 @@ namespace rnd { // builtin BitField classes. // Reference: // https://github.com/gamestabled/OoT3D_Randomizer/blob/e53be23c14090b15c6c39e08933ca7af54f747f7/code/src/spoiler_data.c#L34-L41 - return 0; + return -1; } // Shop checks, will need to be decomped, most likely in common_data.h. @@ -157,4 +185,20 @@ namespace rnd { return 0; } + u8 SpoilerData_GetIsItemLocationRevealed(u16 itemIndex) { + if (gSettingsContext.ingameSpoilers) { + return 1; + } + + SpoilerItemLocation* itemLoc = SpoilerData_ItemLoc(itemIndex); + + if (itemLoc->RevealType == REVEALTYPE_ALWAYS) { + return 1; + } else if (itemLoc->RevealType == REVEALTYPE_NORMAL) { + return 0; + } + + return SaveFile_GetIsSceneDiscovered(itemLoc->LocationScene); + } + } // namespace rnd \ No newline at end of file diff --git a/romfs/exheader.bin b/romfs/exheader.bin index 3aee248f9b75ff5bf1b1d296ff2e185fa626b904..6196c35582e924afb872fc891d908fe267f5e93c 100644 GIT binary patch delta 27 gcmZn=Xb_lS$;4o=(OQIwkzsQH(|=|V#kPSF0AIug;Q#;t delta 27 gcmZn=Xb_lS$&{eF(OQIwk!f=P(|=|V#kPSF0A`s7IsgCw