ref: e6e88979ca7fe9ab4bfd55324450bbb88c72fd46
parent: d113951d387132a75f6350c9c4574a6fa57c010c
author: Snesrev <snesrev@protonmail.com>
date: Sat Sep 17 12:14:27 EDT 2022
L/R for item switching, and reordering of inventory Hold Y and press arrows to reorder inventory items.
--- a/README.md
+++ b/README.md
@@ -14,8 +14,20 @@
I got much assistance from spannierism's Zelda 3 JP disassembly and the other ones that documented loads of function names and variables.
-The game also supports enhanced aspect ratios of 16:9 or 16:10 (see ExtendedAspectRatio in zelda3.ini). It also supports MSU higher quality music soundtracks.
+## Additional features
+Some features have been added that are not supported by the original game.
+
+Support for MSU audio tracks.
+
+Support for enhanced aspect ratios of 16:9 or 16:10.
+
+Switching current item with L/R keys.
+
+Reordering of inventory by pressing Y+Arrows.
+
+Higher quality map screen.
+
## Dependencies
- the `libsdl2-dev` library
@@ -77,8 +89,8 @@
| B | Z |
| X | S |
| Y | A |
-| L | D |
-| R | C |
+| L | C |
+| R | V |
The keys can be reconfigured in zelda3.ini
--- a/config.c
+++ b/config.c
@@ -22,7 +22,7 @@
#define N 0
static const uint16 kDefaultKbdControls[kKeys_Total] = {
// Controls
- _(SDLK_UP), _(SDLK_DOWN), _(SDLK_LEFT), _(SDLK_RIGHT), _(SDLK_RSHIFT), _(SDLK_RETURN), _(SDLK_x), _(SDLK_z), _(SDLK_s), _(SDLK_a), _(SDLK_d), _(SDLK_c),
+ _(SDLK_UP), _(SDLK_DOWN), _(SDLK_LEFT), _(SDLK_RIGHT), _(SDLK_RSHIFT), _(SDLK_RETURN), _(SDLK_x), _(SDLK_z), _(SDLK_s), _(SDLK_a), _(SDLK_c), _(SDLK_v),
// LoadState
_(SDLK_F1), _(SDLK_F2), _(SDLK_F3), _(SDLK_F4), _(SDLK_F5), _(SDLK_F6), _(SDLK_F7), _(SDLK_F8), _(SDLK_F9), _(SDLK_F10), N, N, N, N, N, N, N, N, N, N,
// SaveState
@@ -194,6 +194,8 @@
return 2;
if (StringEqualsNoCase(s, "[General]"))
return 3;
+ if (StringEqualsNoCase(s, "[Features]"))
+ return 4;
return -1;
}
@@ -234,7 +236,7 @@
g_config.audio_samples = (uint16)strtol(value, (char**)NULL, 10);
return true;
} else if (StringEqualsNoCase(key, "EnableMSU")) {
- g_config.enable_msu = (uint16)strtol(value, (char **)NULL, 10);
+ g_config.enable_msu = (bool)strtol(value, (char **)NULL, 10);
return true;
}
} else if (section == 3) {
@@ -261,7 +263,11 @@
g_config.display_perf_title = (bool)strtol(value, (char**)NULL, 10);
return true;
}
-
+ } else if (section == 4) {
+ if (StringEqualsNoCase(key, "ItemSwitchLR")) {
+ g_config.item_switch_lr = (bool)strtol(value, (char **)NULL, 10);
+ return true;
+ }
}
return false;
}
--- a/config.h
+++ b/config.h
@@ -46,6 +46,7 @@
bool extended_aspect_ratio_nospr;
bool display_perf_title;
bool enable_msu;
+ bool item_switch_lr;
} Config;
extern Config g_config;
--- a/dungeon.c
+++ b/dungeon.c
@@ -6605,6 +6605,7 @@
main_module_index = 14;
return;
}
+ Hud_HandleItemSwitchInputs();
}
Link_Main();
}
--- a/hud.c
+++ b/hud.c
@@ -19,11 +19,10 @@
const uint8 kMaxArrowsForLevel[] = { 30, 35, 40, 45, 50, 55, 60, 70 };
static const uint8 kMaxHealthForLevel[] = { 9, 9, 9, 9, 9, 9, 9, 9, 17, 17, 17, 17, 17, 17, 17, 25, 25, 25, 25, 25, 25 };
static const uint16 kHudItemInVramPtr[20] = {
- 0x11c8, 0x11ce, 0x11d4, 0x11da,
- 0x11e0, 0x1288, 0x128e, 0x1294,
- 0x129a, 0x12a0, 0x1348, 0x134e,
- 0x1354, 0x135a, 0x1360, 0x1408,
- 0x140e, 0x1414, 0x141a, 0x1420,
+ 0x11c8, 0x11ce, 0x11d4, 0x11da, 0x11e0,
+ 0x1288, 0x128e, 0x1294, 0x129a, 0x12a0,
+ 0x1348, 0x134e, 0x1354, 0x135a, 0x1360,
+ 0x1408, 0x140e, 0x1414, 0x141a, 0x1420,
};
static const uint16 kHudBottlesGfx[128] = {
0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x24f5, 0x255c, 0x2564, 0x2562, 0x2557, 0x2561, 0x255e, 0x255e, 0x255c,
@@ -349,6 +348,10 @@
};
static const uint16 kDungFloorIndicator_Gfx0[11] = { 0x2508, 0x2509, 0x2509, 0x250a, 0x250b, 0x250c, 0x250d, 0x251d, 0xe51c, 0x250e, 0x7f };
static const uint16 kDungFloorIndicator_Gfx1[11] = { 0x2518, 0x2519, 0xa509, 0x251a, 0x251b, 0x251c, 0x2518, 0xa51d, 0xe50c, 0xa50e, 0x7f };
+
+static int Hud_GetCurrentItemPosition();
+static void Hud_ReorderItem(int direction);
+
void Hud_RefreshIcon() {
Hud_SearchForEquippedItem();
Hud_UpdateHud();
@@ -374,14 +377,35 @@
}
}
+// Returns the zero based index of the currently selected hud item
+static int Hud_GetCurrentItemPosition() {
+ if (hud_inventory_order[0] != 0) {
+ int i = 0;
+ for (; i < 19 && hud_inventory_order[i] != hud_cur_item; i++) {}
+ return i;
+ } else {
+ return hud_cur_item ? hud_cur_item - 1 : hud_cur_item;
+ }
+}
+
void Hud_GotoPrevItem() {
- if (--hud_cur_item < 1)
- hud_cur_item = 20;
+ if (hud_inventory_order[0] != 0) {
+ int i = Hud_GetCurrentItemPosition();
+ hud_cur_item = hud_inventory_order[i == 0 ? 19 : i - 1];
+ } else {
+ if (--hud_cur_item < 1)
+ hud_cur_item = 20;
+ }
}
void Hud_GotoNextItem() {
- if (++hud_cur_item >= 21)
- hud_cur_item = 1;
+ if (hud_inventory_order[0] != 0) {
+ int i = Hud_GetCurrentItemPosition();
+ hud_cur_item = hud_inventory_order[i >= 19 ? 0 : i + 1];
+ } else {
+ if (++hud_cur_item >= 21)
+ hud_cur_item = 1;
+ }
}
void Hud_FloorIndicator() { // 8afd0c
@@ -698,7 +722,17 @@
return;
}
- if (!BYTE(tmp1)) {
+ if (joypad1H_last & 0x40 && enhanced_features0 & kFeatures0_SwitchLR) {
+ if (filtered_joypad_H & 8) {
+ Hud_ReorderItem(-5);
+ } else if (filtered_joypad_H & 4) {
+ Hud_ReorderItem(5);
+ } else if (filtered_joypad_H & 2) {
+ Hud_ReorderItem(-1);
+ } else if (filtered_joypad_H & 1) {
+ Hud_ReorderItem(1);
+ }
+ } else if (!BYTE(tmp1)) {
uint16 old_item = hud_cur_item;
if (filtered_joypad_H & 8) {
Hud_EquipItemAbove();
@@ -845,9 +879,11 @@
Hud_DrawItem(0x1472, &kHudItemBottles[link_bottle_info[1]]);
Hud_DrawItem(0x1572, &kHudItemBottles[link_bottle_info[2]]);
Hud_DrawItem(0x1672, &kHudItemBottles[link_bottle_info[3]]);
- Hud_DrawItem(0x1408, &kHudItemBottles[link_item_bottles ? link_bottle_info[link_item_bottles - 1] : 0]);
+
+ int bottle_vram_pos = kHudItemInVramPtr[Hud_GetCurrentItemPosition()];
+ Hud_DrawItem(bottle_vram_pos, &kHudItemBottles[link_item_bottles ? link_bottle_info[link_item_bottles - 1] : 0]);
- uint16 *p = (uint16 *)&g_ram[kHudItemInVramPtr[hud_cur_item - 1]];
+ uint16 *p = (uint16 *)&g_ram[bottle_vram_pos];
uvram_screen.row[6].col[25] = p[0];
uvram_screen.row[6].col[26] = p[1];
uvram_screen.row[7].col[25] = p[32];
@@ -926,7 +962,6 @@
if (or_all == 0) {
hud_cur_item = 0;
- hud_cur_item_hi = 0;
hud_var1 = 0;
} else {
if (!hud_cur_item)
@@ -968,26 +1003,33 @@
uvram_screen.row[5].col[3] = 0x246E;
uvram_screen.row[5].col[4] = 0x246F;
- Hud_DrawItem(0x11c8, &kHudItemBow[link_item_bow]);
- Hud_DrawItem(0x11ce, &kHudItemBoomerang[link_item_boomerang]);
- Hud_DrawItem(0x11d4, &kHudItemHookshot[link_item_hookshot]);
- Hud_DrawItem(0x11da, &kHudItemBombs[link_item_bombs ? 1 : 0]);
- Hud_DrawItem(0x11e0, &kHudItemMushroom[link_item_mushroom]);
- Hud_DrawItem(0x1288, &kHudItemFireRod[link_item_fire_rod]);
- Hud_DrawItem(0x128e, &kHudItemIceRod[link_item_ice_rod]);
- Hud_DrawItem(0x1294, &kHudItemBombos[link_item_bombos_medallion]);
- Hud_DrawItem(0x129a, &kHudItemEther[link_item_ether_medallion]);
- Hud_DrawItem(0x12a0, &kHudItemQuake[link_item_quake_medallion]);
- Hud_DrawItem(0x1348, &kHudItemTorch[link_item_torch]);
- Hud_DrawItem(0x134e, &kHudItemHammer[link_item_hammer]);
- Hud_DrawItem(0x1354, &kHudItemFlute[link_item_flute]);
- Hud_DrawItem(0x135a, &kHudItemBugNet[link_item_bug_net]);
- Hud_DrawItem(0x1360, &kHudItemBookMudora[link_item_book_of_mudora]);
- Hud_DrawItem(0x1408, &kHudItemBottles[link_item_bottles ? link_bottle_info[link_item_bottles - 1] : 0]);
- Hud_DrawItem(0x140e, &kHudItemCaneSomaria[link_item_cane_somaria]);
- Hud_DrawItem(0x1414, &kHudItemCaneByrna[link_item_cane_byrna]);
- Hud_DrawItem(0x141a, &kHudItemCape[link_item_cape]);
- Hud_DrawItem(0x1420, &kHudItemMirror[link_item_mirror]);
+ const ItemBoxGfx *item_box_gfxs[] = {
+ &kHudItemBow[link_item_bow],
+ &kHudItemBoomerang[link_item_boomerang],
+ &kHudItemHookshot[link_item_hookshot],
+ &kHudItemBombs[link_item_bombs ? 1 : 0],
+ &kHudItemMushroom[link_item_mushroom],
+ &kHudItemFireRod[link_item_fire_rod],
+ &kHudItemIceRod[link_item_ice_rod],
+ &kHudItemBombos[link_item_bombos_medallion],
+ &kHudItemEther[link_item_ether_medallion],
+ &kHudItemQuake[link_item_quake_medallion],
+ &kHudItemTorch[link_item_torch],
+ &kHudItemHammer[link_item_hammer],
+ &kHudItemFlute[link_item_flute],
+ &kHudItemBugNet[link_item_bug_net],
+ &kHudItemBookMudora[link_item_book_of_mudora],
+ &kHudItemBottles[link_item_bottles ? link_bottle_info[link_item_bottles - 1] : 0],
+ &kHudItemCaneSomaria[link_item_cane_somaria],
+ &kHudItemCaneByrna[link_item_cane_byrna],
+ &kHudItemCape[link_item_cape],
+ &kHudItemMirror[link_item_mirror],
+ };
+
+ for (int i = 0; i < 20; i++) {
+ int j = hud_inventory_order[i];
+ Hud_DrawItem(kHudItemInVramPtr[i], item_box_gfxs[j == 0 ? i: j - 1]);
+ }
}
void Hud_DrawUnknownBox(uint16 palmask) { // 8de647
@@ -1151,7 +1193,7 @@
}
void Hud_DrawSelectedYButtonItem() { // 8deb3a
- uint16 *p = (uint16 *)&g_ram[kHudItemInVramPtr[hud_cur_item - 1]];
+ uint16 *p = (uint16 *)&g_ram[kHudItemInVramPtr[Hud_GetCurrentItemPosition()]];
uvram_screen.row[6].col[25] = p[0];
uvram_screen.row[6].col[26] = p[1];
uvram_screen.row[7].col[25] = p[32];
@@ -1495,4 +1537,45 @@
const uint16 *Hud_GetItemBoxPtr(int item) {
return kHudItemBoxGfxPtrs[item]->v;
+}
+
+void Hud_HandleItemSwitchInputs() {
+ if (filtered_joypad_L & (0x20 | 0x10)) { // left/right shoulder
+ int old_item = hud_cur_item;
+ for (int i = 0; ; i++) {
+ if (i >= 20) {
+ hud_cur_item = 0;
+ break;
+ }
+ if (filtered_joypad_L & 0x20)
+ Hud_GotoPrevItem();
+ else
+ Hud_GotoNextItem();
+ if (Hud_DoWeHaveThisItem())
+ break;
+ }
+ if (hud_cur_item != old_item) {
+ sound_effect_2 = 32;
+ Hud_UpdateEquippedItem();
+ Hud_UpdateItemBox();
+ }
+ }
+}
+
+void Hud_ReorderItem(int direction) {
+ // Initialize inventory order on first use
+ if (hud_inventory_order[0] == 0) {
+ for (int i = 0; i < 24; i++)
+ hud_inventory_order[i] = i + 1;
+ }
+ int old_pos = Hud_GetCurrentItemPosition(), new_pos = old_pos + direction;
+ if (new_pos < 0)
+ new_pos += 20;
+ else if (new_pos >= 20)
+ new_pos -= 20;
+ uint8 t = hud_inventory_order[old_pos];
+ hud_inventory_order[old_pos] = hud_inventory_order[new_pos];
+ hud_inventory_order[new_pos] = t;
+ Hud_DrawYButtonItems(Hud_GetPaletteMask(1));
+ sound_effect_2 = 32;
}
--- a/hud.h
+++ b/hud.h
@@ -61,4 +61,6 @@
void Hud_Update_IgnoreItemBox();
void Hud_Update_IgnoreHealth();
void Hud_UpdateHearts(uint16 *dst, const uint16 *src, int n);
-const uint16 *Hud_GetItemBoxPtr(int item);
\ No newline at end of file
+const uint16 *Hud_GetItemBoxPtr(int item);
+
+void Hud_HandleItemSwitchInputs();
--- a/main.c
+++ b/main.c
@@ -182,7 +182,16 @@
ZeldaInitialize();
g_zenv.ppu->extraLeftRight = UintMin(g_config.extended_aspect_ratio, kPpuExtraLeftRight);
g_snes_width = 2 * (g_config.extended_aspect_ratio * 2 + 256);
- g_wanted_zelda_features = (g_zenv.ppu->extraLeftRight && !g_config.extended_aspect_ratio_nospr) ? kFeatures0_ExtendScreen64 : 0;
+
+
+ // Delay actually setting those features in ram until any snapshots finish playing.
+ {
+ uint32 f = 0;
+ f |= (g_zenv.ppu->extraLeftRight && !g_config.extended_aspect_ratio_nospr) ? kFeatures0_ExtendScreen64 : 0;
+ f |= g_config.item_switch_lr * kFeatures0_SwitchLR;
+ g_wanted_zelda_features = f;
+ }
+
g_ppu_render_flags = g_config.new_renderer * kPpuRenderFlags_NewRenderer | g_config.enhanced_mode7 * kPpuRenderFlags_4x4Mode7;
msu_enabled = g_config.enable_msu;
--- a/overworld.c
+++ b/overworld.c
@@ -775,6 +775,7 @@
main_module_index = 14;
return;
}
+ Hud_HandleItemSwitchInputs();
}
if (trigger_special_entrance)
Overworld_AnimateEntrance();
--- a/variables.h
+++ b/variables.h
@@ -184,7 +184,7 @@
#define stk_return_addr (*(uint16*)(g_ram+0x1FE))
#define overworld_map_state (*(uint8*)(g_ram+0x200))
#define hud_cur_item (*(uint8*)(g_ram+0x202))
-#define hud_cur_item_hi (*(uint8*)(g_ram+0x203))
+
#define hud_var1 (*(uint8*)(g_ram+0x204))
#define byte_7E0205 (*(uint8*)(g_ram+0x205))
#define byte_7E0206 (*(uint8*)(g_ram+0x206))
--- a/zelda3.ini
+++ b/zelda3.ini
@@ -8,6 +8,7 @@
# replays will be incompatible
ExtendedAspectRatio = 4:3
+
[Graphics]
# Fullscreen mode (0=windowed, 1=desktop fullscreen, 2=fullscreen w/mode change)
Fullscreen = 0
@@ -31,12 +32,17 @@
EnableMSU = 0
+[Features]
+# Item switch on L/R. Also allows reordering of items in inventory by pressing Y+direction
+ItemSwitchLR = 1
+
+
[KeyMap]
# Change what keyboard keys map to the joypad
# Order: Up, Down, Left, Right, Select, Start, A, B, X, Y, L, R
# This default is suitable for QWERTY keyboards.
-Controls = Up, Down, Left, Right, Right Shift, Return, x, z, s, a, d, c
+Controls = Up, Down, Left, Right, Right Shift, Return, x, z, s, a, c, v
# This default is suitable for QWERTZ keyboards.
#Controls = Up, Down, Left, Right, Right Shift, Return, x, y, s, a, d, c
--- a/zelda_rtl.c
+++ b/zelda_rtl.c
@@ -339,10 +339,6 @@
1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,
};
-#define msu_curr_sample (*(uint32*)(g_ram+0x650))
-#define msu_volume (*(uint8*)(g_ram+0x654))
-#define msu_track (*(uint8*)(g_ram+0x655))
-
bool ZeldaIsMusicPlaying() {
if (msu_track) {
return msu_file != NULL;
--- a/zelda_rtl.h
+++ b/zelda_rtl.h
@@ -103,10 +103,6 @@
kRam_CrystalRotateCounter = 0x649,
kRam_BugsFixed = 0x64a,
kRam_Features0 = 0x64c,
-
- // 4 bytes holding the current msu playback sample, then 2 more more msu misc
- kRam_MsuCurrSample = 0x650,
-
};
enum {
@@ -114,10 +110,20 @@
kBugFix_PolyRenderer = 1,
kBugFix_AncillaOverwrites = 1,
kBugFix_Latest = 1,
+};
- // kRam_Features0
+// Enum values for kRam_Features0
+enum {
kFeatures0_ExtendScreen64 = 1,
+ kFeatures0_SwitchLR = 2,
};
+
+#define enhanced_features0 (*(uint32*)(g_ram+0x64c))
+#define msu_curr_sample (*(uint32*)(g_ram+0x650))
+#define msu_volume (*(uint8*)(g_ram+0x654))
+#define msu_track (*(uint8*)(g_ram+0x655))
+
+#define hud_inventory_order ((uint8*)(g_ram + 0x225)) // 4x6 bytes
extern uint32 g_wanted_zelda_features;