diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua
index 9a1354c76..039f1dd75 100644
--- a/autogen/lua_definitions/functions.lua
+++ b/autogen/lua_definitions/functions.lua
@@ -8670,6 +8670,11 @@ function get_environment_region(index)
-- ...
end
+--- @return boolean
+function get_first_person_camera_enabled()
+ -- ...
+end
+
--- @param index integer
--- @return integer
function get_fog_color(index)
@@ -8881,6 +8886,12 @@ function set_environment_region(index, value)
-- ...
end
+--- @param enable boolean
+--- @return nil
+function set_first_person_camera_enabled(enable)
+ -- ...
+end
+
--- @param index integer
--- @param value integer
--- @return nil
diff --git a/docs/lua/functions-5.md b/docs/lua/functions-5.md
index 80739dd14..1e3741637 100644
--- a/docs/lua/functions-5.md
+++ b/docs/lua/functions-5.md
@@ -793,6 +793,24 @@
+## [get_first_person_camera_enabled](#get_first_person_camera_enabled)
+
+### Lua Example
+`local booleanValue = get_first_person_camera_enabled()`
+
+### Parameters
+- None
+
+### Returns
+- `boolean`
+
+### C Prototype
+`bool get_first_person_camera_enabled(void);`
+
+[:arrow_up_small:](#)
+
+
+
## [get_fog_color](#get_fog_color)
### Lua Example
@@ -1463,6 +1481,26 @@
+## [set_first_person_camera_enabled](#set_first_person_camera_enabled)
+
+### Lua Example
+`set_first_person_camera_enabled(enable)`
+
+### Parameters
+| Field | Type |
+| ----- | ---- |
+| enable | `boolean` |
+
+### Returns
+- None
+
+### C Prototype
+`void set_first_person_camera_enabled(bool enable);`
+
+[:arrow_up_small:](#)
+
+
+
## [set_fog_color](#set_fog_color)
### Lua Example
diff --git a/docs/lua/functions.md b/docs/lua/functions.md
index cd86f053b..10c70ac95 100644
--- a/docs/lua/functions.md
+++ b/docs/lua/functions.md
@@ -1624,6 +1624,7 @@
- [get_dialog_id](functions-5.md#get_dialog_id)
- [get_envfx](functions-5.md#get_envfx)
- [get_environment_region](functions-5.md#get_environment_region)
+ - [get_first_person_camera_enabled](functions-5.md#get_first_person_camera_enabled)
- [get_fog_color](functions-5.md#get_fog_color)
- [get_fog_intensity](functions-5.md#get_fog_intensity)
- [get_got_file_coin_hi_score](functions-5.md#get_got_file_coin_hi_score)
@@ -1658,6 +1659,7 @@
- [save_file_get_using_backup_slot](functions-5.md#save_file_get_using_backup_slot)
- [save_file_set_using_backup_slot](functions-5.md#save_file_set_using_backup_slot)
- [set_environment_region](functions-5.md#set_environment_region)
+ - [set_first_person_camera_enabled](functions-5.md#set_first_person_camera_enabled)
- [set_fog_color](functions-5.md#set_fog_color)
- [set_fog_intensity](functions-5.md#set_fog_intensity)
- [set_got_file_coin_hi_score](functions-5.md#set_got_file_coin_hi_score)
diff --git a/src/game/camera.c b/src/game/camera.c
index dc1343c2a..66749859e 100644
--- a/src/game/camera.c
+++ b/src/game/camera.c
@@ -34,6 +34,7 @@
#include "pc/network/network.h"
#include "pc/lua/smlua_hooks.h"
#include "pc/djui/djui.h"
+#include "first_person_cam.h"
#define CBUTTON_MASK (U_CBUTTONS | D_CBUTTONS | L_CBUTTONS | R_CBUTTONS)
@@ -3147,7 +3148,7 @@ void update_camera(struct Camera *c) {
gCamera = c;
update_camera_hud_status(c);
- if (gOverrideFreezeCamera && !gDjuiInMainMenu) {
+ if ((gOverrideFreezeCamera || update_first_person()) && !gDjuiInMainMenu) {
return;
}
@@ -3447,6 +3448,10 @@ void reset_camera(struct Camera *c) {
gRecentCutscene = 0;
unused8033B30C = 0;
unused8033B310 = 0;
+
+ if (gFirstPersonEnabled) {
+ gFirstPersonYaw = gMarioStates[0].faceAngle[1] + 0x8000;
+ }
}
void init_camera(struct Camera *c) {
diff --git a/src/game/first_person_cam.c b/src/game/first_person_cam.c
new file mode 100644
index 000000000..f2a68a25b
--- /dev/null
+++ b/src/game/first_person_cam.c
@@ -0,0 +1,168 @@
+#include "first_person_cam.h"
+
+#include "sm64.h"
+#include "behavior_data.h"
+#include "camera.h"
+#include "level_update.h"
+#include "object_list_processor.h"
+#include "object_helpers.h"
+
+#include "engine/math_util.h"
+
+#include "pc/controller/controller_mouse.h"
+#include "pc/djui/djui_hud_utils.h"
+#include "pc/lua/utils/smlua_misc_utils.h"
+#include "pc/lua/smlua_hooks.h"
+
+#define MARIO_HEAD_POS 120
+#define MARIO_HEAD_POS_SHORT MARIO_HEAD_POS / 2
+
+#define CLAMP(_val, _min, _max) MAX(MIN((_val), _max), _min)
+
+bool gFirstPersonEnabled = false;
+s16 gFirstPersonYaw = 0;
+static s16 sFirstPersonPitch = 0;
+static f32 sFirstPersonCrouch = 0;
+
+extern s16 gMenuMode;
+
+/**
+ * A mode that implements an first person player camera. (referenced from Gun Mod v3)
+ */
+void update_first_person_camera(void) {
+ struct MarioState *m = &gMarioStates[0];
+ f32 sensX = 0.3f * camera_config_get_x_sensitivity();
+ f32 sensY = 0.4f * camera_config_get_y_sensitivity();
+ u8 invX = camera_config_is_x_inverted() ? 1 : -1;
+ u8 invY = camera_config_is_y_inverted() ? 1 : -1;
+
+ if (gMenuMode == -1) {
+ // update pitch
+ sFirstPersonPitch -= sensY * (invY * m->controller->extStickY - 1.5f * mouse_y);
+ sFirstPersonPitch = CLAMP(sFirstPersonPitch, -0x3F00, 0x3F00);
+
+ // update yaw
+ if (m->controller->buttonPressed & L_TRIG) {
+ gFirstPersonYaw = m->faceAngle[1] + 0x8000;
+ } else {
+ gFirstPersonYaw += sensX * (invX * m->controller->extStickX - 1.5f * mouse_x);
+ }
+
+ gDjuiHudLockMouse = true;
+ } else {
+ gDjuiHudLockMouse = false;
+ }
+
+ // fix yaw for some specific actions
+ // if the left stick is held, use Mario's yaw to set the camera's yaw
+ // otherwise, set Mario's yaw to the camera's yaw
+ u32 actions[] = { ACT_FLYING, ACT_HOLDING_BOWSER, ACT_TORNADO_TWIRLING, ACT_FLAG_ON_POLE, ACT_FLAG_SWIMMING };
+ for (s32 i = 0; i < 4; i++) {
+ u32 flag = actions[i];
+ if ((m->action & flag) == flag) {
+ if (ABS(m->controller->stickX) > 4) {
+ gFirstPersonYaw = m->faceAngle[1] + 0x8000;
+ } else {
+ m->faceAngle[1] = gFirstPersonYaw - 0x8000;
+ }
+ break;
+ }
+ }
+ if (m->action == ACT_LEDGE_GRAB) {
+ gFirstPersonYaw = m->faceAngle[1] + 0x8000;
+ }
+
+ gLakituState.yaw = gFirstPersonYaw;
+ m->area->camera->yaw = gFirstPersonYaw;
+
+ // update crouch
+ if (m->action == ACT_START_CROUCHING || m->action == ACT_CROUCHING || m->action == ACT_STOP_CROUCHING ||
+ m->action == ACT_START_CRAWLING || m->action == ACT_CRAWLING || m->action == ACT_STOP_CRAWLING ||
+ m->action == ACT_CROUCH_SLIDE || m->action == ACT_LEDGE_GRAB) {
+ f32 inc = 10 * (m->controller->buttonDown & Z_TRIG) != 0 || m->action == ACT_CROUCH_SLIDE || m->action == ACT_LEDGE_GRAB ? 1 : -1;
+ sFirstPersonCrouch = CLAMP(sFirstPersonCrouch + inc, 0, MARIO_HEAD_POS - MARIO_HEAD_POS_SHORT);
+ } else {
+ sFirstPersonCrouch = CLAMP(sFirstPersonCrouch - 10, 0, MARIO_HEAD_POS - MARIO_HEAD_POS_SHORT);
+ }
+
+ if (m->action == ACT_LEDGE_GRAB) {
+ sFirstPersonCrouch = MARIO_HEAD_POS - MARIO_HEAD_POS_SHORT;
+ }
+
+ // update pos
+ gLakituState.pos[0] = m->pos[0] + coss(sFirstPersonPitch) * sins(gFirstPersonYaw);
+ gLakituState.pos[1] = m->pos[1] + sins(sFirstPersonPitch) + (MARIO_HEAD_POS - sFirstPersonCrouch);
+ gLakituState.pos[2] = m->pos[2] + coss(sFirstPersonPitch) * coss(gFirstPersonYaw);
+ vec3f_copy(m->area->camera->pos, gLakituState.pos);
+ vec3f_copy(gLakituState.curPos, gLakituState.pos);
+ vec3f_copy(gLakituState.goalPos, gLakituState.pos);
+
+ // update focus
+ gLakituState.focus[0] = m->pos[0] - 100 * coss(sFirstPersonPitch) * sins(gFirstPersonYaw);
+ gLakituState.focus[1] = m->pos[1] - 100 * sins(sFirstPersonPitch) + (MARIO_HEAD_POS - sFirstPersonCrouch);
+ gLakituState.focus[2] = m->pos[2] - 100 * coss(sFirstPersonPitch) * coss(gFirstPersonYaw);
+ vec3f_copy(m->area->camera->focus, gLakituState.focus);
+ vec3f_copy(gLakituState.curFocus, gLakituState.focus);
+ vec3f_copy(gLakituState.goalFocus, gLakituState.focus);
+
+ // set other values
+ gLakituState.posHSpeed = 0;
+ gLakituState.posVSpeed = 0;
+ gLakituState.focHSpeed = 0;
+ gLakituState.focVSpeed = 0;
+ vec3s_set(gLakituState.shakeMagnitude, 0, 0, 0);
+
+ gFOVState.fov = FIRST_PERSON_FOV;
+}
+
+bool update_first_person(void) {
+ if (gFirstPersonEnabled && !gDjuiInMainMenu) {
+ if (gCurrActNum == 99) {
+ return false;
+ }
+
+ struct MarioState *m = &gMarioStates[0];
+
+ // check cancels
+ if (m->action == ACT_FIRST_PERSON || m->action == ACT_IN_CANNON || m->action == ACT_READING_NPC_DIALOG) {
+ gFOVState.fov = 45.0f;
+ return false;
+ }
+ if (m->action == ACT_DISAPPEARED) {
+ gFOVState.fov = 45.0f;
+ return false;
+ }
+ struct Object *bowser = find_object_with_behavior(bhvBowser);
+ if (bowser != NULL && (bowser->oAction == 5 || bowser->oAction == 6)) {
+ gFOVState.fov = 45.0f;
+ return false;
+ }
+
+ if (m->action == ACT_SHOT_FROM_CANNON && m->area->camera->mode == CAMERA_MODE_INSIDE_CANNON) {
+ gFirstPersonYaw = m->faceAngle[1] + 0x8000;
+ m->area->camera->mode = CAMERA_MODE_FREE_ROAM;
+ }
+
+ gDjuiHudLockMouse = gMenuMode == -1;
+
+ m->marioBodyState->modelState = 0x100;
+ if (m->heldObj) {
+ Vec3f camDir = {
+ m->area->camera->focus[0] - m->area->camera->pos[0],
+ m->area->camera->focus[1] - m->area->camera->pos[1],
+ m->area->camera->focus[2] - m->area->camera->pos[2],
+ };
+ vec3f_normalize(camDir);
+ vec3f_mul(camDir, 100);
+ vec3f_sum(m->marioObj->header.gfx.pos, m->pos, camDir);
+ }
+
+ update_first_person_camera();
+
+ return true;
+ } else if (!camera_config_is_mouse_look_enabled()) {
+ gDjuiHudLockMouse = false;
+ }
+
+ return false;
+}
diff --git a/src/game/first_person_cam.h b/src/game/first_person_cam.h
new file mode 100644
index 000000000..479cdebcf
--- /dev/null
+++ b/src/game/first_person_cam.h
@@ -0,0 +1,14 @@
+#ifndef FIRST_PERSON_CAM_H
+#define FIRST_PERSON_CAM_H
+
+#include
+#include
+
+#define FIRST_PERSON_FOV 70
+
+extern bool gFirstPersonEnabled;
+extern s16 gFirstPersonYaw;
+
+bool update_first_person(void);
+
+#endif
\ No newline at end of file
diff --git a/src/game/mario.c b/src/game/mario.c
index 8f02cb418..db283d153 100644
--- a/src/game/mario.c
+++ b/src/game/mario.c
@@ -44,6 +44,7 @@
#include "pc/lua/smlua.h"
#include "pc/network/socket/socket.h"
#include "bettercamera.h"
+#include "first_person_cam.h"
#define MAX_HANG_PREVENTION 64
@@ -1571,6 +1572,8 @@ void update_mario_joystick_inputs(struct MarioState *m) {
if (m->intendedMag > 0.0f) {
if (gLakituState.mode != CAMERA_MODE_NEWCAM) {
m->intendedYaw = atan2s(-controller->stickY, controller->stickX) + m->area->camera->yaw;
+ } else if (gFirstPersonEnabled) {
+ m->intendedYaw = atan2s(-controller->stickY, controller->stickX) + gLakituState.yaw;
} else {
m->intendedYaw = atan2s(-controller->stickY, controller->stickX) - newcam_yaw + 0x4000;
}
diff --git a/src/pc/djui/djui_hud_utils.c b/src/pc/djui/djui_hud_utils.c
index 0e698be91..291c0413b 100644
--- a/src/pc/djui/djui_hud_utils.c
+++ b/src/pc/djui/djui_hud_utils.c
@@ -19,6 +19,9 @@
#include "djui_panel_pause.h"
#include "game/camera.h"
#include "game/hud.h"
+#include "game/rendering_graph_node.h"
+
+#include "engine/math_util.h"
static enum HudUtilsResolution sResolution = RESOLUTION_DJUI;
@@ -575,7 +578,7 @@ bool djui_hud_world_pos_to_screen_pos(Vec3f pos, Vec3f out) {
out[1] *= 256.0f / out[2];
// fov of 45.0 is the default fov
- f32 fovDefault = tanf(45.0f * ((f32)M_PI / 360.0f));
+ f32 fovDefault = tanf(not_zero(45.0f, gOverrideFOV) * ((f32)M_PI / 360.0f));
f32 fovCurrent = tanf((gFOVState.fov + gFOVState.fovOffset) * ((f32)M_PI / 360.0f));
f32 fovDifference = (fovDefault / fovCurrent) * 1.13f;
diff --git a/src/pc/interop.h b/src/pc/interop.h
deleted file mode 100644
index 18f0bbcb9..000000000
--- a/src/pc/interop.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#ifndef INTEROP_H
-#define INTEROP_H
-
-#define C_FUNC extern "C"
-
-#endif
diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c
index 646d14d2b..01d5ebd39 100644
--- a/src/pc/lua/smlua_functions_autogen.c
+++ b/src/pc/lua/smlua_functions_autogen.c
@@ -28670,6 +28670,21 @@ int smlua_func_get_environment_region(lua_State* L) {
return 1;
}
+int smlua_func_get_first_person_camera_enabled(UNUSED lua_State* L) {
+ if (L == NULL) { return 0; }
+
+ int top = lua_gettop(L);
+ if (top != 0) {
+ LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "get_first_person_camera_enabled", 0, top);
+ return 0;
+ }
+
+
+ lua_pushboolean(L, get_first_person_camera_enabled());
+
+ return 1;
+}
+
int smlua_func_get_fog_color(lua_State* L) {
if (L == NULL) { return 0; }
@@ -29262,6 +29277,23 @@ int smlua_func_set_environment_region(lua_State* L) {
return 1;
}
+int smlua_func_set_first_person_camera_enabled(lua_State* L) {
+ if (L == NULL) { return 0; }
+
+ int top = lua_gettop(L);
+ if (top != 1) {
+ LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "set_first_person_camera_enabled", 1, top);
+ return 0;
+ }
+
+ bool enable = smlua_to_boolean(L, 1);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "set_first_person_camera_enabled"); return 0; }
+
+ set_first_person_camera_enabled(enable);
+
+ return 1;
+}
+
int smlua_func_set_fog_color(lua_State* L) {
if (L == NULL) { return 0; }
@@ -32740,6 +32772,7 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "get_dialog_id", smlua_func_get_dialog_id);
smlua_bind_function(L, "get_envfx", smlua_func_get_envfx);
smlua_bind_function(L, "get_environment_region", smlua_func_get_environment_region);
+ smlua_bind_function(L, "get_first_person_camera_enabled", smlua_func_get_first_person_camera_enabled);
smlua_bind_function(L, "get_fog_color", smlua_func_get_fog_color);
smlua_bind_function(L, "get_fog_intensity", smlua_func_get_fog_intensity);
smlua_bind_function(L, "get_got_file_coin_hi_score", smlua_func_get_got_file_coin_hi_score);
@@ -32774,6 +32807,7 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "save_file_get_using_backup_slot", smlua_func_save_file_get_using_backup_slot);
smlua_bind_function(L, "save_file_set_using_backup_slot", smlua_func_save_file_set_using_backup_slot);
smlua_bind_function(L, "set_environment_region", smlua_func_set_environment_region);
+ smlua_bind_function(L, "set_first_person_camera_enabled", smlua_func_set_first_person_camera_enabled);
smlua_bind_function(L, "set_fog_color", smlua_func_set_fog_color);
smlua_bind_function(L, "set_fog_intensity", smlua_func_set_fog_intensity);
smlua_bind_function(L, "set_got_file_coin_hi_score", smlua_func_set_got_file_coin_hi_score);
diff --git a/src/pc/lua/utils/smlua_misc_utils.c b/src/pc/lua/utils/smlua_misc_utils.c
index 17c940a6c..bf0d48b15 100644
--- a/src/pc/lua/utils/smlua_misc_utils.c
+++ b/src/pc/lua/utils/smlua_misc_utils.c
@@ -22,6 +22,7 @@
#include "pc/gfx/gfx_pc.h"
#include "include/course_table.h"
#include "game/level_geo.h"
+#include "game/first_person_cam.h"
static struct DateTime sDateTime;
@@ -624,6 +625,17 @@ void set_override_envfx(s32 envfx) {
///
+bool get_first_person_camera_enabled(void) {
+ return gFirstPersonEnabled;
+}
+
+void set_first_person_camera_enabled(bool enable) {
+ if (gFirstPersonEnabled && !enable) { gFOVState.fov = 45.0f; }
+ gFirstPersonEnabled = enable;
+}
+
+///
+
const char* get_os_name(void) {
#if defined(_WIN32) || defined(_WIN64)
return "Windows";
diff --git a/src/pc/lua/utils/smlua_misc_utils.h b/src/pc/lua/utils/smlua_misc_utils.h
index 1f82b1422..1e997f233 100644
--- a/src/pc/lua/utils/smlua_misc_utils.h
+++ b/src/pc/lua/utils/smlua_misc_utils.h
@@ -151,6 +151,9 @@ struct DateTime* get_date_and_time(void);
u16 get_envfx(void);
void set_override_envfx(s32 envfx);
+bool get_first_person_camera_enabled(void);
+void set_first_person_camera_enabled(bool enable);
+
const char* get_os_name(void);
#endif
diff --git a/src/pc/mods/mod_storage.cpp b/src/pc/mods/mod_storage.cpp
index d7453d609..3fa6f5879 100644
--- a/src/pc/mods/mod_storage.cpp
+++ b/src/pc/mods/mod_storage.cpp
@@ -4,7 +4,6 @@
#include
#include
#include "pc/mini.h"
-#include "pc/interop.h"
extern "C" {
#include "pc/platform.h"
@@ -15,6 +14,8 @@ extern "C" {
#include "pc/debuglog.h"
}
+#define C_FIELD extern "C"
+
void strdelete(char string[], char substr[]) {
// i is used to loop through the string
u16 i = 0;
@@ -62,7 +63,7 @@ void mod_storage_get_filename(char* dest) {
normalize_path(dest); // fix any out of place slashes
}
-C_FUNC bool mod_storage_save(const char* key, const char* value) {
+C_FIELD bool mod_storage_save(const char* key, const char* value) {
if (strlen(key) > MAX_KEY_VALUE_LENGTH || strlen(value) > MAX_KEY_VALUE_LENGTH) {
return false;
}
@@ -96,15 +97,15 @@ C_FUNC bool mod_storage_save(const char* key, const char* value) {
return true;
}
-C_FUNC bool mod_storage_save_number(const char* key, double value) {
+C_FIELD bool mod_storage_save_number(const char* key, double value) {
return mod_storage_save(key, std::to_string(value).c_str());
}
-C_FUNC bool mod_storage_save_bool(const char* key, bool value) {
+C_FIELD bool mod_storage_save_bool(const char* key, bool value) {
return mod_storage_save(key, value ? "true" : "false");
}
-C_FUNC const char* mod_storage_load(const char* key) {
+C_FIELD const char* mod_storage_load(const char* key) {
if (strlen(key) > MAX_KEY_VALUE_LENGTH) {
return NULL;
}
@@ -126,21 +127,21 @@ C_FUNC const char* mod_storage_load(const char* key) {
return const_cast(ini["storage"][key].c_str());
}
-C_FUNC double mod_storage_load_number(const char* key) {
+C_FIELD double mod_storage_load_number(const char* key) {
const char* value = mod_storage_load(key);
if (value == NULL) { return 0; }
return std::strtod(value, nullptr);
}
-C_FUNC bool mod_storage_load_bool(const char* key) {
+C_FIELD bool mod_storage_load_bool(const char* key) {
const char* value = mod_storage_load(key);
if (value == NULL) { return false; }
return !strcmp(value, "true");
}
-C_FUNC bool mod_storage_remove(const char* key) {
+C_FIELD bool mod_storage_remove(const char* key) {
if (strlen(key) > MAX_KEY_VALUE_LENGTH) {
return false;
}
@@ -168,7 +169,7 @@ C_FUNC bool mod_storage_remove(const char* key) {
return false;
}
-C_FUNC bool mod_storage_clear(void) {
+C_FIELD bool mod_storage_clear(void) {
char filename[SYS_MAX_PATH] = {0};
mod_storage_get_filename(filename);
diff --git a/src/pc/mods/mod_storage.h b/src/pc/mods/mod_storage.h
index 9a388a9e3..1b614c1bf 100644
--- a/src/pc/mods/mod_storage.h
+++ b/src/pc/mods/mod_storage.h
@@ -25,4 +25,4 @@ bool mod_storage_clear(void);
}
#endif
-#endif
+#endif // MOD_STORAGE_H