diff --git a/.gitignore b/.gitignore
index 09f29ab48..9c499abb0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,7 @@ tools/asmsplitter/c/*
ctx.c
tools/*dSYM/
graphs/
+.netcoredbg_hist
# Assets
*.png
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL
new file mode 100644
index 000000000..6e3cbc204
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_0 b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_0
new file mode 100644
index 000000000..34ff33a71
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_0
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_1 b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_1
new file mode 100644
index 000000000..e406505e8
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_1
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_2 b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_2
new file mode 100644
index 000000000..d93755356
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_2
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_3 b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_3
new file mode 100644
index 000000000..87fb8da01
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_3
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_4 b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_4
new file mode 100644
index 000000000..6e436b142
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_tri_4
@@ -0,0 +1,245 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_0 b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_0
new file mode 100644
index 000000000..ed4b6e1c0
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_0
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_1 b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_1
new file mode 100644
index 000000000..d2728aae4
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_1
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_2 b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_2
new file mode 100644
index 000000000..eb0050c03
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_2
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_3 b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_3
new file mode 100644
index 000000000..f124650c8
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_3
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_4 b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_4
new file mode 100644
index 000000000..2bbe7895e
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/gGIBossSoulSkullDL_vtx_4
@@ -0,0 +1,267 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_Gem_eyes b/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_Gem_eyes
new file mode 100644
index 000000000..07364ba3a
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_Gem_eyes
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_black b/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_black
new file mode 100644
index 000000000..6aee093d9
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_black
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_eyes b/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_eyes
new file mode 100644
index 000000000..c08beb94a
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_eyes
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_horns b/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_horns
new file mode 100644
index 000000000..78d2a531c
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_horns
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_surface b/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_surface
new file mode 100644
index 000000000..e15a4dce4
--- /dev/null
+++ b/soh/assets/custom/objects/object_boss_soul/mat_gGIBossSoulSkullDL_skull_surface
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_boss_soul/noise_tex b/soh/assets/custom/objects/object_boss_soul/noise_tex
new file mode 100644
index 000000000..aaf4e331f
Binary files /dev/null and b/soh/assets/custom/objects/object_boss_soul/noise_tex differ
diff --git a/soh/assets/custom/objects/object_boss_soul/noise_tex_copy b/soh/assets/custom/objects/object_boss_soul/noise_tex_copy
new file mode 100644
index 000000000..a6d6cf945
Binary files /dev/null and b/soh/assets/custom/objects/object_boss_soul/noise_tex_copy differ
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/eff_unknown_10_i8 b/soh/assets/custom/objects/object_gi_fishing_pole/eff_unknown_10_i8
new file mode 100644
index 000000000..174f53cb7
Binary files /dev/null and b/soh/assets/custom/objects/object_gi_fishing_pole/eff_unknown_10_i8 differ
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/eff_unknown_10_i8_png b/soh/assets/custom/objects/object_gi_fishing_pole/eff_unknown_10_i8_png
new file mode 100644
index 000000000..174f53cb7
Binary files /dev/null and b/soh/assets/custom/objects/object_gi_fishing_pole/eff_unknown_10_i8_png differ
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL
new file mode 100644
index 000000000..49ed9b0e5
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_0 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_0
new file mode 100644
index 000000000..747739cb7
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_0
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_1 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_1
new file mode 100644
index 000000000..6a28afee1
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_1
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_2 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_2
new file mode 100644
index 000000000..0f07ec616
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_2
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_3 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_3
new file mode 100644
index 000000000..b3bccf78a
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_3
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_4 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_4
new file mode 100644
index 000000000..7aa7522e8
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_4
@@ -0,0 +1,341 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_5 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_5
new file mode 100644
index 000000000..e815ce7ed
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_5
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_6 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_6
new file mode 100644
index 000000000..1c9bb3d9e
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_6
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_7 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_7
new file mode 100644
index 000000000..fff6a9fee
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_tri_7
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_0 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_0
new file mode 100644
index 000000000..810b26e0c
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_0
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_1 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_1
new file mode 100644
index 000000000..69b125a3e
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_1
@@ -0,0 +1,209 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_2 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_2
new file mode 100644
index 000000000..606a6ab09
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_2
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_3 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_3
new file mode 100644
index 000000000..8d8b43d0f
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_3
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_4 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_4
new file mode 100644
index 000000000..802a69d26
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_4
@@ -0,0 +1,672 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_5 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_5
new file mode 100644
index 000000000..4137d75a2
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_5
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_6 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_6
new file mode 100644
index 000000000..7ce420cfc
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_6
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_7 b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_7
new file mode 100644
index 000000000..381565a0e
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_7
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_cull b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_cull
new file mode 100644
index 000000000..47672f31f
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/gFishingPoleGiDL_vtx_cull
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_black b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_black
new file mode 100644
index 000000000..26808af42
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_black
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_f3dlite_material_006 b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_f3dlite_material_006
new file mode 100644
index 000000000..3db97957e
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_f3dlite_material_006
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_line b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_line
new file mode 100644
index 000000000..d08aa632a
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_line
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_accent_001 b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_accent_001
new file mode 100644
index 000000000..3fff6a270
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_accent_001
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_handle_metal b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_handle_metal
new file mode 100644
index 000000000..9633e2458
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_handle_metal
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_metal_001 b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_metal_001
new file mode 100644
index 000000000..332c12c8d
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_metal_001
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_white b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_white
new file mode 100644
index 000000000..4b12ae0fb
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_reel_white
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_wood b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_wood
new file mode 100644
index 000000000..82b446f53
--- /dev/null
+++ b/soh/assets/custom/objects/object_gi_fishing_pole/mat_gFishingPoleGiDL_wood
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL
new file mode 100644
index 000000000..0e068a1b2
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_tri_0 b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_tri_0
new file mode 100644
index 000000000..39f541204
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_tri_0
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_tri_1 b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_tri_1
new file mode 100644
index 000000000..8b223d8e3
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_tri_1
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_vtx_0 b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_vtx_0
new file mode 100644
index 000000000..266582a03
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_vtx_0
@@ -0,0 +1,268 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_vtx_1 b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_vtx_1
new file mode 100644
index 000000000..dfcc8cc0d
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_vtx_1
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_vtx_cull b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_vtx_cull
new file mode 100644
index 000000000..38adb91be
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_a_button/gOcarinaAButtonDL_vtx_cull
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_a_button/mat_gOcarinaAButtonDL_f3dlite_ocarina_A_button_edge b/soh/assets/custom/objects/object_ocarina_a_button/mat_gOcarinaAButtonDL_f3dlite_ocarina_A_button_edge
new file mode 100644
index 000000000..45ceb1d42
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_a_button/mat_gOcarinaAButtonDL_f3dlite_ocarina_A_button_edge
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_a_button/mat_gOcarinaAButtonDL_f3dlite_ocarina_A_button_surface b/soh/assets/custom/objects/object_ocarina_a_button/mat_gOcarinaAButtonDL_f3dlite_ocarina_A_button_surface
new file mode 100644
index 000000000..0ed7292b8
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_a_button/mat_gOcarinaAButtonDL_f3dlite_ocarina_A_button_surface
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_a_button/noise_tex b/soh/assets/custom/objects/object_ocarina_a_button/noise_tex
new file mode 100644
index 000000000..aaf4e331f
Binary files /dev/null and b/soh/assets/custom/objects/object_ocarina_a_button/noise_tex differ
diff --git a/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL
new file mode 100644
index 000000000..0bcb32168
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_tri_0 b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_tri_0
new file mode 100644
index 000000000..825b236e8
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_tri_0
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_tri_1 b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_tri_1
new file mode 100644
index 000000000..8b7f7ff56
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_tri_1
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_vtx_0 b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_vtx_0
new file mode 100644
index 000000000..f3d8371d4
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_vtx_0
@@ -0,0 +1,243 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_vtx_1 b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_vtx_1
new file mode 100644
index 000000000..682f39ebb
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_vtx_1
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_vtx_cull b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_vtx_cull
new file mode 100644
index 000000000..38adb91be
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL_vtx_cull
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_down_button/mat_gOcarinaCDownButtonDL_f3dlite_ocarina_C_button_edge b/soh/assets/custom/objects/object_ocarina_c_down_button/mat_gOcarinaCDownButtonDL_f3dlite_ocarina_C_button_edge
new file mode 100644
index 000000000..b741478fb
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_down_button/mat_gOcarinaCDownButtonDL_f3dlite_ocarina_C_button_edge
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_down_button/mat_gOcarinaCDownButtonDL_f3dlite_ocarina_C_button_surface b/soh/assets/custom/objects/object_ocarina_c_down_button/mat_gOcarinaCDownButtonDL_f3dlite_ocarina_C_button_surface
new file mode 100644
index 000000000..9b7410f6f
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_down_button/mat_gOcarinaCDownButtonDL_f3dlite_ocarina_C_button_surface
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_down_button/noise_tex b/soh/assets/custom/objects/object_ocarina_c_down_button/noise_tex
new file mode 100644
index 000000000..aaf4e331f
Binary files /dev/null and b/soh/assets/custom/objects/object_ocarina_c_down_button/noise_tex differ
diff --git a/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL
new file mode 100644
index 000000000..be037cfe2
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_tri_0 b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_tri_0
new file mode 100644
index 000000000..bda98fe0a
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_tri_0
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_tri_1 b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_tri_1
new file mode 100644
index 000000000..937dd564d
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_tri_1
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_vtx_0 b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_vtx_0
new file mode 100644
index 000000000..e84ec9046
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_vtx_0
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_vtx_1 b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_vtx_1
new file mode 100644
index 000000000..fd2bf7af9
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_vtx_1
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_vtx_cull b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_vtx_cull
new file mode 100644
index 000000000..38adb91be
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL_vtx_cull
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_left_button/mat_gOcarinaCLeftButtonDL_f3dlite_ocarina_C_button_edge b/soh/assets/custom/objects/object_ocarina_c_left_button/mat_gOcarinaCLeftButtonDL_f3dlite_ocarina_C_button_edge
new file mode 100644
index 000000000..5bef84867
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_left_button/mat_gOcarinaCLeftButtonDL_f3dlite_ocarina_C_button_edge
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_left_button/mat_gOcarinaCLeftButtonDL_f3dlite_ocarina_C_button_surface b/soh/assets/custom/objects/object_ocarina_c_left_button/mat_gOcarinaCLeftButtonDL_f3dlite_ocarina_C_button_surface
new file mode 100644
index 000000000..2d1b57f5b
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_left_button/mat_gOcarinaCLeftButtonDL_f3dlite_ocarina_C_button_surface
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_left_button/noise_tex b/soh/assets/custom/objects/object_ocarina_c_left_button/noise_tex
new file mode 100644
index 000000000..aaf4e331f
Binary files /dev/null and b/soh/assets/custom/objects/object_ocarina_c_left_button/noise_tex differ
diff --git a/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL
new file mode 100644
index 000000000..9d41daa33
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_tri_0 b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_tri_0
new file mode 100644
index 000000000..f008c7abc
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_tri_0
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_tri_1 b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_tri_1
new file mode 100644
index 000000000..1767f6fe3
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_tri_1
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_vtx_0 b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_vtx_0
new file mode 100644
index 000000000..f3039179f
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_vtx_0
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_vtx_1 b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_vtx_1
new file mode 100644
index 000000000..1c79e5a86
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_vtx_1
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_vtx_cull b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_vtx_cull
new file mode 100644
index 000000000..38adb91be
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL_vtx_cull
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_right_button/mat_gOcarinaCRightButtonDL_f3dlite_ocarina_C_button_edge b/soh/assets/custom/objects/object_ocarina_c_right_button/mat_gOcarinaCRightButtonDL_f3dlite_ocarina_C_button_edge
new file mode 100644
index 000000000..1cb97f9e3
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_right_button/mat_gOcarinaCRightButtonDL_f3dlite_ocarina_C_button_edge
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_right_button/mat_gOcarinaCRightButtonDL_f3dlite_ocarina_C_button_surface b/soh/assets/custom/objects/object_ocarina_c_right_button/mat_gOcarinaCRightButtonDL_f3dlite_ocarina_C_button_surface
new file mode 100644
index 000000000..70fc9e85d
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_right_button/mat_gOcarinaCRightButtonDL_f3dlite_ocarina_C_button_surface
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_right_button/noise_tex b/soh/assets/custom/objects/object_ocarina_c_right_button/noise_tex
new file mode 100644
index 000000000..aaf4e331f
Binary files /dev/null and b/soh/assets/custom/objects/object_ocarina_c_right_button/noise_tex differ
diff --git a/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL
new file mode 100644
index 000000000..36b1ff7a2
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_tri_0 b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_tri_0
new file mode 100644
index 000000000..f46f7c565
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_tri_0
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_tri_1 b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_tri_1
new file mode 100644
index 000000000..80954ffe2
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_tri_1
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_vtx_0 b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_vtx_0
new file mode 100644
index 000000000..a35cf324a
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_vtx_0
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_vtx_1 b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_vtx_1
new file mode 100644
index 000000000..09a4018d3
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_vtx_1
@@ -0,0 +1,251 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_vtx_cull b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_vtx_cull
new file mode 100644
index 000000000..38adb91be
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL_vtx_cull
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_up_button/mat_gOcarinaCUpButtonDL_f3dlite_ocarina_C_button_edge b/soh/assets/custom/objects/object_ocarina_c_up_button/mat_gOcarinaCUpButtonDL_f3dlite_ocarina_C_button_edge
new file mode 100644
index 000000000..301ec9fc4
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_up_button/mat_gOcarinaCUpButtonDL_f3dlite_ocarina_C_button_edge
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_up_button/mat_gOcarinaCUpButtonDL_f3dlite_ocarina_C_button_surface b/soh/assets/custom/objects/object_ocarina_c_up_button/mat_gOcarinaCUpButtonDL_f3dlite_ocarina_C_button_surface
new file mode 100644
index 000000000..a9a701d15
--- /dev/null
+++ b/soh/assets/custom/objects/object_ocarina_c_up_button/mat_gOcarinaCUpButtonDL_f3dlite_ocarina_C_button_surface
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/soh/assets/custom/objects/object_ocarina_c_up_button/noise_tex b/soh/assets/custom/objects/object_ocarina_c_up_button/noise_tex
new file mode 100644
index 000000000..aaf4e331f
Binary files /dev/null and b/soh/assets/custom/objects/object_ocarina_c_up_button/noise_tex differ
diff --git a/soh/assets/custom/textures/parameter_static/gBossSoul.rgba32.png b/soh/assets/custom/textures/parameter_static/gBossSoul.rgba32.png
new file mode 100755
index 000000000..9fe2ac404
Binary files /dev/null and b/soh/assets/custom/textures/parameter_static/gBossSoul.rgba32.png differ
diff --git a/soh/assets/soh_assets.h b/soh/assets/soh_assets.h
index 181ebd434..3f9039e35 100644
--- a/soh/assets/soh_assets.h
+++ b/soh/assets/soh_assets.h
@@ -44,6 +44,21 @@ static const ALIGN_ASSET(2) char gTitleRandomizerSubtitleTex[] = dgTitleRandomiz
#define dgTitleBossRushSubtitleTex "__OTR__objects/object_mag/gTitleBossRushSubtitleTex"
static const ALIGN_ASSET(2) char gTitleBossRushSubtitleTex[] = dgTitleBossRushSubtitleTex;
+#define dgOcarinaAButtonDL "__OTR__objects/object_ocarina_a_button/gOcarinaAButtonDL"
+static const ALIGN_ASSET(2) char gOcarinaAButtonDL[] = dgOcarinaAButtonDL;
+
+#define dgOcarinaCLeftButtonDL "__OTR__objects/object_ocarina_c_left_button/gOcarinaCLeftButtonDL"
+static const ALIGN_ASSET(2) char gOcarinaCLeftButtonDL[] = dgOcarinaCLeftButtonDL;
+
+#define dgOcarinaCRightButtonDL "__OTR__objects/object_ocarina_c_right_button/gOcarinaCRightButtonDL"
+static const ALIGN_ASSET(2) char gOcarinaCRightButtonDL[] = dgOcarinaCRightButtonDL;
+
+#define dgOcarinaCUpButtonDL "__OTR__objects/object_ocarina_c_up_button/gOcarinaCUpButtonDL"
+static const ALIGN_ASSET(2) char gOcarinaCUpButtonDL[] = dgOcarinaCUpButtonDL;
+
+#define dgOcarinaCDownButtonDL "__OTR__objects/object_ocarina_c_down_button/gOcarinaCDownButtonDL"
+static const ALIGN_ASSET(2) char gOcarinaCDownButtonDL[] = dgOcarinaCDownButtonDL;
+
#define dgTriforcePiece0DL "__OTR__objects/object_triforce_piece_0/gTriforcePiece0DL"
static const ALIGN_ASSET(2) char gTriforcePiece0DL[] = dgTriforcePiece0DL;
@@ -56,6 +71,12 @@ static const ALIGN_ASSET(2) char gTriforcePiece2DL[] = dgTriforcePiece2DL;
#define dgTriforcePieceCompletedDL "__OTR__objects/object_triforce_completed/gTriforcePieceCompletedDL"
static const ALIGN_ASSET(2) char gTriforcePieceCompletedDL[] = dgTriforcePieceCompletedDL;
+#define dgBossSoulSkullDL "__OTR__objects/object_boss_soul/gGIBossSoulSkullDL"
+static const ALIGN_ASSET(2) char gBossSoulSkullDL[] = dgBossSoulSkullDL;
+
+#define dgFishingPoleGiDL "__OTR__objects/object_gi_fishing_pole/gFishingPoleGiDL"
+static const ALIGN_ASSET(2) char gFishingPoleGiDL[] = dgFishingPoleGiDL;
+
#define dgMysteryItemDL "__OTR__objects/object_mystery_item/gMysteryItemDL"
static const ALIGN_ASSET(2) char gMysteryItemDL[] = dgMysteryItemDL;
@@ -76,6 +97,9 @@ static const ALIGN_ASSET(2) char gArrowDownTex[] = dgArrowDown;
#define dgTriforcePiece "__OTR__textures/parameter_static/gTriforcePiece"
static const ALIGN_ASSET(2) char gTriforcePieceTex[] = dgTriforcePiece;
+#define dgBossSoul "__OTR__textures/parameter_static/gBossSoul"
+static const ALIGN_ASSET(2) char gBossSoulTex[] = dgBossSoul;
+
#define dgFileSelMQButtonTex "__OTR__textures/title_static/gFileSelMQButtonTex"
static const ALIGN_ASSET(2) char gFileSelMQButtonTex[] = dgFileSelMQButtonTex;
diff --git a/soh/include/functions.h b/soh/include/functions.h
index 77d578997..82b03e637 100644
--- a/soh/include/functions.h
+++ b/soh/include/functions.h
@@ -181,6 +181,8 @@ void __osSetWatchLo(u32);
EnItem00* Item_DropCollectible(PlayState* play, Vec3f* spawnPos, s16 params);
EnItem00* Item_DropCollectible2(PlayState* play, Vec3f* spawnPos, s16 params);
void EnItem00_CustomItemsParticles(Actor* Parent, PlayState* play, GetItemEntry giEntry);
+void EnItem00_SetupAction(EnItem00* this, EnItem00ActionFunc actionFunc);
+void func_8001E5C8(EnItem00* this, PlayState* play);
void Item_DropCollectibleRandom(PlayState* play, Actor* fromActor, Vec3f* spawnPos, s16 params);
void EffectBlure_ChangeType(EffectBlure* this, int type);
void EffectBlure_AddVertex(EffectBlure* this, Vec3f* p1, Vec3f* p2);
@@ -413,6 +415,7 @@ f32 Actor_WorldDistXZToPoint(Actor* actor, Vec3f* refPoint);
void func_8002DBD0(Actor* actor, Vec3f* result, Vec3f* arg2);
f32 Actor_HeightDiff(Actor* actorA, Actor* actorB);
f32 Player_GetHeight(Player* player);
+s32 Player_ActionChange_2(Player* player, PlayState* play);
f32 func_8002DCE4(Player* player);
s32 func_8002DD6C(Player* player);
s32 func_8002DD78(Player* player);
@@ -424,7 +427,7 @@ void Actor_MountHorse(PlayState* play, Player* player, Actor* horse);
s32 func_8002DEEC(Player* player);
void func_8002DF18(PlayState* play, Player* player);
s32 func_8002DF38(PlayState* play, Actor* actor, u8 csMode);
-s32 func_8002DF54(PlayState* play, Actor* actor, u8 arg2);
+s32 Player_SetCsActionWithHaltedActors(PlayState* play, Actor* actor, u8 arg2);
void func_8002DF90(DynaPolyActor* dynaActor);
void func_8002DFA4(DynaPolyActor* dynaActor, f32 arg1, s16 arg2);
s32 Player_IsFacingActor(Actor* actor, s16 angle, PlayState* play);
@@ -452,11 +455,11 @@ u32 Actor_TextboxIsClosing(Actor* actor, PlayState* play);
s8 func_8002F368(PlayState* play);
void Actor_GetScreenPos(PlayState* play, Actor* actor, s16* x, s16* y);
u32 Actor_HasParent(Actor* actor, PlayState* play);
-// TODO: Rename the follwing 3 functions using whatever scheme we use when we rename func_8002F434 and func_8002F554.
+// TODO: Rename the follwing 3 functions using whatever scheme we use when we rename Actor_OfferGetItem and func_8002F554.
s32 GiveItemEntryWithoutActor(PlayState* play, GetItemEntry getItemEntry);
s32 GiveItemEntryFromActor(Actor* actor, PlayState* play, GetItemEntry getItemEntry, f32 xzRange, f32 yRange);
-void GiveItemEntryFromActorWithFixedRange(Actor* actor, PlayState* play, GetItemEntry getItemEntry);
-s32 func_8002F434(Actor* actor, PlayState* play, s32 getItemId, f32 xzRange, f32 yRange);
+s32 GiveItemEntryFromActorWithFixedRange(Actor* actor, PlayState* play, GetItemEntry getItemEntry);
+s32 Actor_OfferGetItem(Actor* actor, PlayState* play, s32 getItemId, f32 xzRange, f32 yRange);
void func_8002F554(Actor* actor, PlayState* play, s32 getItemId);
void func_8002F580(Actor* actor, PlayState* play);
u32 Actor_HasNoParent(Actor* actor, PlayState* play);
@@ -570,8 +573,6 @@ void Flags_UnsetRandomizerInf(RandomizerInf flag);
u16 func_80037C30(PlayState* play, s16 arg1);
s32 func_80037D98(PlayState* play, Actor* actor, s16 arg2, s32* arg3);
s32 func_80038290(PlayState* play, Actor* actor, Vec3s* arg2, Vec3s* arg3, Vec3f arg4);
-GetItemEntry GetChestGameRandoGetItem(s8 room, s16 ogDrawId, PlayState* play);
-s16 GetChestGameRandoGiDrawId(s8 room, s16 ogDrawId, PlayState* play);
// ? func_80038600(?);
u16 DynaSSNodeList_GetNextNodeIdx(DynaSSNodeList*);
@@ -1104,6 +1105,7 @@ s32 FrameAdvance_Update(FrameAdvanceContext* frameAdvCtx, Input* input);
u8 PlayerGrounded(Player* player);
void Player_SetBootData(PlayState* play, Player* player);
s32 Player_InBlockingCsMode(PlayState* play, Player* player);
+s32 Player_TryCsAction(PlayState* play, Actor* actor, s32 csAction);
s32 Player_InCsMode(PlayState* play);
s32 func_8008E9C4(Player* player);
s32 Player_IsChildWithHylianShield(Player* player);
diff --git a/soh/include/variables.h b/soh/include/variables.h
index 692d1b2f8..5c97b26b8 100644
--- a/soh/include/variables.h
+++ b/soh/include/variables.h
@@ -97,6 +97,7 @@ extern "C"
extern u16 gEquipMasks[4];
extern u16 gEquipNegMasks[4];
extern u32 gUpgradeMasks[8];
+ extern u32 gUpgradeNegMasks[8];
extern u8 gEquipShifts[4];
extern u8 gUpgradeShifts[8];
extern u16 gUpgradeCapacities[8][4];
diff --git a/soh/include/z64.h b/soh/include/z64.h
index 97f68341d..36fe91d69 100644
--- a/soh/include/z64.h
+++ b/soh/include/z64.h
@@ -920,7 +920,10 @@ typedef struct {
/* 0x0266 */ u8 worldMapPoints[20]; // 0 = hidden; 1 = displayed; 2 = highlighted
/* 0x027A */ u8 tradeQuestLocation;
/* 0x027C */ SkelAnime playerSkelAnime;
-} PauseContext; // size = 0x2C0
+ // #region SOH [Randomizer]
+ /* 0x02C0 */ u8 randoQuestMode; // 0 = Off (normal quest menu); 1 = On (Misc Collectibles menu)
+ // #endregion
+} PauseContext; // size = 0x2C1
typedef enum {
/* 00 */ GAMEOVER_INACTIVE,
diff --git a/soh/include/z64actor.h b/soh/include/z64actor.h
index 82c8611d1..156f1d679 100644
--- a/soh/include/z64actor.h
+++ b/soh/include/z64actor.h
@@ -7,6 +7,8 @@
#include "z64collision_check.h"
#include "z64bgcheck.h"
#include "soh/Enhancements/item-tables/ItemTableTypes.h"
+#include "z64actor_enum.h"
+#include "soh/Enhancements/randomizer/randomizerTypes.h"
#define ACTOR_NUMBER_MAX 2000
#define INVISIBLE_ACTOR_MAX 20
@@ -264,7 +266,12 @@ typedef enum {
/* 0x17 */ ITEM00_TUNIC_ZORA,
/* 0x18 */ ITEM00_TUNIC_GORON,
/* 0x19 */ ITEM00_BOMBS_SPECIAL,
- /* 0x20 */ ITEM00_BOMBCHU,
+ /* 0x1A */ ITEM00_BOMBCHU,
+ /* 0x1B */ ITEM00_SOH_DUMMY,
+ /* 0x1C */ ITEM00_SOH_GIVE_ITEM_ENTRY,
+ /* 0x1D */ ITEM00_SOH_GIVE_ITEM_ENTRY_GI,
+ /* 0x1E */ ITEM00_MAX,
+ /* 0xFF */ ITEM00_NONE = 0xFF
} Item00Type;
struct EnItem00;
@@ -282,8 +289,13 @@ typedef struct EnItem00 {
/* 0x15A */ s16 unk_15A;
/* 0x15C */ f32 scale;
/* 0x160 */ ColliderCylinder collider;
- s16 ogParams;
+ // #region SOH [Randomizer]
GetItemEntry randoGiEntry;
+ RandomizerCheck randoCheck;
+ RandomizerInf randoInf;
+ /* */ s16 ogParams;
+ /* */ GetItemEntry itemEntry;
+ // #endregion
} EnItem00; // size = 0x1AC
// Only A_OBJ_SIGNPOST_OBLONG and A_OBJ_SIGNPOST_ARROW are used in room files.
@@ -337,22 +349,6 @@ typedef enum {
} ActorCategory;
//#define DEFINE_ACTOR(_0, enum, _2) enum,
-#define DEFINE_ACTOR_INTERNAL(_0, enum, _2) enum,
-#define DEFINE_ACTOR_UNSET(enum) enum,
-#define DEFINE_ACTOR(_0, enum, _2) DEFINE_ACTOR_INTERNAL(_0, enum, _2)
-
-#ifdef __cplusplus
-enum ActorID : int {
-#else
-enum ActorID {
-#endif
- #include "tables/actor_table.h"
- /* 0x0192 */ ACTOR_ID_MAX // originally "ACTOR_DLF_MAX"
-};
-
-#undef DEFINE_ACTOR
-#undef DEFINE_ACTOR_INTERNAL
-#undef DEFINE_ACTOR_UNSET
typedef enum {
DOORLOCK_NORMAL,
diff --git a/soh/include/z64actor_enum.h b/soh/include/z64actor_enum.h
new file mode 100644
index 000000000..1b0a6b986
--- /dev/null
+++ b/soh/include/z64actor_enum.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#ifndef Z64ACTOR_ENUM_H
+#define Z64ACTOR_ENUM_H
+
+#define DEFINE_ACTOR_INTERNAL(_0, enum, _2) enum,
+#define DEFINE_ACTOR_UNSET(enum) enum,
+#define DEFINE_ACTOR(_0, enum, _2) DEFINE_ACTOR_INTERNAL(_0, enum, _2)
+
+enum ActorID {
+#include "tables/actor_table.h"
+ /* 0x0192 */ ACTOR_ID_MAX // originally "ACTOR_DLF_MAX"
+};
+
+#undef DEFINE_ACTOR
+#undef DEFINE_ACTOR_INTERNAL
+#undef DEFINE_ACTOR_UNSET
+
+#endif
\ No newline at end of file
diff --git a/soh/include/z64item.h b/soh/include/z64item.h
index 1fc919a45..214d82711 100644
--- a/soh/include/z64item.h
+++ b/soh/include/z64item.h
@@ -311,6 +311,21 @@ typedef enum {
/* 0xFF */ ITEM_NONE = 0xFF
} ItemID;
+typedef enum {
+ EQUIP_FLAG_SWORD_KOKIRI = 1 << 0,
+ EQUIP_FLAG_SWORD_MASTER = 1 << 1,
+ EQUIP_FLAG_SWORD_BGS = 1 << 2,
+ EQUIP_FLAG_SHIELD_DEKU = 1 << 4,
+ EQUIP_FLAG_SHIELD_HYLIAN = 1 << 5,
+ EQUIP_FLAG_SHIELD_MIRROR = 1 << 6,
+ EQUIP_FLAG_TUNIC_KOKIRI = 1 << 8,
+ EQUIP_FLAG_TUNIC_GORON = 1 << 9,
+ EQUIP_FLAG_TUNIC_ZORA = 1 << 10,
+ EQUIP_FLAG_BOOTS_KOKIRI = 1 << 12,
+ EQUIP_FLAG_BOOTS_IRON = 1 << 13,
+ EQUIP_FLAG_BOOTS_HOVER = 1 << 14,
+} EquipmentFlag;
+
#define ITEM_TRADE_CHILD ITEM_WEIRD_EGG
#define ITEM_TRADE_ADULT ITEM_POCKET_EGG
@@ -372,7 +387,7 @@ typedef enum {
/* 0x35 */ GI_GAUNTLETS_SILVER,
/* 0x36 */ GI_GAUNTLETS_GOLD,
/* 0x37 */ GI_SCALE_SILVER,
- /* 0x38 */ GI_SCALE_GOLD,
+ /* 0x38 */ GI_SCALE_GOLDEN,
/* 0x39 */ GI_STONE_OF_AGONY,
/* 0x3A */ GI_GERUDO_CARD,
/* 0x3B */ GI_OCARINA_FAIRY, // uses Ocarina of Time message ID
@@ -574,6 +589,7 @@ typedef enum {
/* 0x7A */ GID_SONG_TIME,
/* 0x7B */ GID_SONG_STORM,
/* 0x7C */ GID_TRIFORCE_PIECE,
+ /* */ GID_FISHING_POLE,
/* 0x7C */ GID_MAXIMUM
} GetItemDrawID;
diff --git a/soh/include/z64save.h b/soh/include/z64save.h
index c7bee045f..1c2172563 100644
--- a/soh/include/z64save.h
+++ b/soh/include/z64save.h
@@ -68,6 +68,7 @@ typedef enum { // Pre-existing IDs for save sections in base code
SECTION_ID_STATS,
SECTION_ID_ENTRANCES,
SECTION_ID_SCENES,
+ SECTION_ID_TRACKER_DATA,
SECTION_ID_MAX
} SaveFuncIDs;
@@ -147,11 +148,6 @@ typedef struct {
/* 0x24 */ s32 tempCollectFlags;
} FaroresWindData; // size = 0x28
-typedef struct {
- RandomizerCheck check;
- RandomizerGetData get;
-} ItemLocationRando;
-
typedef struct {
RandomizerCheck check;
RandomizerCheck hintedCheck;
@@ -161,11 +157,6 @@ typedef struct {
char hintText[200];
} HintLocationRando;
-typedef struct {
- RandomizerSettingKey key;
- u8 value;
-} RandoSetting;
-
typedef struct {
/* 0x0000 */ s32 entranceIndex; // start of `save` substruct, originally called "memory"
/* 0x0004 */ s32 linkAge; // 0: Adult; 1: Child (see enum `LinkAge`)
@@ -285,42 +276,14 @@ typedef struct {
/* */ u16 pendingSaleMod;
/* */ uint8_t questId;
/* */ uint32_t isBossRushPaused;
- /* */ uint8_t bossRushOptions[BOSSRUSH_OPTIONS_AMOUNT];
+ /* */ uint8_t bossRushOptions[BR_OPTIONS_MAX];
/* */ u8 pendingIceTrapCount;
/* */ SohStats sohStats;
/* */ FaroresWindData backupFW;
- /* */ RandomizerCheckTrackerData checkTrackerData[RC_MAX];
// #endregion
// #region SOH [Randomizer]
// Upstream TODO: Move these to their own struct or name to more obviously specific to Randomizer
- /* */ RandoSetting randoSettings[300];
- /* */ ItemLocationRando itemLocations[RC_MAX];
- /* */ HintLocationRando hintLocations[50];
- /* */ EntranceOverride entranceOverrides[ENTRANCE_OVERRIDES_MAX_COUNT];
- /* */ char childAltarText[250];
- /* */ char adultAltarText[750];
- /* */ RandomizerCheck rewardCheck[9];
- /* */ char ganonHintText[300];
- /* */ char gregHintText[250];
- /* */ char ganonText[250];
- /* */ char dampeText[150];
- /* */ char sheikText[200];
- /* */ char sariaText[150];
- /* */ char warpMinuetText[100];
- /* */ char warpBoleroText[100];
- /* */ char warpSerenadeText[100];
- /* */ char warpRequiemText[100];
- /* */ char warpNocturneText[100];
- /* */ char warpPreludeText[100];
- /* */ RandomizerCheck masterSwordHintCheck;
- /* */ RandomizerCheck lightArrowHintCheck;
- /* */ RandomizerCheck sariaCheck;
- /* */ RandomizerCheck gregCheck;
- /* */ RandomizerCheck dampeCheck;
- /* */ char inputSeed[1024];
- /* */ u32 finalSeed;
- /* */ u8 seedIcons[5];
- /* */ u16 randomizerInf[10];
+ /* */ u16 randomizerInf[17];
/* */ u8 mqDungeonCount;
/* */ u16 adultTradeItems;
/* */ u8 triforcePiecesCollected;
@@ -363,6 +326,18 @@ typedef enum {
/* 0x06 */ HS_DAMPE_RACE
} HighScores;
+// the score value for the fishing minigame also stores many flags.
+#define HS_FISH_LENGTH_CHILD 0x7F // mask for record length of catch as child.
+#define HS_FISH_LENGTH_ADULT 0x7F000000 // mask for record length of catch as adult.
+#define HS_FISH_PLAYED_CHILD 0x100 // set when first talking to owner as child
+#define HS_FISH_PLAYED_ADULT 0x200 // set when first talking to owner as adult
+#define HS_FISH_PRIZE_CHILD 0x400 // won the Piece of Heart
+#define HS_FISH_PRIZE_ADULT 0x800 // won the Golden Scale
+#define HS_FISH_STOLE_HAT 0x1000 // Pond owner is visibly bald as Adult Link.
+#define HS_FISH_CHEAT_CHILD 0x80 // used Sinking Lure as child to catch record fish
+#define HS_FISH_CHEAT_ADULT 0x80000000 // used Sinking Lure as adult to catch record fish
+#define HS_FISH_PLAYED 0x10000 // incremented for every play. controls weather.
+
typedef enum {
/* 0 */ SUNSSONG_INACTIVE,
/* 1 */ SUNSSONG_START, // the suns ocarina effect signals that the song has finished playing
diff --git a/soh/include/z64scene.h b/soh/include/z64scene.h
index 1c2267a05..697c97503 100644
--- a/soh/include/z64scene.h
+++ b/soh/include/z64scene.h
@@ -2,6 +2,8 @@
#define Z64SCENE_H
#include "command_macros_base.h"
+#include "libultraship/libultra.h"
+#include "z64math.h"
typedef struct {
/* 0x00 */ uintptr_t vromStart;
diff --git a/soh/include/z64scene_enum.h b/soh/include/z64scene_enum.h
new file mode 100644
index 000000000..068d1c42f
--- /dev/null
+++ b/soh/include/z64scene_enum.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#ifndef Z64SCENE_ENUM_H
+#define Z64SCENE_ENUM_H
+#define DEFINE_SCENE(_0, _1, enum, _3, _4, _5) enum,
+
+#ifdef __cplusplus
+enum SceneID : int {
+#else
+enum SceneID {
+#endif
+#include "tables/scene_table.h"
+ /* 0x6E */ SCENE_ID_MAX
+};
+
+#undef DEFINE_SCENE
+#endif
\ No newline at end of file
diff --git a/soh/soh/Enhancements/TimeSavers/FasterHeavyBlockLift.cpp b/soh/soh/Enhancements/TimeSavers/FasterHeavyBlockLift.cpp
new file mode 100644
index 000000000..73909cbda
--- /dev/null
+++ b/soh/soh/Enhancements/TimeSavers/FasterHeavyBlockLift.cpp
@@ -0,0 +1,58 @@
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
+#include "soh/OTRGlobals.h"
+#include "spdlog/spdlog.h"
+
+extern "C" {
+ #include "z64save.h"
+ #include "macros.h"
+ #include "variables.h"
+ #include "functions.h"
+ extern PlayState* gPlayState;
+ extern SaveContext gSaveContext;
+}
+
+/**
+ * This primarily handles speeding up the heavy block lifts (OGC and in the Fire Trial) but also handles skipping
+ * the one point cutscene since the two options are so similar in what they do.
+ */
+void FasterHeavyBlockLift_Register() {
+ REGISTER_VB_SHOULD(VB_PLAY_ONEPOINT_ACTOR_CS, {
+ Actor* actor = va_arg(args, Actor*);
+
+ if (
+ actor->id == ACTOR_BG_HEAVY_BLOCK &&
+ (CVarGetInteger(CVAR_ENHANCEMENT("FasterHeavyBlockLift"), 0) || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO))
+ ) {
+ *should = false;
+ }
+ });
+
+ REGISTER_VB_SHOULD(VB_FREEZE_LINK_FOR_BLOCK_THROW, {
+ if (CVarGetInteger(CVAR_ENHANCEMENT("FasterHeavyBlockLift"), 0) || CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.OnePoint"), IS_RANDO)) {
+ *should = false;
+ }
+ });
+
+ REGISTER_VB_SHOULD(VB_PLAY_THROW_ANIMATION, {
+ Player *player = GET_PLAYER(gPlayState);
+ Actor* interactRangeActor = player->interactRangeActor;
+ s32 interactActorId = interactRangeActor->id;
+ LinkAnimationHeader* anim = va_arg(args, LinkAnimationHeader*);
+
+ // Same actor is used for small and large silver rocks, use actor params to identify large ones
+ bool isLargeSilverRock = interactActorId == ACTOR_EN_ISHI && interactRangeActor->params & 1 == 1;
+ if (CVarGetInteger(CVAR_ENHANCEMENT("FasterHeavyBlockLift"), 0) && (isLargeSilverRock || interactActorId == ACTOR_BG_HEAVY_BLOCK)) {
+ *should = false;
+ LinkAnimation_PlayOnceSetSpeed(gPlayState, &player->skelAnime, anim, 5.0f);
+ }
+ });
+
+ REGISTER_VB_SHOULD(VB_MOVE_THROWN_ACTOR, {
+ if (CVarGetInteger(CVAR_ENHANCEMENT("FasterHeavyBlockLift"), 0)) {
+ Actor* heldActor = va_arg(args, Actor*);
+
+ heldActor->shape.rot.x -= 3510;
+ }
+ });
+}
diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/SkipIntro.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/SkipIntro.cpp
new file mode 100644
index 000000000..aca290f79
--- /dev/null
+++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/SkipIntro.cpp
@@ -0,0 +1,48 @@
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
+#include "soh/OTRGlobals.h"
+
+extern "C" {
+#include "z64save.h"
+#include "functions.h"
+#include "soh/Enhancements/randomizer/randomizer_entrance.h"
+extern PlayState* gPlayState;
+extern SaveContext gSaveContext;
+}
+
+void SkipIntro_Register() {
+ REGISTER_VB_SHOULD(VB_PLAY_TRANSITION_CS, {
+ // If we're playing rando and if starting age is adult and/or overworld spawns are shuffled we need to skip
+ // the cutscene regardless of the enhancement being on.
+ bool adultStart = gSaveContext.linkAge == LINK_AGE_ADULT;
+ bool shuffleOverworldSpawns =
+ OTRGlobals::Instance->gRandoContext->GetOption(RSK_SHUFFLE_OVERWORLD_SPAWNS).Is(true);
+ if ((CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Intro"), IS_RANDO) ||
+ (IS_RANDO && (adultStart || shuffleOverworldSpawns))) &&
+ gSaveContext.cutsceneIndex == 0xFFF1) {
+ // Calculate spawn location. Start with vanilla, Link's house.
+ int32_t spawnEntrance = ENTR_LINKS_HOUSE_0;
+ // If we're not in rando, we can skip all of the below.
+ if (IS_RANDO) {
+ // If starting age is shuffled, use vanilla adult spawn/prelude warp.
+ if (adultStart) {
+ spawnEntrance = ENTR_TEMPLE_OF_TIME_7;
+ }
+ // If we're shuffling overworld spawns we'll need to get the Entrance Override
+ if (shuffleOverworldSpawns) {
+ // If we're shuffling overworld spawns the adult spawn is ENTR_HYRULE_FIELD_10 instead of
+ // ENTR_TEMPLE_OF_TIME_7, so that spawn and Prelude don't share an entrance.
+ if (adultStart) {
+ spawnEntrance = ENTR_HYRULE_FIELD_10;
+ }
+ spawnEntrance = Entrance_PeekNextIndexOverride(spawnEntrance);
+ }
+ }
+ // Skip the intro cutscene for whatever the spawnEntrance is calculated to be.
+ if (gSaveContext.entranceIndex == spawnEntrance) {
+ gSaveContext.cutsceneIndex = 0;
+ *should = false;
+ }
+ }
+ });
+}
diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp
new file mode 100644
index 000000000..748982843
--- /dev/null
+++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipBlueWarp.cpp
@@ -0,0 +1,148 @@
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
+#include "soh/OTRGlobals.h"
+
+extern "C" {
+ #include "macros.h"
+ #include "src/overlays/actors/ovl_En_Ko/z_en_ko.h"
+ #include "z64save.h"
+ #include "functions.h"
+ #include "variables.h"
+}
+
+#define RAND_GET_OPTION(option) Rando::Context::GetInstance()->GetOption(option).GetSelectedOptionIndex()
+
+/**
+ * This will override the transitions into the blue warp cutscenes, set any appropriate flags, and
+ * set the entrance index to where you would normally end up after the blue warp cutscene. This
+ * should also account for the difference between your first and following visits to the blue warp.
+ */
+void SkipBlueWarp_ShouldPlayTransitionCS(GIVanillaBehavior _, bool* should, va_list originalArgs) {
+ if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) {
+ uint8_t isBlueWarpCutscene = 0;
+ // Deku Tree Blue warp
+ if (gSaveContext.entranceIndex == ENTR_KOKIRI_FOREST_0 && gSaveContext.cutsceneIndex == 0xFFF1) {
+ gSaveContext.entranceIndex = ENTR_KOKIRI_FOREST_11;
+ isBlueWarpCutscene = 1;
+ // Dodongo's Cavern Blue warp
+ } else if (gSaveContext.entranceIndex == ENTR_DEATH_MOUNTAIN_TRAIL_0 && gSaveContext.cutsceneIndex == 0xFFF1) {
+ gSaveContext.entranceIndex = ENTR_DEATH_MOUNTAIN_TRAIL_5;
+ isBlueWarpCutscene = 1;
+ // Jabu Jabu's Blue warp
+ } else if (gSaveContext.entranceIndex == ENTR_ZORAS_FOUNTAIN_0 && gSaveContext.cutsceneIndex == 0xFFF0) {
+ gSaveContext.entranceIndex = ENTR_ZORAS_FOUNTAIN_0;
+ isBlueWarpCutscene = 1;
+ // Forest Temple Blue warp
+ } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_FOREST) {
+ // Normally set in the blue warp cutscene
+ Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_DEKU_TREE_SPROUT);
+
+ if (IS_RANDO) {
+ gSaveContext.entranceIndex = ENTR_SACRED_FOREST_MEADOW_3;
+ } else {
+ gSaveContext.entranceIndex = ENTR_KOKIRI_FOREST_12;
+ }
+
+ isBlueWarpCutscene = 1;
+ // Fire Temple Blue warp
+ } else if (gSaveContext.entranceIndex == ENTR_KAKARIKO_VILLAGE_0 && gSaveContext.cutsceneIndex == 0xFFF3) {
+ // Normally set in the blue warp cutscene
+ Flags_SetEventChkInf(EVENTCHKINF_DEATH_MOUNTAIN_ERUPTED);
+
+ gSaveContext.entranceIndex = ENTR_DEATH_MOUNTAIN_CRATER_5;
+ isBlueWarpCutscene = 1;
+ // Water Temple Blue warp
+ } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_WATER) {
+ // Normally set in the blue warp cutscene
+ gSaveContext.dayTime = gSaveContext.skyboxTime = 0x4800;
+ Flags_SetEventChkInf(EVENTCHKINF_RAISED_LAKE_HYLIA_WATER);
+
+ gSaveContext.entranceIndex = ENTR_LAKE_HYLIA_9;
+ isBlueWarpCutscene = 1;
+ // Spirit Temple Blue warp
+ } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_SPIRIT) {
+ gSaveContext.entranceIndex = ENTR_DESERT_COLOSSUS_8;
+ isBlueWarpCutscene = 1;
+ // Shadow Temple Blue warp
+ } else if (gSaveContext.entranceIndex == ENTR_CHAMBER_OF_THE_SAGES_0 && gSaveContext.cutsceneIndex == 0x0 && gSaveContext.chamberCutsceneNum == CHAMBER_CS_SHADOW) {
+ gSaveContext.entranceIndex = ENTR_GRAVEYARD_8;
+ isBlueWarpCutscene = 1;
+ }
+
+ if (isBlueWarpCutscene) {
+ if (gSaveContext.entranceIndex != ENTR_LAKE_HYLIA_9) {
+ // Normally set in the blue warp cutscene
+ gSaveContext.dayTime = gSaveContext.skyboxTime = 0x8000;
+ }
+
+ *should = false;
+ gSaveContext.cutsceneIndex = 0;
+ }
+
+ // This is outside the above condition because we want to handle both first and following visits to the blue warp
+ if (IS_RANDO && (RAND_GET_OPTION(RSK_SHUFFLE_DUNGEON_ENTRANCES) != RO_DUNGEON_ENTRANCE_SHUFFLE_OFF || RAND_GET_OPTION(RSK_SHUFFLE_BOSS_ENTRANCES) != RO_BOSS_ROOM_ENTRANCE_SHUFFLE_OFF)) {
+ Entrance_OverrideBlueWarp();
+ }
+ }
+}
+
+/**
+ * While we could rely on the Item_Give that's normally called, it's not very clear to the player that they
+ * received the item when skipping the blue warp cutscene, so we'll prevent that and queue it up to be given
+ * to the player instead.
+ */
+void SkipBlueWarp_ShouldGiveItem(GIVanillaBehavior _, bool* should, va_list originalArgs) {
+ if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) {
+ *should = false;
+ }
+}
+
+// Todo: Move item queueing here
+
+/**
+ * This ensures the Kokiri blocking the forest exit checks if you are eligible to leave the forest
+ * every frame, instead of only at init. The reason we need to do this is when we skip the blue warp cutscene
+ * you end up getting the Kokiri Emerald after the actor has init'd, so the actor doesn't know you have it
+ */
+void EnKo_MoveWhenReady(EnKo* enKo, PlayState* play) {
+ func_80A995CC(enKo, play);
+
+ if ((enKo->actor.params & 0xFF) == ENKO_TYPE_CHILD_3) {
+ if (GameInteractor_Should(VB_OPEN_KOKIRI_FOREST, CHECK_QUEST_ITEM(QUEST_KOKIRI_EMERALD))) {
+ enKo->collider.dim.height -= 200;
+ Path_CopyLastPoint(enKo->path, &enKo->actor.world.pos);
+ enKo->actionFunc = func_80A99384;
+ }
+ }
+}
+
+void SkipBlueWarp_OnActorUpdate(void* actorPtr) {
+ EnKo* enKo = static_cast(actorPtr);
+
+ if (
+ (enKo->actor.params & 0xFF) == ENKO_TYPE_CHILD_3 &&
+ enKo->actionFunc == func_80A995CC &&
+ CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)
+ ) {
+ enKo->actionFunc = EnKo_MoveWhenReady;
+ }
+}
+
+/**
+ * This will ensure that the Deku Tree Sprout considers the Forest Temple finished when you skip the blue warp cutscene.
+ * Typically this checks for if you have the medallion, and when skipping the cutscene at this point you don't have it yet.
+ */
+void SkipBlueWarp_ShouldDekuJrConsiderForestTempleFinished(GIVanillaBehavior _, bool* should, va_list originalArgs) {
+ if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) {
+ if (gSaveContext.entranceIndex == ENTR_KOKIRI_FOREST_11 && gSaveContext.cutsceneIndex == 0xFFF1) {
+ *should = Flags_GetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP);
+ }
+ }
+}
+
+void SkipBlueWarp_Register() {
+ GameInteractor::Instance->RegisterGameHookForID(ACTOR_EN_KO, SkipBlueWarp_OnActorUpdate);
+ GameInteractor::Instance->RegisterGameHookForID(VB_PLAY_TRANSITION_CS, SkipBlueWarp_ShouldPlayTransitionCS);
+ GameInteractor::Instance->RegisterGameHookForID(VB_DEKU_JR_CONSIDER_FOREST_TEMPLE_FINISHED, SkipBlueWarp_ShouldDekuJrConsiderForestTempleFinished);
+ GameInteractor::Instance->RegisterGameHookForID(VB_GIVE_ITEM_FROM_BLUE_WARP, SkipBlueWarp_ShouldGiveItem);
+}
diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipDekuTreeIntro.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipDekuTreeIntro.cpp
new file mode 100644
index 000000000..7000aa391
--- /dev/null
+++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipDekuTreeIntro.cpp
@@ -0,0 +1,22 @@
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
+#include "soh/OTRGlobals.h"
+
+extern "C" {
+ #include "src/overlays/actors/ovl_Bg_Treemouth/z_bg_treemouth.h"
+}
+
+/**
+ * This will skip the Deku Tree intro, and simply open the mouth as you approach it.
+*/
+void SkipDekuTreeIntro_Register() {
+ REGISTER_VB_SHOULD(VB_PLAY_DEKU_TREE_INTRO_CS, {
+ if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) {
+ BgTreemouth* treeMouth = va_arg(args, BgTreemouth*);
+ Flags_SetEventChkInf(EVENTCHKINF_DEKU_TREE_OPENED_MOUTH);
+ Audio_PlaySoundGeneral(NA_SE_EV_WOODDOOR_OPEN, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
+ BgTreemouth_SetupAction(treeMouth, func_808BC6F8);
+ *should = false;
+ }
+ });
+}
diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipLostWoodsBridge.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipLostWoodsBridge.cpp
new file mode 100644
index 000000000..7fa7b99f6
--- /dev/null
+++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipLostWoodsBridge.cpp
@@ -0,0 +1,39 @@
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
+#include "soh/OTRGlobals.h"
+
+extern "C" {
+ #include "z64save.h"
+ #include "functions.h"
+ extern PlayState* gPlayState;
+ extern SaveContext gSaveContext;
+}
+
+void SkipLostWoodsBridge_Register() {
+ /**
+ * This skips the cutscene where you speak to Saria on the bridge in Lost Woods, where she gives you the Fairy Ocarina.
+ */
+ REGISTER_VB_SHOULD(VB_PLAY_TRANSITION_CS, {
+ if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) {
+ if ((gSaveContext.entranceIndex == ENTR_LOST_WOODS_9) && !Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE)) {
+ Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_SARIA_ON_BRIDGE);
+ if (GameInteractor_Should(VB_GIVE_ITEM_FAIRY_OCARINA, true)) {
+ Item_Give(gPlayState, ITEM_OCARINA_FAIRY);
+ }
+ *should = false;
+ }
+ }
+ });
+
+ /**
+ * While we could rely on the Item_Give that's normally called (and that we have above), it's not very clear to the player
+ * that they received the item when skipping the cutscene, so we'll prevent it, and queue it up to be given instead.
+ */
+ REGISTER_VB_SHOULD(VB_GIVE_ITEM_FAIRY_OCARINA, {
+ if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) {
+ *should = false;
+ }
+ });
+
+ // Todo: Move item queueing here
+}
diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipToGivingZeldasLetter.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipToGivingZeldasLetter.cpp
new file mode 100644
index 000000000..357125db8
--- /dev/null
+++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipToGivingZeldasLetter.cpp
@@ -0,0 +1,45 @@
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
+#include "soh/OTRGlobals.h"
+
+extern "C" {
+ #include "src/overlays/actors/ovl_En_Zl4/z_en_zl4.h"
+}
+
+/**
+ * This overrides Zelda's update function to effectively skip the dialog and cutscenes played when
+ * you meet with her in Hyrule Castle Courtyard. As you approach her she will turn around, and talking
+ * with her will place you at the very last dialog option where she gives you the letter.
+ */
+
+u16 EnZl4_GiveItemTextId(PlayState* play, Actor* actor) {
+ return 0x207D;
+}
+
+void EnZl4_SkipToGivingZeldasLetter(EnZl4* enZl4, PlayState* play) {
+ if (enZl4->csState == 0 && enZl4->actor.xzDistToPlayer < 700.0f && EnZl4_SetNextAnim(enZl4, 3)) {
+ Audio_PlayFanfare(NA_BGM_APPEAR);
+ enZl4->csState = 8; // ZL4_CS_PLAN
+ } else {
+ Npc_UpdateTalking(play, &enZl4->actor, &enZl4->interactInfo.talkState, enZl4->collider.dim.radius + 60.0f, EnZl4_GiveItemTextId, func_80B5B9B0);
+ func_80B5BB78(enZl4, play);
+
+ if (enZl4->interactInfo.talkState != NPC_TALK_STATE_IDLE) {
+ enZl4->talkState = 6;
+ enZl4->actionFunc = EnZl4_Cutscene;
+ }
+ }
+}
+
+void SkipToGivingZeldasLetter_OnActorInit(void* actorPtr) {
+ if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) {
+ EnZl4* enZl4 = static_cast(actorPtr);
+ if (enZl4->actionFunc != EnZl4_Cutscene || enZl4->csState != 0) return;
+
+ enZl4->actionFunc = EnZl4_SkipToGivingZeldasLetter;
+ }
+}
+
+void SkipToGivingZeldasLetter_Register() {
+ GameInteractor::Instance->RegisterGameHookForID(ACTOR_EN_ZL4, SkipToGivingZeldasLetter_OnActorInit);
+}
diff --git a/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipZeldaFleeingCastle.cpp b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipZeldaFleeingCastle.cpp
new file mode 100644
index 000000000..fe578c831
--- /dev/null
+++ b/soh/soh/Enhancements/TimeSavers/SkipCutscene/Story/SkipZeldaFleeingCastle.cpp
@@ -0,0 +1,67 @@
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
+#include "soh/OTRGlobals.h"
+
+extern "C" {
+ #include "z64save.h"
+ #include "functions.h"
+ extern SaveContext gSaveContext;
+}
+
+void SkipZeldaFleeingCastle_ShouldPlayTransitionCS(GIVanillaBehavior _, bool* should, va_list originalArgs) {
+ if (CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)) {
+ if (gSaveContext.entranceIndex == ENTR_HYRULE_FIELD_0 && gSaveContext.cutsceneIndex == 0xFFF1) {
+ // Normally set in the cutscene
+ gSaveContext.dayTime = gSaveContext.skyboxTime = 0x4AAA;
+
+ gSaveContext.cutsceneIndex = 0;
+ *should = false;
+ }
+ }
+}
+
+/**
+ * When this cutscene is skipped, walking up to the bridge to castle town triggers a quick fade in/out
+ * which can be confusing to beginners, because they need to then fetch the Ocarina of Time from the water.
+ * To make it more obvious what happened, we'll play the sound of the Ocarina dropping into the water.
+ */
+static int framesSinceSpawn = 0;
+static HOOK_ID itemOcarinaUpdateHook = 0;
+static HOOK_ID sceneInitHook = 0;
+
+void SkipZeldaFleeingCastle_OnActorUpdate(void* actorPtr) {
+ Actor* actor = static_cast(actorPtr);
+
+ framesSinceSpawn++;
+ if (framesSinceSpawn > 20) {
+ Audio_PlayActorSound2(actor, NA_SE_EV_BOMB_DROP_WATER);
+
+ GameInteractor::Instance->UnregisterGameHookForPtr(itemOcarinaUpdateHook);
+ GameInteractor::Instance->UnregisterGameHook(sceneInitHook);
+ itemOcarinaUpdateHook = 0;
+ sceneInitHook = 0;
+ }
+}
+
+void SkipZeldaFleeingCastle_OnActorInit(void* actorPtr) {
+ Actor* actor = static_cast(actorPtr);
+
+ if (
+ actor->params == 3 &&
+ CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipCutscene.Story"), IS_RANDO)
+ ) {
+ framesSinceSpawn = 0;
+ itemOcarinaUpdateHook = GameInteractor::Instance->RegisterGameHookForPtr((uintptr_t)actorPtr, SkipZeldaFleeingCastle_OnActorUpdate);
+ sceneInitHook = GameInteractor::Instance->RegisterGameHook([] (int16_t sceneNum) {
+ GameInteractor::Instance->UnregisterGameHookForPtr(itemOcarinaUpdateHook);
+ GameInteractor::Instance->UnregisterGameHook(sceneInitHook);
+ itemOcarinaUpdateHook = 0;
+ sceneInitHook = 0;
+ });
+ }
+}
+
+void SkipZeldaFleeingCastle_Register() {
+ GameInteractor::Instance->RegisterGameHookForID(ACTOR_ITEM_OCARINA, SkipZeldaFleeingCastle_OnActorInit);
+ GameInteractor::Instance->RegisterGameHookForID(VB_PLAY_TRANSITION_CS, SkipZeldaFleeingCastle_ShouldPlayTransitionCS);
+}
diff --git a/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/MoveMidoInKokiriForest.cpp b/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/MoveMidoInKokiriForest.cpp
new file mode 100644
index 000000000..78a680175
--- /dev/null
+++ b/soh/soh/Enhancements/TimeSavers/SkipMiscInteractions/MoveMidoInKokiriForest.cpp
@@ -0,0 +1,30 @@
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
+#include "soh/OTRGlobals.h"
+
+extern "C" {
+ #include "z64save.h"
+ #include "macros.h"
+ #include "variables.h"
+ #include "functions.h"
+ extern PlayState* gPlayState;
+ extern SaveContext gSaveContext;
+}
+
+/**
+ * This simply skips the Mido interaction in Kokiri Forest, once you equip the Kokiri
+ * Sword and Deku Shield he will move out of the way without you needing to talk to him.
+ */
+void MoveMidoInKokiriForest_Register() {
+ REGISTER_VB_SHOULD(VB_MOVE_MIDO_IN_KOKIRI_FOREST, {
+ if (
+ CVarGetInteger(CVAR_ENHANCEMENT("TimeSavers.SkipMiscInteractions"), IS_RANDO) &&
+ !Flags_GetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD) &&
+ (CUR_EQUIP_VALUE(EQUIP_TYPE_SHIELD) == EQUIP_VALUE_SHIELD_DEKU) &&
+ (CUR_EQUIP_VALUE(EQUIP_TYPE_SWORD) == EQUIP_VALUE_SWORD_KOKIRI)
+ ) {
+ Flags_SetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD);
+ *should = true;
+ }
+ });
+}
diff --git a/soh/soh/Enhancements/TimeSavers/TimeSavers.cpp b/soh/soh/Enhancements/TimeSavers/TimeSavers.cpp
new file mode 100644
index 000000000..68fe71da6
--- /dev/null
+++ b/soh/soh/Enhancements/TimeSavers/TimeSavers.cpp
@@ -0,0 +1,15 @@
+#include "TimeSavers.h"
+
+void TimeSavers_Register() {
+ // SkipCutscene
+ // Story
+ SkipBlueWarp_Register();
+ SkipDekuTreeIntro_Register();
+ SkipLostWoodsBridge_Register();
+ SkipToGivingZeldasLetter_Register();
+ SkipZeldaFleeingCastle_Register();
+ SkipIntro_Register();
+ // SkipMiscInteractions
+ MoveMidoInKokiriForest_Register();
+ FasterHeavyBlockLift_Register();
+}
diff --git a/soh/soh/Enhancements/TimeSavers/TimeSavers.h b/soh/soh/Enhancements/TimeSavers/TimeSavers.h
new file mode 100644
index 000000000..d45ed7f10
--- /dev/null
+++ b/soh/soh/Enhancements/TimeSavers/TimeSavers.h
@@ -0,0 +1,18 @@
+#ifndef TIME_SAVERS_H
+#define TIME_SAVERS_H
+
+void TimeSavers_Register();
+
+// SkipCutscene
+ // Story
+ void SkipBlueWarp_Register();
+ void SkipDekuTreeIntro_Register();
+ void SkipLostWoodsBridge_Register();
+ void SkipToGivingZeldasLetter_Register();
+ void SkipZeldaFleeingCastle_Register();
+ void SkipIntro_Register();
+// SkipMiscInteractions
+ void MoveMidoInKokiriForest_Register();
+void FasterHeavyBlockLift_Register();
+
+#endif // TIME_SAVERS_H
diff --git a/soh/soh/Enhancements/boss-rush/BossRush.cpp b/soh/soh/Enhancements/boss-rush/BossRush.cpp
index 63414f88c..c83078581 100644
--- a/soh/soh/Enhancements/boss-rush/BossRush.cpp
+++ b/soh/soh/Enhancements/boss-rush/BossRush.cpp
@@ -1,19 +1,33 @@
#include "BossRush.h"
#include "soh/OTRGlobals.h"
-#include "functions.h"
-#include "macros.h"
-#include "variables.h"
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include
#include
#include
+extern "C" {
+ #include "functions.h"
+ #include "macros.h"
+ #include "variables.h"
+ #include "src/overlays/actors/ovl_Boss_Goma/z_boss_goma.h"
+ #include "src/overlays/actors/ovl_Boss_Mo/z_boss_mo.h"
+ #include "src/overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
+ extern PlayState* gPlayState;
+
+ Gfx* KaleidoScope_QuadTextureIA8(Gfx* gfx, void* texture, s16 width, s16 height, u16 point);
+ #include "textures/icon_item_nes_static/icon_item_nes_static.h"
+ #include "textures/icon_item_ger_static/icon_item_ger_static.h"
+ #include "textures/icon_item_fra_static/icon_item_fra_static.h"
+}
+
typedef struct BossRushSetting {
std::array name;
std::vector> choices;
} BossRushSetting;
-BossRushSetting BossRushOptions[BOSSRUSH_OPTIONS_AMOUNT] = {
+BossRushSetting BossRushOptions[BR_OPTIONS_MAX] = {
{
{ "BOSSES:", "BOSSE:", "BOSS:" },
{
@@ -112,15 +126,15 @@ BossRushSetting BossRushOptions[BOSSRUSH_OPTIONS_AMOUNT] = {
}
};
-const char* BossRush_GetSettingName(uint8_t optionIndex, uint8_t language) {
+const char* BossRush_GetSettingName(u8 optionIndex, u8 language) {
return BossRushOptions[optionIndex].name[language].c_str();
}
-const char* BossRush_GetSettingChoiceName(uint8_t optionIndex, uint8_t choiceIndex, uint8_t language) {
+const char* BossRush_GetSettingChoiceName(u8 optionIndex, u8 choiceIndex, u8 language) {
return BossRushOptions[optionIndex].choices[choiceIndex][language].c_str();
}
-uint8_t BossRush_GetSettingOptionsAmount(uint8_t optionIndex) {
+u8 BossRush_GetSettingOptionsAmount(u8 optionIndex) {
return BossRushOptions[optionIndex].choices.size();
}
@@ -129,15 +143,15 @@ void BossRush_SpawnBlueWarps(PlayState* play) {
// Spawn blue warps in Chamber of Sages based on what bosses have been defeated.
if (gSaveContext.linkAge == LINK_AGE_CHILD) {
// Forest Medallion (Gohma)
- if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_DEKU_TREE)) {
+ if (!Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, -100, 6, -170, 0, 0, 0, -1, false);
}
// Fire Medallion (King Dodongo)
- if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_DODONGOS_CAVERN)) {
+ if (!Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 100, 6, -170, 0, 0, 0, -1, false);
}
// Water Medallion (Barinade)
- if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_JABU_JABUS_BELLY)) {
+ if (!Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 199, 6, 0, 0, 0, 0, -1, false);
}
} else {
@@ -146,15 +160,15 @@ void BossRush_SpawnBlueWarps(PlayState* play) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, -199, 6, 0, 0, 0, 0, -1, false);
}
// Forest Medallion (Phantom Ganondorf)
- if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_FOREST_TEMPLE)) {
+ if (!Flags_GetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, -100, 6, -170, 0, 0, 0, -1, false);
}
// Fire Medallion (Volvagia)
- if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_FIRE_TEMPLE)) {
+ if (!Flags_GetEventChkInf(EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 100, 6, -170, 0, 0, 0, -1, false);
}
// Water Medallion (Morpha)
- if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_WATER_TEMPLE)) {
+ if (!Flags_GetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 199, 6, 0, 0, 0, 0, -1, false);
}
// Spirit Medallion (Twinrova)
@@ -168,8 +182,44 @@ void BossRush_SpawnBlueWarps(PlayState* play) {
}
}
-void BossRush_HandleBlueWarp(PlayState* play, f32 warpPosX, f32 warpPosZ) {
+void BossRush_SetEquipment(u8 linkAge) {
+ std::array brButtonItems;
+ std::array brCButtonSlots;
+ // Set Child Equipment.
+ if (linkAge == LINK_AGE_CHILD) {
+ brButtonItems = {
+ ITEM_SWORD_KOKIRI, ITEM_STICK, ITEM_NUT, ITEM_BOMB, ITEM_NONE, ITEM_NONE, ITEM_NONE, ITEM_NONE
+ };
+
+ brCButtonSlots = { SLOT_STICK, SLOT_NUT, SLOT_BOMB, SLOT_NONE, SLOT_NONE, SLOT_NONE, SLOT_NONE };
+
+ Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_KOKIRI);
+ Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_DEKU);
+ // Set Adult equipment.
+ } else {
+ brButtonItems = { ITEM_SWORD_MASTER, ITEM_BOW, ITEM_HAMMER, ITEM_BOMB,
+ ITEM_NONE, ITEM_NONE, ITEM_NONE, ITEM_NONE };
+
+ brCButtonSlots = { SLOT_BOW, SLOT_HAMMER, SLOT_BOMB, SLOT_NONE, SLOT_NONE, SLOT_NONE, SLOT_NONE };
+
+ Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER);
+ Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_MIRROR);
+ Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_GORON);
+ }
+
+ // Button Items
+ for (int button = 0; button < ARRAY_COUNT(gSaveContext.equips.buttonItems); button++) {
+ gSaveContext.equips.buttonItems[button] = brButtonItems[button];
+ }
+
+ // C buttons
+ for (int button = 0; button < ARRAY_COUNT(gSaveContext.equips.cButtonSlots); button++) {
+ gSaveContext.equips.cButtonSlots[button] = brCButtonSlots[button];
+ }
+}
+
+void BossRush_HandleBlueWarp(PlayState* play, f32 warpPosX, f32 warpPosZ) {
// If warping from Chamber of Sages, choose the correct boss room to teleport to.
if (play->sceneNum == SCENE_CHAMBER_OF_THE_SAGES) {
// Gohma & Phantom Ganon
@@ -202,10 +252,13 @@ void BossRush_HandleBlueWarp(PlayState* play, f32 warpPosX, f32 warpPosZ) {
// Ganondork
} else if (warpPosX == -199 && warpPosZ == 0) {
play->nextEntranceIndex = ENTR_GANONDORF_BOSS_0;
+ } else {
+ SPDLOG_ERROR("[BossRush]: Unknown blue warp in chamber of sages at position ({}, {}). Warping back to chamber of sages.", warpPosX, warpPosZ);
+ play->nextEntranceIndex = ENTR_CHAMBER_OF_THE_SAGES_0;
}
// If coming from a boss room, teleport back to Chamber of Sages and set flag.
} else {
- play->nextEntranceIndex = SCENE_HAIRAL_NIWA2;
+ play->nextEntranceIndex = ENTR_CHAMBER_OF_THE_SAGES_0;
if (CheckDungeonCount() == 3) {
play->linkAgeOnLoad = LINK_AGE_ADULT;
@@ -223,6 +276,10 @@ void BossRush_HandleBlueWarp(PlayState* play, f32 warpPosX, f32 warpPosZ) {
}
}
}
+
+ play->transitionTrigger = TRANS_TRIGGER_START;
+ play->transitionType = TRANS_TYPE_FADE_WHITE;
+ gSaveContext.nextTransitionType = TRANS_TYPE_FADE_WHITE_SLOW;
}
void BossRush_HandleBlueWarpHeal(PlayState* play) {
@@ -235,29 +292,25 @@ void BossRush_HandleBlueWarpHeal(PlayState* play) {
}
void BossRush_HandleCompleteBoss(PlayState* play) {
- if (!IS_BOSS_RUSH) {
- return;
- }
-
gSaveContext.isBossRushPaused = 1;
switch (play->sceneNum) {
case SCENE_DEKU_TREE_BOSS:
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_DEKU_TREE);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP);
break;
case SCENE_DODONGOS_CAVERN_BOSS:
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_DODONGOS_CAVERN);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP);
break;
case SCENE_JABU_JABU_BOSS:
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_JABU_JABUS_BELLY);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP);
break;
case SCENE_FOREST_TEMPLE_BOSS:
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_FOREST_TEMPLE);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP);
break;
case SCENE_FIRE_TEMPLE_BOSS:
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_FIRE_TEMPLE);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP);
break;
case SCENE_WATER_TEMPLE_BOSS:
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_WATER_TEMPLE);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP);
break;
case SCENE_SPIRIT_TEMPLE_BOSS:
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE);
@@ -308,7 +361,7 @@ void BossRush_InitSave() {
}
// Set health
- uint16_t health = 16;
+ u16 health = 16;
switch (gSaveContext.bossRushOptions[BR_OPTIONS_HEARTS]) {
case BR_CHOICE_HEARTS_7:
health *= 7;
@@ -418,7 +471,7 @@ void BossRush_InitSave() {
}
// Upgrades
- uint8_t upgradeLevel = 1;
+ u8 upgradeLevel = 1;
if (gSaveContext.bossRushOptions[BR_OPTIONS_AMMO] == BR_CHOICE_AMMO_MAXED) {
upgradeLevel = 3;
}
@@ -432,13 +485,13 @@ void BossRush_InitSave() {
// Set flags and Link's age based on chosen settings.
if (gSaveContext.bossRushOptions[BR_OPTIONS_BOSSES] == BR_CHOICE_BOSSES_ADULT ||
gSaveContext.bossRushOptions[BR_OPTIONS_BOSSES] == BR_CHOICE_BOSSES_GANONDORF_GANON) {
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_DEKU_TREE);
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_DODONGOS_CAVERN);
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_JABU_JABUS_BELLY);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP);
if (gSaveContext.bossRushOptions[BR_OPTIONS_BOSSES] == BR_CHOICE_BOSSES_GANONDORF_GANON) {
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_FOREST_TEMPLE);
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_FIRE_TEMPLE);
- Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_WATER_TEMPLE);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_FOREST_TEMPLE_BLUE_WARP);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_FIRE_TEMPLE_BLUE_WARP);
+ Flags_SetEventChkInf(EVENTCHKINF_USED_WATER_TEMPLE_BLUE_WARP);
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE);
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_SHADOW_TEMPLE);
}
@@ -450,40 +503,214 @@ void BossRush_InitSave() {
}
}
-void BossRush_SetEquipment(uint8_t linkAge) {
+static void* sSavePromptNoChoiceTexs[] = {
+ (void*)gPauseNoENGTex,
+ (void*)gPauseNoGERTex,
+ (void*)gPauseNoFRATex
+};
- std::array brButtonItems;
- std::array brCButtonSlots;
+void BossRush_OnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs) {
+ va_list args;
+ va_copy(args, originalArgs);
- // Set Child Equipment.
- if (linkAge == LINK_AGE_CHILD) {
- brButtonItems = {
- ITEM_SWORD_KOKIRI, ITEM_STICK, ITEM_NUT, ITEM_BOMB, ITEM_NONE, ITEM_NONE, ITEM_NONE, ITEM_NONE
- };
+ switch (id) {
+ // Allow not healing before ganon
+ case VB_GANON_HEAL_BEFORE_FIGHT: {
+ if (gSaveContext.bossRushOptions[BR_OPTIONS_HEAL] == BR_CHOICE_HEAL_NEVER) {
+ *should = false;
+ }
+ break;
+ }
+ // Replace the blue warp transitions with ones that lead back to the chamber of sages
+ case VB_BLUE_WARP_APPLY_ENTRANCE_AND_CUTSCENE: {
+ DoorWarp1* blueWarp = va_arg(args, DoorWarp1*);
+ BossRush_HandleBlueWarp(gPlayState, blueWarp->actor.world.pos.x, blueWarp->actor.world.pos.z);
+ *should = false;
+ break;
+ }
+ // Spawn clean blue warps (no ruto, adult animation, etc)
+ case VB_SPAWN_BLUE_WARP: {
+ switch (gPlayState->sceneNum) {
+ case SCENE_DEKU_TREE_BOSS: {
+ BossGoma* bossGoma = va_arg(args, BossGoma*);
+ static Vec3f roomCenter = { -150.0f, 0.0f, -350.0f };
+ Vec3f childPos = roomCenter;
- brCButtonSlots = { SLOT_STICK, SLOT_NUT, SLOT_BOMB, SLOT_NONE, SLOT_NONE, SLOT_NONE, SLOT_NONE };
+ for (s32 i = 0; i < 10000; i++) {
+ if ((fabsf(childPos.x - GET_PLAYER(gPlayState)->actor.world.pos.x) < 100.0f &&
+ fabsf(childPos.z - GET_PLAYER(gPlayState)->actor.world.pos.z) < 100.0f) ||
+ (fabsf(childPos.x - bossGoma->actor.world.pos.x) < 150.0f &&
+ fabsf(childPos.z - bossGoma->actor.world.pos.z) < 150.0f)) {
+ childPos.x = Rand_CenteredFloat(400.0f) + -150.0f;
+ childPos.z = Rand_CenteredFloat(400.0f) + -350.0f;
+ } else {
+ break;
+ }
+ }
+ Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_WARP1, childPos.x, bossGoma->actor.world.pos.y, childPos.z, 0, 0, 0, WARP_DUNGEON_ADULT, false);
+ break;
+ }
+ case SCENE_DODONGOS_CAVERN_BOSS: {
+ Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_WARP1, -890.0f, -1523.76f, -3304.0f, 0, 0, 0, WARP_DUNGEON_ADULT, false);
+ break;
+ }
+ case SCENE_JABU_JABU_BOSS: {
+ static Vec3f sWarpPos[] = {
+ { 10.0f, 0.0f, 30.0f },
+ { 260.0f, 0.0f, -470.0f },
+ { -240.0f, 0.0f, -470.0f },
+ };
- Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_KOKIRI);
- Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_DEKU);
- // Set Adult equipment.
- } else {
- brButtonItems = { ITEM_SWORD_MASTER, ITEM_BOW, ITEM_HAMMER, ITEM_BOMB,
- ITEM_NONE, ITEM_NONE, ITEM_NONE, ITEM_NONE };
+ s32 sp7C = 2;
+ for (s32 i = 2; i > 0; i -= 1) {
+ if (Math_Vec3f_DistXYZ(&sWarpPos[i], &GET_PLAYER(gPlayState)->actor.world.pos) <
+ Math_Vec3f_DistXYZ(&sWarpPos[i - 1], &GET_PLAYER(gPlayState)->actor.world.pos)) {
+ sp7C = i - 1;
+ }
+ }
- brCButtonSlots = { SLOT_BOW, SLOT_HAMMER, SLOT_BOMB, SLOT_NONE, SLOT_NONE, SLOT_NONE, SLOT_NONE };
-
- Inventory_ChangeEquipment(EQUIP_TYPE_SWORD, EQUIP_VALUE_SWORD_MASTER);
- Inventory_ChangeEquipment(EQUIP_TYPE_SHIELD, EQUIP_VALUE_SHIELD_MIRROR);
- Inventory_ChangeEquipment(EQUIP_TYPE_TUNIC, EQUIP_VALUE_TUNIC_GORON);
+ Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_WARP1, sWarpPos[sp7C].x, sWarpPos[sp7C].y, sWarpPos[sp7C].z, 0, 0, 0, WARP_DUNGEON_ADULT, false);
+ break;
+ }
+ case SCENE_FOREST_TEMPLE_BOSS: {
+ Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_WARP1, 14.0f, -33.0f, -3315.0f, 0, 0, 0, WARP_DUNGEON_ADULT, true);
+ break;
+ }
+ case SCENE_FIRE_TEMPLE_BOSS: {
+ Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_WARP1, 0.0f, 100.0f, 0.0f, 0, 0, 0, WARP_DUNGEON_ADULT, true);
+ break;
+ }
+ case SCENE_WATER_TEMPLE_BOSS: {
+ BossMo* bossMo = va_arg(args, BossMo*);
+ Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_WARP1, bossMo->actor.world.pos.x, -280.0f, bossMo->actor.world.pos.z, 0, 0, 0, WARP_DUNGEON_ADULT, true);
+ break;
+ }
+ case SCENE_SPIRIT_TEMPLE_BOSS: {
+ Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_WARP1, 600.0f, 230.0f, 0.0f, 0, 0, 0, WARP_DUNGEON_ADULT, true);
+ break;
+ }
+ case SCENE_SHADOW_TEMPLE_BOSS: {
+ Actor_Spawn(&gPlayState->actorCtx, gPlayState, ACTOR_DOOR_WARP1, -50.0f, 0.0f, 400.0f, 0, 0, 0, WARP_DUNGEON_ADULT, true);
+ break;
+ }
+ default: {
+ SPDLOG_WARN("[BossRush]: Blue warp spawned in unhandled scene, ignoring");
+ return;
+ }
+ }
+ *should = false;
+ break;
+ }
+ // Skip past the "Save?" window when pressing B while paused and instead close the menu.
+ case VB_CLOSE_PAUSE_MENU: {
+ if (CHECK_BTN_ALL(gPlayState->state.input[0].press.button, BTN_B)) {
+ *should = true;
+ }
+ break;
+ }
+ // Show "No" twice because the player can't continue.
+ case VB_RENDER_YES_ON_CONTINUE_PROMPT: {
+ Gfx** disp = va_arg(args, Gfx**);
+ *disp = KaleidoScope_QuadTextureIA8(*disp, sSavePromptNoChoiceTexs[gSaveContext.language], 48, 16, 12);
+ *should = false;
+ break;
+ }
+ // Break the dodongo breakable floor immediately so the player can jump in the hole immediately.
+ case VB_BG_BREAKWALL_BREAK: {
+ *should = true;
+ break;
+ }
+ // Skip past the "Save?" window when dying and go to the "Continue?" screen immediately.
+ case VB_TRANSITION_TO_SAVE_SCREEN_ON_DEATH: {
+ PauseContext* pauseCtx = va_arg(args, PauseContext*);
+ pauseCtx->state = 0xF;
+ *should = false;
+ break;
+ }
+ // Prevent saving
+ case VB_BE_ABLE_TO_SAVE:
+ // Disable doors so the player can't leave the boss rooms backwards.
+ case VB_BE_ABLE_TO_OPEN_DOORS:
+ // There's no heart containers in boss rush
+ case VB_SPAWN_HEART_CONTAINER:
+ // Rupees are useless in boss rush
+ case VB_RENDER_RUPEE_COUNTER: {
+ *should = false;
+ break;
+ }
+ // Prevent warning spam
+ default: {
+ break;
+ }
}
- // Button Items
- for (int button = 0; button < ARRAY_COUNT(gSaveContext.equips.buttonItems); button++) {
- gSaveContext.equips.buttonItems[button] = brButtonItems[button];
+ va_end(args);
+}
+
+void BossRush_OnActorInitHandler(void* actorRef) {
+ Actor* actor = static_cast(actorRef);
+
+ if (actor->id == ACTOR_DEMO_SA && gPlayState->sceneNum == SCENE_CHAMBER_OF_THE_SAGES) {
+ BossRush_SpawnBlueWarps(gPlayState);
+ Actor_Kill(actor);
+ GET_PLAYER(gPlayState)->actor.world.rot.y = GET_PLAYER(gPlayState)->actor.shape.rot.y = 27306;
+ return;
}
- // C buttons
- for (int button = 0; button < ARRAY_COUNT(gSaveContext.equips.cButtonSlots); button++) {
- gSaveContext.equips.cButtonSlots[button] = brCButtonSlots[button];
+ // Remove chests, mainly for the chest in King Dodongo's boss room.
+ // Remove bushes, used in Gohma's arena.
+ // Remove pots, used in Barinade's and Ganondorf's arenas.
+ if (actor->id == ACTOR_EN_KUSA || actor->id == ACTOR_OBJ_TSUBO || actor->id == ACTOR_EN_BOX) {
+ Actor_Kill(actor);
+ return;
}
}
+
+void BossRush_OnSceneInitHandler(s16 sceneNum) {
+ // Unpause the timer when the scene loaded isn't the Chamber of Sages.
+ if (sceneNum != SCENE_CHAMBER_OF_THE_SAGES) {
+ gSaveContext.isBossRushPaused = 0;
+ }
+}
+
+void BossRush_OnBossDefeatHandler(void* refActor) {
+ BossRush_HandleCompleteBoss(gPlayState);
+}
+
+void BossRush_OnBlueWarpUpdate(void* actor) {
+ DoorWarp1* blueWarp = static_cast(actor);
+
+ if (blueWarp->warpTimer > 160) {
+ BossRush_HandleBlueWarp(gPlayState, blueWarp->actor.world.pos.x, blueWarp->actor.world.pos.z);
+ }
+}
+
+void BossRush_RegisterHooks() {
+ static u32 onVanillaBehaviorHook = 0;
+ static u32 onSceneInitHook = 0;
+ static u32 onActorInitHook = 0;
+ static u32 onBossDefeatHook = 0;
+ static u32 onActorUpdate = 0;
+
+ GameInteractor::Instance->RegisterGameHook([](int32_t fileNum) {
+ GameInteractor::Instance->UnregisterGameHook(onVanillaBehaviorHook);
+ GameInteractor::Instance->UnregisterGameHook(onSceneInitHook);
+ GameInteractor::Instance->UnregisterGameHook(onActorInitHook);
+ GameInteractor::Instance->UnregisterGameHook(onBossDefeatHook);
+ GameInteractor::Instance->UnregisterGameHookForID(onActorUpdate);
+
+ onVanillaBehaviorHook = 0;
+ onSceneInitHook = 0;
+ onActorInitHook = 0;
+ onBossDefeatHook = 0;
+ onActorUpdate = 0;
+
+ if (!IS_BOSS_RUSH) return;
+
+ onVanillaBehaviorHook = GameInteractor::Instance->RegisterGameHook(BossRush_OnVanillaBehaviorHandler);
+ onSceneInitHook = GameInteractor::Instance->RegisterGameHook(BossRush_OnSceneInitHandler);
+ onActorInitHook = GameInteractor::Instance->RegisterGameHook(BossRush_OnActorInitHandler);
+ onBossDefeatHook = GameInteractor::Instance->RegisterGameHook(BossRush_OnBossDefeatHandler);
+ onActorUpdate = GameInteractor::Instance->RegisterGameHookForID(ACTOR_DOOR_WARP1, BossRush_OnBlueWarpUpdate);
+ });
+}
diff --git a/soh/soh/Enhancements/boss-rush/BossRush.h b/soh/soh/Enhancements/boss-rush/BossRush.h
index caa6ce86e..292cbaf26 100644
--- a/soh/soh/Enhancements/boss-rush/BossRush.h
+++ b/soh/soh/Enhancements/boss-rush/BossRush.h
@@ -1,20 +1,16 @@
#pragma once
-#include "BossRushTypes.h"
-#include "variables.h"
+#include "z64.h"
#ifdef __cplusplus
extern "C" {
#endif
-void BossRush_SpawnBlueWarps(PlayState* play);
-void BossRush_HandleBlueWarp(PlayState* play, f32 warpPosX, f32 warpPosZ);
-void BossRush_HandleBlueWarpHeal(PlayState* play);
-void BossRush_InitSave();
-void BossRush_SetEquipment(uint8_t linkAge);
-void BossRush_HandleCompleteBoss(PlayState* play);
-const char* BossRush_GetSettingName(uint8_t optionIndex, uint8_t language);
-const char* BossRush_GetSettingChoiceName(uint8_t optionIndex, uint8_t choiceIndex, uint8_t language);
-uint8_t BossRush_GetSettingOptionsAmount(uint8_t optionIndex);
+ void BossRush_HandleBlueWarpHeal(PlayState* play);
+ void BossRush_InitSave();
+ const char* BossRush_GetSettingName(u8 optionIndex, u8 language);
+ const char* BossRush_GetSettingChoiceName(u8 optionIndex, u8 choiceIndex, u8 language);
+ u8 BossRush_GetSettingOptionsAmount(u8 optionIndex);
+ void BossRush_RegisterHooks();
#ifdef __cplusplus
};
#endif
diff --git a/soh/soh/Enhancements/boss-rush/BossRushTypes.h b/soh/soh/Enhancements/boss-rush/BossRushTypes.h
index e39cba997..cf4d3aaba 100644
--- a/soh/soh/Enhancements/boss-rush/BossRushTypes.h
+++ b/soh/soh/Enhancements/boss-rush/BossRushTypes.h
@@ -1,6 +1,5 @@
#pragma once
-#define BOSSRUSH_OPTIONS_AMOUNT 12
#define BOSSRUSH_MAX_OPTIONS_ON_SCREEN 6
typedef enum {
@@ -15,7 +14,8 @@ typedef enum {
BR_OPTIONS_LONGSHOT,
BR_OPTIONS_HOVERBOOTS,
BR_OPTIONS_BUNNYHOOD,
- BR_OPTIONS_TIMER
+ BR_OPTIONS_TIMER,
+ BR_OPTIONS_MAX,
} BossRushOptionEnums;
typedef enum {
diff --git a/soh/soh/Enhancements/cheat_hook_handlers.cpp b/soh/soh/Enhancements/cheat_hook_handlers.cpp
new file mode 100644
index 000000000..ef69d6174
--- /dev/null
+++ b/soh/soh/Enhancements/cheat_hook_handlers.cpp
@@ -0,0 +1,56 @@
+#include
+#include "soh/OTRGlobals.h"
+#include "soh/Enhancements/game-interactor/GameInteractor.h"
+#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
+#include "soh/Enhancements/enhancementTypes.h"
+
+extern "C" {
+#include "macros.h"
+#include "variables.h"
+
+extern SaveContext gSaveContext;
+extern PlayState* gPlayState;
+}
+
+void CheatsOnVanillaBehaviorHandler(GIVanillaBehavior id, bool* should, va_list originalArgs) {
+ switch (id) {
+ case VB_DEKU_STICK_BREAK: {
+ if (CVarGetInteger(CVAR_CHEAT("DekuStickCheat"), DEKU_STICK_NORMAL) != DEKU_STICK_NORMAL) {
+ *should = false;
+ }
+ break;
+ }
+ case VB_DEKU_STICK_BE_ON_FIRE: {
+ if (CVarGetInteger(CVAR_CHEAT("DekuStickCheat"), DEKU_STICK_NORMAL) == DEKU_STICK_UNBREAKABLE_AND_ALWAYS_ON_FIRE) {
+ Player* player = GET_PLAYER(gPlayState);
+ player->unk_860 = 200; // Keeps the stick's flame lit
+ player->unk_85C = 1.0f; // Ensures the stick is the proper length
+ *should = true;
+ }
+ break;
+ }
+ case VB_DEKU_STICK_BURN_OUT: {
+ if (CVarGetInteger(CVAR_CHEAT("DekuStickCheat"), DEKU_STICK_NORMAL) != DEKU_STICK_NORMAL) {
+ *should = false;
+ }
+ break;
+ }
+ case VB_DEKU_STICK_BURN_DOWN: {
+ if (CVarGetInteger(CVAR_CHEAT("DekuStickCheat"), DEKU_STICK_NORMAL) != DEKU_STICK_NORMAL) {
+ *should = false;
+ }
+ break;
+ }
+ }
+}
+
+static uint32_t onVanillaBehaviorHook = 0;
+void CheatsRegisterHooks() {
+ GameInteractor::Instance->RegisterGameHook([](int32_t fileNum) mutable {
+
+ GameInteractor::Instance->UnregisterGameHook(onVanillaBehaviorHook);
+ onVanillaBehaviorHook = 0;
+ onVanillaBehaviorHook = GameInteractor::Instance->RegisterGameHook(CheatsOnVanillaBehaviorHandler);
+
+ });
+}
diff --git a/soh/soh/Enhancements/cheat_hook_handlers.h b/soh/soh/Enhancements/cheat_hook_handlers.h
new file mode 100644
index 000000000..88a79f285
--- /dev/null
+++ b/soh/soh/Enhancements/cheat_hook_handlers.h
@@ -0,0 +1,6 @@
+#ifndef CHEAT_HOOK_HANDLERS_H
+#define CHEAT_HOOK_HANDLERS_H
+
+void CheatsRegisterHooks();
+
+#endif // CHEAT_HOOK_HANDLERS_H
diff --git a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
index b0e81c16c..deb4ce8f5 100644
--- a/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
+++ b/soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
@@ -220,6 +220,7 @@ static std::map cosmeticOptions = {
COSMETIC_OPTION("Consumable.DDHeartBorder", "DD Heart Border", COSMETICS_GROUP_CONSUMABLE, ImVec4(255, 255, 255, 255), false, true, true),
COSMETIC_OPTION("Consumable.Magic", "Magic", COSMETICS_GROUP_CONSUMABLE, ImVec4( 0, 200, 0, 255), false, true, false),
COSMETIC_OPTION("Consumable.MagicActive", "Magic Active", COSMETICS_GROUP_CONSUMABLE, ImVec4(250, 250, 0, 255), false, true, true),
+ COSMETIC_OPTION("Consumable_MagicInfinite", "Infinite Magic", COSMETICS_GROUP_CONSUMABLE, ImVec4( 0, 0, 200, 255), false, true, true),
COSMETIC_OPTION("Consumable.MagicBorder", "Magic Border", COSMETICS_GROUP_CONSUMABLE, ImVec4(255, 255, 255, 255), false, false, true),
COSMETIC_OPTION("Consumable.MagicBorderActive", "Magic Border Active", COSMETICS_GROUP_CONSUMABLE, ImVec4(255, 255, 255, 255), false, false, true),
COSMETIC_OPTION("Consumable.GreenRupee", "Green Rupee", COSMETICS_GROUP_CONSUMABLE, ImVec4( 50, 255, 50, 255), false, true, true),
diff --git a/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp b/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp
index e552a9672..c24b83fb6 100644
--- a/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp
+++ b/soh/soh/Enhancements/custom-message/CustomMessageManager.cpp
@@ -3,7 +3,9 @@
#include
#include
#include
-#include
+#include