Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions docs/PC.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,12 @@ key is optional; these are the defaults:
"input": {
"mouse_sensitivity": 0.1, // right-stick deflection (1 = full) per pixel moved in one tick
"stick_sensitivity": 1.33, // gamepad stick scale before the game's dead zone (PCSX2's default)
"gyro_sensitivity": 0.5, // right-stick deflection per radian per second the pad turns, while gyro is on
"stick_invert_x": false, // the gamepad's camera stick, flipped left to right
"stick_invert_y": false, // ...and up and down
"gyro": "held", // when the gyroscope turns the camera: off, always, first_person (R2's view) or held (gyro_hold)
"gyro_sensitivity": 0.5, // camera-stick deflection per radian per second the pad turns
"gyro_invert_x": false,
"gyro_invert_y": false,
"mouse_invert_y": false,
"mouse_capture": true, // SDL relative mouse mode while the window has focus
"mouse_release": ["Escape"], // keys that give the cursor back in a window ([]: none)
Expand Down Expand Up @@ -238,7 +243,7 @@ dungeon's `PadInput_OK` is cross and `PadInput_NO` circle,
| F3 | none | the FPS counter on and off (see "The FPS counter") |
| gamepad L4 (`paddle2`) | none | the developer menu, in debug mode |
| gamepad R4 (`paddle1`) | none | the town or dungeon debug menu, in debug mode, without Select+L2 |
| gamepad L5 (`paddle4`) | none | gyro camera on and off |
| gamepad L5 (`paddle4`) | none | held, the gyroscope turns the camera (`gyro` set to `held`) |

The square button is not a guard in this game: the guard is R1 held while
locked on, so right click is R1. The camera turns the way the view moves on
Expand All @@ -250,7 +255,7 @@ default `ry = -MouseY` makes mouse up look up and `mouse_invert_y` flips it.

The actions are `up down left right cross circle square triangle l1 r1 l2
r2 l3 r3 start select lx- lx+ ly- ly+ rx- rx+ ry- ry+ lx ly rx ry`, and the
port's own `fps_toggle`, `developer_menu`, `debug_menu` and `gyro_toggle`, which press no pad button. Keys are
port's own `fps_toggle`, `developer_menu`, `debug_menu` and `gyro_hold`, which press no pad button. Keys are
SDL names (any case, `_` for a space; `Grave`, `Backquote` or `Backtick` for
the key left of 1); `Mouse1` to `Mouse5` are left, right,
middle and the two side buttons. The port's own actions also take
Expand All @@ -270,10 +275,13 @@ Each key-down of a toggle's key counts once, however briefly it is held.
onto the DualShock 2's square, keeping its direction: the game's dead zone
takes 38% of the travel and the town runs past 0.85, so an unscaled stick
only runs at over 90% tilt, and a round one never on a diagonal.
- **Gyro.** While `gyro_toggle` has it on, a gamepad's gyroscope turns the
camera when its right stick is centred: turning the pad left or right
turns the view, tilting it looks up or down, `gyro_sensitivity` times the
rate in radians per second, below 0.03 ignored as drift.
- **Gyro.** When `gyro` says (always, only in first-person view, or while
`gyro_hold` is held), a gamepad's gyroscope turns the camera while the
camera stick is centred: the right stick, or the left in first-person view
(R2). Turning the pad left or right turns the view and tilting it looks up
or down, `gyro_sensitivity` times the rate in radians per second; below 0.03
is ignored as drift. `gyro_invert_x`/`_y` flip it, and `stick_invert_x`/`_y`
flip the camera stick the same way.
- **Mouse.** The right stick at each pad read is the motion since the
previous read, divided by the ticks between them, times
`mouse_sensitivity` (0.1: ten pixels in one tick is full deflection,
Expand Down
2 changes: 2 additions & 0 deletions port/src/gameloop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ int EditLoop();
void SndInit();

extern s32 mode;
extern s32 gameTask;
extern s32 mc_mode;
extern s32 NextMapNo;

Expand Down Expand Up @@ -789,6 +790,7 @@ int RunGame(int argc, char **argv) {
result = ModeLoop(skip_title);
GameApplyLoopResult(old_main_mode, result);

InputSetLookOnLeftStick(mode == GAME_MODE_DUNGEON && gameTask == GAME_TASK_EYE_CAMERA);
GamePad.UpDate();
GamePad.VibrationEnable(ConfigWords()[kConfigVibrationOff] == 0);
GamePad.Step();
Expand Down
22 changes: 22 additions & 0 deletions port/src/menu_option.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,18 @@ void RestoreStickSensitivity(Config &config, const Config &defaults) {
config.stick_sensitivity = defaults.stick_sensitivity;
}

int GyroCount(const Config &) {
return 4;
}

int GyroChoice(const Config &config) {
return static_cast<int>(config.gyro);
}

void SetGyro(Config &config, int choice) {
config.gyro = static_cast<ConfigGyro>(choice);
}

int GyroSensitivityCount(const Config &) {
return 39;
}
Expand Down Expand Up @@ -465,9 +477,19 @@ const Row kControlRows[] = {
Row{"input.stick_sensitivity", "Stick Sensitivity", "\"Stick Sensitivity\"\nHow far a gamepad's\nstick has to tilt.",
-1, StickSensitivityCount, StickSensitivityChoice, SetStickSensitivity, StickSensitivityText, nullptr,
RestoreStickSensitivity},
OnOffRow<&Config::stick_invert_x>("input.stick_invert_x", "Invert Stick X",
"\"Invert Stick X\"\nFlips the camera's\nleft and right."),
OnOffRow<&Config::stick_invert_y>("input.stick_invert_y", "Invert Stick Y",
"\"Invert Stick Y\"\nFlips the camera's\nup and down."),
Row{"input.gyro", "Gyro", "\"Gyro\"\nWhen tilting the pad\nturns the camera.", -1, GyroCount, GyroChoice, SetGyro,
nullptr, "Off|Always|First Person|While Held"},
Row{"input.gyro_sensitivity", "Gyro Sensitivity", "\"Gyro Sensitivity\"\nHow fast tilting\nturns the camera.",
-1, GyroSensitivityCount, GyroSensitivityChoice, SetGyroSensitivity, GyroSensitivityText, nullptr,
RestoreGyroSensitivity},
OnOffRow<&Config::gyro_invert_x>("input.gyro_invert_x", "Invert Gyro X",
"\"Invert Gyro X\"\nFlips the gyro's\nleft and right."),
OnOffRow<&Config::gyro_invert_y>("input.gyro_invert_y", "Invert Gyro Y",
"\"Invert Gyro Y\"\nFlips the gyro's\nup and down."),
};

// A page of the screen. Every page so far is a list of rows; one that needs its own layout, such as
Expand Down
36 changes: 36 additions & 0 deletions port/src/platform/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,22 @@ bool ReadAspect(const Json &value, ConfigAspect &out) {
return true;
}

constexpr const char *kGyroNames[] = {"off", "always", "first_person", "held"};

bool ReadGyro(const Json &value, ConfigGyro &out) {
if (!value.is_string()) {
return false;
}
std::string gyro = Lower(value.get<std::string>());
for (std::size_t i = 0; i < std::size(kGyroNames); ++i) {
if (gyro == kGyroNames[i]) {
out = static_cast<ConfigGyro>(i);
return true;
}
}
return false;
}

bool ReadList(const Json &value, std::vector<std::string> &out) {
std::vector<std::string> items;
if (value.is_string()) {
Expand Down Expand Up @@ -226,6 +242,21 @@ bool Apply(Config &config, std::string_view name, const Json &value) {
config.stick_sensitivity = sensitivity;
return true;
}
if (name == "input.gyro") {
return ReadGyro(value, config.gyro);
}
if (name == "input.stick_invert_x") {
return ReadBool(value, config.stick_invert_x);
}
if (name == "input.stick_invert_y") {
return ReadBool(value, config.stick_invert_y);
}
if (name == "input.gyro_invert_x") {
return ReadBool(value, config.gyro_invert_x);
}
if (name == "input.gyro_invert_y") {
return ReadBool(value, config.gyro_invert_y);
}
if (name == "input.gyro_sensitivity") {
float sensitivity = 0.0f;
if (!ReadNumber(value, sensitivity) || !(sensitivity > 0.0f) || !std::isfinite(sensitivity)) {
Expand Down Expand Up @@ -431,7 +462,12 @@ std::string ConfigSerialize(const Config &config) {
root["audio"]["sound"] = options.stereo ? "stereo" : "mono";
root["input"]["mouse_sensitivity"] = Shortest(config.mouse_sensitivity);
root["input"]["stick_sensitivity"] = Shortest(config.stick_sensitivity);
root["input"]["stick_invert_x"] = config.stick_invert_x;
root["input"]["stick_invert_y"] = config.stick_invert_y;
root["input"]["gyro"] = kGyroNames[static_cast<std::size_t>(config.gyro)];
root["input"]["gyro_sensitivity"] = Shortest(config.gyro_sensitivity);
root["input"]["gyro_invert_x"] = config.gyro_invert_x;
root["input"]["gyro_invert_y"] = config.gyro_invert_y;
root["input"]["mouse_invert_y"] = config.mouse_invert_y;
root["input"]["mouse_capture"] = config.mouse_capture;
root["input"]["mouse_release"] = config.mouse_release_keys;
Expand Down
12 changes: 12 additions & 0 deletions port/src/platform/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ enum class ConfigAspect {
FourThree,
};

enum class ConfigGyro {
Off,
Always,
FirstPerson,
Held,
};

struct ConfigKeyBinding {
std::string action;
std::vector<std::string> keys;
Expand Down Expand Up @@ -63,7 +70,12 @@ struct Config {
std::vector<ConfigKeyBinding> key_bindings;
float mouse_sensitivity = 0.1f;
float stick_sensitivity = 1.33f;
bool stick_invert_x = false;
bool stick_invert_y = false;
ConfigGyro gyro = ConfigGyro::Held;
float gyro_sensitivity = 0.5f;
bool gyro_invert_x = false;
bool gyro_invert_y = false;
bool mouse_invert_y = false;
bool mouse_capture = true;
std::vector<std::string> mouse_release_keys = {"Escape"};
Expand Down
89 changes: 65 additions & 24 deletions port/src/platform/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ constexpr Action kActions[] = {
{"fps_toggle", ActionKind::Host, 0, kAxisLeftX, 0, "F3"},
{"developer_menu", ActionKind::Host, 0, kAxisLeftX, 0, "Gamepad:paddle2"},
{"debug_menu", ActionKind::Host, 0, kAxisLeftX, 0, "Gamepad:paddle1"},
{"gyro_toggle", ActionKind::Host, 0, kAxisLeftX, 0, "Gamepad:paddle4"},
{"gyro_hold", ActionKind::Host, 0, kAxisLeftX, 0, "Gamepad:paddle4"},
};

struct ButtonMap {
Expand Down Expand Up @@ -109,7 +109,7 @@ constexpr std::size_t kFirstHostAction = kActionCount - 4;
constexpr std::size_t kHostActionCount = kActionCount - kFirstHostAction;
static_assert(kActions[kFirstHostAction].name == "fps_toggle");
static_assert(static_cast<std::size_t>(InputHostAction::FpsToggle) == 0);
static_assert(static_cast<std::size_t>(InputHostAction::GyroToggle) == kHostActionCount - 1);
static_assert(static_cast<std::size_t>(InputHostAction::GyroHold) == kHostActionCount - 1);

// AxisCalibration (ps2/src/gamepad.cpp): a byte within 49 above or 50 below the centre reads as
// zero, and the remaining 78 steps each side span the game's +-128.
Expand Down Expand Up @@ -287,8 +287,13 @@ std::uint8_t StickToByte(Sint16 value) {
// input.stick_sensitivity (1.33 by default, PCSX2's analog sensitivity) and the circle is stretched
// onto the square, keeping the direction.
float g_stick_sensitivity = 1.33f;
float g_gyro_sensitivity = 0.5f;
bool g_gyro_on = false;
float g_gyro_sensitivity = 0.5f;
ConfigGyro g_gyro = ConfigGyro::Off;
bool g_gyro_invert_x = false;
bool g_gyro_invert_y = false;
bool g_stick_invert_x = false;
bool g_stick_invert_y = false;
bool g_look_left = false;

void StickPairToBytes(Sint16 x, Sint16 y, std::uint8_t &byte_x, std::uint8_t &byte_y) {
float fx = std::clamp(static_cast<float>(x) / 32767.0f, -1.0f, 1.0f);
Expand Down Expand Up @@ -322,6 +327,23 @@ std::uint8_t &AxisOf(InputPadState &state, Axis axis) {
return state.right_y;
}

Sint16 Flip(Sint16 value) {
return value == SDL_JOYSTICK_AXIS_MIN ? SDL_JOYSTICK_AXIS_MAX : static_cast<Sint16>(-value);
}

bool GyroActive() {
switch (g_gyro) {
case ConfigGyro::Always:
return true;
case ConfigGyro::FirstPerson:
return g_look_left;
case ConfigGyro::Held:
return InputHostHeld(InputHostAction::GyroHold);
default:
return false;
}
}

void ReadGamepad(SDL_Gamepad *gamepad, InputPadState &state) {
for (const ButtonMap &map : kGamepadButtons) {
if (SDL_GetGamepadButton(gamepad, map.button)) {
Expand All @@ -334,24 +356,35 @@ void ReadGamepad(SDL_Gamepad *gamepad, InputPadState &state) {
if (SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) > kTriggerThreshold) {
state.buttons |= kInputR2;
}
StickPairToBytes(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX),
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY), state.left_x, state.left_y);
StickPairToBytes(SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX),
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY), state.right_x, state.right_y);
float rate[3];
if (!g_gyro_on || Deflected(state.right_x) || Deflected(state.right_y) ||
!SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_GYRO, rate, 3)) {
Sint16 axes[4] = {SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX),
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY),
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX),
SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY)};
int look = g_look_left ? 0 : 2;
if (g_stick_invert_x) {
axes[look] = Flip(axes[look]);
}
if (g_stick_invert_y) {
axes[look + 1] = Flip(axes[look + 1]);
}
StickPairToBytes(axes[0], axes[1], state.left_x, state.left_y);
StickPairToBytes(axes[2], axes[3], state.right_x, state.right_y);
std::uint8_t &x = g_look_left ? state.left_x : state.right_x;
std::uint8_t &y = g_look_left ? state.left_y : state.right_y;
float rate[3];
if (!GyroActive() || Deflected(x) || Deflected(y) || !SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_GYRO, rate, 3)) {
return;
}
float yaw = std::fabs(rate[1]) > kGyroDeadband ? -rate[1] * g_gyro_sensitivity : 0.0f;
float pitch = std::fabs(rate[0]) > kGyroDeadband ? rate[0] * g_gyro_sensitivity : 0.0f;
state.right_x = InputStickByte(yaw);
state.right_y = InputStickByte(pitch);
float pitch = std::fabs(rate[0]) > kGyroDeadband ? -rate[0] * g_gyro_sensitivity : 0.0f;
x = InputStickByte(g_gyro_invert_x ? -yaw : yaw);
pitch = g_gyro_invert_y ? -pitch : pitch;
y = InputStickByte(g_look_left ? -pitch : pitch);
}

void EnableGyro(SDL_Gamepad *gamepad) {
if (SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO)) {
SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, g_gyro_on);
SDL_SetGamepadSensorEnabled(gamepad, SDL_SENSOR_GYRO, g_gyro != ConfigGyro::Off);
}
}

Expand Down Expand Up @@ -491,6 +524,18 @@ void InputApplyConfig(const Config &config) {
}
g_stick_sensitivity = config.stick_sensitivity;
g_gyro_sensitivity = config.gyro_sensitivity;
g_gyro_invert_x = config.gyro_invert_x;
g_gyro_invert_y = config.gyro_invert_y;
g_stick_invert_x = config.stick_invert_x;
g_stick_invert_y = config.stick_invert_y;
if (config.gyro != g_gyro) {
g_gyro = config.gyro;
for (PadSlot &slot : g_slots) {
if (slot.gamepad != nullptr) {
EnableGyro(slot.gamepad);
}
}
}
InputMouseSettings mouse;
mouse.sensitivity = config.mouse_sensitivity;
mouse.invert_y = config.mouse_invert_y;
Expand All @@ -515,7 +560,7 @@ void InputShutdown() {
g_scripted = {};
g_host_presses.fill(0);
g_host_polled.fill(false);
g_gyro_on = false;
g_gyro = ConfigGyro::Off;
for (PadSlot &slot : g_slots) {
if (slot.gamepad != nullptr) {
SDL_CloseGamepad(slot.gamepad);
Expand All @@ -533,14 +578,6 @@ void InputPoll() {
if (g_gamepad_subsystem) {
SyncGamepads();
}
if (InputHostPressed(InputHostAction::GyroToggle)) {
g_gyro_on = !g_gyro_on;
for (PadSlot &slot : g_slots) {
if (slot.gamepad != nullptr) {
EnableGyro(slot.gamepad);
}
}
}
for (int pad = 0; pad < kInputPadCount; ++pad) {
InputPadState state;
state.connected = pad == 0 || g_slots[pad].gamepad != nullptr;
Expand Down Expand Up @@ -823,6 +860,10 @@ bool InputHostHeld(InputHostAction action) {
return HostPolledHeld(host);
}

void InputSetLookOnLeftStick(bool left) {
g_look_left = left;
}

bool InputHostPressed(InputHostAction action) {
int &presses = g_host_presses[static_cast<std::size_t>(action)];
if (presses == 0) {
Expand Down
4 changes: 3 additions & 1 deletion port/src/platform/input.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ bool InputLeftStickLive();
// The pad as the game reads it: the device state or the override, stick_dpad applied.
const InputPadState &InputGetPad(int pad);

void InputSetLookOnLeftStick(bool left);

void InputSetRumble(int pad, InputRumble rumble);

InputRumble InputGetRumble(int pad);
Expand Down Expand Up @@ -155,7 +157,7 @@ enum class InputHostAction {
FpsToggle,
DeveloperMenu,
DebugMenu,
GyroToggle,
GyroHold,
};

// Whether a key, mouse button or gamepad button bound to the action is held, live or scripted.
Expand Down
6 changes: 6 additions & 0 deletions port/src/tests/platform_config_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ TEST(PlatformConfig, ParsesMouseSettingsAndBindings) {
Config defaults = ConfigParse("");
ASSERT_TRUE(defaults.mouse_sensitivity == 0.1f && !defaults.mouse_invert_y && defaults.mouse_capture);
ASSERT_TRUE(defaults.stick_sensitivity == 1.33f && defaults.gyro_sensitivity == 0.5f);
ASSERT_TRUE(defaults.gyro == ConfigGyro::Held && !defaults.gyro_invert_x && !defaults.stick_invert_y);
ASSERT_TRUE(ConfigParse(R"({"input": {"gyro": "first_person"}})").gyro == ConfigGyro::FirstPerson);
ASSERT_TRUE(ConfigParse(R"({"input": {"gyro": true}})").gyro == ConfigGyro::Held);
Config inverted =
ConfigParse(ConfigSerialize(ConfigParse(R"({"input": {"gyro": "always", "gyro_invert_y": true, "stick_invert_x": true}})")));
ASSERT_TRUE(inverted.gyro == ConfigGyro::Always && inverted.gyro_invert_y && inverted.stick_invert_x);
ASSERT_TRUE(ConfigParse(R"({"input": {"gyro_sensitivity": 1.25}})").gyro_sensitivity == 1.25f);
ASSERT_TRUE(ConfigParse(R"({"input": {"gyro_sensitivity": -1}})").gyro_sensitivity == 0.5f);
ASSERT_TRUE(ConfigParse(ConfigSerialize(ConfigParse(R"({"input": {"gyro_sensitivity": 1.25}})"))).gyro_sensitivity ==
Expand Down
2 changes: 1 addition & 1 deletion port/src/tests/platform_pad_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ TEST(PlatformPad, BackButtonActions) {
std::string_view paddles[][1] = {{"Gamepad:paddle2"}, {"Gamepad:paddle1"}, {"Gamepad:paddle4"}};
ASSERT_TRUE(InputBindKeys("developer_menu", paddles[0]));
ASSERT_TRUE(InputBindKeys("debug_menu", paddles[1]));
ASSERT_TRUE(InputBindKeys("gyro_toggle", paddles[2]));
ASSERT_TRUE(InputBindKeys("gyro_hold", paddles[2]));
std::string_view f9[] = {"F9"};
std::string_view f10[] = {"F10"};
ASSERT_TRUE(InputBindKeys("debug_menu", f9));
Expand Down
Loading