mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2024-11-22 12:59:14 +01:00
Implemented rumble, added control options menu with rumble strength and targeting mode
This commit is contained in:
parent
9ef243bc05
commit
57475d058b
@ -28,6 +28,7 @@
|
|||||||
</style>
|
</style>
|
||||||
<link type="text/template" href="config_menu/graphics.rml" />
|
<link type="text/template" href="config_menu/graphics.rml" />
|
||||||
<link type="text/template" href="config_menu/controls.rml" />
|
<link type="text/template" href="config_menu/controls.rml" />
|
||||||
|
<link type="text/template" href="config_menu/control_options.rml" />
|
||||||
<link type="text/template" href="config_menu/sound.rml" />
|
<link type="text/template" href="config_menu/sound.rml" />
|
||||||
<link type="text/template" href="config_menu/debug.rml" />
|
<link type="text/template" href="config_menu/debug.rml" />
|
||||||
</head>
|
</head>
|
||||||
@ -51,6 +52,13 @@
|
|||||||
<panel class="config" data-model="controls_model">
|
<panel class="config" data-model="controls_model">
|
||||||
<template src="config-menu__controls" />
|
<template src="config-menu__controls" />
|
||||||
</panel>
|
</panel>
|
||||||
|
<tab class="tab">
|
||||||
|
<div>Control Options</div>
|
||||||
|
<div class="tab__indicator"></div>
|
||||||
|
</tab>
|
||||||
|
<panel class="config" data-model="control_options_model">
|
||||||
|
<template src="config-menu__control-options" />
|
||||||
|
</panel>
|
||||||
<tab class="tab">
|
<tab class="tab">
|
||||||
<div>Sound</div>
|
<div>Sound</div>
|
||||||
<div class="tab__indicator"></div>
|
<div class="tab__indicator"></div>
|
||||||
|
28
assets/config_menu/control_options.rml
Normal file
28
assets/config_menu/control_options.rml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<template name="config-menu__control-options">
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form class="config__form">
|
||||||
|
<div class="config__wrapper">
|
||||||
|
<div class="config__row">
|
||||||
|
<div class="config-option">
|
||||||
|
<label class="config-option__title">Targeting Mode</label>
|
||||||
|
<div class="config-option__list config-option__list">
|
||||||
|
<input type="radio" name="targeting_mode" data-checked="targeting_mode" value="Switch" id="tm_switch"/>
|
||||||
|
<label class="config-option__tab-label" for="tm_switch">Switch</label>
|
||||||
|
<input type="radio" name="targeting_mode" data-checked="targeting_mode" value="Hold" id="tm_hold"/>
|
||||||
|
<label class="config-option__tab-label" for="tm_hold">Hold</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="config-option">
|
||||||
|
<label class="config-option__title">Rumble Strength</label>
|
||||||
|
<div class="config-option__list config-option__list">
|
||||||
|
<label class="config-option__range-label">{{rumble_strength}}</label>
|
||||||
|
<input id="rumble_strength_input" type="range" min="0" max="100" style="flex:1;margin: 0dp;nav-up:auto;nav-down:auto;" data-value="rumble_strength"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</template>
|
@ -9,6 +9,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "json/json.hpp"
|
||||||
|
|
||||||
namespace recomp {
|
namespace recomp {
|
||||||
// x-macros to build input enums and arrays.
|
// x-macros to build input enums and arrays.
|
||||||
// First parameter is the enum name, second parameter is the bit field for the input (or 0 if there is no associated one), third is the readable name.
|
// First parameter is the enum name, second parameter is the bit field for the input (or 0 if there is no associated one), third is the readable name.
|
||||||
@ -112,8 +114,27 @@ namespace recomp {
|
|||||||
void set_input_binding(GameInput input, size_t binding_index, InputDevice device, InputField value);
|
void set_input_binding(GameInput input, size_t binding_index, InputDevice device, InputField value);
|
||||||
|
|
||||||
void get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out);
|
void get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out);
|
||||||
|
void set_rumble(bool);
|
||||||
void handle_events();
|
void handle_events();
|
||||||
|
|
||||||
|
// Rumble strength ranges from 0 to 100.
|
||||||
|
int get_rumble_strength();
|
||||||
|
void set_rumble_strength(int strength);
|
||||||
|
|
||||||
|
enum class TargetingMode {
|
||||||
|
Switch,
|
||||||
|
Hold,
|
||||||
|
OptionCount
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::TargetingMode, {
|
||||||
|
{recomp::TargetingMode::Switch, "Switch"},
|
||||||
|
{recomp::TargetingMode::Hold, "Hold"}
|
||||||
|
});
|
||||||
|
|
||||||
|
TargetingMode get_targeting_mode();
|
||||||
|
void set_targeting_mode(TargetingMode mode);
|
||||||
|
|
||||||
bool game_input_disabled();
|
bool game_input_disabled();
|
||||||
bool all_input_disabled();
|
bool all_input_disabled();
|
||||||
|
|
||||||
|
@ -11,5 +11,6 @@ typedef enum {
|
|||||||
extern RecompCameraMode recomp_camera_mode;
|
extern RecompCameraMode recomp_camera_mode;
|
||||||
|
|
||||||
DECLARE_FUNC(void, recomp_get_gyro_deltas, float* x, float* y);
|
DECLARE_FUNC(void, recomp_get_gyro_deltas, float* x, float* y);
|
||||||
|
DECLARE_FUNC(int, recomp_get_targeting_mode);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
#include "play_patches.h"
|
#include "play_patches.h"
|
||||||
#include "z64debug_display.h"
|
#include "z64debug_display.h"
|
||||||
|
#include "input.h"
|
||||||
|
|
||||||
extern Input D_801F6C18;
|
extern Input D_801F6C18;
|
||||||
|
|
||||||
|
void controls_play_update(PlayState* play) {
|
||||||
|
gSaveContext.options.zTargetSetting = recomp_get_targeting_mode();
|
||||||
|
}
|
||||||
|
|
||||||
// @recomp Patched to add hooks for various added functionality.
|
// @recomp Patched to add hooks for various added functionality.
|
||||||
void Play_Main(GameState* thisx) {
|
void Play_Main(GameState* thisx) {
|
||||||
static Input* prevInput = NULL;
|
static Input* prevInput = NULL;
|
||||||
@ -10,6 +15,7 @@ void Play_Main(GameState* thisx) {
|
|||||||
|
|
||||||
// @recomp
|
// @recomp
|
||||||
debug_play_update(this);
|
debug_play_update(this);
|
||||||
|
controls_play_update(this);
|
||||||
|
|
||||||
// @recomp avoid unused variable warning
|
// @recomp avoid unused variable warning
|
||||||
(void)prevInput;
|
(void)prevInput;
|
||||||
|
@ -4,14 +4,15 @@ __start = 0x80000000;
|
|||||||
sSceneEntranceTable = 0x801C5720;
|
sSceneEntranceTable = 0x801C5720;
|
||||||
|
|
||||||
/* Dummy addresses that get recompiled into function calls */
|
/* Dummy addresses that get recompiled into function calls */
|
||||||
recomp_puts = 0x81000000;
|
recomp_puts = 0x8F000000;
|
||||||
recomp_exit = 0x81000004;
|
recomp_exit = 0x8F000004;
|
||||||
recomp_handle_quicksave_actions = 0x81000008;
|
recomp_handle_quicksave_actions = 0x8F000008;
|
||||||
recomp_handle_quicksave_actions_main = 0x8100000C;
|
recomp_handle_quicksave_actions_main = 0x8F00000C;
|
||||||
osRecvMesg_recomp = 0x81000010;
|
osRecvMesg_recomp = 0x8F000010;
|
||||||
osSendMesg_recomp = 0x81000014;
|
osSendMesg_recomp = 0x8F000014;
|
||||||
recomp_get_gyro_deltas = 0x81000018;
|
recomp_get_gyro_deltas = 0x8F000018;
|
||||||
recomp_get_aspect_ratio = 0x8100001C;
|
recomp_get_aspect_ratio = 0x8F00001C;
|
||||||
recomp_get_pending_warp = 0x81000020;
|
recomp_get_pending_warp = 0x8F000020;
|
||||||
recomp_powf = 0x81000024;
|
recomp_powf = 0x8F000024;
|
||||||
recomp_get_target_framerate = 0x81000028;
|
recomp_get_target_framerate = 0x8F000028;
|
||||||
|
recomp_get_targeting_mode = 0x8F00002C;
|
||||||
|
@ -23,6 +23,17 @@ constexpr auto rr_default = RT64::UserConfiguration::RefreshRate::Di
|
|||||||
constexpr int rr_manual_default = 60;
|
constexpr int rr_manual_default = 60;
|
||||||
constexpr bool developer_mode_default = false;
|
constexpr bool developer_mode_default = false;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void from_or_default(const json& j, const std::string& key, T& out, T default_value) {
|
||||||
|
auto find_it = j.find(key);
|
||||||
|
if (find_it != j.end()) {
|
||||||
|
find_it->get_to(out);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
out = default_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace ultramodern {
|
namespace ultramodern {
|
||||||
void to_json(json& j, const GraphicsConfig& config) {
|
void to_json(json& j, const GraphicsConfig& config) {
|
||||||
j = json{
|
j = json{
|
||||||
@ -36,17 +47,6 @@ namespace ultramodern {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void from_or_default(const json& j, const std::string& key, T& out, T default_value) {
|
|
||||||
auto find_it = j.find(key);
|
|
||||||
if (find_it != j.end()) {
|
|
||||||
find_it->get_to(out);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
out = default_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void from_json(const json& j, GraphicsConfig& config) {
|
void from_json(const json& j, GraphicsConfig& config) {
|
||||||
from_or_default(j, "res_option", config.res_option, res_default);
|
from_or_default(j, "res_option", config.res_option, res_default);
|
||||||
from_or_default(j, "wm_option", config.wm_option, wm_default);
|
from_or_default(j, "wm_option", config.wm_option, wm_default);
|
||||||
@ -171,6 +171,11 @@ void add_input_bindings(nlohmann::json& out, recomp::GameInput input, recomp::In
|
|||||||
|
|
||||||
void save_controls_config(const std::filesystem::path& path) {
|
void save_controls_config(const std::filesystem::path& path) {
|
||||||
nlohmann::json config_json{};
|
nlohmann::json config_json{};
|
||||||
|
|
||||||
|
config_json["options"] = {};
|
||||||
|
recomp::to_json(config_json["options"]["targeting_mode"], recomp::get_targeting_mode());
|
||||||
|
config_json["options"]["rumble_strength"] = recomp::get_rumble_strength();
|
||||||
|
|
||||||
config_json["keyboard"] = {};
|
config_json["keyboard"] = {};
|
||||||
config_json["controller"] = {};
|
config_json["controller"] = {};
|
||||||
|
|
||||||
@ -222,6 +227,14 @@ void load_controls_config(const std::filesystem::path& path) {
|
|||||||
|
|
||||||
config_file >> config_json;
|
config_file >> config_json;
|
||||||
|
|
||||||
|
recomp::TargetingMode targeting_mode;
|
||||||
|
from_or_default(config_json["options"], "targeting_mode", targeting_mode, recomp::TargetingMode::Switch);
|
||||||
|
recomp::set_targeting_mode(targeting_mode);
|
||||||
|
|
||||||
|
int rumble_strength;
|
||||||
|
from_or_default(config_json["options"], "rumble_strength", rumble_strength, 25);
|
||||||
|
recomp::set_rumble_strength(rumble_strength);
|
||||||
|
|
||||||
if (!load_input_device_from_json(config_json, recomp::InputDevice::Keyboard, "keyboard")) {
|
if (!load_input_device_from_json(config_json, recomp::InputDevice::Keyboard, "keyboard")) {
|
||||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||||
}
|
}
|
||||||
|
@ -345,6 +345,14 @@ void recomp::poll_inputs() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void recomp::set_rumble(bool on) {
|
||||||
|
uint16_t rumble_strength = recomp::get_rumble_strength() * 0xFFFF / 100;
|
||||||
|
uint32_t duration = 1000000; // Dummy duration value that lasts long enough to matter as the game will reset rumble on its own.
|
||||||
|
for (const auto& controller : InputState.cur_controllers) {
|
||||||
|
SDL_GameControllerRumble(controller, 0, on ? rumble_strength : 0, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool controller_button_state(int32_t input_id) {
|
bool controller_button_state(int32_t input_id) {
|
||||||
if (input_id >= 0 && input_id < SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MAX) {
|
if (input_id >= 0 && input_id < SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MAX) {
|
||||||
SDL_GameControllerButton button = (SDL_GameControllerButton)input_id;
|
SDL_GameControllerButton button = (SDL_GameControllerButton)input_id;
|
||||||
|
@ -62,3 +62,7 @@ extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void recomp_get_targeting_mode(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
_return(ctx, static_cast<int>(recomp::get_targeting_mode()));
|
||||||
|
}
|
||||||
|
@ -253,6 +253,7 @@ int main(int argc, char** argv) {
|
|||||||
ultramodern::input_callbacks_t input_callbacks{
|
ultramodern::input_callbacks_t input_callbacks{
|
||||||
.poll_input = recomp::poll_inputs,
|
.poll_input = recomp::poll_inputs,
|
||||||
.get_input = recomp::get_n64_input,
|
.get_input = recomp::get_n64_input,
|
||||||
|
.set_rumble = recomp::set_rumble,
|
||||||
};
|
};
|
||||||
|
|
||||||
recomp::start({}, audio_callbacks, input_callbacks, gfx_callbacks);
|
recomp::start({}, audio_callbacks, input_callbacks, gfx_callbacks);
|
||||||
|
@ -75,7 +75,7 @@ extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) {
|
|||||||
|
|
||||||
// Mark controller 0 as present
|
// Mark controller 0 as present
|
||||||
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
|
MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus)
|
||||||
MEM_B(2, status) = 0x00; // status: 0 (from joybus)
|
MEM_B(2, status) = 0x01; // status: 0x01 (from joybus, indicates that a pak is plugged into the controller)
|
||||||
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
|
MEM_B(3, status) = 0x00; // errno: 0 (from libultra)
|
||||||
|
|
||||||
// Mark controllers 1-3 as not connected
|
// Mark controllers 1-3 as not connected
|
||||||
@ -91,17 +91,46 @@ extern "C" void osContSetCh_recomp(uint8_t* rdram, recomp_context* ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void __osMotorAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void __osMotorAccess_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
|
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||||
|
s32 flag = _arg<1, s32>(rdram, ctx);
|
||||||
|
s32 channel = MEM_W(8, pfs);
|
||||||
|
|
||||||
|
// Only respect accesses to controller 0.
|
||||||
|
if (channel == 0) {
|
||||||
|
input_callbacks.set_rumble(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
_return<s32>(ctx, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osMotorInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osMotorInit_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
;
|
PTR(void) pfs = _arg<1, PTR(void)>(rdram, ctx);
|
||||||
|
s32 channel = _arg<2, s32>(rdram, ctx);
|
||||||
|
MEM_W(8, pfs) = channel;
|
||||||
|
|
||||||
|
_return<s32>(ctx, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
;
|
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||||
|
s32 channel = MEM_W(8, pfs);
|
||||||
|
|
||||||
|
// Only respect accesses to controller 0.
|
||||||
|
if (channel == 0) {
|
||||||
|
input_callbacks.set_rumble(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_return<s32>(ctx, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) {
|
extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) {
|
||||||
;
|
PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx);
|
||||||
|
s32 channel = MEM_W(8, pfs);
|
||||||
|
|
||||||
|
// Only respect accesses to controller 0.
|
||||||
|
if (channel == 0) {
|
||||||
|
input_callbacks.set_rumble(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_return<s32>(ctx, 0);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
ultramodern::GraphicsConfig new_options;
|
ultramodern::GraphicsConfig new_options;
|
||||||
Rml::DataModelHandle graphics_model_handle;
|
Rml::DataModelHandle graphics_model_handle;
|
||||||
Rml::DataModelHandle controls_model_handle;
|
Rml::DataModelHandle controls_model_handle;
|
||||||
|
Rml::DataModelHandle control_options_model_handle;
|
||||||
// True if controller config menu is open, false if keyboard config menu is open, undefined otherwise
|
// True if controller config menu is open, false if keyboard config menu is open, undefined otherwise
|
||||||
bool configuring_controller = false;
|
bool configuring_controller = false;
|
||||||
|
|
||||||
@ -70,6 +71,35 @@ void close_config_menu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ControlOptionsContext {
|
||||||
|
int rumble_strength = 50; // 0 to 100
|
||||||
|
recomp::TargetingMode targeting_mode = recomp::TargetingMode::Switch;
|
||||||
|
};
|
||||||
|
|
||||||
|
ControlOptionsContext control_options_context;
|
||||||
|
|
||||||
|
int recomp::get_rumble_strength() {
|
||||||
|
return control_options_context.rumble_strength;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::set_rumble_strength(int strength) {
|
||||||
|
control_options_context.rumble_strength = strength;
|
||||||
|
if (control_options_model_handle) {
|
||||||
|
control_options_model_handle.DirtyVariable("rumble_strength");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recomp::TargetingMode recomp::get_targeting_mode() {
|
||||||
|
return control_options_context.targeting_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recomp::set_targeting_mode(recomp::TargetingMode mode) {
|
||||||
|
control_options_context.targeting_mode = mode;
|
||||||
|
if (control_options_model_handle) {
|
||||||
|
control_options_model_handle.DirtyVariable("targeting_mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct DebugContext {
|
struct DebugContext {
|
||||||
Rml::DataModelHandle model_handle;
|
Rml::DataModelHandle model_handle;
|
||||||
std::vector<std::string> area_names;
|
std::vector<std::string> area_names;
|
||||||
@ -345,6 +375,18 @@ public:
|
|||||||
controls_model_handle = constructor.GetModelHandle();
|
controls_model_handle = constructor.GetModelHandle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void make_control_options_bindings(Rml::Context* context) {
|
||||||
|
Rml::DataModelConstructor constructor = context->CreateDataModel("control_options_model");
|
||||||
|
if (!constructor) {
|
||||||
|
throw std::runtime_error("Failed to make RmlUi data model for the control options menu");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor.Bind("rumble_strength", &control_options_context.rumble_strength);
|
||||||
|
bind_option(constructor, "targeting_mode", &control_options_context.targeting_mode);
|
||||||
|
|
||||||
|
control_options_model_handle = constructor.GetModelHandle();
|
||||||
|
}
|
||||||
|
|
||||||
void make_debug_bindings(Rml::Context* context) {
|
void make_debug_bindings(Rml::Context* context) {
|
||||||
Rml::DataModelConstructor constructor = context->CreateDataModel("debug_model");
|
Rml::DataModelConstructor constructor = context->CreateDataModel("debug_model");
|
||||||
if (!constructor) {
|
if (!constructor) {
|
||||||
@ -370,6 +412,7 @@ public:
|
|||||||
void make_bindings(Rml::Context* context) override {
|
void make_bindings(Rml::Context* context) override {
|
||||||
make_graphics_bindings(context);
|
make_graphics_bindings(context);
|
||||||
make_controls_bindings(context);
|
make_controls_bindings(context);
|
||||||
|
make_control_options_bindings(context);
|
||||||
make_debug_bindings(context);
|
make_debug_bindings(context);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user