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>
|
||||
<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/control_options.rml" />
|
||||
<link type="text/template" href="config_menu/sound.rml" />
|
||||
<link type="text/template" href="config_menu/debug.rml" />
|
||||
</head>
|
||||
|
@ -51,6 +52,13 @@
|
|||
<panel class="config" data-model="controls_model">
|
||||
<template src="config-menu__controls" />
|
||||
</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">
|
||||
<div>Sound</div>
|
||||
<div class="tab__indicator"></div>
|
||||
|
|
|
@ -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_view>
|
||||
|
||||
#include "json/json.hpp"
|
||||
|
||||
namespace recomp {
|
||||
// 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.
|
||||
|
@ -112,7 +114,26 @@ namespace recomp {
|
|||
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 set_rumble(bool);
|
||||
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 all_input_disabled();
|
||||
|
|
|
@ -11,5 +11,6 @@ typedef enum {
|
|||
extern RecompCameraMode recomp_camera_mode;
|
||||
|
||||
DECLARE_FUNC(void, recomp_get_gyro_deltas, float* x, float* y);
|
||||
DECLARE_FUNC(int, recomp_get_targeting_mode);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
#include "play_patches.h"
|
||||
#include "z64debug_display.h"
|
||||
#include "input.h"
|
||||
|
||||
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.
|
||||
void Play_Main(GameState* thisx) {
|
||||
static Input* prevInput = NULL;
|
||||
|
@ -10,6 +15,7 @@ void Play_Main(GameState* thisx) {
|
|||
|
||||
// @recomp
|
||||
debug_play_update(this);
|
||||
controls_play_update(this);
|
||||
|
||||
// @recomp avoid unused variable warning
|
||||
(void)prevInput;
|
||||
|
|
|
@ -4,14 +4,15 @@ __start = 0x80000000;
|
|||
sSceneEntranceTable = 0x801C5720;
|
||||
|
||||
/* Dummy addresses that get recompiled into function calls */
|
||||
recomp_puts = 0x81000000;
|
||||
recomp_exit = 0x81000004;
|
||||
recomp_handle_quicksave_actions = 0x81000008;
|
||||
recomp_handle_quicksave_actions_main = 0x8100000C;
|
||||
osRecvMesg_recomp = 0x81000010;
|
||||
osSendMesg_recomp = 0x81000014;
|
||||
recomp_get_gyro_deltas = 0x81000018;
|
||||
recomp_get_aspect_ratio = 0x8100001C;
|
||||
recomp_get_pending_warp = 0x81000020;
|
||||
recomp_powf = 0x81000024;
|
||||
recomp_get_target_framerate = 0x81000028;
|
||||
recomp_puts = 0x8F000000;
|
||||
recomp_exit = 0x8F000004;
|
||||
recomp_handle_quicksave_actions = 0x8F000008;
|
||||
recomp_handle_quicksave_actions_main = 0x8F00000C;
|
||||
osRecvMesg_recomp = 0x8F000010;
|
||||
osSendMesg_recomp = 0x8F000014;
|
||||
recomp_get_gyro_deltas = 0x8F000018;
|
||||
recomp_get_aspect_ratio = 0x8F00001C;
|
||||
recomp_get_pending_warp = 0x8F000020;
|
||||
recomp_powf = 0x8F000024;
|
||||
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 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 {
|
||||
void to_json(json& j, const GraphicsConfig& config) {
|
||||
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) {
|
||||
from_or_default(j, "res_option", config.res_option, res_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) {
|
||||
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["controller"] = {};
|
||||
|
||||
|
@ -221,6 +226,14 @@ void load_controls_config(const std::filesystem::path& path) {
|
|||
nlohmann::json 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")) {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
|
|
|
@ -345,6 +345,14 @@ void recomp::poll_inputs() {
|
|||
#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) {
|
||||
if (input_id >= 0 && input_id < SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MAX) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
.poll_input = recomp::poll_inputs,
|
||||
.get_input = recomp::get_n64_input,
|
||||
.set_rumble = recomp::set_rumble,
|
||||
};
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
;
|
||||
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) {
|
||||
;
|
||||
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) {
|
||||
;
|
||||
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;
|
||||
Rml::DataModelHandle graphics_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
|
||||
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 {
|
||||
Rml::DataModelHandle model_handle;
|
||||
std::vector<std::string> area_names;
|
||||
|
@ -345,6 +375,18 @@ public:
|
|||
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) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("debug_model");
|
||||
if (!constructor) {
|
||||
|
@ -370,6 +412,7 @@ public:
|
|||
void make_bindings(Rml::Context* context) override {
|
||||
make_graphics_bindings(context);
|
||||
make_controls_bindings(context);
|
||||
make_control_options_bindings(context);
|
||||
make_debug_bindings(context);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue