From 4afb655c41463e0ecde3eb1be0e36dc7d12e5d67 Mon Sep 17 00:00:00 2001
From: louist103 <35883445+louist103@users.noreply.github.com>
Date: Wed, 17 Apr 2024 20:30:30 -0400
Subject: [PATCH 01/13] Fix order of gQuestItems (#224)
---
mm/assets/xml/archives/icon_item_24_static.xml | 2 +-
mm/src/code/z_message.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/mm/assets/xml/archives/icon_item_24_static.xml b/mm/assets/xml/archives/icon_item_24_static.xml
index 6bf637534..6bc8c740b 100644
--- a/mm/assets/xml/archives/icon_item_24_static.xml
+++ b/mm/assets/xml/archives/icon_item_24_static.xml
@@ -13,8 +13,8 @@
-
+
diff --git a/mm/src/code/z_message.c b/mm/src/code/z_message.c
index edad4f499..a74ba2a4d 100644
--- a/mm/src/code/z_message.c
+++ b/mm/src/code/z_message.c
@@ -55,8 +55,8 @@ const char* gQuestIcons[] = {
gQuestIconCompassTex,
gQuestIconDungeonMapTex,
gQuestIconGoldSkulltula2Tex,
- gQuestIconSmallMagicJarTex,
gQuestIconSmallKeyTex,
+ gQuestIconSmallMagicJarTex,
gQuestIconBigMagicJarTex,
gQuestIconLinkHumanFaceTex,
};
From 7c88be30a39f4eaef430f3736e15ac2ec0f5b28c Mon Sep 17 00:00:00 2001
From: Garrett Cox
Date: Wed, 17 Apr 2024 19:31:09 -0500
Subject: [PATCH 02/13] Collision viewer UI Pass (#212)
* Initial Collision Viewer
Issues:
- UIWidget stuff was copied in to colViewer.cpp
- Pause screen still renders collision.
- Colour CVars are not working correctly.
* Move collision viewer files and small adjustments
* Redo collision viewer UI
* use identity mtx
* prevent draw collision on pause
---------
Co-authored-by: Kenix
Co-authored-by: Adam Bird
---
mm/2s2h/BenGui/BenGui.cpp | 5 +
mm/2s2h/BenGui/BenGui.hpp | 1 +
mm/2s2h/BenGui/BenMenuBar.cpp | 5 +
mm/2s2h/BenGui/BenMenuBar.h | 1 +
mm/2s2h/BenGui/UIWidgets.cpp | 18 +
mm/2s2h/BenGui/UIWidgets.hpp | 1 +
mm/2s2h/DeveloperTools/CollisionViewer.cpp | 726 +++++++++++++++++++++
mm/2s2h/DeveloperTools/CollisionViewer.h | 23 +
mm/src/code/z_play.c | 4 +
9 files changed, 784 insertions(+)
create mode 100644 mm/2s2h/DeveloperTools/CollisionViewer.cpp
create mode 100644 mm/2s2h/DeveloperTools/CollisionViewer.h
diff --git a/mm/2s2h/BenGui/BenGui.cpp b/mm/2s2h/BenGui/BenGui.cpp
index d6e77392c..ffd40b6d6 100644
--- a/mm/2s2h/BenGui/BenGui.cpp
+++ b/mm/2s2h/BenGui/BenGui.cpp
@@ -35,6 +35,7 @@ namespace BenGui {
std::shared_ptr mSaveEditorWindow;
std::shared_ptr mHudEditorWindow;
std::shared_ptr mActorViewerWindow;
+ std::shared_ptr mCollisionViewerWindow;
void SetupGuiElements() {
auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui();
@@ -83,6 +84,9 @@ namespace BenGui {
mActorViewerWindow = std::make_shared("gWindows.ActorViewer", "Actor Viewer");
gui->AddGuiWindow(mActorViewerWindow);
+
+ mCollisionViewerWindow = std::make_shared("gWindows.CollisionViewer", "Collision Viewer");
+ gui->AddGuiWindow(mCollisionViewerWindow);
}
void Destroy() {
@@ -91,6 +95,7 @@ namespace BenGui {
mConsoleWindow = nullptr;
mInputEditorWindow = nullptr;
mGfxDebuggerWindow = nullptr;
+ mCollisionViewerWindow = nullptr;
mSaveEditorWindow = nullptr;
mHudEditorWindow = nullptr;
diff --git a/mm/2s2h/BenGui/BenGui.hpp b/mm/2s2h/BenGui/BenGui.hpp
index dbb7f783b..c75fca8be 100644
--- a/mm/2s2h/BenGui/BenGui.hpp
+++ b/mm/2s2h/BenGui/BenGui.hpp
@@ -5,6 +5,7 @@
#include "BenMenuBar.h"
#include "DeveloperTools/SaveEditor.h"
#include "DeveloperTools/ActorViewer.h"
+#include "DeveloperTools/CollisionViewer.h"
namespace BenGui {
void SetupHooks();
diff --git a/mm/2s2h/BenGui/BenMenuBar.cpp b/mm/2s2h/BenGui/BenMenuBar.cpp
index 418c30021..8d58999d6 100644
--- a/mm/2s2h/BenGui/BenMenuBar.cpp
+++ b/mm/2s2h/BenGui/BenMenuBar.cpp
@@ -328,6 +328,7 @@ extern std::shared_ptr mConsoleWindow;
extern std::shared_ptr mGfxDebuggerWindow;
extern std::shared_ptr mSaveEditorWindow;
extern std::shared_ptr mActorViewerWindow;
+extern std::shared_ptr mCollisionViewerWindow;
void DrawDeveloperToolsMenu() {
if (UIWidgets::BeginMenu("Developer Tools", UIWidgets::Colors::Yellow)) {
@@ -356,6 +357,10 @@ void DrawDeveloperToolsMenu() {
}
}
ImGui::Separator();
+ if (mCollisionViewerWindow) {
+ UIWidgets::WindowButton("Collision Viewer", "gWindows.CollisionViewer", mCollisionViewerWindow,
+ { .tooltip = "Draws collision to the screen" });
+ }
if (mStatsWindow) {
UIWidgets::WindowButton("Stats", "gWindows.Stats", mStatsWindow, {
.tooltip = "Shows the stats window, with your FPS and frametimes, and the OS you're playing on"
diff --git a/mm/2s2h/BenGui/BenMenuBar.h b/mm/2s2h/BenGui/BenMenuBar.h
index 17b8b116f..fd4f702e3 100644
--- a/mm/2s2h/BenGui/BenMenuBar.h
+++ b/mm/2s2h/BenGui/BenMenuBar.h
@@ -5,6 +5,7 @@
#include "window/gui/GuiElement.h"
#include "DeveloperTools/SaveEditor.h"
#include "DeveloperTools/ActorViewer.h"
+#include "DeveloperTools/CollisionViewer.h"
namespace BenGui {
class BenMenuBar : public LUS::GuiMenuBar {
diff --git a/mm/2s2h/BenGui/UIWidgets.cpp b/mm/2s2h/BenGui/UIWidgets.cpp
index 33d5be450..9ed46b090 100644
--- a/mm/2s2h/BenGui/UIWidgets.cpp
+++ b/mm/2s2h/BenGui/UIWidgets.cpp
@@ -394,6 +394,24 @@ namespace UIWidgets {
return dirty;
}
+ bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor) {
+ Color_RGBA8 color = CVarGetColor(cvarName, defaultColor);
+ ImVec4 colorVec = ImVec4(color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f);
+ bool changed = false;
+ PushStyleCombobox(Colors::Gray);
+ if (ImGui::ColorEdit3(label, (float*)&colorVec, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoBorder)) {
+ color.r = (uint8_t)(colorVec.x * 255.0f);
+ color.g = (uint8_t)(colorVec.y * 255.0f);
+ color.b = (uint8_t)(colorVec.z * 255.0f);
+ color.a = (uint8_t)(colorVec.w * 255.0f);
+ CVarSetColor(cvarName, color);
+ LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
+ changed = true;
+ }
+ PopStyleCombobox();
+ return changed;
+ }
+
void DrawFlagArray32(const std::string& name, uint32_t& flags) {
ImGui::PushID(name.c_str());
for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) {
diff --git a/mm/2s2h/BenGui/UIWidgets.hpp b/mm/2s2h/BenGui/UIWidgets.hpp
index fa9de88b8..8936d949f 100644
--- a/mm/2s2h/BenGui/UIWidgets.hpp
+++ b/mm/2s2h/BenGui/UIWidgets.hpp
@@ -409,6 +409,7 @@ namespace UIWidgets {
bool CVarSliderInt(const char* label, const char* cvarName, int32_t min, int32_t max, const int32_t defaultValue, const IntSliderOptions& options = {});
bool SliderFloat(const char* label, float* value, float min, float max, const FloatSliderOptions& options = {});
bool CVarSliderFloat(const char* label, const char* cvarName, float min, float max, const float defaultValue, const FloatSliderOptions& options = {});
+ bool CVarColorPicker(const char* label, const char* cvarName, Color_RGBA8 defaultColor);
void DrawFlagArray32(const std::string& name, uint32_t& flags);
void DrawFlagArray16(const std::string& name, uint16_t& flags);
void DrawFlagArray8(const std::string& name, uint8_t& flags);
diff --git a/mm/2s2h/DeveloperTools/CollisionViewer.cpp b/mm/2s2h/DeveloperTools/CollisionViewer.cpp
new file mode 100644
index 000000000..1f82ed6d0
--- /dev/null
+++ b/mm/2s2h/DeveloperTools/CollisionViewer.cpp
@@ -0,0 +1,726 @@
+#include "CollisionViewer.h"
+#include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h"
+#include "2s2h/BenGui/UIWidgets.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+extern "C" {
+#include
+#include "variables.h"
+#include "functions.h"
+#include "macros.h"
+extern PlayState* gPlayState;
+}
+
+extern "C" WallType SurfaceType_GetWallType(CollisionContext* colCtx, CollisionPoly* poly, s32 bgId);
+
+enum class ColRenderSetting { Disabled, Solid, Transparent };
+
+static const char* ColRenderSettingNames[] = {
+ "Disabled",
+ "Solid",
+ "Transparent",
+};
+
+static std::vector opaDl;
+static std::vector xluDl;
+static std::vector vtxDl;
+static std::vector mtxDl;
+
+// These DLs contain a cylinder/sphere model scaled to 128x (to have less error)
+// The idea is to push a model view matrix, then draw the DL, to draw the shape somewhere with a certain size
+static std::vector cylinderGfx;
+static std::vector cylinderVtx;
+static std::vector sphereGfx;
+static std::vector sphereVtx;
+
+// Draws the ImGui window for the collision viewer
+void CollisionViewerWindow::DrawElement() {
+ ImGui::SetNextWindowSize(ImVec2(390, 475), ImGuiCond_FirstUseEver);
+ if (!ImGui::Begin("Collision Viewer", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) {
+ ImGui::End();
+ return;
+ }
+
+ UIWidgets::CVarCheckbox("Enabled", "gCollisionViewer.Enabled");
+
+ ImGui::SameLine();
+
+ ImGui::BeginDisabled(CVarGetInteger("gCollisionViewer.Enabled", 0) == 0);
+
+ UIWidgets::CVarCheckbox("Apply Shading", "gCollisionViewer.ApplyShading");
+
+ ImGui::SameLine();
+
+ if (UIWidgets::Button("Reset Colors")) {
+ CVarClear("gCollisionViewer.SceneCollisionColor");
+ CVarClear("gCollisionViewer.VoidCollisionColor");
+ CVarClear("gCollisionViewer.EntranceCollisionColor");
+ CVarClear("gCollisionViewer.SlopeCollisionColor");
+ CVarClear("gCollisionViewer.HookshotCollisionColor");
+ CVarClear("gCollisionViewer.WaterboxCollisionColor");
+ CVarClear("gCollisionViewer.OCollisionColor");
+ CVarClear("gCollisionViewer.ACollisionColor");
+ CVarClear("gCollisionViewer.ATCollisionColor");
+ CVarClear("gCollisionViewer.SpecialSurfaceColor");
+ CVarClear("gCollisionViewer.InteractableColor");
+ LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
+ }
+
+ ImGui::SeparatorText("Collision Types");
+
+ UIWidgets::CVarCombobox("Scene", "gCollisionViewer.SceneCollisionMode", ColRenderSettingNames, { .color = UIWidgets::Colors::Gray });
+ UIWidgets::CVarCombobox("BG Actors", "gCollisionViewer.BGActorsCollisionMode", ColRenderSettingNames, { .color = UIWidgets::Colors::Gray });
+ UIWidgets::CVarCombobox("Col Check", "gCollisionViewer.ColCheckCollisionMode", ColRenderSettingNames, { .color = UIWidgets::Colors::Gray });
+ UIWidgets::CVarCombobox("Waterbox", "gCollisionViewer.WaterboxCollisionMode", ColRenderSettingNames, { .color = UIWidgets::Colors::Gray });
+
+ ImGui::SeparatorText("Colors");
+
+ if (ImGui::BeginTable("table table", 3, ImGuiTableFlags_NoBordersInBody)) {
+ ImGui::TableNextColumn();
+ UIWidgets::CVarColorPicker("Normal", "gCollisionViewer.SceneCollisionColor", { 255, 255, 255, 255 });
+ ImGui::TableNextColumn();
+ UIWidgets::CVarColorPicker("Void", "gCollisionViewer.VoidCollisionColor", { 255, 0, 0, 255 });
+ ImGui::TableNextColumn();
+ UIWidgets::CVarColorPicker("Entrance", "gCollisionViewer.EntranceCollisionColor", { 0, 255, 0, 255 });
+ ImGui::TableNextColumn();
+ UIWidgets::CVarColorPicker("Slope", "gCollisionViewer.SlopeCollisionColor", { 255, 255, 128, 255 });
+ ImGui::TableNextColumn();
+ UIWidgets::CVarColorPicker("Hookshotable", "gCollisionViewer.HookshotCollisionColor", { 128, 128, 255, 255 });
+ ImGui::TableNextColumn();
+ UIWidgets::CVarColorPicker("Waterbox", "gCollisionViewer.WaterboxCollisionColor", { 0, 0, 255, 255 });
+ ImGui::TableNextColumn();
+ UIWidgets::CVarColorPicker("OC", "gCollisionViewer.OCollisionColor", { 255, 255, 255, 255 });
+ ImGui::TableNextColumn();
+ UIWidgets::CVarColorPicker("AC", "gCollisionViewer.ACollisionColor", { 0, 0, 255, 255 });
+ ImGui::TableNextColumn();
+ UIWidgets::CVarColorPicker("AT", "gCollisionViewer.ATCollisionColor", { 255, 0, 0, 255 });
+ ImGui::EndTable();
+ }
+ UIWidgets::CVarColorPicker("Special Surface (Grass/Sand/Etc)", "gCollisionViewer.SpecialSurfaceColor", { 192, 255, 192, 255 });
+ UIWidgets::CVarColorPicker("Interactable (Vines/Crawlspace/Etc)", "gCollisionViewer.InteractableColor", { 192, 0, 192, 255 });
+
+ ImGui::EndDisabled();
+
+ ImGui::End();
+}
+
+// Calculates the normal for a triangle at the 3 specified points
+void CalcTriNorm(const Vec3f& v1, const Vec3f& v2, const Vec3f& v3, Vec3f& norm) {
+ norm.x = (v2.y - v1.y) * (v3.z - v1.z) - (v2.z - v1.z) * (v3.y - v1.y);
+ norm.y = (v2.z - v1.z) * (v3.x - v1.x) - (v2.x - v1.x) * (v3.z - v1.z);
+ norm.z = (v2.x - v1.x) * (v3.y - v1.y) - (v2.y - v1.y) * (v3.x - v1.x);
+ float norm_d = sqrtf(norm.x * norm.x + norm.y * norm.y + norm.z * norm.z);
+ if (norm_d != 0.f) {
+ norm.x *= 127.f / norm_d;
+ norm.y *= 127.f / norm_d;
+ norm.z *= 127.f / norm_d;
+ }
+}
+
+// Various macros used for creating verticies and rendering that aren't in gbi.h
+#define G_CC_MODULATERGB_PRIM_ENVA PRIMITIVE, 0, SHADE, 0, 0, 0, 0, ENVIRONMENT
+#define G_CC_PRIMITIVE_ENVA 0, 0, 0, PRIMITIVE, 0, 0, 0, ENVIRONMENT
+#define qs105(n) ((int16_t)((n)*0x0020))
+#define gdSPDefVtxN(x, y, z, s, t, nx, ny, nz, ca) \
+ { \
+ .n = {.ob = { x, y, z }, .tc = { qs105(s), qs105(t) }, .n = { nx, ny, nz }, .a = ca } \
+ }
+
+void CreateCylinderData() {
+ constexpr int32_t CYL_DIVS = 12;
+ cylinderGfx.reserve(5 + CYL_DIVS * 2);
+ cylinderVtx.reserve(2 + CYL_DIVS * 2);
+
+ cylinderVtx.push_back(gdSPDefVtxN(0, 0, 0, 0, 0, 0, -127, 0, 0xFF)); // Bottom center vertex
+ cylinderVtx.push_back(gdSPDefVtxN(0, 128, 0, 0, 0, 0, 127, 0, 0xFF)); // Top center vertex
+ // Create two rings of vertices
+ for (int i = 0; i < CYL_DIVS; ++i) {
+ short vtx_x = floorf(0.5f + cosf(2.f * M_PI * i / CYL_DIVS) * 128.f);
+ short vtx_z = floorf(0.5f - sinf(2.f * M_PI * i / CYL_DIVS) * 128.f);
+ signed char norm_x = cosf(2.f * M_PI * i / CYL_DIVS) * 127.f;
+ signed char norm_z = -sinf(2.f * M_PI * i / CYL_DIVS) * 127.f;
+ cylinderVtx.push_back(gdSPDefVtxN(vtx_x, 0, vtx_z, 0, 0, norm_x, 0, norm_z, 0xFF));
+ cylinderVtx.push_back(gdSPDefVtxN(vtx_x, 128, vtx_z, 0, 0, norm_x, 0, norm_z, 0xFF));
+ }
+
+ // Draw edges
+ cylinderGfx.push_back(gsSPSetGeometryMode(G_CULL_BACK | G_SHADING_SMOOTH));
+ cylinderGfx.push_back(gsSPVertex((uintptr_t)cylinderVtx.data(), 2 + CYL_DIVS * 2, 0));
+ for (int i = 0; i < CYL_DIVS; ++i) {
+ int p = (i + CYL_DIVS - 1) % CYL_DIVS;
+ int v[4] = {
+ 2 + p * 2 + 0,
+ 2 + i * 2 + 0,
+ 2 + i * 2 + 1,
+ 2 + p * 2 + 1,
+ };
+ cylinderGfx.push_back(gsSP2Triangles(v[0], v[1], v[2], 0, v[0], v[2], v[3], 0));
+ }
+
+ // Draw top & bottom
+ cylinderGfx.push_back(gsSPClearGeometryMode(G_SHADING_SMOOTH));
+ for (int i = 0; i < CYL_DIVS; ++i) {
+ int p = (i + CYL_DIVS - 1) % CYL_DIVS;
+ int v[4] = {
+ 2 + p * 2 + 0,
+ 2 + i * 2 + 0,
+ 2 + i * 2 + 1,
+ 2 + p * 2 + 1,
+ };
+ cylinderGfx.push_back(gsSP2Triangles(0, v[1], v[0], 0, 1, v[3], v[2], 0));
+ }
+
+ cylinderGfx.push_back(gsSPClearGeometryMode(G_CULL_BACK));
+ cylinderGfx.push_back(gsSPEndDisplayList());
+}
+
+// This subdivides a face into four tris by placing new verticies at the midpoints of the sides (Like a triforce!), then
+// blowing up the verticies so they are on the unit sphere
+void CreateSphereFace(std::vector>& faces, int32_t v0Index, int32_t v1Index,
+ int32_t v2Index) {
+ size_t nextIndex = sphereVtx.size();
+
+ size_t v01Index = nextIndex;
+ size_t v12Index = nextIndex + 1;
+ size_t v20Index = nextIndex + 2;
+
+ faces.emplace_back(v0Index, v01Index, v20Index);
+ faces.emplace_back(v1Index, v12Index, v01Index);
+ faces.emplace_back(v2Index, v20Index, v12Index);
+ faces.emplace_back(v01Index, v12Index, v20Index);
+
+ const Vtx& v0 = sphereVtx[v0Index];
+ const Vtx& v1 = sphereVtx[v1Index];
+ const Vtx& v2 = sphereVtx[v2Index];
+
+ // Create 3 new verticies at the midpoints
+ Vec3f vs[3] = {
+ Vec3f{(v0.n.ob[0] + v1.n.ob[0]) / 2.0f, (v0.n.ob[1] + v1.n.ob[1]) / 2.0f, (v0.n.ob[2] + v1.n.ob[2]) / 2.0f},
+ Vec3f{(v1.n.ob[0] + v2.n.ob[0]) / 2.0f, (v1.n.ob[1] + v2.n.ob[1]) / 2.0f, (v1.n.ob[2] + v2.n.ob[2]) / 2.0f},
+ Vec3f{(v2.n.ob[0] + v0.n.ob[0]) / 2.0f, (v2.n.ob[1] + v0.n.ob[1]) / 2.0f, (v2.n.ob[2] + v0.n.ob[2]) / 2.0f}
+ };
+
+ // Normalize vertex positions so they are on the sphere
+ for (int32_t vAddIndex = 0; vAddIndex < 3; vAddIndex++) {
+ Vec3f& v = vs[vAddIndex];
+ float mag = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
+ v.x /= mag;
+ v.y /= mag;
+ v.z /= mag;
+ sphereVtx.push_back(gdSPDefVtxN((short)(v.x * 127), (short)(v.y * 127), (short)(v.z * 127), 0, 0,
+ (signed char)(v.x * 127), (signed char)(v.y * 127), (signed char)(v.z * 127),
+ 0xFF));
+ }
+}
+
+// Creates a sphere following the idea in here:
+// http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html Spcifically, create a icosahedron by
+// realizing that the points can be placed on 3 rectangles that are on each unit plane. Then, subdividing each face.
+void CreateSphereData() {
+ std::vector base;
+
+ float d = (1.0f + sqrtf(5.0f)) / 2.0f;
+
+ // Create the 12 starting verticies, 4 on each rectangle
+ base.emplace_back(Vec3f({-1, d, 0}));
+ base.emplace_back(Vec3f({1, d, 0}));
+ base.emplace_back(Vec3f({-1, -d, 0}));
+ base.emplace_back(Vec3f({1, -d, 0}));
+
+ base.emplace_back(Vec3f({0, -1, d}));
+ base.emplace_back(Vec3f({0, 1, d}));
+ base.emplace_back(Vec3f({0, -1, -d}));
+ base.emplace_back(Vec3f({0, 1, -d}));
+
+ base.emplace_back(Vec3f({d, 0, -1}));
+ base.emplace_back(Vec3f({d, 0, 1}));
+ base.emplace_back(Vec3f({-d, 0, -1}));
+ base.emplace_back(Vec3f({-d, 0, 1}));
+
+ // Normalize verticies so they are on the unit sphere
+ for (Vec3f& v : base) {
+ float mag = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
+ v.x /= mag;
+ v.y /= mag;
+ v.z /= mag;
+ sphereVtx.push_back(gdSPDefVtxN((short)(v.x * 128), (short)(v.y * 128), (short)(v.z * 128), 0, 0,
+ (signed char)(v.x * 127), (signed char)(v.y * 127), (signed char)(v.z * 127),
+ 0xFF));
+ }
+
+ std::vector> faces;
+
+ // Subdivide faces
+ CreateSphereFace(faces, 0, 11, 5);
+ CreateSphereFace(faces, 0, 5, 1);
+ CreateSphereFace(faces, 0, 1, 7);
+ CreateSphereFace(faces, 0, 7, 10);
+ CreateSphereFace(faces, 0, 10, 11);
+
+ CreateSphereFace(faces, 1, 5, 9);
+ CreateSphereFace(faces, 5, 11, 4);
+ CreateSphereFace(faces, 11, 10, 2);
+ CreateSphereFace(faces, 10, 7, 6);
+ CreateSphereFace(faces, 7, 1, 8);
+
+ CreateSphereFace(faces, 3, 9, 4);
+ CreateSphereFace(faces, 3, 4, 2);
+ CreateSphereFace(faces, 3, 2, 6);
+ CreateSphereFace(faces, 3, 6, 8);
+ CreateSphereFace(faces, 3, 8, 9);
+
+ CreateSphereFace(faces, 4, 9, 5);
+ CreateSphereFace(faces, 2, 4, 11);
+ CreateSphereFace(faces, 6, 2, 10);
+ CreateSphereFace(faces, 8, 6, 7);
+ CreateSphereFace(faces, 9, 8, 1);
+
+ size_t vtxStartIndex = sphereVtx.size();
+ sphereVtx.reserve(sphereVtx.size() + faces.size() * 3);
+ for (int32_t faceIndex = 0; faceIndex < faces.size(); faceIndex++) {
+ sphereVtx.push_back(sphereVtx[std::get<0>(faces[faceIndex])]);
+ sphereVtx.push_back(sphereVtx[std::get<1>(faces[faceIndex])]);
+ sphereVtx.push_back(sphereVtx[std::get<2>(faces[faceIndex])]);
+ sphereGfx.push_back(gsSPVertex((uintptr_t)(sphereVtx.data() + vtxStartIndex + faceIndex * 3), 3, 0));
+ sphereGfx.push_back(gsSP1Triangle(0, 1, 2, 0));
+ }
+
+ sphereGfx.push_back(gsSPEndDisplayList());
+}
+
+void CollisionViewerWindow::InitElement() {
+ CreateCylinderData();
+ CreateSphereData();
+}
+
+// Initializes the display list for a ColRenderSetting
+void InitGfx(std::vector& gfx, ColRenderSetting setting) {
+ uint32_t rm;
+ uint32_t blc1;
+ uint32_t blc2;
+ uint8_t alpha;
+ uint64_t cm;
+ uint32_t gm;
+
+ if (setting == ColRenderSetting::Transparent) {
+ rm = Z_CMP | IM_RD | CVG_DST_FULL | FORCE_BL;
+ blc1 = GBL_c1(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA);
+ blc2 = GBL_c2(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA);
+ alpha = 0x80;
+ } else {
+ rm = Z_CMP | Z_UPD | CVG_DST_CLAMP | FORCE_BL;
+ blc1 = GBL_c1(G_BL_CLR_IN, G_BL_0, G_BL_CLR_IN, G_BL_1);
+ blc2 = GBL_c2(G_BL_CLR_IN, G_BL_0, G_BL_CLR_IN, G_BL_1);
+ alpha = 0xFF;
+ }
+
+ // Default decal mode to on, users can override it manually but there's not really a use case for the other modes
+ if (CVarGetInteger("gCollisionViewer.DecalMode", 1)) {
+ rm |= ZMODE_DEC;
+ } else if (setting == ColRenderSetting::Transparent) {
+ rm |= ZMODE_XLU;
+ } else {
+ rm |= ZMODE_OPA;
+ }
+
+ gfx.push_back(gsSPTexture(0, 0, 0, G_TX_RENDERTILE, G_OFF));
+ gfx.push_back(gsDPSetCycleType(G_CYC_1CYCLE));
+ gfx.push_back(gsDPSetRenderMode(rm | blc1, rm | blc2));
+
+ if (CVarGetInteger("gCollisionViewer.ApplyShading", 0) != 0) {
+ gfx.push_back(gsDPSetCombineMode(G_CC_MODULATERGB_PRIM_ENVA, G_CC_MODULATERGB_PRIM_ENVA));
+ gfx.push_back(gsSPLoadGeometryMode(G_CULL_BACK | G_ZBUFFER | G_LIGHTING));
+ } else {
+ gfx.push_back(gsDPSetCombineMode(G_CC_PRIMITIVE_ENVA, G_CC_PRIMITIVE_ENVA));
+ gfx.push_back(gsSPLoadGeometryMode(G_ZBUFFER));
+ }
+
+ gfx.push_back(gsDPSetEnvColor(0xFF, 0xFF, 0xFF, alpha));
+}
+
+// Draws a dynapoly structure (scenes or Bg Actors)
+void DrawDynapoly(std::vector& dl, CollisionHeader* col, int32_t bgId) {
+ Color_RGBA8 sceneCollisionColor = CVarGetColor("gCollisionViewer.SceneCollisionColor", { 255, 255, 255, 255 });
+ Color_RGBA8 voidCollisionColor = CVarGetColor("gCollisionViewer.VoidCollisionColor", { 255, 0, 0, 255 });
+ Color_RGBA8 entranceCollisionColor = CVarGetColor("gCollisionViewer.EntranceCollisionColor", { 0, 255, 0, 255 });
+ Color_RGBA8 slopeCollisionColor = CVarGetColor("gCollisionViewer.SlopeCollisionColor", { 255, 255, 128, 255 });
+ Color_RGBA8 hookshotCollisionColor = CVarGetColor("gCollisionViewer.HookshotCollisionColor", { 128, 128, 255, 255 });
+ Color_RGBA8 specialSurfaceColor = CVarGetColor("gCollisionViewer.SpecialSurfaceColor", { 192, 255, 192, 255 });
+ Color_RGBA8 interactableColor = CVarGetColor("gCollisionViewer.InteractableColor", { 192, 0, 192, 255 });
+ uint32_t colorR = sceneCollisionColor.r;
+ uint32_t colorG = sceneCollisionColor.g;
+ uint32_t colorB = sceneCollisionColor.b;
+ uint32_t colorA = 255;
+
+ uint32_t lastColorR = colorR;
+ uint32_t lastColorG = colorG;
+ uint32_t lastColorB = colorB;
+
+ dl.push_back(gsDPSetPrimColor(0, 0, colorR, colorG, colorB, colorA));
+
+ // This keeps track of if we have processed a poly, but not drawn it yet so we can batch them.
+ // This saves several hundred commands in larger scenes
+ bool previousPoly = false;
+
+ for (int i = 0; i < col->numPolygons; i++) {
+ CollisionPoly* poly = &col->polyList[i];
+
+ if (SurfaceType_IsHookshotSurface(&gPlayState->colCtx, poly, bgId)) {
+ colorR = hookshotCollisionColor.r;
+ colorG = hookshotCollisionColor.g;
+ colorB = hookshotCollisionColor.b;
+ } else if (SurfaceType_GetWallType(&gPlayState->colCtx, poly, bgId) > 0x01) {
+ colorR = interactableColor.r;
+ colorG = interactableColor.g;
+ colorB = interactableColor.b;
+ } else if (SurfaceType_GetFloorProperty(&gPlayState->colCtx, poly, bgId) == 0x0C) {
+ colorR = voidCollisionColor.r;
+ colorG = voidCollisionColor.g;
+ colorB = voidCollisionColor.b;
+ } else if (SurfaceType_GetSceneExitIndex(&gPlayState->colCtx, poly, bgId) ||
+ SurfaceType_GetFloorProperty(&gPlayState->colCtx, poly, bgId) == 0x05) {
+ colorR = entranceCollisionColor.r;
+ colorG = entranceCollisionColor.g;
+ colorB = entranceCollisionColor.b;
+ } else if (SurfaceType_GetFloorType(&gPlayState->colCtx, poly, bgId) != 0 ||
+ SurfaceType_IsWallDamage(&gPlayState->colCtx, poly, bgId)) {
+ colorR = specialSurfaceColor.r;
+ colorG = specialSurfaceColor.g;
+ colorB = specialSurfaceColor.b;
+ } else if (SurfaceType_GetFloorEffect(&gPlayState->colCtx, poly, bgId) == 0x01) {
+ colorR = slopeCollisionColor.r;
+ colorG = slopeCollisionColor.g;
+ colorB = slopeCollisionColor.b;
+ } else {
+ colorR = sceneCollisionColor.r;
+ colorG = sceneCollisionColor.g;
+ colorB = sceneCollisionColor.b;
+ }
+
+ if (colorR != lastColorR || colorG != lastColorG || colorB != lastColorB) {
+ // Color changed, flush previous poly
+ if (previousPoly) {
+ dl.push_back(gsSPVertex((uintptr_t)&vtxDl.at(vtxDl.size() - 3), 3, 0));
+ dl.push_back(gsSP1Triangle(0, 1, 2, 0));
+ previousPoly = false;
+ }
+ dl.push_back(gsDPSetPrimColor(0, 0, colorR, colorG, colorB, colorA));
+ }
+ lastColorR = colorR;
+ lastColorG = colorG;
+ lastColorB = colorB;
+
+ Vec3s* va = &col->vtxList[COLPOLY_VTX_INDEX(poly->flags_vIA)];
+ Vec3s* vb = &col->vtxList[COLPOLY_VTX_INDEX(poly->flags_vIB)];
+ Vec3s* vc = &col->vtxList[COLPOLY_VTX_INDEX(poly->vIC)];
+ vtxDl.push_back(gdSPDefVtxN(va->x, va->y, va->z, 0, 0, (signed char)(poly->normal.x / 0x100),
+ (signed char)(poly->normal.y / 0x100), (signed char)(poly->normal.z / 0x100),
+ 0xFF));
+ vtxDl.push_back(gdSPDefVtxN(vb->x, vb->y, vb->z, 0, 0, (signed char)(poly->normal.x / 0x100),
+ (signed char)(poly->normal.y / 0x100), (signed char)(poly->normal.z / 0x100),
+ 0xFF));
+ vtxDl.push_back(gdSPDefVtxN(vc->x, vc->y, vc->z, 0, 0, (signed char)(poly->normal.x / 0x100),
+ (signed char)(poly->normal.y / 0x100), (signed char)(poly->normal.z / 0x100),
+ 0xFF));
+
+ if (previousPoly) {
+ dl.push_back(gsSPVertex((uintptr_t)&vtxDl.at(vtxDl.size() - 6), 6, 0));
+ dl.push_back(gsSP2Triangles(0, 1, 2, 0, 3, 4, 5, 0));
+ previousPoly = false;
+ } else {
+ previousPoly = true;
+ }
+ }
+
+ // Flush previous poly if this is the end and there's no more coming
+ if (previousPoly) {
+ dl.push_back(gsSPVertex((uintptr_t)&vtxDl.at(vtxDl.size() - 3), 3, 0));
+ dl.push_back(gsSP1Triangle(0, 1, 2, 0));
+ previousPoly = false;
+ }
+}
+
+// Draws the scene
+void DrawSceneCollision() {
+ ColRenderSetting showSceneColSetting = (ColRenderSetting)CVarGetInteger("gCollisionViewer.SceneCollisionMode", (uint32_t)ColRenderSetting::Disabled);
+
+ if (showSceneColSetting == ColRenderSetting::Disabled) {
+ return;
+ }
+
+ std::vector& dl = (showSceneColSetting == ColRenderSetting::Transparent) ? xluDl : opaDl;
+ InitGfx(dl, showSceneColSetting);
+ dl.push_back(gsSPMatrix(&gIdentityMtx, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH));
+
+ DrawDynapoly(dl, gPlayState->colCtx.colHeader, BGCHECK_SCENE);
+}
+
+// Draws all Bg Actors
+void DrawBgActorCollision() {
+ ColRenderSetting showBgActorSetting = (ColRenderSetting)CVarGetInteger("gCollisionViewer.BGActorsCollisionMode", (uint32_t)ColRenderSetting::Disabled);
+ if (showBgActorSetting == ColRenderSetting::Disabled) {
+ return;
+ }
+
+ std::vector& dl = (showBgActorSetting == ColRenderSetting::Transparent) ? xluDl : opaDl;
+ InitGfx(dl, showBgActorSetting);
+ dl.push_back(gsSPMatrix(&gIdentityMtx, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH));
+
+ for (int32_t bgIndex = 0; bgIndex < BG_ACTOR_MAX; bgIndex++) {
+ if (gPlayState->colCtx.dyna.bgActorFlags[bgIndex] & 1) {
+ BgActor& bg = gPlayState->colCtx.dyna.bgActors[bgIndex];
+ Mtx m;
+ MtxF mf;
+ SkinMatrix_SetScaleRotateRPYTranslate(&mf, bg.curTransform.scale.x, bg.curTransform.scale.y,
+ bg.curTransform.scale.z, bg.curTransform.rot.x, bg.curTransform.rot.y,
+ bg.curTransform.rot.z, bg.curTransform.pos.x, bg.curTransform.pos.y,
+ bg.curTransform.pos.z);
+ guMtxF2L(mf.mf, &m);
+ mtxDl.push_back(m);
+ dl.push_back(gsSPMatrix(&mtxDl.back(), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_PUSH));
+
+ DrawDynapoly(dl, bg.colHeader, bgIndex);
+
+ dl.push_back(gsSPPopMatrix(G_MTX_MODELVIEW));
+ }
+ }
+}
+
+// Draws a quad
+void DrawQuad(std::vector& dl, Vec3f& v0, Vec3f& v1, Vec3f& v2, Vec3f& v3) {
+ Vec3f norm;
+ CalcTriNorm(v0, v1, v2, norm);
+
+ vtxDl.push_back(gdSPDefVtxN((short)v0.x, (short)v0.y, (short)v0.z, 0, 0, (signed char)norm.x, (signed char)norm.y,
+ (signed char)norm.z, 0xFF));
+ vtxDl.push_back(gdSPDefVtxN((short)v1.x, (short)v1.y, (short)v1.z, 0, 0, (signed char)norm.x, (signed char)norm.y,
+ (signed char)norm.z, 0xFF));
+ vtxDl.push_back(gdSPDefVtxN((short)v2.x, (short)v2.y, (short)v2.z, 0, 0, (signed char)norm.x, (signed char)norm.y,
+ (signed char)norm.z, 0xFF));
+ vtxDl.push_back(gdSPDefVtxN((short)v3.x, (short)v3.y, (short)v3.z, 0, 0, (signed char)norm.x, (signed char)norm.y,
+ (signed char)norm.z, 0xFF));
+ dl.push_back(gsSPVertex((uintptr_t)&vtxDl.at(vtxDl.size() - 4), 4, 0));
+ dl.push_back(gsSP2Triangles(0, 1, 2, 0, 0, 2, 3, 0));
+}
+
+// Draws a list of Col Check objects
+void DrawColCheckList(std::vector& dl, Collider** objects, int32_t count) {
+ for (int32_t colIndex = 0; colIndex < count; colIndex++) {
+ Collider* col = objects[colIndex];
+ switch (col->shape) {
+ case COLSHAPE_JNTSPH: {
+ ColliderJntSph* jntSph = (ColliderJntSph*)col;
+
+ for (int32_t sphereIndex = 0; sphereIndex < jntSph->count; sphereIndex++) {
+ ColliderJntSphElement* sph = &jntSph->elements[sphereIndex];
+
+ Mtx m;
+ MtxF mf;
+ SkinMatrix_SetTranslate(&mf, sph->dim.worldSphere.center.x, sph->dim.worldSphere.center.y,
+ sph->dim.worldSphere.center.z);
+ MtxF ms;
+ int32_t radius = sph->dim.worldSphere.radius == 0 ? 1 : sph->dim.worldSphere.radius;
+ SkinMatrix_SetScale(&ms, radius / 128.0f, radius / 128.0f, radius / 128.0f);
+ MtxF dest;
+ SkinMatrix_MtxFMtxFMult(&mf, &ms, &dest);
+ guMtxF2L(dest.mf, &m);
+ mtxDl.push_back(m);
+
+ dl.push_back(gsSPMatrix(&mtxDl.back(), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_PUSH));
+ dl.push_back(gsSPDisplayList(sphereGfx.data()));
+ dl.push_back(gsSPPopMatrix(G_MTX_MODELVIEW));
+ }
+ } break;
+ case COLSHAPE_CYLINDER: {
+ ColliderCylinder* cyl = (ColliderCylinder*)col;
+
+ Mtx m;
+ MtxF mt;
+ SkinMatrix_SetTranslate(&mt, cyl->dim.pos.x, cyl->dim.pos.y + cyl->dim.yShift, cyl->dim.pos.z);
+ MtxF ms;
+ int32_t radius = cyl->dim.radius == 0 ? 1 : cyl->dim.radius;
+ SkinMatrix_SetScale(&ms, radius / 128.0f, cyl->dim.height / 128.0f, radius / 128.0f);
+ MtxF dest;
+ SkinMatrix_MtxFMtxFMult(&mt, &ms, &dest);
+ guMtxF2L(dest.mf, &m);
+ mtxDl.push_back(m);
+
+ dl.push_back(gsSPMatrix(&mtxDl.back(), G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_PUSH));
+ dl.push_back(gsSPDisplayList(cylinderGfx.data()));
+ dl.push_back(gsSPPopMatrix(G_MTX_MODELVIEW));
+ } break;
+ case COLSHAPE_TRIS: {
+ ColliderTris* tris = (ColliderTris*)col;
+ for (int32_t triIndex = 0; triIndex < tris->count; triIndex++) {
+ ColliderTrisElement* tri = &tris->elements[triIndex];
+
+ vtxDl.push_back(gdSPDefVtxN((short)tri->dim.vtx[0].x, (short)tri->dim.vtx[0].y,
+ (short)tri->dim.vtx[0].z, 0, 0, (signed char)tri->dim.plane.normal.x,
+ (signed char)tri->dim.plane.normal.y,
+ (signed char)tri->dim.plane.normal.z, 0xFF));
+ vtxDl.push_back(gdSPDefVtxN((short)tri->dim.vtx[1].x, (short)tri->dim.vtx[1].y,
+ (short)tri->dim.vtx[1].z, 0, 0, (signed char)tri->dim.plane.normal.x,
+ (signed char)tri->dim.plane.normal.y,
+ (signed char)tri->dim.plane.normal.z, 0xFF));
+ vtxDl.push_back(gdSPDefVtxN((short)tri->dim.vtx[2].x, (short)tri->dim.vtx[2].y,
+ (short)tri->dim.vtx[2].z, 0, 0, (signed char)tri->dim.plane.normal.x,
+ (signed char)tri->dim.plane.normal.y,
+ (signed char)tri->dim.plane.normal.z, 0xFF));
+ dl.push_back(gsSPVertex((uintptr_t)&vtxDl.at(vtxDl.size() - 3), 3, 0));
+ dl.push_back(gsSP1Triangle(0, 1, 2, 0));
+ }
+ } break;
+ case COLSHAPE_QUAD: {
+ ColliderQuad* quad = (ColliderQuad*)col;
+ DrawQuad(dl, quad->dim.quad[0], quad->dim.quad[2], quad->dim.quad[3], quad->dim.quad[1]);
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
+// Draws all Col Check objects
+void DrawColCheckCollision() {
+ Color_RGBA8 oCollisionColor = CVarGetColor("gCollisionViewer.OCollisionColor", { 255, 255, 255, 255 });
+ Color_RGBA8 aCollisionColor = CVarGetColor("gCollisionViewer.ACollisionColor", { 0, 0, 255, 255 });
+ Color_RGBA8 aTCollisionColor = CVarGetColor("gCollisionViewer.ATCollisionColor", { 255, 0, 0, 255 });
+ ColRenderSetting showColCheckSetting = (ColRenderSetting)CVarGetInteger("gCollisionViewer.ColCheckCollisionMode", (uint32_t)ColRenderSetting::Disabled);
+ if (showColCheckSetting == ColRenderSetting::Disabled) {
+ return;
+ }
+
+ std::vector& dl = (showColCheckSetting == ColRenderSetting::Transparent) ? xluDl : opaDl;
+ InitGfx(dl, showColCheckSetting);
+ dl.push_back(gsSPMatrix(&gIdentityMtx, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH));
+
+ CollisionCheckContext& col = gPlayState->colChkCtx;
+
+ dl.push_back(gsDPSetPrimColor(0, 0, oCollisionColor.r, oCollisionColor.g, oCollisionColor.b, 255));
+ DrawColCheckList(dl, col.colOC, col.colOCCount);
+ dl.push_back(gsDPSetPrimColor(0, 0, aCollisionColor.r, aCollisionColor.g, aCollisionColor.b, 255));
+ DrawColCheckList(dl, col.colAC, col.colACCount);
+ dl.push_back(gsDPSetPrimColor(0, 0, aTCollisionColor.r, aTCollisionColor.g, aTCollisionColor.b, 255));
+
+ DrawColCheckList(dl, col.colAT, col.colATCount);
+}
+
+// Draws a waterbox
+void DrawWaterbox(std::vector& dl, WaterBox* water, float water_max_depth = -4000.0f) {
+ // Skip waterboxes that would be disabled in current room
+ int32_t room = ((water->properties >> 13) & 0x3F);
+ if ((room != gPlayState->roomCtx.curRoom.num) && (room != 0x3F)) {
+ return;
+ }
+
+ Vec3f vtx[] = {
+ { water->minPos.x, water->minPos.y, water->minPos.z + water->zLength },
+ { water->minPos.x + water->xLength, water->minPos.y, water->minPos.z + water->zLength },
+ { water->minPos.x + water->xLength, water->minPos.y, water->minPos.z },
+ { water->minPos.x, water->minPos.y, water->minPos.z },
+ { water->minPos.x, water_max_depth, water->minPos.z + water->zLength },
+ { water->minPos.x + water->xLength, water_max_depth, water->minPos.z + water->zLength },
+ { water->minPos.x + water->xLength, water_max_depth, water->minPos.z },
+ { water->minPos.x, water_max_depth, water->minPos.z },
+ };
+ DrawQuad(dl, vtx[0], vtx[1], vtx[2], vtx[3]);
+ DrawQuad(dl, vtx[0], vtx[3], vtx[7], vtx[4]);
+ DrawQuad(dl, vtx[1], vtx[0], vtx[4], vtx[5]);
+ DrawQuad(dl, vtx[2], vtx[1], vtx[5], vtx[6]);
+ DrawQuad(dl, vtx[3], vtx[2], vtx[6], vtx[7]);
+}
+
+// Draws all waterboxes
+void DrawWaterboxList() {
+ Color_RGBA8 waterboxCollisionColor = CVarGetColor("gCollisionViewer.WaterboxCollisionColor", { 0, 0, 255, 255 });
+ ColRenderSetting showWaterboxSetting = (ColRenderSetting)CVarGetInteger("gCollisionViewer.WaterboxCollisionMode", (uint32_t)ColRenderSetting::Disabled);
+ if (showWaterboxSetting == ColRenderSetting::Disabled) {
+ return;
+ }
+
+ std::vector& dl = (showWaterboxSetting == ColRenderSetting::Transparent) ? xluDl : opaDl;
+ InitGfx(dl, showWaterboxSetting);
+ dl.push_back(gsSPMatrix(&gIdentityMtx, G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH));
+
+ dl.push_back(gsDPSetPrimColor(0, 0, waterboxCollisionColor.r, waterboxCollisionColor.g, waterboxCollisionColor.b, 255));
+
+ CollisionHeader* col = gPlayState->colCtx.colHeader;
+ for (int32_t waterboxIndex = 0; waterboxIndex < col->numWaterBoxes; waterboxIndex++) {
+ WaterBox* water = &col->waterBoxes[waterboxIndex];
+ DrawWaterbox(dl, water);
+ }
+}
+
+// Resets a vector for the next frame and returns the capacity
+template size_t ResetVector(T& vec) {
+ size_t oldSize = vec.size();
+ vec.clear();
+ // Reserve slightly more space than last frame to account for variance (such as different amounts of bg actors)
+ vec.reserve(oldSize * 1.2);
+ return vec.capacity();
+}
+
+extern "C" void DrawCollisionViewer() {
+ if (gPlayState == nullptr || !CVarGetInteger("gCollisionViewer.Enabled", 0)) {
+ return;
+ }
+
+ ResetVector(opaDl);
+ ResetVector(xluDl);
+ size_t vtxDlCapacity = ResetVector(vtxDl);
+ size_t mtxDlCapacity = ResetVector(mtxDl);
+
+ DrawSceneCollision();
+ DrawBgActorCollision();
+ DrawColCheckCollision();
+ DrawWaterboxList();
+
+ // Check if we used up more space than we reserved. If so, redo the drawing with our new sizes.
+ // This is because we resized the vectors while drawing, invalidating pointers to them.
+ // This only matters for the Vtx and Mtx vectors.
+ if ((vtxDl.size() > vtxDlCapacity) || (mtxDl.size() > mtxDlCapacity)) {
+ ResetVector(opaDl);
+ ResetVector(xluDl);
+ vtxDlCapacity = ResetVector(vtxDl);
+ mtxDlCapacity = ResetVector(mtxDl);
+
+ DrawSceneCollision();
+ DrawBgActorCollision();
+ DrawColCheckCollision();
+ DrawWaterboxList();
+ }
+
+ if ((vtxDl.size() > vtxDlCapacity) || (mtxDl.size() > mtxDlCapacity)) {
+ // If the sizes somehow changed between the two draws, we can't continue because we may be using invalid data
+ printf("Error drawing collision, vertex/matrix sizes didn't settle.\n");
+ return;
+ }
+
+ OPEN_DISPS(gPlayState->state.gfxCtx);
+
+ uint8_t mirroredWorld = CVarGetInteger("gMirroredWorld", 0);
+ // Col viewer needs inverted culling in mirror mode for both OPA and XLU buffers
+ if (mirroredWorld) {
+ gSPSetExtraGeometryMode(POLY_OPA_DISP++, G_EX_INVERT_CULLING);
+ gSPSetExtraGeometryMode(POLY_XLU_DISP++, G_EX_INVERT_CULLING);
+ }
+
+ opaDl.push_back(gsSPEndDisplayList());
+ gSPDisplayList(POLY_OPA_DISP++, opaDl.data());
+
+ xluDl.push_back(gsSPEndDisplayList());
+ gSPDisplayList(POLY_XLU_DISP++, xluDl.data());
+
+ if (mirroredWorld) {
+ gSPClearExtraGeometryMode(POLY_OPA_DISP++, G_EX_INVERT_CULLING);
+ gSPClearExtraGeometryMode(POLY_XLU_DISP++, G_EX_INVERT_CULLING);
+ }
+
+ CLOSE_DISPS(gPlayState->state.gfxCtx);
+}
diff --git a/mm/2s2h/DeveloperTools/CollisionViewer.h b/mm/2s2h/DeveloperTools/CollisionViewer.h
new file mode 100644
index 000000000..931436895
--- /dev/null
+++ b/mm/2s2h/DeveloperTools/CollisionViewer.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#ifdef __cplusplus
+#include
+
+extern "C" {
+#endif
+
+void DrawCollisionViewer();
+
+#ifdef __cplusplus
+}
+
+class CollisionViewerWindow : public LUS::GuiWindow {
+ public:
+ using GuiWindow::GuiWindow;
+
+ void InitElement() override;
+ void DrawElement() override;
+ void UpdateElement() override {};
+};
+
+#endif
diff --git a/mm/src/code/z_play.c b/mm/src/code/z_play.c
index 22cefb632..7f218a2fa 100644
--- a/mm/src/code/z_play.c
+++ b/mm/src/code/z_play.c
@@ -35,6 +35,7 @@ u8 sMotionBlurStatus;
#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h"
#include "debug.h"
#include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h"
+#include "2s2h/DeveloperTools/CollisionViewer.h"
#include "2s2h/framebuffer_effects.h"
#include
@@ -1523,6 +1524,9 @@ void Play_DrawMain(PlayState* this) {
}
}
+ // Draw collision before the PostWorldDraw label so that collision is not drawn during pause
+ DrawCollisionViewer();
+
PostWorldDraw:
if (1) {
Play_PostWorldDraw(this);
From ab76a843ea1f14f148b9062223a15e0fc88cf099 Mon Sep 17 00:00:00 2001
From: PurpleHato
Date: Thu, 18 Apr 2024 03:06:31 +0200
Subject: [PATCH 03/13] ADD: Widescreen Ocarina Effects (#219)
* ADD: Widescreen Ocarina Effect
* TWEAK: remove unecessary reassignment
* TWEAK: better assignments
* TWEAK: forgot some vanilla value
---
.../actors/ovl_Oceff_Wipe/z_oceff_wipe.c | 15 +++++++++++++--
.../actors/ovl_Oceff_Wipe2/z_oceff_wipe2.c | 13 +++++++++++--
.../actors/ovl_Oceff_Wipe3/z_oceff_wipe3.c | 13 +++++++++++--
.../actors/ovl_Oceff_Wipe4/z_oceff_wipe4.c | 13 +++++++++++--
.../actors/ovl_Oceff_Wipe5/z_oceff_wipe5.c | 17 ++++++++++++++++-
.../actors/ovl_Oceff_Wipe6/z_oceff_wipe6.c | 13 +++++++++++--
.../actors/ovl_Oceff_Wipe7/z_oceff_wipe7.c | 13 +++++++++++--
7 files changed, 84 insertions(+), 13 deletions(-)
diff --git a/mm/src/overlays/actors/ovl_Oceff_Wipe/z_oceff_wipe.c b/mm/src/overlays/actors/ovl_Oceff_Wipe/z_oceff_wipe.c
index bd843f589..c52373242 100644
--- a/mm/src/overlays/actors/ovl_Oceff_Wipe/z_oceff_wipe.c
+++ b/mm/src/overlays/actors/ovl_Oceff_Wipe/z_oceff_wipe.c
@@ -77,10 +77,21 @@ void OceffWipe_Draw(Actor* thisx, PlayState* play) {
OPEN_DISPS(play->state.gfxCtx);
+ // #region 2S2H [Widescreen] Ocarina Effects
+ f32 effectDistance;
+ s32 x = OTRGetRectDimensionFromLeftEdge(0) << 2;
+ if (x < 0) {
+ // Only render if the screen is wider then original
+ effectDistance = 1360.0f / (OTRGetAspectRatio() * 0.85f); // Widescreen value
+ } else {
+ effectDistance = 1360.0f; // Vanilla value
+ }
+ // #endregion
+
if (this->counter < 32) {
- z = Math_SinS(this->counter << 9) * 1360.0f;
+ z = Math_SinS(this->counter << 9) * effectDistance;
} else {
- z = 1360.0f;
+ z = effectDistance;
}
if (this->counter >= 80) {
diff --git a/mm/src/overlays/actors/ovl_Oceff_Wipe2/z_oceff_wipe2.c b/mm/src/overlays/actors/ovl_Oceff_Wipe2/z_oceff_wipe2.c
index 3478ecf35..86eacc6d7 100644
--- a/mm/src/overlays/actors/ovl_Oceff_Wipe2/z_oceff_wipe2.c
+++ b/mm/src/overlays/actors/ovl_Oceff_Wipe2/z_oceff_wipe2.c
@@ -71,10 +71,19 @@ void OceffWipe2_Draw(Actor* thisx, PlayState* play) {
vtxPtr = ResourceMgr_LoadVtxByName(sEponaSongFrustumVtx);
+ // #region 2S2H [Widescreen] Ocarina Effects
+ f32 effectDistance = 1220.0f; // Vanilla value
+ s32 x = OTRGetRectDimensionFromLeftEdge(0) << 2;
+ if (x < 0) {
+ // Only render if the screen is wider then original
+ effectDistance = 1220.0f / (OTRGetAspectRatio() * 0.85f); // Widescreen value
+ }
+ // #endregion
+
if (this->timer < 32) {
- z = Math_SinS(this->timer * 0x200) * 1220.0f;
+ z = Math_SinS(this->timer * 0x200) * effectDistance;
} else {
- z = 1220.0f;
+ z = effectDistance;
}
if (this->timer >= 80) {
diff --git a/mm/src/overlays/actors/ovl_Oceff_Wipe3/z_oceff_wipe3.c b/mm/src/overlays/actors/ovl_Oceff_Wipe3/z_oceff_wipe3.c
index 350620170..61aafc64d 100644
--- a/mm/src/overlays/actors/ovl_Oceff_Wipe3/z_oceff_wipe3.c
+++ b/mm/src/overlays/actors/ovl_Oceff_Wipe3/z_oceff_wipe3.c
@@ -72,10 +72,19 @@ void OceffWipe3_Draw(Actor* thisx, PlayState* play) {
vtxPtr = ResourceMgr_LoadVtxByName(sSariaSongFrustumVtx);
+ // #region 2S2H [Widescreen] Ocarina Effects
+ f32 effectDistance = 1220.0f; // Vanilla value
+ s32 x = OTRGetRectDimensionFromLeftEdge(0) << 2;
+ if (x < 0) {
+ // Only render if the screen is wider then original
+ effectDistance = 1220.0f / (OTRGetAspectRatio() * 0.85f); // Widescreen value
+ }
+ // #endregion
+
if (this->counter < 32) {
- z = Math_SinS(this->counter * 512) * 1220.0f;
+ z = Math_SinS(this->counter * 512) * effectDistance;
} else {
- z = 1220.0f;
+ z = effectDistance;
}
if (this->counter >= 80) {
diff --git a/mm/src/overlays/actors/ovl_Oceff_Wipe4/z_oceff_wipe4.c b/mm/src/overlays/actors/ovl_Oceff_Wipe4/z_oceff_wipe4.c
index 49eed5cba..089c078bc 100644
--- a/mm/src/overlays/actors/ovl_Oceff_Wipe4/z_oceff_wipe4.c
+++ b/mm/src/overlays/actors/ovl_Oceff_Wipe4/z_oceff_wipe4.c
@@ -69,10 +69,19 @@ void OceffWipe4_Draw(Actor* thisx, PlayState* play) {
quakeOffset = Camera_GetQuakeOffset(GET_ACTIVE_CAM(play));
+ // #region 2S2H [Widescreen] Ocarina Effects
+ f32 effectDistance = 1220.0f; // Vanilla value
+ s32 x = OTRGetRectDimensionFromLeftEdge(0) << 2;
+ if (x < 0) {
+ // Only render if the screen is wider then original
+ effectDistance = 1220.0f / (OTRGetAspectRatio() * 0.85f); // Widescreen value
+ }
+ // #endregion
+
if (this->counter < 16) {
- z = Math_SinS(this->counter * 0x400) * 1220.0f;
+ z = Math_SinS(this->counter * 0x400) * effectDistance;
} else {
- z = 1220.0f;
+ z = effectDistance;
}
vtxPtr = ResourceMgr_LoadVtxByName(sScarecrowSongFrustumVtx);
diff --git a/mm/src/overlays/actors/ovl_Oceff_Wipe5/z_oceff_wipe5.c b/mm/src/overlays/actors/ovl_Oceff_Wipe5/z_oceff_wipe5.c
index 6afa73c1a..e30a6f764 100644
--- a/mm/src/overlays/actors/ovl_Oceff_Wipe5/z_oceff_wipe5.c
+++ b/mm/src/overlays/actors/ovl_Oceff_Wipe5/z_oceff_wipe5.c
@@ -82,10 +82,25 @@ void OceffWipe5_Draw(Actor* thisx, PlayState* play) {
s32 colorIndex = OCEFF_WIPE5_GET_SONG_TYPE(thisx) * 3;
f32 phi_fv1 = 1220.0f;
+ // #region 2S2H [Widescreen] Ocarina Effects
+ s32 x = OTRGetRectDimensionFromLeftEdge(0) << 2;
+ if (x < 0) {
+ // Only render if the screen is wider then original
+ phi_fv1 = 1220.0f / (OTRGetAspectRatio() * 0.85f); // Widescreen value
+ }
+ // #endregion
+
if ((((OCEFF_WIPE5_GET_SONG_TYPE(thisx) == 2) && (play->sceneId == SCENE_LABO)) &&
((play->csCtx.scriptIndex == 0) || (play->csCtx.scriptIndex == 1))) &&
(play->csCtx.state != CS_STATE_IDLE)) {
- phi_fv1 = 1150.0f;
+ // #region 2S2H [Widescreen] Ocarina Effects
+ if (x < 0) {
+ // Only render if the screen is wider then original
+ phi_fv1 = 1150.0f / (OTRGetAspectRatio() * 0.85f); // Widescreen value
+ } else {
+ phi_fv1 = 1150.0f; // Vanilla value
+ }
+ // #endregion
}
if (colorIndex >= 13) {
diff --git a/mm/src/overlays/actors/ovl_Oceff_Wipe6/z_oceff_wipe6.c b/mm/src/overlays/actors/ovl_Oceff_Wipe6/z_oceff_wipe6.c
index 444279b78..555f19367 100644
--- a/mm/src/overlays/actors/ovl_Oceff_Wipe6/z_oceff_wipe6.c
+++ b/mm/src/overlays/actors/ovl_Oceff_Wipe6/z_oceff_wipe6.c
@@ -72,11 +72,20 @@ void OceffWipe6_Draw(Actor* thisx, PlayState* play) {
activeCamEye = GET_ACTIVE_CAM(play)->eye;
quakeOffset = Camera_GetQuakeOffset(GET_ACTIVE_CAM(play));
+ // #region 2S2H [Widescreen] Ocarina Effects
+ f32 effectDistance = 1220.0f; // Vanilla value
+ s32 x = OTRGetRectDimensionFromLeftEdge(0) << 2;
+ if (x < 0) {
+ // Only render if the screen is wider then original
+ effectDistance = 1220.0f / (OTRGetAspectRatio() * 0.85f); // Widescreen value
+ }
+ // #endregion
+
if (this->counter < 32) {
counter = this->counter;
- z = Math_SinS(counter * 0x200) * 1220.0f;
+ z = Math_SinS(counter * 0x200) * effectDistance;
} else {
- z = 1220.0f;
+ z = effectDistance;
}
if (this->counter >= 80) {
diff --git a/mm/src/overlays/actors/ovl_Oceff_Wipe7/z_oceff_wipe7.c b/mm/src/overlays/actors/ovl_Oceff_Wipe7/z_oceff_wipe7.c
index e319dce87..ad93e9934 100644
--- a/mm/src/overlays/actors/ovl_Oceff_Wipe7/z_oceff_wipe7.c
+++ b/mm/src/overlays/actors/ovl_Oceff_Wipe7/z_oceff_wipe7.c
@@ -74,10 +74,19 @@ void OceffWipe7_Draw(Actor* thisx, PlayState* play) {
quakeOffset = Camera_GetQuakeOffset(GET_ACTIVE_CAM(play));
+ // #region 2S2H [Widescreen] Ocarina Effects
+ f32 effectDistance = 1220.0f; // Vanilla value
+ s32 x = OTRGetRectDimensionFromLeftEdge(0) << 2;
+ if (x < 0) {
+ // Only render if the screen is wider then original
+ effectDistance = 1220.0f / (OTRGetAspectRatio() * 0.85f); // Widescreen value
+ }
+ // #endregion
+
if (this->counter < 32) {
- z = Math_SinS(this->counter * 0x200) * 1220.0f;
+ z = Math_SinS(this->counter * 0x200) * effectDistance;
} else {
- z = 1220.0f;
+ z = effectDistance;
}
if (this->counter >= 80) {
From 422bb6054428b26b79cf9a4d5618fa57d6e54495 Mon Sep 17 00:00:00 2001
From: PurpleHato
Date: Thu, 18 Apr 2024 03:18:20 +0200
Subject: [PATCH 04/13] ADD: Transition widescre (#221)
---
mm/src/overlays/fbdemos/ovl_fbdemo_wipe3/z_fbdemo_wipe3.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/mm/src/overlays/fbdemos/ovl_fbdemo_wipe3/z_fbdemo_wipe3.c b/mm/src/overlays/fbdemos/ovl_fbdemo_wipe3/z_fbdemo_wipe3.c
index 39b14c3ee..3ff8463dd 100644
--- a/mm/src/overlays/fbdemos/ovl_fbdemo_wipe3/z_fbdemo_wipe3.c
+++ b/mm/src/overlays/fbdemos/ovl_fbdemo_wipe3/z_fbdemo_wipe3.c
@@ -125,6 +125,14 @@ void TransitionWipe3_Draw(void* thisx, Gfx** gfxP) {
f32 scale = 14.8f;
Gfx* texScroll;
+ // #region 2S2H [Widescreen] Ocarina Effects
+ s32 x = OTRGetRectDimensionFromLeftEdge(0) << 2;
+ if (x < 0) {
+ // Only render if the screen is wider then original
+ scale = 14.8f * (OTRGetAspectRatio() * 0.94f); // Widescreen value
+ }
+ // #endregion
+
THIS->frame ^= 1;
gDPPipeSync(gfx++);
texScroll = Gfx_BranchTexScroll(&gfx, THIS->scrollX, THIS->scrollY, 16, 64);
From 94c0d082fcf5bd6d7ba67963701328fda15c6474 Mon Sep 17 00:00:00 2001
From: Archez
Date: Wed, 17 Apr 2024 21:20:18 -0400
Subject: [PATCH 05/13] Add more time options to save editor (#198)
* Add more time options to save editor
* todo the todo
---
mm/2s2h/DeveloperTools/SaveEditor.cpp | 81 +++++++++++++++++++++++++--
1 file changed, 76 insertions(+), 5 deletions(-)
diff --git a/mm/2s2h/DeveloperTools/SaveEditor.cpp b/mm/2s2h/DeveloperTools/SaveEditor.cpp
index 41ac5112a..157cd4f1a 100644
--- a/mm/2s2h/DeveloperTools/SaveEditor.cpp
+++ b/mm/2s2h/DeveloperTools/SaveEditor.cpp
@@ -4,11 +4,13 @@
extern "C" {
#include
#include
+#include
extern PlayState* gPlayState;
extern SaveContext gSaveContext;
extern TexturePtr gItemIcons[131];
extern u8 gItemSlots[77];
void Interface_LoadItemIconImpl(PlayState* play, u8 btn);
+void Interface_NewDay(PlayState* play, s32 day);
extern RegEditor* gRegEditor;
}
@@ -122,6 +124,7 @@ void DrawGeneralTab() {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 8.0f));
ImGui::BeginChild("generalTab", ImVec2(0, 0), true);
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5f - 4.0f);
+
ImGui::BeginGroup();
ImGui::Text("Player Name");
ImGui::PushStyleColor(ImGuiCol_FrameBg, UIWidgets::Colors::Gray);
@@ -132,6 +135,7 @@ void DrawGeneralTab() {
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(1);
ImGui::EndGroup();
+
ImGui::SameLine();
ImGui::SetCursorPosY(0.0f);
ImGui::BeginGroup();
@@ -139,9 +143,27 @@ void DrawGeneralTab() {
UIWidgets::PushStyleSlider();
static u16 minTime = 0;
static u16 maxTime = 0xFFFF;
- ImGui::SliderScalar("##timeInput", ImGuiDataType_U16, &gSaveContext.save.time, &minTime, &maxTime, "%x");
+ // Get current time and format as digital string
+ u16 curMinutes = (s32)TIME_TO_MINUTES_F(gSaveContext.save.time) % 60;
+ u16 curHours = (s32)TIME_TO_MINUTES_F(gSaveContext.save.time) / 60;
+ std::string minutes = (curMinutes < 10 ? "0" : "") + std::to_string(curMinutes);
+ std::string hours = "";
+ std::string ampm = "";
+ // BENTODO: Switch to CVar if we ever add 24 hour mode display
+ if (false) {
+ if (curHours < 10) {
+ hours += "0";
+ }
+ } else {
+ ampm = curHours >= 12 ? " pm" : " am";
+ curHours = curHours % 12 ? curHours % 12 : 12;
+ }
+ hours += std::to_string(curHours);
+ std::string timeString = hours + ":" + minutes + ampm + " (0x%x)";
+ ImGui::SliderScalar("##timeInput", ImGuiDataType_U16, &gSaveContext.save.time, &minTime, &maxTime, timeString.c_str());
UIWidgets::PopStyleSlider();
ImGui::EndGroup();
+
ImGui::BeginGroup();
if (UIWidgets::Button("Max Health", { .color = UIWidgets::Colors::Red, .size = UIWidgets::Sizes::Inline })) {
gSaveContext.save.saveInfo.playerData.doubleDefense = 1;
@@ -173,7 +195,49 @@ void DrawGeneralTab() {
ImGui::SliderScalar("##healthSlider", ImGuiDataType_S16, &gSaveContext.save.saveInfo.playerData.health, &S16_ZERO, &gSaveContext.save.saveInfo.playerData.healthCapacity, "Health: %d");
UIWidgets::PopStyleSlider();
ImGui::EndGroup();
+
+ // Time skip buttons
ImGui::SameLine();
+ ImGui::BeginGroup();
+ static const std::array, 4> timeSkipAmounts = {
+ { { -60, "-1hr" }, { -15, "-15m" }, { 15, "+15m" }, { 60, "+1hr" } }
+ };
+ for (size_t i = 0; i < timeSkipAmounts.size(); i++) {
+ const auto& skip = timeSkipAmounts.at(i);
+ if (UIWidgets::Button(skip.second, { .color = UIWidgets::Colors::Indigo, .size = UIWidgets::Sizes::Inline })) {
+ gSaveContext.save.time += CLOCK_TIME(0, skip.first);
+ }
+ if (i < timeSkipAmounts.size() - 1) {
+ ImGui::SameLine();
+ }
+ }
+ // Day slider
+ UIWidgets::PushStyleSlider();
+ static s32 minDay = 1;
+ static s32 maxDay = 3;
+ if (ImGui::SliderScalar("##dayInput", ImGuiDataType_S32, &gSaveContext.save.day, &minDay, &maxDay, "Day: %d")) {
+ gSaveContext.save.eventDayCount = CURRENT_DAY;
+ // Reset the time to the start of day/night
+ if (gSaveContext.save.time < CLOCK_TIME(6, 0) || gSaveContext.save.time > CLOCK_TIME(18, 0)) {
+ gSaveContext.save.time = CLOCK_TIME(18, 1);
+ } else {
+ gSaveContext.save.time = CLOCK_TIME(6, 1);
+ }
+ if (gPlayState != nullptr) {
+ Interface_NewDay(gPlayState, CURRENT_DAY);
+ // Inverting setup actors forces actors to kill/respawn for new day
+ gPlayState->numSetupActors = -gPlayState->numSetupActors;
+ }
+ }
+ // Time speed slider
+ // Values are added to R_TIME_SPEED which is generally 3 in normal play state
+ static s32 minSpeed = -3; // Time frozen
+ static s32 maxSpeed = 7;
+ std::string timeSpeedString = "Time Speed: " + std::to_string(gSaveContext.save.timeSpeedOffset + 3);
+ ImGui::SliderScalar("##timeSpeedInput", ImGuiDataType_S32, &gSaveContext.save.timeSpeedOffset, &minSpeed, &maxSpeed, timeSpeedString.c_str());
+ UIWidgets::PopStyleSlider();
+ ImGui::EndGroup();
+
ImGui::BeginGroup();
if (UIWidgets::Button("Max Magic", { .color = UIWidgets::Colors::Green, .size = UIWidgets::Sizes::Inline })) {
gSaveContext.magicCapacity = gSaveContext.save.saveInfo.playerData.magic = MAGIC_DOUBLE_METER;
@@ -383,8 +447,10 @@ void DrawSlot(InventorySlot slot) {
void DrawItemsAndMasksTab() {
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 8.0f));
+ // Group left side in a child for a scroll bar
+ ImGui::BeginChild("leftSide##items", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX);
ImGui::BeginGroup();
- ImGui::BeginChild("itemsBox", ImVec2(INV_GRID_WIDTH * 6 + INV_GRID_PADDING * 2, INV_GRID_HEIGHT * 4 + INV_GRID_PADDING * 2 + INV_GRID_TOP_MARGIN), true);
+ ImGui::BeginChild("itemsBox", ImVec2(INV_GRID_WIDTH * 6 + INV_GRID_PADDING * 2, INV_GRID_HEIGHT * 4 + INV_GRID_PADDING * 2 + INV_GRID_TOP_MARGIN), ImGuiChildFlags_Border);
ImGui::Text("Items");
for (uint32_t i = SLOT_OCARINA; i <= SLOT_BOTTLE_6; i++) {
InventorySlot slot = static_cast(i);
@@ -392,7 +458,7 @@ void DrawItemsAndMasksTab() {
DrawSlot(slot);
}
ImGui::EndChild();
- ImGui::BeginChild("masksBox", ImVec2(INV_GRID_WIDTH * 6 + INV_GRID_PADDING * 2, 0), true);
+ ImGui::BeginChild("masksBox", ImVec2(INV_GRID_WIDTH * 6 + INV_GRID_PADDING * 2, INV_GRID_HEIGHT * 4 + INV_GRID_PADDING * 2 + INV_GRID_TOP_MARGIN), ImGuiChildFlags_Border);
ImGui::Text("Masks");
for (uint32_t i = SLOT_MASK_POSTMAN; i <= SLOT_MASK_FIERCE_DEITY; i++) {
InventorySlot slot = static_cast(i);
@@ -401,6 +467,8 @@ void DrawItemsAndMasksTab() {
}
ImGui::EndChild();
ImGui::EndGroup();
+ ImGui::EndChild();
+
ImGui::SameLine();
ImGui::BeginChild("equipsBox", ImVec2(0, 0), true);
if (UIWidgets::Button("Give All##items")) {
@@ -472,6 +540,9 @@ void DrawRegEditorTab() {
UIWidgets::PushStyleSlider();
ImGui::SliderScalar("Reg Group", ImGuiDataType_U8, &gRegEditor->regGroup, &S8_ZERO, ®_GROUPS_MAX, regGroupNames[gRegEditor->regGroup]);
ImGui::SliderScalar("Reg Page", ImGuiDataType_U8, &gRegEditor->regPage, &S8_ZERO, ®_PAGES_MAX, "%d");
+ UIWidgets::PopStyleSlider();
+
+ ImGui::BeginChild("regSliders", ImVec2(0, 0), ImGuiChildFlags_Border);
for (int i = 0; i < REG_PER_PAGE; i++) {
ImGui::PushID(i);
@@ -490,7 +561,7 @@ void DrawRegEditorTab() {
ImGui::PopID();
}
- UIWidgets::PopStyleSlider();
+ ImGui::EndChild();
}
void SaveEditorWindow::DrawElement() {
@@ -549,4 +620,4 @@ void SaveEditorWindow::InitElement() {
const char* path = static_cast(entry);
LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1));
}
-}
\ No newline at end of file
+}
From 24921d31502508ca200aefa8383fe96f4c2fac4b Mon Sep 17 00:00:00 2001
From: PurpleHato
Date: Thu, 18 Apr 2024 03:32:54 +0200
Subject: [PATCH 06/13] ADD: Widescreen compatible sandstorm (#223)
Co-authored-by: Archez
---
mm/src/code/z_kankyo.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/mm/src/code/z_kankyo.c b/mm/src/code/z_kankyo.c
index d2b120555..b01788eeb 100644
--- a/mm/src/code/z_kankyo.c
+++ b/mm/src/code/z_kankyo.c
@@ -2923,8 +2923,26 @@ void Environment_DrawSandstorm(PlayState* play, u8 sandstormState) {
Gfx_TwoTexScroll(play->state.gfxCtx, G_TX_RENDERTILE, (u32)sp96 % 4096, 0, 512, 32, 1,
(u32)sp94 % 4096, 4095 - ((u32)sp92 % 4096), 256, 64));
gDPSetTextureLUT(POLY_XLU_DISP++, G_TT_NONE);
- gSPDisplayList(POLY_XLU_DISP++, gFieldSandstormDL);
-
+ // #region 2S2H [Widescreen] Widescreen Sandstorm
+ //gSPDisplayList(POLY_XLU_DISP++, gFieldSandstormDL); // Original Dlist call
+ gDPPipeSync(POLY_XLU_DISP++);
+ gDPSetTextureLUT(POLY_XLU_DISP++, G_TT_NONE);
+ gDPSetRenderMode(POLY_XLU_DISP++, G_RM_PASS, G_RM_CLD_SURF2);
+ gDPSetCombineLERP(POLY_XLU_DISP++, TEXEL1, TEXEL0, PRIM_LOD_FRAC, TEXEL0, TEXEL1, TEXEL0, ENVIRONMENT, TEXEL0,
+ PRIMITIVE,
+ ENVIRONMENT, COMBINED, ENVIRONMENT, COMBINED, 0, PRIMITIVE, 0);
+ gSPClearGeometryMode(POLY_XLU_DISP++, G_CULL_BACK | G_FOG | G_LIGHTING | G_TEXTURE_GEN | G_TEXTURE_GEN_LINEAR);
+ gDPLoadTextureBlock(POLY_XLU_DISP++, gFieldSandstorm1Tex, G_IM_FMT_I, G_IM_SIZ_8b, 64, 32, 0,
+ G_TX_NOMIRROR | G_TX_WRAP,
+ G_TX_NOMIRROR | G_TX_WRAP, 6, 5, 1, G_TX_NOLOD);
+ gDPLoadMultiBlock(POLY_XLU_DISP++, gFieldSandstorm2Tex, 0x0100, 1, G_IM_FMT_IA, G_IM_SIZ_8b, 64, 32, 0,
+ G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 6, 5, 2, 1);
+ gSPDisplayList(POLY_XLU_DISP++, 0x08000000 | 1);
+ gSPWideTextureRectangle(POLY_XLU_DISP++, OTRGetRectDimensionFromLeftEdge(0) << 2, 0,
+ OTRGetRectDimensionFromRightEdge(SCREEN_WIDTH) << 2, SCREEN_HEIGHT << 2,
+ G_TX_RENDERTILE, 0, 0, 0x008C, -0x008C);
+ // #endregion
+
CLOSE_DISPS(play->state.gfxCtx);
}
From a10653e1e4500398f026dc1f29d13ef975eabe0d Mon Sep 17 00:00:00 2001
From: Garrett Cox
Date: Wed, 17 Apr 2024 21:29:15 -0500
Subject: [PATCH 07/13] Actor hooks (#135)
---
mm/2s2h/BenGui/BenMenuBar.cpp | 12 ++
mm/2s2h/BenPort.cpp | 2 +
mm/2s2h/DeveloperTools/ActorViewer.cpp | 39 ++++
mm/2s2h/DeveloperTools/DeveloperTools.cpp | 56 ++++++
mm/2s2h/DeveloperTools/DeveloperTools.h | 14 ++
mm/2s2h/Enhancements/Enhancements.cpp | 14 ++
.../GameInteractor/GameInteractor.cpp | 76 +++++++-
.../GameInteractor/GameInteractor.h | 182 ++++++++++++++++--
.../GameInteractor/GameInteractorHooks.cpp | 15 --
.../GameInteractor/GameInteractorHooks.h | 14 --
mm/include/z64actor.h | 2 +-
mm/src/code/game.c | 2 +-
mm/src/code/z_actor.c | 33 +++-
mm/src/code/z_demo.c | 7 +-
14 files changed, 412 insertions(+), 56 deletions(-)
create mode 100644 mm/2s2h/DeveloperTools/DeveloperTools.cpp
create mode 100644 mm/2s2h/DeveloperTools/DeveloperTools.h
delete mode 100644 mm/2s2h/Enhancements/GameInteractor/GameInteractorHooks.cpp
delete mode 100644 mm/2s2h/Enhancements/GameInteractor/GameInteractorHooks.h
diff --git a/mm/2s2h/BenGui/BenMenuBar.cpp b/mm/2s2h/BenGui/BenMenuBar.cpp
index 8d58999d6..a715c5ad3 100644
--- a/mm/2s2h/BenGui/BenMenuBar.cpp
+++ b/mm/2s2h/BenGui/BenMenuBar.cpp
@@ -7,6 +7,7 @@
#include
#include
#include "2s2h/Enhancements/Enhancements.h"
+#include "2s2h/DeveloperTools/DeveloperTools.h"
#include "HudEditor.h"
extern bool ShouldClearTextureCacheAtEndOfFrame;
@@ -294,6 +295,8 @@ void DrawEnhancementsMenu() {
UIWidgets::CVarCheckbox("Authentic logo", "gEnhancements.General.AuthenticLogo", {
.tooltip = "Hide the game version and build details and display the authentic model and texture on the boot logo start screen"
});
+ UIWidgets::CVarCheckbox("Skip Entrance Cutscenes", "gEnhancements.TimeSavers.SkipEntranceCutscenes");
+ UIWidgets::CVarCheckbox("Hide Title Cards", "gEnhancements.TimeSavers.HideTitleCards");
if (mHudEditorWindow) {
UIWidgets::WindowButton("Hud Editor", "gWindows.HudEditor", mHudEditorWindow, {
@@ -337,6 +340,15 @@ void DrawDeveloperToolsMenu() {
});
UIWidgets::CVarCheckbox("Better Map Select", "gDeveloperTools.BetterMapSelect.Enabled");
+ if (UIWidgets::CVarCheckbox("Prevent Actor Update", "gDeveloperTools.PreventActorUpdate")) {
+ RegisterPreventActorUpdateHooks();
+ }
+ if (UIWidgets::CVarCheckbox("Prevent Actor Draw", "gDeveloperTools.PreventActorDraw")) {
+ RegisterPreventActorDrawHooks();
+ }
+ if (UIWidgets::CVarCheckbox("Prevent Actor Init", "gDeveloperTools.PreventActorInit")) {
+ RegisterPreventActorInitHooks();
+ }
if (gPlayState != NULL) {
ImGui::Separator();
diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp
index d00509caa..73cac26b8 100644
--- a/mm/2s2h/BenPort.cpp
+++ b/mm/2s2h/BenPort.cpp
@@ -58,6 +58,7 @@ CrowdControl* CrowdControl::Instance;
#include "Enhancements/Enhancements.h"
#include "2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h"
#include "2s2h/DeveloperTools/DebugConsole.h"
+#include "2s2h/DeveloperTools/DeveloperTools.h"
// Resource Types/Factories
#include "2s2h/resource//type/2shResourceType.h"
@@ -394,6 +395,7 @@ extern "C" void InitOTR() {
GameInteractor::Instance = new GameInteractor();
BenGui::SetupGuiElements();
InitEnhancements();
+ InitDeveloperTools();
GfxPatcher_ApplyNecessaryAuthenticPatches();
DebugConsole_Init();
diff --git a/mm/2s2h/DeveloperTools/ActorViewer.cpp b/mm/2s2h/DeveloperTools/ActorViewer.cpp
index 35ab36206..86e1e7188 100644
--- a/mm/2s2h/DeveloperTools/ActorViewer.cpp
+++ b/mm/2s2h/DeveloperTools/ActorViewer.cpp
@@ -1,6 +1,7 @@
#include "ActorViewer.h"
#include "2s2h/BenGui/UIWidgets.hpp"
#include "global.h"
+#include "2s2h/Enhancements/GameInteractor/GameInteractor.h"
typedef struct ActorInfo {
u16 id;
@@ -76,11 +77,17 @@ static s16 newActorId = 0;
static u8 category = 0;
static s8 method = -1;
static std::string filler = "Please select";
+static HOOK_ID preventActorDrawHookId = 0;
+static HOOK_ID preventActorUpdateHookId = 0;
void ResetVariables() {
display = fetch = {};
newActor = {};
filler = "Please select";
+ GameInteractor::Instance->UnregisterGameHookForPtr(preventActorDrawHookId);
+ GameInteractor::Instance->UnregisterGameHookForPtr(preventActorUpdateHookId);
+ preventActorDrawHookId = 0;
+ preventActorUpdateHookId = 0;
}
void ActorViewerWindow::DrawElement() {
@@ -120,6 +127,10 @@ void ActorViewerWindow::DrawElement() {
display = list[i];
newActorId = i;
filler = label;
+ GameInteractor::Instance->UnregisterGameHookForPtr(preventActorDrawHookId);
+ GameInteractor::Instance->UnregisterGameHookForPtr(preventActorUpdateHookId);
+ preventActorDrawHookId = 0;
+ preventActorUpdateHookId = 0;
break;
}
}
@@ -136,6 +147,34 @@ void ActorViewerWindow::DrawElement() {
ImGui::Text("Params: %hd", display->params);
ImGui::EndGroup();
+ ImGui::BeginGroup();
+ if (preventActorDrawHookId) {
+ if (ImGui::Button("Continue Drawing", ImVec2(ImGui::GetFontSize() * 10, 0))) {
+ GameInteractor::Instance->UnregisterGameHookForPtr(preventActorDrawHookId);
+ preventActorDrawHookId = 0;
+ }
+ } else {
+ if (ImGui::Button("Stop Drawing", ImVec2(ImGui::GetFontSize() * 10, 0))) {
+ preventActorDrawHookId = GameInteractor::Instance->RegisterGameHookForPtr((uintptr_t)display, [](Actor* _, bool* result) {
+ *result = false;
+ });
+ }
+ }
+ ImGui::SameLine();
+ if (preventActorUpdateHookId) {
+ if (ImGui::Button("Continue Updating", ImVec2(ImGui::GetFontSize() * 10, 0))) {
+ GameInteractor::Instance->UnregisterGameHookForPtr(preventActorUpdateHookId);
+ preventActorUpdateHookId = 0;
+ }
+ } else {
+ if (ImGui::Button("Stop Updating", ImVec2(ImGui::GetFontSize() * 10, 0))) {
+ preventActorUpdateHookId = GameInteractor::Instance->RegisterGameHookForPtr((uintptr_t)display, [](Actor* _, bool* result) {
+ *result = false;
+ });
+ }
+ }
+ ImGui::EndGroup();
+
ImGui::PushItemWidth(ImGui::GetFontSize() * 10);
ImGui::BeginGroup();
diff --git a/mm/2s2h/DeveloperTools/DeveloperTools.cpp b/mm/2s2h/DeveloperTools/DeveloperTools.cpp
new file mode 100644
index 000000000..78a41cb1a
--- /dev/null
+++ b/mm/2s2h/DeveloperTools/DeveloperTools.cpp
@@ -0,0 +1,56 @@
+#include "DeveloperTools.h"
+#include
+#include
+#include "2s2h/Enhancements/GameInteractor/GameInteractor.h"
+
+extern "C" {
+#include "z64actor.h"
+}
+
+void RegisterPreventActorUpdateHooks() {
+ static HOOK_ID hookId = 0;
+ if (hookId != 0) {
+ GameInteractor::Instance->UnregisterGameHook(hookId);
+ hookId = 0;
+ }
+
+ if (CVarGetInteger("gDeveloperTools.PreventActorUpdate", 0)) {
+ hookId = GameInteractor::Instance->RegisterGameHook([](Actor* actor, bool* result) {
+ *result = false;
+ });
+ }
+}
+
+void RegisterPreventActorDrawHooks() {
+ static HOOK_ID hookId = 0;
+ if (hookId != 0) {
+ GameInteractor::Instance->UnregisterGameHook(hookId);
+ hookId = 0;
+ }
+
+ if (CVarGetInteger("gDeveloperTools.PreventActorDraw", 0)) {
+ hookId = GameInteractor::Instance->RegisterGameHook([](Actor* actor, bool* result) {
+ *result = false;
+ });
+ }
+}
+
+void RegisterPreventActorInitHooks() {
+ static HOOK_ID hookId = 0;
+ if (hookId != 0) {
+ GameInteractor::Instance->UnregisterGameHookForFilter(hookId);
+ hookId = 0;
+ }
+
+ if (CVarGetInteger("gDeveloperTools.PreventActorInit", 0)) {
+ hookId = GameInteractor::Instance->RegisterGameHookForFilter(GameInteractor::HookFilter::SActorNotPlayer, [](Actor* actor, bool* result) {
+ *result = false;
+ });
+ }
+}
+
+void InitDeveloperTools() {
+ RegisterPreventActorUpdateHooks();
+ RegisterPreventActorDrawHooks();
+ RegisterPreventActorInitHooks();
+}
diff --git a/mm/2s2h/DeveloperTools/DeveloperTools.h b/mm/2s2h/DeveloperTools/DeveloperTools.h
new file mode 100644
index 000000000..e52744d1d
--- /dev/null
+++ b/mm/2s2h/DeveloperTools/DeveloperTools.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void RegisterPreventActorUpdateHooks();
+void RegisterPreventActorDrawHooks();
+void RegisterPreventActorInitHooks();
+void InitDeveloperTools();
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/mm/2s2h/Enhancements/Enhancements.cpp b/mm/2s2h/Enhancements/Enhancements.cpp
index a9f6bc1f7..b0f306e9c 100644
--- a/mm/2s2h/Enhancements/Enhancements.cpp
+++ b/mm/2s2h/Enhancements/Enhancements.cpp
@@ -30,6 +30,19 @@ void RegisterMoonJumpOnL() {
}
}
+void RegisterTimeSaversHooks() {
+ GameInteractor::Instance->RegisterGameHookForID(GI_VB_PLAY_ENTRANCE_CS, [](GIVanillaBehavior _, bool* should, void* __) {
+ if (CVarGetInteger("gEnhancements.TimeSavers.SkipEntranceCutscenes", 0)) {
+ *should = false;
+ }
+ });
+ GameInteractor::Instance->RegisterGameHookForID(GI_VB_SHOW_TITLE_CARD, [](GIVanillaBehavior _, bool* should, void* __) {
+ if (CVarGetInteger("gEnhancements.TimeSavers.HideTitleCards", 0)) {
+ *should = false;
+ }
+ });
+}
+
void RegisterInfiniteCheats() {
GameInteractor::Instance->RegisterGameHook([]() {
if (!gPlayState) return;
@@ -66,4 +79,5 @@ void RegisterInfiniteCheats() {
void InitEnhancements() {
RegisterMoonJumpOnL();
RegisterInfiniteCheats();
+ RegisterTimeSaversHooks();
}
\ No newline at end of file
diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp
index ebede2459..44e718262 100644
--- a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp
+++ b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp
@@ -1,3 +1,77 @@
#include "GameInteractor.h"
-#include
\ No newline at end of file
+extern "C" {
+#include "z64actor.h"
+}
+
+#include
+
+void GameInteractor_ExecuteOnGameStateMainFinish() {
+ GameInteractor::Instance->ExecuteHooks();
+}
+
+void GameInteractor_ExecuteOnGameStateDrawFinish() {
+ GameInteractor::Instance->ExecuteHooks();
+}
+
+void GameInteractor_ExecuteOnGameStateUpdate() {
+ GameInteractor::Instance->ExecuteHooks();
+}
+
+bool GameInteractor_ShouldActorInit(Actor* actor) {
+ bool result = true;
+ GameInteractor::Instance->ExecuteHooks(actor, &result);
+ GameInteractor::Instance->ExecuteHooksForID(actor->id, actor, &result);
+ GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor, &result);
+ GameInteractor::Instance->ExecuteHooksForFilter(actor, &result);
+ return result;
+}
+
+void GameInteractor_ExecuteOnActorInit(Actor* actor) {
+ GameInteractor::Instance->ExecuteHooks(actor);
+ GameInteractor::Instance->ExecuteHooksForID(actor->id, actor);
+ GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor);
+ GameInteractor::Instance->ExecuteHooksForFilter(actor);
+}
+
+bool GameInteractor_ShouldActorUpdate(Actor* actor) {
+ bool result = true;
+ GameInteractor::Instance->ExecuteHooks(actor, &result);
+ GameInteractor::Instance->ExecuteHooksForID(actor->id, actor, &result);
+ GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor, &result);
+ GameInteractor::Instance->ExecuteHooksForFilter(actor, &result);
+ return result;
+}
+
+void GameInteractor_ExecuteOnActorUpdate(Actor* actor) {
+ GameInteractor::Instance->ExecuteHooks(actor);
+ GameInteractor::Instance->ExecuteHooksForID(actor->id, actor);
+ GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor);
+ GameInteractor::Instance->ExecuteHooksForFilter(actor);
+}
+
+bool GameInteractor_ShouldActorDraw(Actor* actor) {
+ bool result = true;
+ GameInteractor::Instance->ExecuteHooks(actor, &result);
+ GameInteractor::Instance->ExecuteHooksForID(actor->id, actor, &result);
+ GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor, &result);
+ GameInteractor::Instance->ExecuteHooksForFilter(actor, &result);
+ return result;
+}
+
+void GameInteractor_ExecuteOnActorDraw(Actor* actor) {
+ GameInteractor::Instance->ExecuteHooks(actor);
+ GameInteractor::Instance->ExecuteHooksForID(actor->id, actor);
+ GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor);
+ GameInteractor::Instance->ExecuteHooksForFilter(actor);
+}
+
+bool GameInteractor_Should(GIVanillaBehavior flag, bool result, void* opt) {
+ GameInteractor::Instance->ExecuteHooks(flag, &result, opt);
+ GameInteractor::Instance->ExecuteHooksForID(flag, flag, &result, opt);
+ if (opt != nullptr) {
+ GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)opt, flag, &result, opt);
+ }
+ GameInteractor::Instance->ExecuteHooksForFilter(flag, &result, opt);
+ return result;
+}
diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h
index 1947deb74..9e56bc67f 100644
--- a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h
+++ b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h
@@ -4,10 +4,16 @@
#ifdef __cplusplus
extern "C" {
#endif
+#include "z64actor.h"
#ifdef __cplusplus
}
#endif
+typedef enum {
+ // Vanilla condition: gSaveContext.showTitleCard
+ GI_VB_SHOW_TITLE_CARD,
+ GI_VB_PLAY_ENTRANCE_CS,
+} GIVanillaBehavior;
#ifdef __cplusplus
@@ -16,9 +22,12 @@ extern "C" {
#include
#include
-#define DEFINE_HOOK(name, type) \
- struct name { \
- typedef std::function fn; \
+typedef uint32_t HOOK_ID;
+
+#define DEFINE_HOOK(name, args) \
+ struct name { \
+ typedef std::function fn; \
+ typedef std::function filter; \
}
class GameInteractor {
@@ -31,11 +40,23 @@ class GameInteractor {
};
// Game Hooks
- uint32_t nextHookId = 1;
- template struct RegisteredGameHooks { inline static std::unordered_map functions; };
- template struct HooksToUnregister { inline static std::vector hooks; };
- template uint32_t RegisterGameHook(typename H::fn h) {
- // Ensure hook id is unique and not 0, which is reserved for invalid hooks
+ HOOK_ID nextHookId = 1;
+
+ template struct RegisteredGameHooks {
+ inline static std::unordered_map functions;
+ inline static std::unordered_map> functionsForID;
+ inline static std::unordered_map> functionsForPtr;
+ inline static std::unordered_map> functionsForFilter;
+ };
+ template struct HooksToUnregister {
+ inline static std::vector hooks;
+ inline static std::vector hooksForID;
+ inline static std::vector hooksForPtr;
+ inline static std::vector hooksForFilter;
+ };
+
+ // General Hooks
+ template HOOK_ID RegisterGameHook(typename H::fn h) {
if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1;
while (RegisteredGameHooks::functions.find(this->nextHookId) != RegisteredGameHooks::functions.end()) {
this->nextHookId++;
@@ -44,10 +65,10 @@ class GameInteractor {
RegisteredGameHooks::functions[this->nextHookId] = h;
return this->nextHookId++;
}
- template void UnregisterGameHook(uint32_t id) {
- HooksToUnregister::hooks.push_back(id);
+ template void UnregisterGameHook(HOOK_ID hookId) {
+ if (hookId == 0) return;
+ HooksToUnregister::hooks.push_back(hookId);
}
-
template void ExecuteHooks(Args&&... args) {
for (auto& hookId : HooksToUnregister::hooks) {
RegisteredGameHooks::functions.erase(hookId);
@@ -58,17 +79,146 @@ class GameInteractor {
}
}
- DEFINE_HOOK(OnGameStateMainFinish, void());
- DEFINE_HOOK(OnGameStateDrawFinish, void());
- DEFINE_HOOK(OnGameStateUpdate, void());
+ // ID based Hooks
+ template HOOK_ID RegisterGameHookForID(int32_t id, typename H::fn h) {
+ if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1;
+ while (RegisteredGameHooks::functionsForID[id].find(this->nextHookId) != RegisteredGameHooks::functionsForID[id].end()) {
+ this->nextHookId++;
+ }
- // Game Actions
- class RawAction {
+ RegisteredGameHooks::functionsForID[id][this->nextHookId] = h;
+ return this->nextHookId++;
+ }
+ template void UnregisterGameHookForID(HOOK_ID hookId) {
+ if (hookId == 0) return;
+ HooksToUnregister::hooksForID.push_back(hookId);
+ }
+ template void ExecuteHooksForID(int32_t id, Args&&... args) {
+ for (auto& hookId : HooksToUnregister::hooksForID) {
+ for (auto it = RegisteredGameHooks::functionsForID[id].begin(); it != RegisteredGameHooks::functionsForID[id].end(); ) {
+ if (it->first == hookId) {
+ it = RegisteredGameHooks::functionsForID[id].erase(it);
+ HooksToUnregister::hooksForID.erase(std::remove(HooksToUnregister::hooksForID.begin(), HooksToUnregister::hooksForID.end(), hookId), HooksToUnregister::hooksForID.end());
+ } else {
+ ++it;
+ }
+ }
+ }
+ for (auto& hook : RegisteredGameHooks::functionsForID[id]) {
+ hook.second(std::forward(args)...);
+ }
+ }
+ // PTR based Hooks
+ template HOOK_ID RegisterGameHookForPtr(uintptr_t ptr, typename H::fn h) {
+ if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1;
+ while (RegisteredGameHooks::functionsForPtr[ptr].find(this->nextHookId) != RegisteredGameHooks::functionsForPtr[ptr].end()) {
+ this->nextHookId++;
+ }
+
+ RegisteredGameHooks::functionsForPtr[ptr][this->nextHookId] = h;
+ return this->nextHookId++;
+ }
+ template void UnregisterGameHookForPtr(HOOK_ID hookId) {
+ if (hookId == 0) return;
+ HooksToUnregister::hooksForPtr.push_back(hookId);
+ }
+ template void ExecuteHooksForPtr(uintptr_t ptr, Args&&... args) {
+ for (auto& hookId : HooksToUnregister::hooksForPtr) {
+ for (auto it = RegisteredGameHooks::functionsForPtr[ptr].begin(); it != RegisteredGameHooks::functionsForPtr[ptr].end(); ) {
+ if (it->first == hookId) {
+ it = RegisteredGameHooks::functionsForPtr[ptr].erase(it);
+ HooksToUnregister::hooksForPtr.erase(std::remove(HooksToUnregister::hooksForPtr.begin(), HooksToUnregister::hooksForPtr.end(), hookId), HooksToUnregister::hooksForPtr.end());
+ } else {
+ ++it;
+ }
+ }
+ }
+ for (auto& hook : RegisteredGameHooks::functionsForPtr[ptr]) {
+ hook.second(std::forward(args)...);
+ }
+ }
+
+ // Filter based Hooks
+ template HOOK_ID RegisterGameHookForFilter(typename H::filter f, typename H::fn h) {
+ if (this->nextHookId == 0 || this->nextHookId >= UINT32_MAX) this->nextHookId = 1;
+ while (RegisteredGameHooks::functionsForFilter.find(this->nextHookId) != RegisteredGameHooks::functionsForFilter.end()) {
+ this->nextHookId++;
+ }
+
+ RegisteredGameHooks::functionsForFilter[this->nextHookId] = std::make_pair(f, h);
+ return this->nextHookId++;
+ }
+ template void UnregisterGameHookForFilter(HOOK_ID hookId) {
+ if (hookId == 0) return;
+ HooksToUnregister::hooksForFilter.push_back(hookId);
+ }
+ template void ExecuteHooksForFilter(Args&&... args) {
+ for (auto& hookId : HooksToUnregister::hooksForFilter) {
+ RegisteredGameHooks::functionsForFilter.erase(hookId);
+ }
+ HooksToUnregister::hooksForFilter.clear();
+ for (auto& hook : RegisteredGameHooks::functionsForFilter) {
+ if (hook.second.first(std::forward(args)...)) {
+ hook.second.second(std::forward(args)...);
+ }
+ }
+ }
+
+ class HookFilter {
+ public:
+ static auto ActorNotPlayer(Actor* actor) {
+ return actor->id != ACTOR_PLAYER;
+ }
+ // For use with Should hooks
+ static auto SActorNotPlayer(Actor* actor, bool* result) {
+ return actor->id != ACTOR_PLAYER;
+ }
+ static auto ActorMatchIdAndParams(int16_t id, int16_t params) {
+ return [id, params](Actor* actor) {
+ return actor->id == id && actor->params == params;
+ };
+ }
+ // For use with Should hooks
+ static auto SActorMatchIdAndParams(int16_t id, int16_t params) {
+ return [id, params](Actor* actor, bool* result) {
+ return actor->id == id && actor->params == params;
+ };
+ }
};
+ DEFINE_HOOK(OnGameStateMainFinish, ());
+ DEFINE_HOOK(OnGameStateDrawFinish, ());
+ DEFINE_HOOK(OnGameStateUpdate, ());
+
+ DEFINE_HOOK(ShouldActorInit, (Actor* actor, bool* should));
+ DEFINE_HOOK(OnActorInit, (Actor* actor));
+ DEFINE_HOOK(ShouldActorUpdate, (Actor* actor, bool* should));
+ DEFINE_HOOK(OnActorUpdate, (Actor* actor));
+ DEFINE_HOOK(ShouldActorDraw, (Actor* actor, bool* should));
+ DEFINE_HOOK(OnActorDraw, (Actor* actor));
+
+ DEFINE_HOOK(ShouldVanillaBehavior, (GIVanillaBehavior flag, bool* should, void* optionalArg));
};
+extern "C" {
#endif // __cplusplus
+void GameInteractor_ExecuteOnGameStateMainFinish();
+void GameInteractor_ExecuteOnGameStateDrawFinish();
+void GameInteractor_ExecuteOnGameStateUpdate();
+
+bool GameInteractor_ShouldActorInit(Actor* actor);
+void GameInteractor_ExecuteOnActorInit(Actor* actor);
+bool GameInteractor_ShouldActorUpdate(Actor* actor);
+void GameInteractor_ExecuteOnActorUpdate(Actor* actor);
+bool GameInteractor_ShouldActorDraw(Actor* actor);
+void GameInteractor_ExecuteOnActorDraw(Actor* actor);
+
+bool GameInteractor_Should(GIVanillaBehavior flag, bool result, void* optionalArg);
+
+#ifdef __cplusplus
+}
+#endif
+
#endif // GAME_INTERACTOR_H
\ No newline at end of file
diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractorHooks.cpp b/mm/2s2h/Enhancements/GameInteractor/GameInteractorHooks.cpp
deleted file mode 100644
index 904ac7c9e..000000000
--- a/mm/2s2h/Enhancements/GameInteractor/GameInteractorHooks.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-#include "GameInteractorHooks.h"
-
-// Gameplay
-
-void GameInteractor_ExecuteOnGameStateMainFinish() {
- GameInteractor::Instance->ExecuteHooks();
-}
-
-void GameInteractor_ExecuteOnGameStateDrawFinish() {
- GameInteractor::Instance->ExecuteHooks();
-}
-
-void GameInteractor_ExecuteOnGameStateUpdate() {
- GameInteractor::Instance->ExecuteHooks();
-}
\ No newline at end of file
diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractorHooks.h b/mm/2s2h/Enhancements/GameInteractor/GameInteractorHooks.h
deleted file mode 100644
index 032626ea4..000000000
--- a/mm/2s2h/Enhancements/GameInteractor/GameInteractorHooks.h
+++ /dev/null
@@ -1,14 +0,0 @@
-#include "GameInteractor.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// Gameplay
-void GameInteractor_ExecuteOnGameStateMainFinish();
-void GameInteractor_ExecuteOnGameStateDrawFinish();
-void GameInteractor_ExecuteOnGameStateUpdate();
-
-#ifdef __cplusplus
-}
-#endif
\ No newline at end of file
diff --git a/mm/include/z64actor.h b/mm/include/z64actor.h
index 5e70de775..aba0fd0b0 100644
--- a/mm/include/z64actor.h
+++ b/mm/include/z64actor.h
@@ -23,7 +23,7 @@ struct CollisionPoly;
struct EnBox;
struct EnTorch2;
-typedef void (*ActorFunc)(struct Actor* this, struct PlayState* play);
+typedef void (*ActorFunc)(struct Actor*, struct PlayState* play); // 2S2H [Port] Removed reserved `this` keyword to fix compilation error
typedef u16 (*NpcGetTextIdFunc)(struct PlayState*, struct Actor*);
typedef s16 (*NpcUpdateTalkStateFunc)(struct PlayState*, struct Actor*);
diff --git a/mm/src/code/game.c b/mm/src/code/game.c
index 2c94fd314..1b349289c 100644
--- a/mm/src/code/game.c
+++ b/mm/src/code/game.c
@@ -13,7 +13,7 @@
#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h"
#include "debug.h"
-#include "Enhancements/GameInteractor/GameInteractorHooks.h"
+#include "Enhancements/GameInteractor/GameInteractor.h"
s32 gFramerateDivisor = 1;
f32 gFramerateDivisorF = 1.0f;
diff --git a/mm/src/code/z_actor.c b/mm/src/code/z_actor.c
index 1b79e7542..f410da34f 100644
--- a/mm/src/code/z_actor.c
+++ b/mm/src/code/z_actor.c
@@ -22,6 +22,7 @@
#include
#include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h"
+#include "2s2h/Enhancements/GameInteractor/GameInteractor.h"
// bss
// FaultClient sActorFaultClient; // 2 funcs
@@ -1135,8 +1136,15 @@ void Actor_Init(Actor* actor, PlayState* play) {
ActorShape_Init(&actor->shape, 0.0f, NULL, 0.0f);
if (Object_IsLoaded(&play->objectCtx, actor->objectSlot)) {
Actor_SetObjectDependency(play, actor);
- actor->init(actor, play);
- actor->init = NULL;
+
+ if (GameInteractor_ShouldActorInit(actor)) {
+ actor->init(actor, play);
+ actor->init = NULL;
+ GameInteractor_ExecuteOnActorInit(actor);
+ } else {
+ actor->init = NULL;
+ Actor_Kill(actor);
+ }
}
}
@@ -2518,8 +2526,15 @@ Actor* Actor_UpdateActor(UpdateActor_Params* params) {
if (actor->init != NULL) {
if (Object_IsLoaded(&play->objectCtx, actor->objectSlot)) {
Actor_SetObjectDependency(play, actor);
- actor->init(actor, play);
- actor->init = NULL;
+
+ if (GameInteractor_ShouldActorInit(actor)) {
+ actor->init(actor, play);
+ actor->init = NULL;
+ GameInteractor_ExecuteOnActorInit(actor);
+ } else {
+ actor->init = NULL;
+ Actor_Kill(actor);
+ }
}
nextActor = actor->next;
} else if (actor->update == NULL) {
@@ -2565,7 +2580,10 @@ Actor* Actor_UpdateActor(UpdateActor_Params* params) {
actor->colorFilterTimer--;
}
- actor->update(actor, play);
+ if (GameInteractor_ShouldActorUpdate(actor)) {
+ actor->update(actor, play);
+ GameInteractor_ExecuteOnActorUpdate(actor);
+ }
DynaPoly_UnsetAllInteractFlags(play, &play->colCtx.dyna, actor);
}
@@ -2759,7 +2777,10 @@ void Actor_Draw(PlayState* play, Actor* actor) {
}
}
- actor->draw(actor, play);
+ if (GameInteractor_ShouldActorDraw(actor)) {
+ actor->draw(actor, play);
+ GameInteractor_ExecuteOnActorDraw(actor);
+ }
if (actor->colorFilterTimer != 0) {
if (actor->colorFilterParams & COLORFILTER_BUFFLAG_XLU) {
diff --git a/mm/src/code/z_demo.c b/mm/src/code/z_demo.c
index 4e1b54e0c..769b9498c 100644
--- a/mm/src/code/z_demo.c
+++ b/mm/src/code/z_demo.c
@@ -8,6 +8,7 @@
#include "overlays/gamestates/ovl_daytelop/z_daytelop.h"
#include "overlays/actors/ovl_En_Elf/z_en_elf.h"
#include
+#include "2s2h/Enhancements/GameInteractor/GameInteractor.h"
s16 sCutsceneQuakeIndex;
struct CutsceneCamera sCutsceneCameraInfo;
@@ -1544,7 +1545,9 @@ void Cutscene_HandleEntranceTriggers(PlayState* play) {
} else if (!CHECK_CS_SPAWN_FLAG_WEEKEVENTREG(play->csCtx.scriptList[scriptIndex].spawnFlags)) {
// Entrance cutscenes that only run once
SET_CS_SPAWN_FLAG_WEEKEVENTREG(play->csCtx.scriptList[scriptIndex].spawnFlags);
- CutsceneManager_Start(csId, NULL);
+ if (GameInteractor_Should(GI_VB_PLAY_ENTRANCE_CS, true, NULL)) {
+ CutsceneManager_Start(csId, NULL);
+ }
// The title card will be used by the cs misc command if necessary.
gSaveContext.showTitleCard = false;
}
@@ -1558,7 +1561,7 @@ void Cutscene_HandleEntranceTriggers(PlayState* play) {
if ((gSaveContext.respawnFlag == 0) || (gSaveContext.respawnFlag == -2)) {
scene = play->loadedScene;
- if ((scene->titleTextId != 0) && gSaveContext.showTitleCard) {
+ if ((scene->titleTextId != 0) && GameInteractor_Should(GI_VB_SHOW_TITLE_CARD, gSaveContext.showTitleCard, NULL)) {
if ((Entrance_GetTransitionFlags(((void)0, gSaveContext.save.entrance) +
((void)0, gSaveContext.sceneLayer)) &
0x4000) != 0) {
From 715b699eb058b55a4a09c36d810f18572d4caf05 Mon Sep 17 00:00:00 2001
From: briaguya <70942617+briaguya-ai@users.noreply.github.com>
Date: Sat, 20 Apr 2024 16:20:27 -0400
Subject: [PATCH 08/13] fd anywhere using hooks (plus splitting enhancements
out into files) (#226)
* fd anywhere using hooks (plus splitting enhancements out into files)
Co-authored-by: aMannus
Co-authored-by: Archez
* add guards to headers
* Update mm/src/code/z_parameter.c
Co-authored-by: Garrett Cox
* clean
* macro magic
* Apply suggestions from code review
---------
Co-authored-by: aMannus
Co-authored-by: Archez
Co-authored-by: Garrett Cox
---
mm/2s2h/BenGui/BenMenuBar.cpp | 8 ++
mm/2s2h/Enhancements/Cheats/Infinite.cpp | 37 ++++++++
mm/2s2h/Enhancements/Cheats/Infinite.h | 6 ++
mm/2s2h/Enhancements/Cheats/MoonJump.cpp | 24 ++++++
mm/2s2h/Enhancements/Cheats/MoonJump.h | 6 ++
mm/2s2h/Enhancements/Enhancements.cpp | 86 ++-----------------
mm/2s2h/Enhancements/Enhancements.h | 13 ++-
.../GameInteractor/GameInteractor.h | 3 +
.../Masks/FierceDeityAnywhere.cpp | 10 +++
.../Enhancements/Masks/FierceDeityAnywhere.h | 6 ++
.../Enhancements/TimeSavers/TimeSavers.cpp | 15 ++++
mm/2s2h/Enhancements/TimeSavers/TimeSavers.h | 6 ++
mm/src/code/z_parameter.c | 11 ++-
13 files changed, 148 insertions(+), 83 deletions(-)
create mode 100644 mm/2s2h/Enhancements/Cheats/Infinite.cpp
create mode 100644 mm/2s2h/Enhancements/Cheats/Infinite.h
create mode 100644 mm/2s2h/Enhancements/Cheats/MoonJump.cpp
create mode 100644 mm/2s2h/Enhancements/Cheats/MoonJump.h
create mode 100644 mm/2s2h/Enhancements/Masks/FierceDeityAnywhere.cpp
create mode 100644 mm/2s2h/Enhancements/Masks/FierceDeityAnywhere.h
create mode 100644 mm/2s2h/Enhancements/TimeSavers/TimeSavers.cpp
create mode 100644 mm/2s2h/Enhancements/TimeSavers/TimeSavers.h
diff --git a/mm/2s2h/BenGui/BenMenuBar.cpp b/mm/2s2h/BenGui/BenMenuBar.cpp
index a715c5ad3..a543d35c7 100644
--- a/mm/2s2h/BenGui/BenMenuBar.cpp
+++ b/mm/2s2h/BenGui/BenMenuBar.cpp
@@ -288,7 +288,15 @@ extern std::shared_ptr mHudEditorWindow;
void DrawEnhancementsMenu() {
if (UIWidgets::BeginMenu("Enhancements")) {
+
+ if (UIWidgets::BeginMenu("Masks")) {
+ UIWidgets::CVarCheckbox("Fierce Deity's Mask Anywhere", "gEnhancements.Masks.FierceDeitysAnywhere", {
+ .tooltip = "Allow using Fierce Deity's mask outside of boss rooms."
+ });
+ ImGui::EndMenu();
+ }
+
UIWidgets::CVarCheckbox("Fast Text", "gEnhancements.TimeSavers.FastText", {
.tooltip = "Speeds up text rendering, and enables holding of B progress to next message"
});
diff --git a/mm/2s2h/Enhancements/Cheats/Infinite.cpp b/mm/2s2h/Enhancements/Cheats/Infinite.cpp
new file mode 100644
index 000000000..2cebeca2e
--- /dev/null
+++ b/mm/2s2h/Enhancements/Cheats/Infinite.cpp
@@ -0,0 +1,37 @@
+#include "Infinite.h"
+#include
+#include "Enhancements/GameInteractor/GameInteractor.h"
+#include "variables.h"
+
+void RegisterInfiniteCheats() {
+ GameInteractor::Instance->RegisterGameHook([]() {
+ if (gPlayState == nullptr) return;
+
+ if (CVarGetInteger("gCheats.InfiniteHealth", 0)) {
+ gSaveContext.save.saveInfo.playerData.health = gSaveContext.save.saveInfo.playerData.healthCapacity;
+ }
+
+ if (CVarGetInteger("gCheats.InfiniteMagic", 0)) {
+ uint8_t magicLevel = gSaveContext.save.saveInfo.playerData.magicLevel;
+ if (magicLevel == 1) {
+ gSaveContext.save.saveInfo.playerData.magic = MAGIC_NORMAL_METER;
+ } else if (magicLevel == 2) {
+ gSaveContext.save.saveInfo.playerData.magic = MAGIC_DOUBLE_METER;
+ }
+ }
+
+ if (CVarGetInteger("gCheats.InfiniteRupees", 0)) {
+ gSaveContext.save.saveInfo.playerData.rupees = CUR_CAPACITY(UPG_WALLET);
+ }
+
+ if (CVarGetInteger("gCheats.InfiniteConsumables", 0)) {
+ AMMO(ITEM_BOW) = CUR_CAPACITY(UPG_QUIVER);
+ AMMO(ITEM_BOMB) = CUR_CAPACITY(UPG_BOMB_BAG);
+ AMMO(ITEM_BOMBCHU) = CUR_CAPACITY(UPG_BOMB_BAG);
+ AMMO(ITEM_DEKU_STICK) = CUR_CAPACITY(UPG_DEKU_STICKS);
+ AMMO(ITEM_DEKU_NUT) = CUR_CAPACITY(UPG_DEKU_NUTS);
+ AMMO(ITEM_MAGIC_BEANS) = 20;
+ AMMO(ITEM_POWDER_KEG) = 1;
+ }
+ });
+}
diff --git a/mm/2s2h/Enhancements/Cheats/Infinite.h b/mm/2s2h/Enhancements/Cheats/Infinite.h
new file mode 100644
index 000000000..7bdffc6cc
--- /dev/null
+++ b/mm/2s2h/Enhancements/Cheats/Infinite.h
@@ -0,0 +1,6 @@
+#ifndef CHEATS_INFINITE_H
+#define CHEATS_INFINITE_H
+
+void RegisterInfiniteCheats();
+
+#endif // CHEATS_INFINITE_H
diff --git a/mm/2s2h/Enhancements/Cheats/MoonJump.cpp b/mm/2s2h/Enhancements/Cheats/MoonJump.cpp
new file mode 100644
index 000000000..7963e6f4b
--- /dev/null
+++ b/mm/2s2h/Enhancements/Cheats/MoonJump.cpp
@@ -0,0 +1,24 @@
+#include "MoonJump.h"
+#include
+#include "Enhancements/GameInteractor/GameInteractor.h"
+#include "variables.h"
+
+static uint32_t moonJumpOnLGameStateUpdateHookId = 0;
+void RegisterMoonJumpOnL() {
+ if (moonJumpOnLGameStateUpdateHookId) {
+ GameInteractor::Instance->UnregisterGameHook(moonJumpOnLGameStateUpdateHookId);
+ moonJumpOnLGameStateUpdateHookId = 0;
+ }
+
+ if (CVarGetInteger("gCheats.MoonJumpOnL", 0)) {
+ moonJumpOnLGameStateUpdateHookId = GameInteractor::Instance->RegisterGameHook([]() {
+ if (gPlayState == nullptr) return;
+
+ Player* player = GET_PLAYER(gPlayState);
+
+ if (CHECK_BTN_ANY(gPlayState->state.input[0].cur.button, BTN_L)) {
+ player->actor.velocity.y = 6.34375f;
+ }
+ });
+ }
+}
diff --git a/mm/2s2h/Enhancements/Cheats/MoonJump.h b/mm/2s2h/Enhancements/Cheats/MoonJump.h
new file mode 100644
index 000000000..5f2727e82
--- /dev/null
+++ b/mm/2s2h/Enhancements/Cheats/MoonJump.h
@@ -0,0 +1,6 @@
+#ifndef CHEATS_MOONJUMP_H
+#define CHEATS_MOONJUMP_H
+
+void RegisterMoonJumpOnL();
+
+#endif // CHEATS_MOONJUMP_H
diff --git a/mm/2s2h/Enhancements/Enhancements.cpp b/mm/2s2h/Enhancements/Enhancements.cpp
index b0f306e9c..b7d82a9d7 100644
--- a/mm/2s2h/Enhancements/Enhancements.cpp
+++ b/mm/2s2h/Enhancements/Enhancements.cpp
@@ -1,83 +1,13 @@
#include "Enhancements.h"
-#include
-#include "GameInteractor/GameInteractor.h"
-
-extern "C" {
-#include
-#include "macros.h"
-// #include "functions.h"
-#include "variables.h"
-extern PlayState* gPlayState;
-}
-
-static uint32_t moonJumpOnLGameStateUpdateHookId = 0;
-void RegisterMoonJumpOnL() {
- if (moonJumpOnLGameStateUpdateHookId) {
- GameInteractor::Instance->UnregisterGameHook(moonJumpOnLGameStateUpdateHookId);
- moonJumpOnLGameStateUpdateHookId = 0;
- }
-
- if (CVarGetInteger("gCheats.MoonJumpOnL", 0)) {
- moonJumpOnLGameStateUpdateHookId = GameInteractor::Instance->RegisterGameHook([]() {
- if (!gPlayState) return;
-
- Player* player = GET_PLAYER(gPlayState);
-
- if (CHECK_BTN_ANY(gPlayState->state.input[0].cur.button, BTN_L)) {
- player->actor.velocity.y = 6.34375f;
- }
- });
- }
-}
-
-void RegisterTimeSaversHooks() {
- GameInteractor::Instance->RegisterGameHookForID(GI_VB_PLAY_ENTRANCE_CS, [](GIVanillaBehavior _, bool* should, void* __) {
- if (CVarGetInteger("gEnhancements.TimeSavers.SkipEntranceCutscenes", 0)) {
- *should = false;
- }
- });
- GameInteractor::Instance->RegisterGameHookForID(GI_VB_SHOW_TITLE_CARD, [](GIVanillaBehavior _, bool* should, void* __) {
- if (CVarGetInteger("gEnhancements.TimeSavers.HideTitleCards", 0)) {
- *should = false;
- }
- });
-}
-
-void RegisterInfiniteCheats() {
- GameInteractor::Instance->RegisterGameHook([]() {
- if (!gPlayState) return;
-
- if (CVarGetInteger("gCheats.InfiniteHealth", 0)) {
- gSaveContext.save.saveInfo.playerData.health = gSaveContext.save.saveInfo.playerData.healthCapacity;
- }
-
- if (CVarGetInteger("gCheats.InfiniteMagic", 0)) {
- uint8_t magicLevel = gSaveContext.save.saveInfo.playerData.magicLevel;
- if (magicLevel == 1) {
- gSaveContext.save.saveInfo.playerData.magic = MAGIC_NORMAL_METER;
- } else if (magicLevel == 2) {
- gSaveContext.save.saveInfo.playerData.magic = MAGIC_DOUBLE_METER;
- }
- }
-
- if (CVarGetInteger("gCheats.InfiniteRupees", 0)) {
- gSaveContext.save.saveInfo.playerData.rupees = CUR_CAPACITY(UPG_WALLET);
- }
-
- if (CVarGetInteger("gCheats.InfiniteConsumables", 0)) {
- AMMO(ITEM_BOW) = CUR_CAPACITY(UPG_QUIVER);
- AMMO(ITEM_BOMB) = CUR_CAPACITY(UPG_BOMB_BAG);
- AMMO(ITEM_BOMBCHU) = CUR_CAPACITY(UPG_BOMB_BAG);
- AMMO(ITEM_DEKU_STICK) = CUR_CAPACITY(UPG_DEKU_STICKS);
- AMMO(ITEM_DEKU_NUT) = CUR_CAPACITY(UPG_DEKU_NUTS);
- AMMO(ITEM_MAGIC_BEANS) = 20;
- AMMO(ITEM_POWDER_KEG) = 1;
- }
- });
-}
void InitEnhancements() {
- RegisterMoonJumpOnL();
+ // Cheats
RegisterInfiniteCheats();
+ RegisterMoonJumpOnL();
+
+ // Masks
+ RegisterFierceDeityAnywhere();
+
+ // Time Savers
RegisterTimeSaversHooks();
-}
\ No newline at end of file
+}
diff --git a/mm/2s2h/Enhancements/Enhancements.h b/mm/2s2h/Enhancements/Enhancements.h
index 9565c3625..9332c3860 100644
--- a/mm/2s2h/Enhancements/Enhancements.h
+++ b/mm/2s2h/Enhancements/Enhancements.h
@@ -1,10 +1,19 @@
+#ifndef ENHANCEMENTS_H
+#define ENHANCEMENTS_H
+
+#include "Cheats/MoonJump.h"
+#include "Cheats/Infinite.h"
+#include "Masks/FierceDeityAnywhere.h"
+#include "TimeSavers/TimeSavers.h"
+
#ifdef __cplusplus
extern "C" {
#endif
void InitEnhancements();
-void RegisterMoonJumpOnL();
#ifdef __cplusplus
}
-#endif
\ No newline at end of file
+#endif
+
+#endif // ENHANCEMENTS_H
diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h
index 9e56bc67f..250d75d03 100644
--- a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h
+++ b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h
@@ -13,6 +13,7 @@ typedef enum {
// Vanilla condition: gSaveContext.showTitleCard
GI_VB_SHOW_TITLE_CARD,
GI_VB_PLAY_ENTRANCE_CS,
+ GI_VB_DISABLE_FD_MASK
} GIVanillaBehavior;
#ifdef __cplusplus
@@ -216,6 +217,8 @@ bool GameInteractor_ShouldActorDraw(Actor* actor);
void GameInteractor_ExecuteOnActorDraw(Actor* actor);
bool GameInteractor_Should(GIVanillaBehavior flag, bool result, void* optionalArg);
+#define REGISTER_VB_SHOULD(flag, body) \
+ GameInteractor::Instance->RegisterGameHookForID(flag, [](GIVanillaBehavior _, bool* should, void* opt) body)
#ifdef __cplusplus
}
diff --git a/mm/2s2h/Enhancements/Masks/FierceDeityAnywhere.cpp b/mm/2s2h/Enhancements/Masks/FierceDeityAnywhere.cpp
new file mode 100644
index 000000000..cf14f605b
--- /dev/null
+++ b/mm/2s2h/Enhancements/Masks/FierceDeityAnywhere.cpp
@@ -0,0 +1,10 @@
+#include
+#include "Enhancements/GameInteractor/GameInteractor.h"
+
+void RegisterFierceDeityAnywhere() {
+ REGISTER_VB_SHOULD(GI_VB_DISABLE_FD_MASK, {
+ if (CVarGetInteger("gEnhancements.Masks.FierceDeitysAnywhere", 0)) {
+ *should = false;
+ }
+ });
+}
diff --git a/mm/2s2h/Enhancements/Masks/FierceDeityAnywhere.h b/mm/2s2h/Enhancements/Masks/FierceDeityAnywhere.h
new file mode 100644
index 000000000..ee5b87e4d
--- /dev/null
+++ b/mm/2s2h/Enhancements/Masks/FierceDeityAnywhere.h
@@ -0,0 +1,6 @@
+#ifndef MASKS_FD_ANYWHERE_H
+#define MASKS_FD_ANYWHERE_H
+
+void RegisterFierceDeityAnywhere();
+
+#endif // MASKS_FD_ANYWHERE_H
diff --git a/mm/2s2h/Enhancements/TimeSavers/TimeSavers.cpp b/mm/2s2h/Enhancements/TimeSavers/TimeSavers.cpp
new file mode 100644
index 000000000..86c47fc28
--- /dev/null
+++ b/mm/2s2h/Enhancements/TimeSavers/TimeSavers.cpp
@@ -0,0 +1,15 @@
+#include
+#include "Enhancements/GameInteractor/GameInteractor.h"
+
+void RegisterTimeSaversHooks() {
+ REGISTER_VB_SHOULD(GI_VB_PLAY_ENTRANCE_CS, {
+ if (CVarGetInteger("gEnhancements.TimeSavers.SkipEntranceCutscenes", 0)) {
+ *should = false;
+ }
+ });
+ REGISTER_VB_SHOULD(GI_VB_SHOW_TITLE_CARD, {
+ if (CVarGetInteger("gEnhancements.TimeSavers.HideTitleCards", 0)) {
+ *should = false;
+ }
+ });
+}
diff --git a/mm/2s2h/Enhancements/TimeSavers/TimeSavers.h b/mm/2s2h/Enhancements/TimeSavers/TimeSavers.h
new file mode 100644
index 000000000..03466f19b
--- /dev/null
+++ b/mm/2s2h/Enhancements/TimeSavers/TimeSavers.h
@@ -0,0 +1,6 @@
+#ifndef TIMESAVERS_H
+#define TIMESAVERS_H
+
+void RegisterTimeSaversHooks();
+
+#endif // TIMESAVERS_H
diff --git a/mm/src/code/z_parameter.c b/mm/src/code/z_parameter.c
index 3396cdfc0..4ebca32ae 100644
--- a/mm/src/code/z_parameter.c
+++ b/mm/src/code/z_parameter.c
@@ -16,6 +16,7 @@
#include "BenPort.h"
#include
#include "BenGui/HudEditor.h"
+#include "Enhancements/GameInteractor/GameInteractor.h"
// #region 2S2H [Port] Asset tables we can pull from instead of from ROM
#define dgEmptyTexture "__OTR__textures/virtual/gEmptyTexture"
@@ -2191,9 +2192,13 @@ void Interface_UpdateButtonsPart2(PlayState* play) {
}
} else if (GET_CUR_FORM_BTN_ITEM(i) == ITEM_MASK_FIERCE_DEITY) {
// Fierce Deity's Mask is equipped
- if ((play->sceneId != SCENE_MITURIN_BS) && (play->sceneId != SCENE_HAKUGIN_BS) &&
- (play->sceneId != SCENE_SEA_BS) && (play->sceneId != SCENE_INISIE_BS) &&
- (play->sceneId != SCENE_LAST_BS)) {
+ u8 vanillaSceneConditionResult =
+ (play->sceneId != SCENE_MITURIN_BS) &&
+ (play->sceneId != SCENE_HAKUGIN_BS) &&
+ (play->sceneId != SCENE_SEA_BS) &&
+ (play->sceneId != SCENE_INISIE_BS) &&
+ (play->sceneId != SCENE_LAST_BS);
+ if (GameInteractor_Should(GI_VB_DISABLE_FD_MASK, vanillaSceneConditionResult, NULL)) {
if (gSaveContext.buttonStatus[i] != BTN_DISABLED) {
gSaveContext.buttonStatus[i] = BTN_DISABLED;
restoreHudVisibility = true;
From 9133fc2d756572301cd9ceffce4b5c91804edb02 Mon Sep 17 00:00:00 2001
From: Archez
Date: Sun, 21 Apr 2024 17:42:39 -0400
Subject: [PATCH 09/13] Fix save corruption from non null-terminated cstrings
(variation) (#230)
* verify save slots better; allow global.sav to work again
* fix save corruption and restore global.sav
* add comments
---
mm/2s2h/BenJsonConversions.hpp | 52 ++++++++++++++++------------------
mm/2s2h/BenPort.cpp | 22 +++++++-------
mm/include/z64save.h | 2 ++
3 files changed, 37 insertions(+), 39 deletions(-)
diff --git a/mm/2s2h/BenJsonConversions.hpp b/mm/2s2h/BenJsonConversions.hpp
index 57c044c5f..849d5d01f 100644
--- a/mm/2s2h/BenJsonConversions.hpp
+++ b/mm/2s2h/BenJsonConversions.hpp
@@ -17,15 +17,18 @@ void to_json(json& j, const ItemEquips& itemEquips) {
void from_json(const json& j, ItemEquips& itemEquips) {
j.at("equipment").get_to(itemEquips.equipment);
// buttonItems and cButtonSlots are arrays of arrays, so we need to manually parse them
- for (int x = 0; x < 4; x++) {
- for (int y = 0; y < 4; y++) {
- itemEquips.buttonItems[x][y] = j.at("buttonItems")[x][y].get();
- itemEquips.cButtonSlots[x][y] = j.at("cButtonSlots")[x][y].get();
- }
+ for (int i = 0; i < ARRAY_COUNT(itemEquips.buttonItems); i++) {
+ j.at("buttonItems").at(i).get_to(itemEquips.buttonItems[i]);
+ j.at("cButtonSlots").at(i).get_to(itemEquips.cButtonSlots[i]);
}
}
void to_json(json& j, const Inventory& inventory) {
+ // Setup and copy u8 arrays to avoid json treating char[] as strings
+ // These char[] are not null-terminated, so saving as strings causes overflow/corruption
+ uint8_t dekuPlaygroundPlayerName[3][8];
+ memcpy(dekuPlaygroundPlayerName, inventory.dekuPlaygroundPlayerName, sizeof(dekuPlaygroundPlayerName));
+
j = json{
{ "items", inventory.items },
{ "ammo", inventory.ammo },
@@ -35,7 +38,7 @@ void to_json(json& j, const Inventory& inventory) {
{ "dungeonKeys", inventory.dungeonKeys },
{ "defenseHearts", inventory.defenseHearts },
{ "strayFairies", inventory.strayFairies },
- { "dekuPlaygroundPlayerName", inventory.dekuPlaygroundPlayerName },
+ { "dekuPlaygroundPlayerName", dekuPlaygroundPlayerName },
};
}
@@ -48,12 +51,9 @@ void from_json(const json& j, Inventory& inventory) {
j.at("dungeonKeys").get_to(inventory.dungeonKeys);
j.at("defenseHearts").get_to(inventory.defenseHearts);
j.at("strayFairies").get_to(inventory.strayFairies);
- // dekuPlaygroundPlayerName is an array of char arrays, so we need to manually parse it
- for (int i = 0; i < 3; i++) {
- std::string name = j.at("dekuPlaygroundPlayerName")[i].get();
- for (int j = 0; j < 8; j++) {
- inventory.dekuPlaygroundPlayerName[i][j] = name[j];
- }
+ // dekuPlaygroundPlayerName is an array of arrays, so we need to manually parse it
+ for (int i = 0; i < ARRAY_COUNT(inventory.dekuPlaygroundPlayerName); i++) {
+ j.at("dekuPlaygroundPlayerName").at(i).get_to(inventory.dekuPlaygroundPlayerName[i]);
}
}
@@ -80,10 +80,17 @@ void from_json(const json& j, PermanentSceneFlags& permanentSceneFlags) {
}
void to_json(json& j, const SavePlayerData& savePlayerData) {
+ // Setup and copy u8 arrays to avoid json treating char[] as strings
+ // These char[] are not null-terminated, so saving as strings causes overflow/corruption
+ u8 newf[6];
+ u8 playerName[8];
+ memcpy(newf, savePlayerData.newf, sizeof(newf));
+ memcpy(playerName, savePlayerData.playerName, sizeof(playerName));
+
j = json{
- { "newf", savePlayerData.newf },
+ { "newf", newf },
{ "threeDayResetCount", savePlayerData.threeDayResetCount },
- { "playerName", savePlayerData.playerName },
+ { "playerName", playerName },
{ "healthCapacity", savePlayerData.healthCapacity },
{ "health", savePlayerData.health },
{ "magicLevel", savePlayerData.magicLevel },
@@ -103,16 +110,9 @@ void to_json(json& j, const SavePlayerData& savePlayerData) {
}
void from_json(const json& j, SavePlayerData& savePlayerData) {
- // newf is an array of chars, so we need to manually parse it
- std::string newf = j.at("newf").get();
- for (int i = 0; i < 6; i++) {
- savePlayerData.newf[i] = newf[i];
- }
+ j.at("newf").get_to(savePlayerData.newf);
j.at("threeDayResetCount").get_to(savePlayerData.threeDayResetCount);
- std::string playerName = j.at("playerName").get();
- for (int i = 0; i < 8; i++) {
- savePlayerData.playerName[i] = playerName[i];
- }
+ j.at("playerName").get_to(savePlayerData.playerName);
j.at("healthCapacity").get_to(savePlayerData.healthCapacity);
j.at("health").get_to(savePlayerData.health);
j.at("magicLevel").get_to(savePlayerData.magicLevel);
@@ -224,10 +224,8 @@ void from_json(const json& j, SaveInfo& saveInfo) {
j.at("bombersCaughtNum").get_to(saveInfo.bombersCaughtNum);
j.at("bombersCaughtOrder").get_to(saveInfo.bombersCaughtOrder);
// lotteryCodes is an array of arrays, so we need to manually parse it
- for (int x = 0; x < 3; x++) {
- for (int y = 0; y < 3; y++) {
- saveInfo.lotteryCodes[x][y] = j.at("lotteryCodes")[x][y].get();
- }
+ for (int i = 0; i < ARRAY_COUNT(saveInfo.lotteryCodes); i++) {
+ j.at("lotteryCodes").at(i).get_to(saveInfo.lotteryCodes[i]);
}
j.at("spiderHouseMaskOrder").get_to(saveInfo.spiderHouseMaskOrder);
j.at("bomberCode").get_to(saveInfo.bomberCode);
diff --git a/mm/2s2h/BenPort.cpp b/mm/2s2h/BenPort.cpp
index 73cac26b8..145db1cb0 100644
--- a/mm/2s2h/BenPort.cpp
+++ b/mm/2s2h/BenPort.cpp
@@ -1587,18 +1587,17 @@ extern "C" void BenSysFlashrom_WriteData(u8* saveBuffer, u32 pageNum, u32 pageCo
FlashSlotFile flashSlotFile = FLASH_SLOT_FILE_UNAVAILABLE;
bool isBackup = false;
for (u32 i = 0; i < ARRAY_COUNT(gFlashSaveStartPages) - 1; i++) {
- if (pageNum == gFlashSaveStartPages[i]) {
+ // Verify that the requested pages align with expected values
+ if (pageNum == (u32)gFlashSaveStartPages[i] &&
+ (pageCount == (u32)gFlashSaveNumPages[i] || pageCount == (u32)gFlashSpecialSaveNumPages[i])) {
flashSlotFile = static_cast(i);
break;
}
}
- // Exclude debug file from saving
- if (flashSlotFile == FLASH_SLOT_FILE_UNAVAILABLE || gSaveContext.fileNum == 255) {
- return;
- }
-
switch (flashSlotFile) {
+ case FLASH_SLOT_FILE_UNAVAILABLE:
+ return;
case FLASH_SLOT_FILE_1_NEW_CYCLE_BACKUP:
case FLASH_SLOT_FILE_2_NEW_CYCLE_BACKUP:
isBackup = true;
@@ -1653,17 +1652,17 @@ extern "C" s32 BenSysFlashrom_ReadData(void* saveBuffer, u32 pageNum, u32 pageCo
FlashSlotFile flashSlotFile = FLASH_SLOT_FILE_UNAVAILABLE;
bool isBackup = false;
for (u32 i = 0; i < ARRAY_COUNT(gFlashSaveStartPages) - 1; i++) {
- if (pageNum == gFlashSaveStartPages[i]) {
+ // Verify that the requested pages align with expected values
+ if (pageNum == (u32)gFlashSaveStartPages[i] &&
+ (pageCount == (u32)gFlashSaveNumPages[i] || pageCount == (u32)gFlashSpecialSaveNumPages[i])) {
flashSlotFile = static_cast(i);
break;
}
}
- if (flashSlotFile == FLASH_SLOT_FILE_UNAVAILABLE) {
- return -1;
- }
-
switch (flashSlotFile) {
+ case FLASH_SLOT_FILE_UNAVAILABLE:
+ return -1;
case FLASH_SLOT_FILE_1_NEW_CYCLE_BACKUP:
case FLASH_SLOT_FILE_2_NEW_CYCLE_BACKUP:
isBackup = true;
@@ -1712,7 +1711,6 @@ extern "C" s32 BenSysFlashrom_ReadData(void* saveBuffer, u32 pageNum, u32 pageCo
memcpy(saveBuffer, &saveOptions, sizeof(SaveOptions));
return 0;
- break;
}
}
}
diff --git a/mm/include/z64save.h b/mm/include/z64save.h
index 865b0650a..aed5487a8 100644
--- a/mm/include/z64save.h
+++ b/mm/include/z64save.h
@@ -214,6 +214,7 @@ typedef struct Inventory {
/* 0x5A */ s8 dungeonKeys[9]; // "key_register"
/* 0x63 */ s8 defenseHearts;
/* 0x64 */ s8 strayFairies[10]; // "orange_fairy"
+ // 2S2H [Comment] These char[] are not null-terminated, they will not work correctly in string functions
/* 0x6E */ char dekuPlaygroundPlayerName[3][8]; // "degnuts_memory_name" Stores playerName (8 char) over (3 days) when getting a new high score
} Inventory; // size = 0x88
@@ -262,6 +263,7 @@ typedef struct SaveOptions {
} SaveOptions; // size = 0x6
typedef struct SavePlayerData {
+ // 2S2H [Comment] These char[] are not null-terminated, they will not work correctly in string functions
/* 0x00 */ char newf[6]; // "newf" Will always be "ZELDA3 for a valid save
/* 0x06 */ u16 threeDayResetCount; // "savect"
/* 0x08 */ char playerName[8]; // "player_name"
From c8267576ff1a03e8a94a6f92f12fcb8777eb83cc Mon Sep 17 00:00:00 2001
From: louist103 <35883445+louist103@users.noreply.github.com>
Date: Sun, 21 Apr 2024 17:51:17 -0400
Subject: [PATCH 10/13] Update main.yml
Add Windows tag to build-windows work flow so the builds aren't RNG.
---
.github/workflows/main.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d0397da64..4335d1d81 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -112,7 +112,7 @@ jobs:
# name: 2ship-windows
# path: 2ship-windows
build-windows-self-hosted:
- runs-on: self-hosted
+ runs-on: [self-hosted, Windows]
steps:
- name: Install dependencies
run: |
From dc2d311de0eed2fa28ec822d7bd66af8fe59c819 Mon Sep 17 00:00:00 2001
From: PurpleHato
Date: Mon, 22 Apr 2024 01:43:09 +0200
Subject: [PATCH 11/13] ADD: 24 Hours Clock + .gitignore on the 2s2h.h (#229)
* ADD: 24 Hours Clock
* TWEAK: gitignore
---
mm/2s2h/BenGui/BenMenuBar.cpp | 1 +
mm/assets/.gitignore | 3 +-
mm/assets/2s2h_assets.h | 57 ++++++++++++++++++
.../icon_item_24_static_yar.h | 6 +-
.../gThreeDayClockHour13Tex.ia4.png | Bin 0 -> 1974 bytes
.../gThreeDayClockHour14Tex.ia4.png | Bin 0 -> 1927 bytes
.../gThreeDayClockHour15Tex.ia4.png | Bin 0 -> 1967 bytes
.../gThreeDayClockHour16Tex.ia4.png | Bin 0 -> 1974 bytes
.../gThreeDayClockHour17Tex.ia4.png | Bin 0 -> 1852 bytes
.../gThreeDayClockHour18Tex.ia4.png | Bin 0 -> 1946 bytes
.../gThreeDayClockHour19Tex.ia4.png | Bin 0 -> 1926 bytes
.../gThreeDayClockHour20Tex.ia4.png | Bin 0 -> 1871 bytes
.../gThreeDayClockHour21Tex.ia4.png | Bin 0 -> 1857 bytes
.../gThreeDayClockHour22Tex.ia4.png | Bin 0 -> 1834 bytes
.../gThreeDayClockHour23Tex.ia4.png | Bin 0 -> 2032 bytes
.../gThreeDayClockHour24Tex.ia4.png | Bin 0 -> 1942 bytes
mm/src/code/z_parameter.c | 21 ++++++-
17 files changed, 82 insertions(+), 6 deletions(-)
create mode 100644 mm/assets/2s2h_assets.h
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour13Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour14Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour15Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour16Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour17Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour18Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour19Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour20Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour21Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour22Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour23Tex.ia4.png
create mode 100644 mm/assets/custom/textures/parameter_static/gThreeDayClockHour24Tex.ia4.png
diff --git a/mm/2s2h/BenGui/BenMenuBar.cpp b/mm/2s2h/BenGui/BenMenuBar.cpp
index a543d35c7..6480ed54b 100644
--- a/mm/2s2h/BenGui/BenMenuBar.cpp
+++ b/mm/2s2h/BenGui/BenMenuBar.cpp
@@ -305,6 +305,7 @@ void DrawEnhancementsMenu() {
});
UIWidgets::CVarCheckbox("Skip Entrance Cutscenes", "gEnhancements.TimeSavers.SkipEntranceCutscenes");
UIWidgets::CVarCheckbox("Hide Title Cards", "gEnhancements.TimeSavers.HideTitleCards");
+ UIWidgets::CVarCheckbox("24 Hours Clock", "gEnhancements.General.24HoursClock");
if (mHudEditorWindow) {
UIWidgets::WindowButton("Hud Editor", "gWindows.HudEditor", mHudEditorWindow, {
diff --git a/mm/assets/.gitignore b/mm/assets/.gitignore
index fec19cf96..d42a6436f 100644
--- a/mm/assets/.gitignore
+++ b/mm/assets/.gitignore
@@ -5,5 +5,6 @@
*.vtx.inc
*.dlist.inc
-# Unignore our custom assets
+# Unignore our custom assets and header file
!custom/**/*
+!2s2h_assets.h
diff --git a/mm/assets/2s2h_assets.h b/mm/assets/2s2h_assets.h
new file mode 100644
index 000000000..1281181a4
--- /dev/null
+++ b/mm/assets/2s2h_assets.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "align_asset_macro.h"
+
+// This file is manually made
+// When new assets are added to the soh.otr file
+// We need to add the aligned version of the resource names here and use in code
+// On Mac, not using aligned resource names was causing crashes in release builds
+
+// textures
+#define dgDPad "__OTR__textures/parameter_static/gDPad"
+static const ALIGN_ASSET(2) char gDPadTex[] = dgDPad;
+
+#define dgArrowUp "__OTR__textures/parameter_static/gArrowUp"
+static const ALIGN_ASSET(2) char gArrowUpTex[] = dgArrowUp;
+
+#define dgArrowDown "__OTR__textures/parameter_static/gArrowDown"
+static const ALIGN_ASSET(2) char gArrowDownTex[] = dgArrowDown;
+
+#define dgTriforcePiece "__OTR__textures/parameter_static/gTriforcePiece"
+static const ALIGN_ASSET(2) char gTriforcePieceTex[] = dgTriforcePiece;
+
+#define dgThreeDayClockHour13Tex "__OTR__textures/parameter_static/gThreeDayClockHour13Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour13Tex[] = dgThreeDayClockHour13Tex;
+
+#define dgThreeDayClockHour14Tex "__OTR__textures/parameter_static/gThreeDayClockHour14Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour14Tex[] = dgThreeDayClockHour14Tex;
+
+#define dgThreeDayClockHour15Tex "__OTR__textures/parameter_static/gThreeDayClockHour15Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour15Tex[] = dgThreeDayClockHour15Tex;
+
+#define dgThreeDayClockHour16Tex "__OTR__textures/parameter_static/gThreeDayClockHour16Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour16Tex[] = dgThreeDayClockHour16Tex;
+
+#define dgThreeDayClockHour17Tex "__OTR__textures/parameter_static/gThreeDayClockHour17Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour17Tex[] = dgThreeDayClockHour17Tex;
+
+#define dgThreeDayClockHour18Tex "__OTR__textures/parameter_static/gThreeDayClockHour18Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour18Tex[] = dgThreeDayClockHour18Tex;
+
+#define dgThreeDayClockHour19Tex "__OTR__textures/parameter_static/gThreeDayClockHour19Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour19Tex[] = dgThreeDayClockHour19Tex;
+
+#define dgThreeDayClockHour20Tex "__OTR__textures/parameter_static/gThreeDayClockHour20Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour20Tex[] = dgThreeDayClockHour20Tex;
+
+#define dgThreeDayClockHour21Tex "__OTR__textures/parameter_static/gThreeDayClockHour21Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour21Tex[] = dgThreeDayClockHour21Tex;
+
+#define dgThreeDayClockHour22Tex "__OTR__textures/parameter_static/gThreeDayClockHour22Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour22Tex[] = dgThreeDayClockHour22Tex;
+
+#define dgThreeDayClockHour23Tex "__OTR__textures/parameter_static/gThreeDayClockHour23Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour23Tex[] = dgThreeDayClockHour23Tex;
+
+#define dgThreeDayClockHour24Tex "__OTR__textures/parameter_static/gThreeDayClockHour24Tex"
+static const ALIGN_ASSET(2) char gThreeDayClockHour24Tex[] = dgThreeDayClockHour24Tex;
diff --git a/mm/assets/archives/icon_item_24_static/icon_item_24_static_yar.h b/mm/assets/archives/icon_item_24_static/icon_item_24_static_yar.h
index 4d441bd8a..515902d7b 100644
--- a/mm/assets/archives/icon_item_24_static/icon_item_24_static_yar.h
+++ b/mm/assets/archives/icon_item_24_static/icon_item_24_static_yar.h
@@ -33,12 +33,12 @@ static const ALIGN_ASSET(2) char gQuestIconDungeonMapTex[] = dgQuestIconDungeonM
#define dgQuestIconGoldSkulltula2Tex "__OTR__icon_item_24_static_yar/gQuestIconGoldSkulltula2Tex"
static const ALIGN_ASSET(2) char gQuestIconGoldSkulltula2Tex[] = dgQuestIconGoldSkulltula2Tex;
-#define dgQuestIconSmallMagicJarTex "__OTR__icon_item_24_static_yar/gQuestIconSmallMagicJarTex"
-static const ALIGN_ASSET(2) char gQuestIconSmallMagicJarTex[] = dgQuestIconSmallMagicJarTex;
-
#define dgQuestIconSmallKeyTex "__OTR__icon_item_24_static_yar/gQuestIconSmallKeyTex"
static const ALIGN_ASSET(2) char gQuestIconSmallKeyTex[] = dgQuestIconSmallKeyTex;
+#define dgQuestIconSmallMagicJarTex "__OTR__icon_item_24_static_yar/gQuestIconSmallMagicJarTex"
+static const ALIGN_ASSET(2) char gQuestIconSmallMagicJarTex[] = dgQuestIconSmallMagicJarTex;
+
#define dgQuestIconBigMagicJarTex "__OTR__icon_item_24_static_yar/gQuestIconBigMagicJarTex"
static const ALIGN_ASSET(2) char gQuestIconBigMagicJarTex[] = dgQuestIconBigMagicJarTex;
diff --git a/mm/assets/custom/textures/parameter_static/gThreeDayClockHour13Tex.ia4.png b/mm/assets/custom/textures/parameter_static/gThreeDayClockHour13Tex.ia4.png
new file mode 100644
index 0000000000000000000000000000000000000000..7388c6969598a8e06e470ece678ad415999d0852
GIT binary patch
literal 1974
zcmah~Yfuwc6kb3p1|M-mWz^bbVH~QGO+tXOO+-kLhXgW^U_fxXB%81$$%gC(5=83*
ztF0(vRYZ|NtXQfR-%1fsVd!W_YzHX{V{5Jaaf-C!1EfgrCI~3n{@BMo-*>)q?m74I
zg(5C;DrYVSf}p9+%qBq`x)}Ip%_sspC`_gVQOgyul(GYs+x$(m4=dr+kP)=TLYf(pGQq4S
zo=#aA34#C|9u{Xv-Ke^W9-clZ1j-_)fX7G2(g_>+4myjBqnk4*hN3el^T-5V3o!zZ
za|GpkyNeAszl|)H|2N%ed~F&u6Oj!HFxo#xq-QcDh(PcLu#(g|>_MY;R>)vwJE1a>Ahd84QjcO1Bv^=!QA>U+#FiRU
zkT$}gmXSunL<3cjg)`bHm^>DAiaJ0kWuPpHU|AEu$&toLA20_dr?dbHcms--N);AL
zM;ZV|Cq#zB(Gk)hK}b-LzmVsY0ZqezFR8Q3a0al1e4)snFY*`im4aZ5FT@0b#o*27
zJCq%4uotvAga03&UD2=@c*x~g6iG9b#qKaUmw6(Qf
zrL}4Zts^*)bD}h*V{&i{5vB(XmLOp|9SQnlhZXF~g@v$?C*nDKHR6(!K>)dTPYxYU
z4Q037tK7usxa`Fa!k9e@{G2wh_rW28j+`Q3FmetNCUE#zz$w#t{TdEIE*nVDmvQ}o
zAipAOQSA!1Fz%KHaZ_LT70=nT!)`5#QMz}iWqBJ?d}rhZ7WeLQuX%8B1^3h}gBzz}
zdv*Abt8rpxn8Gvc_=#CYWlN20>(&w?yMC~{<>bd6k8I1^{3-V*hF=SIM7q+w9veFJ
zm-@)7kq0YMni<2AIMtel#;&FV3yvl|rH4`)PwT4gJ)S)M*ykm4%D81IH|9;FJOn+@)3m>{msAwY^uFwSxPV((=v~tI=9pk)7@*nmr9qliqEc=4yM=WLkH&nCe2H$f5^#x
zUcRa&7+vc&eUrZad={q-To;qgQOPv8U1qY63$(w~3v_5fv@A^NSG1=n^xI9#xXpT1
zcJQ{7#|}=4s@{2B80mhy3);2DJ?L3Dyjc~4mfzkis#!MIV|qEfa9+}5j?~@mUHCjO
zz^(T{XdX1=nOi;MeyLBsmj*J|t^OhF&+i|tYlxrP;hEZQ>^yH=)lySQ_V!=$`?B$O
z?gSU_eEMZ+AC%9vm&J!@4Chid;}<)-?|<}6-19Vll0v0QYlqCWcVE;O{&72Nehk?v
zim6(8**1WCy;mUc*4C{?XK(B6OsYYUn%R8(rad+PLP-oqt@WG{Q&32ji0W-p~cK
ziW*!h$@Bu3%htPt>!{>kvTFyY+_I*r_TXOiJ;g*xC7HbU$F9dValLC#^JH%(?dWdT
k^}7>3Y_q-(iMJ29AP)>S`Idd@>~$L*5hwdD{F9CU0)KbI#{d8T
literal 0
HcmV?d00001
diff --git a/mm/assets/custom/textures/parameter_static/gThreeDayClockHour14Tex.ia4.png b/mm/assets/custom/textures/parameter_static/gThreeDayClockHour14Tex.ia4.png
new file mode 100644
index 0000000000000000000000000000000000000000..abf5e7abd04a261fdee86d608fe0d992d4a69d4f
GIT binary patch
literal 1927
zcmeAS@N?(olHy`uVBq!ia0vp^0zk~o!2~3KHq6RnU|`vp84^(v;p=0SoS&v55FG|-pw6wI;H!#vS
zGSUUA&@HaaD@m--%_~+0838fFCAB!YD6^m>Ge1uOWM*PgerbuVk`ho}0cvKRGtfs?o?4NKLpewWLwP|)eo20DMt%X@11MI&r7<)DgAcowio^nJ
zihX_YE6>eE@kVipYZ)*gpn;0wCYS}qA(aKG`a!A1`K3k4sjg+Ic_qr=kN|~?m1k~Z
zda6%qUV2G}t&*W3nnZYJ3Rv6>Ar3MqrxX+up?R4=tBnv+5ePY3C37QvB-0|XN`Zpf
zDj>5WH7CL)GdDG_7-%Zc6%gAHl99-g5JSO|&iOg{MZv&ioeIBsGXXk*T{Itv*pj>f~yOV;atFxJ*rJ0$oksibtV5C_A^=773x+InWRT&u=ndlmr
z=o%S>7@Aud7+D#aYXeCG1F*?p)xf-vl30>RNIfV;E7$;a`1)FTWEPj?7gd6VAjv#9
zwGhICaC0)j>03bo9LrXT$)HfTRVpaTPbp1KO#xdDG!7zLoS#-wo>-LXm=26!TP4S|
zv`k?9R4N33Qm%rLf{~ty9we^dTFNt0fyUwXoIccP`k*WabDNDmhU;y>3Xt+BP#;7L
znD>E&h(5d&0dnBwP--5q@F@b8GJ2~e8W|WE8#96Nf)O7K3>p(lC+_xUb`)seZ*$~_
z(?Jn2F~=)fULhQ}B3x=3qf`WIOs9XI#If_q4sU^tqB+c5i+a~P{b$N$&2|hBaa^-j
zG&khjPM403nW<&_{=Pl8!J7MBh1*r#e2yx=nTkJiH{MF$^CR<+``krQ$4YW^
z{6eeuAAWjdw%qctuf_kP4sS|Z`QQ^L|LV4x;`0RNMV*!A%jf=mb;~B58Z8-Je5vpt86}U|GN7%)?(U_^)5HVv@Yp)AJK1$AqReoBD)#OjPp^xY87IT}93HdP+(}
z@vr~e*M}7?*dMdkkjd14+uvKPTcbaOZh719eEXjj(|P5X6z!ionI&q3_eNZ_`o6G|
z`JA41R1*6+^M!8@2KX#hsXTJ`q{^N-4wr4V#5w;>+L>A-;71y;o@VLJB;b37Y
zVSdBVsG_06*O@M<)3C=^z(lU`@Poh?fhsJrDhh-tE{>fZPJ;~uj+Zpq?fIuqhpiK>}8%_o-W@{
z&gVXCz@f-6V;QG{@8v}r#|r<%ovr`c_xMO*Oz73uUvF*m?VgtUmyApNw6)Q62#T;dROl4
ubD#IT-yK$BW6#GRXn0H_`FZ6%`R{Bdhoa4AiCJF;)t{cOelF{r5}E*}OsZu7
literal 0
HcmV?d00001
diff --git a/mm/assets/custom/textures/parameter_static/gThreeDayClockHour15Tex.ia4.png b/mm/assets/custom/textures/parameter_static/gThreeDayClockHour15Tex.ia4.png
new file mode 100644
index 0000000000000000000000000000000000000000..b0a1174e18714030ddba7646983be24fffd67aee
GIT binary patch
literal 1967
zcmah~eM}Q)7(YJDutY^sAe*}5AcmmrwY7lUS`b2nE4>3+h9BU9
zxF8B{AWRW);t&O8iX$kf5Y7G&wje<Whzf;Vz~It0l2Kc^B<;saI5cE9t+5bhhM-88
z)l_Ft*^C5101i)xGlXtRountG4+?>@2rA(5(dl&DN_>FMA~Wb_1I19Zfih1{;H?mo
z@C-*#zW2LW)#mq+{cgn@n#_gJz;~Kp`fI`c_VF0?
zV+z8G8&xvGgp)K-1z9+vO@XP?L5HXvlu|~@k_47D4xH?1O!fh@V{%Fhpq1}Hu~MnR
zLg@%2!05#2NH{i18Y~D64(1AZ4jIri4EPc{n@r6BmXI$Larq*ykgpVkV0Q!m}JB$IwXF*EbB?yNszdY}F-*
z*+OZvHMrIu9LPCPn$j`3Y6~8r2Mv}W5jq_K`eTC??8=3Ou#hL>IeInel54<$+=nNJ
zPNasiTkcJ6Vsu9KVmo2l9tD058`%5c5J4wT5ipoMhj0=cJ{EAwWF_sUAjqkJ0DYO!
z4+sh<%PwnQ=Mv$ut5e+lGV=05FRzFjfoqj+gDP2mL7M;k{GiI`E_NeFdXYP-!Yx=pA+Y95}lG12r`nh|-
zp#H*3;!5QTS@=F<*0~K&lg~cdE4}>>=G^O-u3i4B&BZ6*
z^P=h(Y~+Z|rH8jXE#sq}kRy*SE8>p%+q&=kOS$=P13Q9HhuW$;(OBkjU5j-
zIU}_jdP2~xF7tNkJ6f|i{ouNoZH`H$!R_M7K2A{eK_Ac|?^s!cGNA06vQwoyS9@Ih
zy|!S7p(wDBY*~=bU2ykk(eWMo!d!~J)WZ$kCx@K21iCdGaXu%y&gsZ2)HM$@wjHiM
zR5hN@y%hfH^}|S6Y4`#@(&mB8jRR0^z}kBwovzJ>>WcaXqPpV=
z-7~bR>BL&MZN+uPg8CESS6o~?I#wg_a&hWHQdRnJfT@
zAkhk&MWao{X*d}-5N0tl(9(v$giega2P$|9ixf{GA~UUcY-Y4do0+N&(IG2B*&-VT
z1WY)sfo&$EnZj&h#G#9Uf5wa=u!BOUiV?L!2}?;U4hM1rxjZD44U4QgJr*NdHR23L
zVkCv8Ef|VsWMptNmUBs~0p*8;grGbDDi8z!MgV0u(;8cVnet^MoEkEm(pm`%O^{}o
z(bOc9X|xzY01l6c(}aFZ-As*49~1&*5R}j5q2uYejd%&2K_<{GDI`r&DWqj|0?&jP
zg(o&P;&Nf-y)vhKZ420Xj}C;e`-W
zYD`JkaHCpAm~b-%R6!QbXk%c)c+e^80Hvgnw8nyEjRPk~8l!!{9GHUC0ciPikX$NN
zT1h=&1Q-<)5eCb{r9u3Vpr8N&*C_*TL8qZL|Ho%mG%Nxh3I!HPP&8?^J50`H9*b{sj+{dy;aJ}=?C3J4(K1yR
zBNi*EOVi>yM{pqLL@82FXK1Xr!~hyBMkIPY0s3QymCVY81+aiCXMOy1G$$^
z4joAiWwzYY+(hVv>_ra3xIGH|oHnrc!6AZaxmkW-M&}NGpB$4?xySW_7k!5DzD&XCE-0fotpaR$+3oY)}3wZkMmi9Ya53zP5wIRcHGfhTcwvDW2`RU^}3}G
zn_RuJW*t}mhz;$syLNJyCMLUe2JgCcN*VP9$KKI*j?-GbvRia?qjWgA$*|&)_Z4Z@
zh7&j0=|ek{I)l+{*BSYS6OEhL7r}Kg#S)o7f!k#k^SD4&)n1@Oi{vti%CGF>vV*1h
zO3(M){w{1QzkF)r)ET)e