mirror of
https://github.com/Zelda64Recomp/Zelda64Recomp
synced 2026-07-11 15:29:05 -04:00
Implemented rumble, added control options menu with rumble strength and targeting mode
This commit is contained in:
+24
-11
@@ -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);
|
||||
|
||||
+33
-4
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user