input_common/tas: Add swap controller
This commit is contained in:
parent
9bb6580d89
commit
e6c4bf52f0
|
@ -23,7 +23,7 @@ enum class YuzuPath {
|
||||||
ScreenshotsDir, // Where yuzu screenshots are stored.
|
ScreenshotsDir, // Where yuzu screenshots are stored.
|
||||||
SDMCDir, // Where the emulated SDMC is stored.
|
SDMCDir, // Where the emulated SDMC is stored.
|
||||||
ShaderDir, // Where shaders are stored.
|
ShaderDir, // Where shaders are stored.
|
||||||
TASDir, // Where the current script file is stored.
|
TASDir, // Where TAS scripts are stored.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -155,28 +155,28 @@ public:
|
||||||
/// Retrieves the underlying udp touch handler.
|
/// Retrieves the underlying udp touch handler.
|
||||||
[[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
|
[[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
|
||||||
|
|
||||||
/// Retrieves the underlying GameCube button handler.
|
/// Retrieves the underlying mouse button handler.
|
||||||
[[nodiscard]] MouseButtonFactory* GetMouseButtons();
|
[[nodiscard]] MouseButtonFactory* GetMouseButtons();
|
||||||
|
|
||||||
/// Retrieves the underlying GameCube button handler.
|
/// Retrieves the underlying mouse button handler.
|
||||||
[[nodiscard]] const MouseButtonFactory* GetMouseButtons() const;
|
[[nodiscard]] const MouseButtonFactory* GetMouseButtons() const;
|
||||||
|
|
||||||
/// Retrieves the underlying udp touch handler.
|
/// Retrieves the underlying mouse analog handler.
|
||||||
[[nodiscard]] MouseAnalogFactory* GetMouseAnalogs();
|
[[nodiscard]] MouseAnalogFactory* GetMouseAnalogs();
|
||||||
|
|
||||||
/// Retrieves the underlying udp touch handler.
|
/// Retrieves the underlying mouse analog handler.
|
||||||
[[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const;
|
[[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const;
|
||||||
|
|
||||||
/// Retrieves the underlying udp motion handler.
|
/// Retrieves the underlying mouse motion handler.
|
||||||
[[nodiscard]] MouseMotionFactory* GetMouseMotions();
|
[[nodiscard]] MouseMotionFactory* GetMouseMotions();
|
||||||
|
|
||||||
/// Retrieves the underlying udp motion handler.
|
/// Retrieves the underlying mouse motion handler.
|
||||||
[[nodiscard]] const MouseMotionFactory* GetMouseMotions() const;
|
[[nodiscard]] const MouseMotionFactory* GetMouseMotions() const;
|
||||||
|
|
||||||
/// Retrieves the underlying udp touch handler.
|
/// Retrieves the underlying mouse touch handler.
|
||||||
[[nodiscard]] MouseTouchFactory* GetMouseTouch();
|
[[nodiscard]] MouseTouchFactory* GetMouseTouch();
|
||||||
|
|
||||||
/// Retrieves the underlying udp touch handler.
|
/// Retrieves the underlying mouse touch handler.
|
||||||
[[nodiscard]] const MouseTouchFactory* GetMouseTouch() const;
|
[[nodiscard]] const MouseTouchFactory* GetMouseTouch() const;
|
||||||
|
|
||||||
/// Retrieves the underlying tas button handler.
|
/// Retrieves the underlying tas button handler.
|
||||||
|
@ -185,10 +185,10 @@ public:
|
||||||
/// Retrieves the underlying tas button handler.
|
/// Retrieves the underlying tas button handler.
|
||||||
[[nodiscard]] const TasButtonFactory* GetTasButtons() const;
|
[[nodiscard]] const TasButtonFactory* GetTasButtons() const;
|
||||||
|
|
||||||
/// Retrieves the underlying tas touch handler.
|
/// Retrieves the underlying tas analogs handler.
|
||||||
[[nodiscard]] TasAnalogFactory* GetTasAnalogs();
|
[[nodiscard]] TasAnalogFactory* GetTasAnalogs();
|
||||||
|
|
||||||
/// Retrieves the underlying tas touch handler.
|
/// Retrieves the underlying tas analogs handler.
|
||||||
[[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const;
|
[[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const;
|
||||||
|
|
||||||
/// Reloads the input devices
|
/// Reloads the input devices
|
||||||
|
|
|
@ -61,8 +61,8 @@ void Tas::LoadTasFile(size_t player_index) {
|
||||||
commands[player_index].clear();
|
commands[player_index].clear();
|
||||||
}
|
}
|
||||||
std::string file =
|
std::string file =
|
||||||
Common::FS::ReadStringFromFile(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir) +
|
Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
|
||||||
"script0-" + std::to_string(player_index + 1) + ".txt",
|
fmt::format("script0-{}.txt", player_index + 1),
|
||||||
Common::FS::FileType::BinaryFile);
|
Common::FS::FileType::BinaryFile);
|
||||||
std::stringstream command_line(file);
|
std::stringstream command_line(file);
|
||||||
std::string line;
|
std::string line;
|
||||||
|
@ -102,7 +102,7 @@ void Tas::LoadTasFile(size_t player_index) {
|
||||||
LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
|
LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tas::WriteTasFile(std::string file_name) {
|
void Tas::WriteTasFile(std::u8string file_name) {
|
||||||
std::string output_text;
|
std::string output_text;
|
||||||
for (size_t frame = 0; frame < record_commands.size(); frame++) {
|
for (size_t frame = 0; frame < record_commands.size(); frame++) {
|
||||||
if (!output_text.empty()) {
|
if (!output_text.empty()) {
|
||||||
|
@ -112,8 +112,8 @@ void Tas::WriteTasFile(std::string file_name) {
|
||||||
output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
|
output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
|
||||||
WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
|
WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
|
||||||
}
|
}
|
||||||
const size_t bytes_written = Common::FS::WriteStringToFile(
|
const auto bytes_written = Common::FS::WriteStringToFile(
|
||||||
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir) + file_name,
|
Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
|
||||||
Common::FS::FileType::TextFile, output_text);
|
Common::FS::FileType::TextFile, output_text);
|
||||||
if (bytes_written == output_text.size()) {
|
if (bytes_written == output_text.size()) {
|
||||||
LOG_INFO(Input, "TAS file written to file!");
|
LOG_INFO(Input, "TAS file written to file!");
|
||||||
|
@ -217,6 +217,9 @@ void Tas::UpdateThread() {
|
||||||
is_running = Settings::values.tas_loop;
|
is_running = Settings::values.tas_loop;
|
||||||
current_command = 0;
|
current_command = 0;
|
||||||
tas_data.fill({});
|
tas_data.fill({});
|
||||||
|
if (!is_running) {
|
||||||
|
SwapToStoredController();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tas_data.fill({});
|
tas_data.fill({});
|
||||||
|
@ -290,6 +293,52 @@ std::string Tas::WriteCommandButtons(u32 data) const {
|
||||||
|
|
||||||
void Tas::StartStop() {
|
void Tas::StartStop() {
|
||||||
is_running = !is_running;
|
is_running = !is_running;
|
||||||
|
if (is_running) {
|
||||||
|
SwapToTasController();
|
||||||
|
} else {
|
||||||
|
SwapToStoredController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tas::SwapToTasController() {
|
||||||
|
if (!Settings::values.tas_swap_controllers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& players = Settings::values.players.GetValue();
|
||||||
|
for (std::size_t index = 0; index < players.size(); index++) {
|
||||||
|
auto& player = players[index];
|
||||||
|
player_mappings[index] = player;
|
||||||
|
|
||||||
|
// Only swap active controllers
|
||||||
|
if (!player.connected) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tas_param = Common::ParamPackage{{"pad", static_cast<u8>(index)}};
|
||||||
|
auto button_mapping = GetButtonMappingForDevice(tas_param);
|
||||||
|
auto analog_mapping = GetAnalogMappingForDevice(tas_param);
|
||||||
|
auto& buttons = player.buttons;
|
||||||
|
auto& analogs = player.analogs;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < buttons.size(); ++i) {
|
||||||
|
buttons[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)].Serialize();
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < analogs.size(); ++i) {
|
||||||
|
analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Settings::values.is_device_reload_pending.store(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tas::SwapToStoredController() {
|
||||||
|
if (!Settings::values.tas_swap_controllers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& players = Settings::values.players.GetValue();
|
||||||
|
for (std::size_t index = 0; index < players.size(); index++) {
|
||||||
|
players[index] = player_mappings[index];
|
||||||
|
}
|
||||||
|
Settings::values.is_device_reload_pending.store(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tas::Reset() {
|
void Tas::Reset() {
|
||||||
|
@ -308,9 +357,9 @@ void Tas::SaveRecording(bool overwrite_file) {
|
||||||
if (record_commands.empty()) {
|
if (record_commands.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WriteTasFile("record.txt");
|
WriteTasFile(u8"record.txt");
|
||||||
if (overwrite_file) {
|
if (overwrite_file) {
|
||||||
WriteTasFile("script0-1.txt");
|
WriteTasFile(u8"script0-1.txt");
|
||||||
}
|
}
|
||||||
needs_reset = true;
|
needs_reset = true;
|
||||||
record_commands.clear();
|
record_commands.clear();
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "common/settings_input.h"
|
||||||
#include "core/frontend/input.h"
|
#include "core/frontend/input.h"
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ private:
|
||||||
};
|
};
|
||||||
void LoadTasFiles();
|
void LoadTasFiles();
|
||||||
void LoadTasFile(size_t player_index);
|
void LoadTasFile(size_t player_index);
|
||||||
void WriteTasFile(std::string file_name);
|
void WriteTasFile(std::u8string file_name);
|
||||||
TasAnalog ReadCommandAxis(const std::string& line) const;
|
TasAnalog ReadCommandAxis(const std::string& line) const;
|
||||||
u32 ReadCommandButtons(const std::string& line) const;
|
u32 ReadCommandButtons(const std::string& line) const;
|
||||||
std::string WriteCommandButtons(u32 data) const;
|
std::string WriteCommandButtons(u32 data) const;
|
||||||
|
@ -105,6 +106,9 @@ private:
|
||||||
std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const;
|
std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const;
|
||||||
std::string ButtonsToString(u32 button) const;
|
std::string ButtonsToString(u32 button) const;
|
||||||
|
|
||||||
|
void SwapToTasController();
|
||||||
|
void SwapToStoredController();
|
||||||
|
|
||||||
size_t script_length{0};
|
size_t script_length{0};
|
||||||
std::array<TasData, PLAYER_NUMBER> tas_data;
|
std::array<TasData, PLAYER_NUMBER> tas_data;
|
||||||
bool is_recording{false};
|
bool is_recording{false};
|
||||||
|
@ -114,5 +118,8 @@ private:
|
||||||
std::vector<TASCommand> record_commands{};
|
std::vector<TASCommand> record_commands{};
|
||||||
size_t current_command{0};
|
size_t current_command{0};
|
||||||
TASCommand last_input{}; // only used for recording
|
TASCommand last_input{}; // only used for recording
|
||||||
|
|
||||||
|
// Old settings for swapping controllers
|
||||||
|
std::array<Settings::PlayerInput, 10> player_mappings;
|
||||||
};
|
};
|
||||||
} // namespace TasInput
|
} // namespace TasInput
|
||||||
|
|
|
@ -1205,6 +1205,11 @@ void Config::SaveControlValues() {
|
||||||
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
|
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
|
||||||
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
|
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
|
||||||
|
|
||||||
|
WriteSetting(QStringLiteral("enable_tas"), Settings::values.tas_enable, false);
|
||||||
|
WriteSetting(QStringLiteral("loop_tas"), Settings::values.tas_loop, false);
|
||||||
|
WriteSetting(QStringLiteral("swap_tas_controllers"), Settings::values.tas_swap_controllers,
|
||||||
|
true);
|
||||||
|
WriteSetting(QStringLiteral("tas_pause_on_load"), Settings::values.pause_tas_on_load, true);
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,10 +175,7 @@ void PlayerControlPreview::ResetInputs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerControlPreview::UpdateInput() {
|
void PlayerControlPreview::UpdateInput() {
|
||||||
if (controller_callback.update != nullptr) {
|
if (!is_enabled && !mapping_active && !Settings::values.tas_enable) {
|
||||||
controller_callback.update(std::move(true));
|
|
||||||
}
|
|
||||||
if (!is_enabled && !mapping_active) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool input_changed = false;
|
bool input_changed = false;
|
||||||
|
@ -223,20 +220,25 @@ void PlayerControlPreview::UpdateInput() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ControllerInput input{};
|
|
||||||
if (input_changed) {
|
if (input_changed) {
|
||||||
update();
|
update();
|
||||||
input.changed = true;
|
ControllerInput input{
|
||||||
|
.axis_values =
|
||||||
|
{std::pair<float, float>{axis_values[Settings::NativeAnalog::LStick].value.x(),
|
||||||
|
axis_values[Settings::NativeAnalog::LStick].value.y()},
|
||||||
|
std::pair<float, float>{axis_values[Settings::NativeAnalog::RStick].value.x(),
|
||||||
|
axis_values[Settings::NativeAnalog::RStick].value.y()}},
|
||||||
|
.button_values = button_values,
|
||||||
|
.changed = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (controller_callback.input != nullptr) {
|
||||||
|
controller_callback.input(std::move(input));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
input.axis_values[Settings::NativeAnalog::LStick] = {
|
|
||||||
axis_values[Settings::NativeAnalog::LStick].value.x(),
|
if (controller_callback.update != nullptr) {
|
||||||
axis_values[Settings::NativeAnalog::LStick].value.y()};
|
controller_callback.update(std::move(true));
|
||||||
input.axis_values[Settings::NativeAnalog::RStick] = {
|
|
||||||
axis_values[Settings::NativeAnalog::RStick].value.x(),
|
|
||||||
axis_values[Settings::NativeAnalog::RStick].value.y()};
|
|
||||||
input.button_values = button_values;
|
|
||||||
if (controller_callback.input != nullptr) {
|
|
||||||
controller_callback.input(std::move(input));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mapping_active) {
|
if (mapping_active) {
|
||||||
|
|
|
@ -31,9 +31,6 @@
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0" colspan="4">
|
<item row="1" column="0" colspan="4">
|
||||||
<widget class="QCheckBox" name="tas_control_swap">
|
<widget class="QCheckBox" name="tas_control_swap">
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Automatic controller profile swapping</string>
|
<string>Automatic controller profile swapping</string>
|
||||||
</property>
|
</property>
|
||||||
|
|
|
@ -2921,7 +2921,7 @@ QString GMainWindow::GetTasStateDescription() const {
|
||||||
case TasInput::TasState::Stopped:
|
case TasInput::TasState::Stopped:
|
||||||
return tr("TAS state: Idle %1/%2").arg(current_tas_frame).arg(total_tas_frames);
|
return tr("TAS state: Idle %1/%2").arg(current_tas_frame).arg(total_tas_frames);
|
||||||
default:
|
default:
|
||||||
return tr("INVALID TAS STATE");
|
return tr("TAS State: Invalid");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in New Issue