-- SM64DS Controls Fix script by YoshiRulz
-- revision 2023.11.24 for EmuHawk >= 2.9.2
-- This program is published under the GNU General Public License, version 3, or (at your option) any later version. Full text at gnu.org/licenses.
-- While this script is running, you'll be able to use the left analog stick on your gamepad to more precisely control Yoshi and the plumbers, similar to the original SM64.
-- It also lets you control the camera with either the bumpers or the right analog stick. As a bonus, you can exit course without a mouse since it's in the same spot as the camera controls.
-- At time of writing, the default binds have the DPad bound to the left stick and L/R bound to the bumpers--you'll need to rebind those to the DPad and triggers.
-- And make sure your controls are set to the default 'Standard Mode' in the pause menu. You don't need to unbind X or Y, though you are free to rearrange the face buttons.
-- config:
local GAMEPAD_PFX = "X1 "; -- with trailing space; easiest way to find this is to bind something to your gamepad
-- keeping in mind sticks have a radius of 10000 (i.e. each axis is -10000..10000)
local DEADZONE = 1000; -- "radius" of the inner square; when any individual stick axis is less than this (in the range -DEADZONE..DEADZONE), it's clipped to 0
local WALK_THRESHOLD = 6000; -- "radius" of the outer square; when the stick is outside the square you'll run, within, you'll walk
INVERT_LSTICK_X = false; INVERT_LSTICK_Y = false; -- self-explanatory
INVERT_RSTICK_X = false; INVERT_RSTICK_Y = false; -- self-explanatory, though inverting Y is pointless, and inverting X also applies to the bumpers since they do the same thing
-- That's it! Past here is all implementation.
--TODO detect control scheme
--TODO work under the other 2 control schemes
--TODO use touch screen for movement
local AXES = {
["LSTICK_X"] = GAMEPAD_PFX.."LeftThumbX Axis",
["LSTICK_Y"] = GAMEPAD_PFX.."LeftThumbY Axis",
["RSTICK_X"] = GAMEPAD_PFX.."RightThumbX Axis",
["RSTICK_Y"] = GAMEPAD_PFX.."RightThumbY Axis",
};
local BUTTONS = {
["LB"] = GAMEPAD_PFX.."LeftShoulder",
["RB"] = GAMEPAD_PFX.."RightShoulder",
};
local buttons_down_prev = {};
event.onloadstate(function() buttons_down_prev = {}; end);
local rotate = 0;
while true do
local axis_values = input.get_pressed_axes();
local buttons_down = input.get();
for k, v in pairs(AXES) do
local axis_val = axis_values[v] or 0;
if math.abs(axis_val) < DEADZONE then
axis_values[v] = 0;
elseif _G["INVERT_"..k] then -- galaxy brain code golf
axis_values[v] = -axis_val;
end
end
if INVERT_RSTICK_X then
buttons_down[BUTTONS.LB], buttons_down[BUTTONS.RB] = buttons_down[BUTTONS.RB], buttons_down[BUTTONS.LB];
end
local to_set = {};
local to_set_axes = {};
local x, y = axis_values[AXES.LSTICK_X], axis_values[AXES.LSTICK_Y];
local run_x, run_y = WALK_THRESHOLD < math.abs(x), WALK_THRESHOLD < math.abs(y);
if run_x or run_y then to_set.Y = true; end
to_set[(run_x or not run_y) and (x < 0 and "Left" or x > 0 and "Right")] = true;
to_set[(run_y or not run_x) and (y < 0 and "Down" or y > 0 and "Up")] = true;
local x, y = axis_values[AXES.RSTICK_X], axis_values[AXES.RSTICK_Y];
if WALK_THRESHOLD < math.abs(y) then to_set.X = true; end
if not buttons_down[BUTTONS.LB] and x < -WALK_THRESHOLD then buttons_down[BUTTONS.LB] = true; end
if not buttons_down[BUTTONS.RB] and WALK_THRESHOLD < x then buttons_down[BUTTONS.RB] = true; end
if buttons_down[BUTTONS.LB] then
if not (buttons_down[BUTTONS.RB] and buttons_down_prev[BUTTONS.LB]) then
rotate = -1;
elseif not buttons_down_prev[BUTTONS.RB] then
rotate = 1;
end
elseif buttons_down[BUTTONS.RB] then
rotate = 1;
else
if rotate ~= 0 then client.clearautohold(); end -- warning: possibly conflicts with other scripts
rotate = 0;
end
if rotate < 0 then
to_set_axes["Touch X"] = 0x1F;
to_set_axes["Touch Y"] = 0x9F;
to_set.Touch = true;
elseif rotate ~= 0 then
to_set_axes["Touch X"] = 0x3F;
to_set_axes["Touch Y"] = 0x9F;
to_set.Touch = true;
end
joypad.set(to_set);
joypad.setanalog(to_set_axes);
buttons_down_prev = buttons_down;
emu.frameadvance();
end