From b42c3ce21db249d5e3bc04b4f73202e757da317c Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Fri, 18 Jun 2021 16:15:42 +0200 Subject: input_common/tas: Base playback & recording system The base playback system supports up to 8 controllers (specified by `PLAYER_NUMBER` in `tas_input.h`), which all change their inputs simulataneously when `TAS::UpdateThread` is called. The recording system uses the controller debugger to read the state of the first controller and forwards that data to the TASing system for recording. Currently, this process sadly is not frame-perfect and pixel-accurate. Co-authored-by: Naii-the-Baf Co-authored-by: Narr-the-Reg --- src/common/settings.h | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/common') diff --git a/src/common/settings.h b/src/common/settings.h index b1bddb895..29b888f10 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -14,6 +14,7 @@ #include #include +#include #include "common/common_types.h" #include "common/settings_input.h" @@ -499,6 +500,7 @@ struct Values { // Controls InputSetting> players; + std::shared_ptr inputSubsystem = NULL; Setting use_docked_mode{true, "use_docked_mode"}; @@ -512,9 +514,14 @@ struct Values { "motion_device"}; BasicSetting udp_input_servers{"127.0.0.1:26760", "udp_input_servers"}; + BasicSetting tas_enable{false, "tas_enable"}; + BasicSetting tas_reset{ false, "tas_reset" }; + BasicSetting tas_record{ false, "tas_record" }; + BasicSetting mouse_panning{false, "mouse_panning"}; BasicRangedSetting mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; BasicSetting mouse_enabled{false, "mouse_enabled"}; + std::string mouse_device; MouseButtonsRaw mouse_buttons; -- cgit v1.2.3 From f25d6ebc45dc02904ef3ca6ad8efea9a0853d194 Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Fri, 18 Jun 2021 16:20:10 +0200 Subject: settings: File selector & other settings First of all, TASing requires a script to play back. The user can select the parent directory at `System -> Filesystem`, next to an option to pause TAS during loads: This requires a "hacky" setup deeper in the code and will be added in the last commit. Also, Hotkeys are being introduced: CTRL+F5 for playback start/stop, CTRL+F6 for re-reading the script and CTRL+F7 for recording a new script. --- src/common/fs/path_util.cpp | 2 + src/common/fs/path_util.h | 2 + src/common/settings.h | 3 ++ src/yuzu/configuration/config.cpp | 24 +++++++++++- src/yuzu/configuration/config.h | 2 +- src/yuzu/configuration/configure_filesystem.cpp | 10 +++++ src/yuzu/configuration/configure_filesystem.h | 1 + src/yuzu/configuration/configure_filesystem.ui | 49 +++++++++++++++++++++++++ src/yuzu/main.cpp | 13 +++++++ 9 files changed, 104 insertions(+), 2 deletions(-) (limited to 'src/common') diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 6cdd14f13..5f76adedb 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -116,6 +116,8 @@ private: GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); + + GenerateYuzuPath(YuzuPath::TASFile, fs::path{""}); } ~PathManagerImpl() = default; diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index f956ac9a2..6079de4c6 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -23,6 +23,8 @@ enum class YuzuPath { ScreenshotsDir, // Where yuzu screenshots are stored. SDMCDir, // Where the emulated SDMC is stored. ShaderDir, // Where shaders are stored. + + TASFile, // Where the current script file is stored. }; /** diff --git a/src/common/settings.h b/src/common/settings.h index 29b888f10..884ea55f8 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -550,6 +550,9 @@ struct Values { BasicSetting gamecard_current_game{false, "gamecard_current_game"}; BasicSetting gamecard_path{std::string(), "gamecard_path"}; + // TAS + bool pauseTasOnLoad; + // Debugging bool record_frame_times; BasicSetting use_gdbstub{false, "use_gdbstub"}; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 952e96769..99e318a8f 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -221,7 +221,7 @@ const std::array Config::default // This must be in alphabetical order according to action name as it must have the same order as // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off -const std::array Config::default_hotkeys{{ +const std::array Config::default_hotkeys{{ {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}}, {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, @@ -235,6 +235,9 @@ const std::array Config::default_hotkeys{{ {QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, + {QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), Qt::ApplicationShortcut}}, + {QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), Qt::ApplicationShortcut}}, + {QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, {QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}}, @@ -564,6 +567,9 @@ void Config::ReadControlValues() { Settings::values.mouse_panning = false; ReadBasicSetting(Settings::values.mouse_panning_sensitivity); + ReadBasicSetting(Settings::values.tas_enable = false); + ReadBasicSetting(Settings::values.tas_reset = false); + ReadGlobalSetting(Settings::values.use_docked_mode); // Disable docked mode if handheld is selected @@ -661,9 +667,20 @@ void Config::ReadDataStorageValues() { QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))) .toString() .toStdString()); + FS::SetYuzuPath( + FS::YuzuPath::TASFile, + qt_config + ->value(QStringLiteral("tas_path"), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASFile))) + .toString() + .toStdString()); + + ReadBasicSetting(Settings::values.pauseTasOnLoad); + ReadBasicSetting(Settings::values.gamecard_inserted); ReadBasicSetting(Settings::values.gamecard_current_game); ReadBasicSetting(Settings::values.gamecard_path); + qt_config->endGroup(); } @@ -1215,6 +1232,11 @@ void Config::SaveDataStorageValues() { WriteSetting(QStringLiteral("dump_directory"), QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)), QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); + WriteSetting(QStringLiteral("tas_path"), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASFile)), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASFile))); + WriteSetting(QStringLiteral("tas_pause_on_load"), Settings::values.pauseTasOnLoad, true); + WriteBasicSetting(Settings::values.gamecard_inserted); WriteBasicSetting(Settings::values.gamecard_current_game); WriteBasicSetting(Settings::values.gamecard_path); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 4733227b6..3ee694e7c 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -42,7 +42,7 @@ public: default_mouse_buttons; static const std::array default_keyboard_keys; static const std::array default_keyboard_mods; - static const std::array default_hotkeys; + static const std::array default_hotkeys; private: void Initialize(const std::string& config_name); diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index 9cb317822..cd9ba0a90 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -26,6 +26,8 @@ ConfigureFilesystem::ConfigureFilesystem(QWidget* parent) [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); }); connect(ui->load_path_button, &QToolButton::pressed, this, [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); }); + connect(ui->tas_path_button, &QToolButton::pressed, this, + [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); }); connect(ui->reset_game_list_cache, &QPushButton::pressed, this, &ConfigureFilesystem::ResetMetadata); @@ -49,9 +51,12 @@ void ConfigureFilesystem::setConfiguration() { QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::DumpDir))); ui->load_path_edit->setText( QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LoadDir))); + ui->tas_path_edit->setText( + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile))); ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted.GetValue()); ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game.GetValue()); + ui->tas_pause_on_load->setChecked(Settings::values.pauseTasOnLoad); ui->dump_exefs->setChecked(Settings::values.dump_exefs.GetValue()); ui->dump_nso->setChecked(Settings::values.dump_nso.GetValue()); @@ -69,9 +74,11 @@ void ConfigureFilesystem::applyConfiguration() { ui->dump_path_edit->text().toStdString()); Common::FS::SetYuzuPath(Common::FS::YuzuPath::LoadDir, ui->load_path_edit->text().toStdString()); + Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASFile, ui->tas_path_edit->text().toStdString()); Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked(); + Settings::values.pauseTasOnLoad = ui->tas_pause_on_load->isChecked(); Settings::values.dump_exefs = ui->dump_exefs->isChecked(); Settings::values.dump_nso = ui->dump_nso->isChecked(); @@ -97,6 +104,9 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) case DirectoryTarget::Load: caption = tr("Select Mod Load Directory..."); break; + case DirectoryTarget::TAS: + caption = tr("Select TAS Directory..."); + break; } QString str; diff --git a/src/yuzu/configuration/configure_filesystem.h b/src/yuzu/configuration/configure_filesystem.h index 2147cd405..86dab8684 100644 --- a/src/yuzu/configuration/configure_filesystem.h +++ b/src/yuzu/configuration/configure_filesystem.h @@ -32,6 +32,7 @@ private: Gamecard, Dump, Load, + TAS, }; void SetDirectory(DirectoryTarget target, QLineEdit* edit); diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui index 62b9abc7a..8ac7250fd 100644 --- a/src/yuzu/configuration/configure_filesystem.ui +++ b/src/yuzu/configuration/configure_filesystem.ui @@ -219,6 +219,55 @@ + + + + TAS Directories + + + + + + Path + + + + + + + ... + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 60 + 20 + + + + + + + + Pause TAS execution during loads (SMO - 1.3) + + + + + + diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 22489231b..f3529d151 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1015,6 +1015,19 @@ void GMainWindow::InitializeHotkeys() { render_window->setAttribute(Qt::WA_Hover, true); } }); + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this), + &QShortcut::activated, this, [&] { + Settings::values.tas_enable = !Settings::values.tas_enable; + LOG_INFO(Frontend, "Tas enabled {}", Settings::values.tas_enable); + }); + + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), + &QShortcut::activated, this, [&] { Settings::values.tas_reset = true; }); + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), + &QShortcut::activated, this, [&] { + Settings::values.tas_record = !Settings::values.tas_record; + LOG_INFO(Frontend, "Tas recording {}", Settings::values.tas_record); + }); } void GMainWindow::SetDefaultUIGeometry() { -- cgit v1.2.3 From 4297d2fea2228ff4afe2a7c244fb8b3f1a97491a Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Fri, 18 Jun 2021 16:32:46 +0200 Subject: core: Hacky TAS syncing & load pausing To keep the TAS inputs synced to the game speed even through lag spikes and loading zones, deeper access is required. First, the `TAS::UpdateThread` has to be executed exactly once per frame. This is done by connecting it to the service method the game calls to pass parameters to the GPU: `Service::VI::QueueBuffer`. Second, the loading time of new subareas and/or kingdoms (SMO) can vary. To counteract that, the `CPU_BOOST_MODE` can be detected: In the `APM`-interface, the call to enabling/disabling the boost mode can be caught and forwarded to the TASing system, which can pause the script execution if neccessary and enabled in the settings. --- src/common/fs/fs_paths.h | 1 + src/common/fs/path_util.cpp | 3 +- src/common/settings.h | 9 +- src/core/hle/service/apm/apm_interface.cpp | 2 + src/core/hle/service/vi/vi.cpp | 3 + src/input_common/tas/tas_input.cpp | 142 +++++++++++++++--------- src/input_common/tas/tas_input.h | 58 ++++------ src/yuzu/configuration/configure_filesystem.cpp | 2 +- src/yuzu/main.cpp | 27 ++++- 9 files changed, 140 insertions(+), 107 deletions(-) (limited to 'src/common') diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index b32614797..84968b8e0 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h @@ -21,6 +21,7 @@ #define SCREENSHOTS_DIR "screenshots" #define SDMC_DIR "sdmc" #define SHADER_DIR "shader" +#define TAS_DIR "scripts" // yuzu-specific files diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 5f76adedb..97d026eb8 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -116,8 +116,7 @@ private: GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); - - GenerateYuzuPath(YuzuPath::TASFile, fs::path{""}); + GenerateYuzuPath(YuzuPath::TASFile, yuzu_path / TAS_DIR); } ~PathManagerImpl() = default; diff --git a/src/common/settings.h b/src/common/settings.h index 884ea55f8..7333a64dc 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -500,7 +500,6 @@ struct Values { // Controls InputSetting> players; - std::shared_ptr inputSubsystem = NULL; Setting use_docked_mode{true, "use_docked_mode"}; @@ -514,9 +513,12 @@ struct Values { "motion_device"}; BasicSetting udp_input_servers{"127.0.0.1:26760", "udp_input_servers"}; - BasicSetting tas_enable{false, "tas_enable"}; + BasicSetting pause_tas_on_load { false, "pause_tas_on_load" }; + BasicSetting tas_enable{ false, "tas_enable" }; BasicSetting tas_reset{ false, "tas_reset" }; BasicSetting tas_record{ false, "tas_record" }; + BasicSetting is_cpu_boxted{ false, " BasicSetting is_cpu_boxted{ false, "cpuBoosted" }; +" }; BasicSetting mouse_panning{false, "mouse_panning"}; BasicRangedSetting mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; @@ -550,9 +552,6 @@ struct Values { BasicSetting gamecard_current_game{false, "gamecard_current_game"}; BasicSetting gamecard_path{std::string(), "gamecard_path"}; - // TAS - bool pauseTasOnLoad; - // Debugging bool record_frame_times; BasicSetting use_gdbstub{false, "use_gdbstub"}; diff --git a/src/core/hle/service/apm/apm_interface.cpp b/src/core/hle/service/apm/apm_interface.cpp index e58bad083..724483107 100644 --- a/src/core/hle/service/apm/apm_interface.cpp +++ b/src/core/hle/service/apm/apm_interface.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include "common/logging/log.h" +#include "common/settings.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/apm/apm_controller.h" @@ -120,6 +121,7 @@ void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_APM, "called, mode={:08X}", mode); + Settings::values.is_cpu_boosted = (static_cast(mode) == 1); controller.SetFromCpuBoostMode(mode); IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 8e8fc40ca..f4eac0bca 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -32,6 +32,8 @@ #include "core/hle/service/vi/vi_s.h" #include "core/hle/service/vi/vi_u.h" +#include "input_common/tas/tas_input.h" + namespace Service::VI { constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1}; @@ -595,6 +597,7 @@ private: IGBPQueueBufferResponseParcel response{1280, 720}; ctx.WriteBuffer(response.Serialize()); + Settings::values.input_subsystem->GetTas()->UpdateThread(); break; } case TransactionId::Query: { diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp index 343641945..7320a7004 100644 --- a/src/input_common/tas/tas_input.cpp +++ b/src/input_common/tas/tas_input.cpp @@ -19,6 +19,29 @@ namespace TasInput { +constexpr std::array, 20> text_to_tas_button = { + std::pair{"KEY_A", TasButton::BUTTON_A}, + {"KEY_B", TasButton::BUTTON_B}, + {"KEY_X", TasButton::BUTTON_X}, + {"KEY_Y", TasButton::BUTTON_Y}, + {"KEY_LSTICK", TasButton::STICK_L}, + {"KEY_RSTICK", TasButton::STICK_R}, + {"KEY_L", TasButton::TRIGGER_L}, + {"KEY_R", TasButton::TRIGGER_R}, + {"KEY_PLUS", TasButton::BUTTON_PLUS}, + {"KEY_MINUS", TasButton::BUTTON_MINUS}, + {"KEY_DLEFT", TasButton::BUTTON_LEFT}, + {"KEY_DUP", TasButton::BUTTON_UP}, + {"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, + {"KEY_DDOWN", TasButton::BUTTON_DOWN}, + {"KEY_SL", TasButton::BUTTON_SL}, + {"KEY_SR", TasButton::BUTTON_SR}, + {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, + {"KEY_HOME", TasButton::BUTTON_HOME}, + {"KEY_ZL", TasButton::TRIGGER_ZL}, + {"KEY_ZR", TasButton::TRIGGER_ZR}, +}; + Tas::Tas() { LoadTasFiles(); } @@ -31,29 +54,31 @@ void Tas::RefreshTasFile() { refresh_tas_fle = true; } void Tas::LoadTasFiles() { - scriptLength = 0; - for (int i = 0; i < PLAYER_NUMBER; i++) { + script_length = 0; + for (size_t i = 0; i < PLAYER_NUMBER; i++) { LoadTasFile(i); - if (newCommands[i].size() > scriptLength) - scriptLength = newCommands[i].size(); + if (commands[i].size() > script_length) { + script_length = commands[i].size(); + } } } -void Tas::LoadTasFile(int playerIndex) { +void Tas::LoadTasFile(size_t player_index) { LOG_DEBUG(Input, "LoadTasFile()"); - if (!newCommands[playerIndex].empty()) { - newCommands[playerIndex].clear(); + if (!commands[player_index].empty()) { + commands[player_index].clear(); } std::string file = Common::FS::ReadStringFromFile( Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "script0-" + - std::to_string(playerIndex + 1) + ".txt", + std::to_string(player_index + 1) + ".txt", Common::FS::FileType::BinaryFile); std::stringstream command_line(file); std::string line; int frameNo = 0; TASCommand empty = {.buttons = 0, .l_axis = {0.f, 0.f}, .r_axis = {0.f, 0.f}}; while (std::getline(command_line, line, '\n')) { - if (line.empty()) + if (line.empty()) { continue; + } LOG_DEBUG(Input, "Loading line: {}", line); std::smatch m; @@ -65,11 +90,12 @@ void Tas::LoadTasFile(int playerIndex) { seglist.push_back(segment); } - if (seglist.size() < 4) + if (seglist.size() < 4) { continue; + } while (frameNo < std::stoi(seglist.at(0))) { - newCommands[playerIndex].push_back(empty); + commands[player_index].push_back(empty); frameNo++; } @@ -78,7 +104,7 @@ void Tas::LoadTasFile(int playerIndex) { .l_axis = ReadCommandAxis(seglist.at(2)), .r_axis = ReadCommandAxis(seglist.at(3)), }; - newCommands[playerIndex].push_back(command); + commands[player_index].push_back(command); frameNo++; } LOG_INFO(Input, "TAS file loaded! {} frames", frameNo); @@ -87,84 +113,89 @@ void Tas::LoadTasFile(int playerIndex) { void Tas::WriteTasFile() { LOG_DEBUG(Input, "WriteTasFile()"); std::string output_text = ""; - for (int frame = 0; frame < (signed)recordCommands.size(); frame++) { - if (!output_text.empty()) + for (int frame = 0; frame < (signed)record_commands.size(); frame++) { + if (!output_text.empty()) { output_text += "\n"; - TASCommand line = recordCommands.at(frame); + } + TASCommand line = record_commands.at(frame); output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " + WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis); } size_t bytesWritten = Common::FS::WriteStringToFile( Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "record.txt", Common::FS::FileType::TextFile, output_text); - if (bytesWritten == output_text.size()) + if (bytesWritten == output_text.size()) { LOG_INFO(Input, "TAS file written to file!"); - else + } + else { LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytesWritten, output_text.size()); + } } -void Tas::RecordInput(u32 buttons, std::array, 2> axes) { - lastInput = {buttons, flipY(axes[0]), flipY(axes[1])}; -} - -std::pair Tas::flipY(std::pair old) const { +static std::pair FlipY(std::pair old) { auto [x, y] = old; return {x, -y}; } -std::string Tas::GetStatusDescription() { +void Tas::RecordInput(u32 buttons, const std::array, 2>& axes) { + last_input = {buttons, FlipY(axes[0]), FlipY(axes[1])}; +} + +std::tuple Tas::GetStatus() { + TasState state; if (Settings::values.tas_record) { - return "Recording TAS: " + std::to_string(recordCommands.size()); - } - if (Settings::values.tas_enable) { - return "Playing TAS: " + std::to_string(current_command) + "/" + - std::to_string(scriptLength); + return {TasState::RECORDING, record_commands.size(), record_commands.size()}; + } else if (Settings::values.tas_enable) { + state = TasState::RUNNING; + } else { + state = TasState::STOPPED; } - return "TAS not running: " + std::to_string(current_command) + "/" + - std::to_string(scriptLength); + + return {state, current_command, script_length}; } -std::string debugButtons(u32 buttons) { - return "{ " + TasInput::Tas::buttonsToString(buttons) + " }"; +static std::string DebugButtons(u32 buttons) { + return "{ " + TasInput::Tas::ButtonsToString(buttons) + " }"; } -std::string debugJoystick(float x, float y) { +static std::string DebugJoystick(float x, float y) { return "[ " + std::to_string(x) + "," + std::to_string(y) + " ]"; } -std::string debugInput(TasData data) { - return "{ " + debugButtons(data.buttons) + " , " + debugJoystick(data.axis[0], data.axis[1]) + - " , " + debugJoystick(data.axis[2], data.axis[3]) + " }"; +static std::string DebugInput(const TasData& data) { + return "{ " + DebugButtons(data.buttons) + " , " + DebugJoystick(data.axis[0], data.axis[1]) + + " , " + DebugJoystick(data.axis[2], data.axis[3]) + " }"; } -std::string debugInputs(std::array arr) { +static std::string DebugInputs(const std::array& arr) { std::string returns = "[ "; for (size_t i = 0; i < arr.size(); i++) { - returns += debugInput(arr[i]); - if (i != arr.size() - 1) + returns += DebugInput(arr[i]); + if (i != arr.size() - 1) { returns += " , "; + } } return returns + "]"; } void Tas::UpdateThread() { if (update_thread_running) { - if (Settings::values.pauseTasOnLoad && Settings::values.cpuBoosted) { - for (int i = 0; i < PLAYER_NUMBER; i++) { + if (Settings::values.pause_tas_on_load && Settings::values.is_cpu_boosted) { + for (size_t i = 0; i < PLAYER_NUMBER; i++) { tas_data[i].buttons = 0; tas_data[i].axis = {}; } } if (Settings::values.tas_record) { - recordCommands.push_back(lastInput); + record_commands.push_back(last_input); } - if (!Settings::values.tas_record && !recordCommands.empty()) { + if (!Settings::values.tas_record && !record_commands.empty()) { WriteTasFile(); Settings::values.tas_reset = true; refresh_tas_fle = true; - recordCommands.clear(); + record_commands.clear(); } if (Settings::values.tas_reset) { current_command = 0; @@ -177,12 +208,12 @@ void Tas::UpdateThread() { LOG_DEBUG(Input, "tas_reset done"); } if (Settings::values.tas_enable) { - if ((signed)current_command < scriptLength) { - LOG_INFO(Input, "Playing TAS {}/{}", current_command, scriptLength); + if ((signed)current_command < script_length) { + LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length); size_t frame = current_command++; - for (int i = 0; i < PLAYER_NUMBER; i++) { - if (frame < newCommands[i].size()) { - TASCommand command = newCommands[i][frame]; + for (size_t i = 0; i < PLAYER_NUMBER; i++) { + if (frame < commands[i].size()) { + TASCommand command = commands[i][frame]; tas_data[i].buttons = command.buttons; auto [l_axis_x, l_axis_y] = command.l_axis; tas_data[i].axis[0] = l_axis_x; @@ -198,22 +229,22 @@ void Tas::UpdateThread() { } else { Settings::values.tas_enable = false; current_command = 0; - for (int i = 0; i < PLAYER_NUMBER; i++) { + for (size_t i = 0; i < PLAYER_NUMBER; i++) { tas_data[i].buttons = 0; tas_data[i].axis = {}; } } } else { - for (int i = 0; i < PLAYER_NUMBER; i++) { + for (size_t i = 0; i < PLAYER_NUMBER; i++) { tas_data[i].buttons = 0; tas_data[i].axis = {}; } } } - LOG_DEBUG(Input, "TAS inputs: {}", debugInputs(tas_data)); + LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data)); } -TasAnalog Tas::ReadCommandAxis(const std::string line) const { +TasAnalog Tas::ReadCommandAxis(const std::string& line) const { std::stringstream linestream(line); std::string segment; std::vector seglist; @@ -228,7 +259,7 @@ TasAnalog Tas::ReadCommandAxis(const std::string line) const { return {x, y}; } -u32 Tas::ReadCommandButtons(const std::string data) const { +u32 Tas::ReadCommandButtons(const std::string& data) const { std::stringstream button_text(data); std::string line; u32 buttons = 0; @@ -262,8 +293,9 @@ std::string Tas::WriteCommandButtons(u32 data) const { if ((data & 1) == 1) { for (auto [text, tas_button] : text_to_tas_button) { if (tas_button == static_cast(1 << index)) { - if (line.size() > 0) + if (line.size() > 0) { line += ";"; + } line += text; break; } diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h index 0a152a04f..8ee70bcaf 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/tas/tas_input.h @@ -12,11 +12,17 @@ #include "core/frontend/input.h" #include "input_common/main.h" -#define PLAYER_NUMBER 8 - namespace TasInput { -using TasAnalog = std::tuple; +constexpr int PLAYER_NUMBER = 8; + +using TasAnalog = std::pair; + +enum class TasState { + RUNNING, + RECORDING, + STOPPED, +}; enum class TasButton : u32 { BUTTON_A = 0x000001, @@ -41,29 +47,6 @@ enum class TasButton : u32 { BUTTON_CAPTURE = 0x080000, }; -static const std::array, 20> text_to_tas_button = { - std::pair{"KEY_A", TasButton::BUTTON_A}, - {"KEY_B", TasButton::BUTTON_B}, - {"KEY_X", TasButton::BUTTON_X}, - {"KEY_Y", TasButton::BUTTON_Y}, - {"KEY_LSTICK", TasButton::STICK_L}, - {"KEY_RSTICK", TasButton::STICK_R}, - {"KEY_L", TasButton::TRIGGER_L}, - {"KEY_R", TasButton::TRIGGER_R}, - {"KEY_PLUS", TasButton::BUTTON_PLUS}, - {"KEY_MINUS", TasButton::BUTTON_MINUS}, - {"KEY_DLEFT", TasButton::BUTTON_LEFT}, - {"KEY_DUP", TasButton::BUTTON_UP}, - {"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, - {"KEY_DDOWN", TasButton::BUTTON_DOWN}, - {"KEY_SL", TasButton::BUTTON_SL}, - {"KEY_SR", TasButton::BUTTON_SR}, - {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, - {"KEY_HOME", TasButton::BUTTON_HOME}, - {"KEY_ZL", TasButton::TRIGGER_ZL}, - {"KEY_ZR", TasButton::TRIGGER_ZR}, -}; - enum class TasAxes : u8 { StickX, StickY, @@ -82,7 +65,7 @@ public: Tas(); ~Tas(); - static std::string buttonsToString(u32 button) { + static std::string ButtonsToString(u32 button) { std::string returns; if ((button & static_cast(TasInput::TasButton::BUTTON_A)) != 0) returns += ", A"; @@ -124,14 +107,14 @@ public: returns += ", HOME"; if ((button & static_cast(TasInput::TasButton::BUTTON_CAPTURE)) != 0) returns += ", CAPTURE"; - return returns.length() != 0 ? returns.substr(2) : ""; + return returns.empty() ? "" : returns.substr(2); } void RefreshTasFile(); void LoadTasFiles(); - void RecordInput(u32 buttons, std::array, 2> axes); + void RecordInput(u32 buttons, const std::array, 2>& axes); void UpdateThread(); - std::string GetStatusDescription(); + std::tuple GetStatus(); InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; @@ -143,21 +126,20 @@ private: TasAnalog l_axis{}; TasAnalog r_axis{}; }; - void LoadTasFile(int playerIndex); + void LoadTasFile(size_t player_index); void WriteTasFile(); - TasAnalog ReadCommandAxis(const std::string line) const; - u32 ReadCommandButtons(const std::string line) const; + TasAnalog ReadCommandAxis(const std::string& line) const; + u32 ReadCommandButtons(const std::string& line) const; std::string WriteCommandButtons(u32 data) const; std::string WriteCommandAxis(TasAnalog data) const; - std::pair flipY(std::pair old) const; - size_t scriptLength{0}; + size_t script_length{0}; std::array tas_data; bool update_thread_running{true}; bool refresh_tas_fle{false}; - std::array, PLAYER_NUMBER> newCommands{}; - std::vector recordCommands{}; + std::array, PLAYER_NUMBER> commands{}; + std::vector record_commands{}; std::size_t current_command{0}; - TASCommand lastInput{}; // only used for recording + TASCommand last_input{}; // only used for recording }; } // namespace TasInput diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index cd9ba0a90..4636d476e 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -78,7 +78,7 @@ void ConfigureFilesystem::applyConfiguration() { Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked(); - Settings::values.pauseTasOnLoad = ui->tas_pause_on_load->isChecked(); + Settings::values.pause_tas_on_load = ui->tas_pause_on_load->isChecked(); Settings::values.dump_exefs = ui->dump_exefs->isChecked(); Settings::values.dump_nso = ui->dump_nso->isChecked(); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 10057b9ca..0ee0fd8cd 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -826,11 +826,11 @@ void GMainWindow::InitializeWidgets() { }); statusBar()->insertPermanentWidget(0, renderer_status_button); - TASlabel = new QLabel(); - TASlabel->setObjectName(QStringLiteral("TASlabel")); - TASlabel->setText(tr("TAS not running")); - TASlabel->setFocusPolicy(Qt::NoFocus); - statusBar()->insertPermanentWidget(0, TASlabel); + tas_label = new QLabel(); + tas_label->setObjectName(QStringLiteral("TASlabel")); + tas_label->setText(tr("TAS not running")); + tas_label->setFocusPolicy(Qt::NoFocus); + statusBar()->insertPermanentWidget(0, tas_label); statusBar()->setVisible(true); setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}")); @@ -2896,13 +2896,28 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie } } +static std::string GetTasStateDescription(TasInput::TasState state) { + switch (state) { + case TasInput::TasState::RUNNING: + return "Running"; + case TasInput::TasState::RECORDING: + return "Recording"; + case TasInput::TasState::STOPPED: + return "Stopped"; + default: + return "INVALID STATE"; + } +} + void GMainWindow::UpdateStatusBar() { if (emu_thread == nullptr) { status_bar_update_timer.stop(); return; } - TASlabel->setText(tr(input_subsystem->GetTas()->GetStatusDescription().c_str())); + auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus(); + tas_label->setText(tr("%1 TAS %2/%3").arg(tr(GetTasStateDescription(tas_status).c_str())).arg(current_tas_frame).arg(total_tas_frames)); + auto& system = Core::System::GetInstance(); auto results = system.GetAndResetPerfStats(); auto& shader_notify = system.GPU().ShaderNotify(); -- cgit v1.2.3 From c01a872c8efa90065b6ba1a74079ddf6ec12058f Mon Sep 17 00:00:00 2001 From: german77 Date: Sat, 19 Jun 2021 14:38:49 -0500 Subject: config: Move TAS options to it's own menu --- src/common/fs/fs_paths.h | 2 +- src/common/fs/path_util.cpp | 2 +- src/common/fs/path_util.h | 3 +- src/core/hle/service/apm/apm_interface.cpp | 2 +- src/input_common/main.cpp | 6 +- src/input_common/tas/tas_input.cpp | 189 ++++++++++++--------- src/input_common/tas/tas_input.h | 26 ++- src/yuzu/CMakeLists.txt | 3 + src/yuzu/configuration/configure_filesystem.cpp | 9 - src/yuzu/configuration/configure_filesystem.h | 1 - src/yuzu/configuration/configure_filesystem.ui | 49 ------ .../configure_input_player_widget.cpp | 4 +- src/yuzu/configuration/configure_tas.cpp | 84 +++++++++ src/yuzu/configuration/configure_tas.h | 38 +++++ src/yuzu/configuration/configure_tas.ui | 143 ++++++++++++++++ src/yuzu/debugger/controller.h | 1 - src/yuzu/main.cpp | 67 +++++--- src/yuzu/main.h | 1 + src/yuzu/main.ui | 6 + 19 files changed, 452 insertions(+), 184 deletions(-) create mode 100644 src/yuzu/configuration/configure_tas.cpp create mode 100644 src/yuzu/configuration/configure_tas.h create mode 100644 src/yuzu/configuration/configure_tas.ui (limited to 'src/common') diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index 84968b8e0..5d447f108 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h @@ -21,7 +21,7 @@ #define SCREENSHOTS_DIR "screenshots" #define SDMC_DIR "sdmc" #define SHADER_DIR "shader" -#define TAS_DIR "scripts" +#define TAS_DIR "tas" // yuzu-specific files diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 97d026eb8..43b79bd6d 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -116,7 +116,7 @@ private: GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); - GenerateYuzuPath(YuzuPath::TASFile, yuzu_path / TAS_DIR); + GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); } ~PathManagerImpl() = default; diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 6079de4c6..52e4670e2 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -23,8 +23,7 @@ enum class YuzuPath { ScreenshotsDir, // Where yuzu screenshots are stored. SDMCDir, // Where the emulated SDMC is stored. ShaderDir, // Where shaders are stored. - - TASFile, // Where the current script file is stored. + TASDir, // Where the current script file is stored. }; /** diff --git a/src/core/hle/service/apm/apm_interface.cpp b/src/core/hle/service/apm/apm_interface.cpp index 724483107..520ccfa88 100644 --- a/src/core/hle/service/apm/apm_interface.cpp +++ b/src/core/hle/service/apm/apm_interface.cpp @@ -121,7 +121,7 @@ void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_APM, "called, mode={:08X}", mode); - Settings::values.is_cpu_boosted = (static_cast(mode) == 1); + Settings::values.is_cpu_boosted = (mode == CpuBoostMode::Full); controller.SetFromCpuBoostMode(mode); IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 4f170493e..3b9906b53 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -5,6 +5,7 @@ #include #include #include "common/param_package.h" +#include "common/settings.h" #include "input_common/analog_from_button.h" #include "input_common/gcadapter/gc_adapter.h" #include "input_common/gcadapter/gc_poller.h" @@ -114,8 +115,11 @@ struct InputSubsystem::Impl { std::vector devices = { Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, - Common::ParamPackage{{"display", "TAS"}, {"class", "tas"}}, }; + if (Settings::values.tas_enable) { + devices.push_back( + Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}}); + } #ifdef HAVE_SDL2 auto sdl_devices = sdl->GetInputDevices(); devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp index 7320a7004..6efa1234a 100644 --- a/src/input_common/tas/tas_input.cpp +++ b/src/input_common/tas/tas_input.cpp @@ -67,14 +67,13 @@ void Tas::LoadTasFile(size_t player_index) { if (!commands[player_index].empty()) { commands[player_index].clear(); } - std::string file = Common::FS::ReadStringFromFile( - Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "script0-" + - std::to_string(player_index + 1) + ".txt", - Common::FS::FileType::BinaryFile); + std::string file = + Common::FS::ReadStringFromFile(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir) + + "script0-" + std::to_string(player_index + 1) + ".txt", + Common::FS::FileType::BinaryFile); std::stringstream command_line(file); std::string line; - int frameNo = 0; - TASCommand empty = {.buttons = 0, .l_axis = {0.f, 0.f}, .r_axis = {0.f, 0.f}}; + int frame_no = 0; while (std::getline(command_line, line, '\n')) { if (line.empty()) { continue; @@ -94,9 +93,9 @@ void Tas::LoadTasFile(size_t player_index) { continue; } - while (frameNo < std::stoi(seglist.at(0))) { - commands[player_index].push_back(empty); - frameNo++; + while (frame_no < std::stoi(seglist.at(0))) { + commands[player_index].push_back({}); + frame_no++; } TASCommand command = { @@ -105,30 +104,29 @@ void Tas::LoadTasFile(size_t player_index) { .r_axis = ReadCommandAxis(seglist.at(3)), }; commands[player_index].push_back(command); - frameNo++; + frame_no++; } - LOG_INFO(Input, "TAS file loaded! {} frames", frameNo); + LOG_INFO(Input, "TAS file loaded! {} frames", frame_no); } void Tas::WriteTasFile() { LOG_DEBUG(Input, "WriteTasFile()"); - std::string output_text = ""; - for (int frame = 0; frame < (signed)record_commands.size(); frame++) { + std::string output_text; + for (size_t frame = 0; frame < record_commands.size(); frame++) { if (!output_text.empty()) { output_text += "\n"; } - TASCommand line = record_commands.at(frame); + const TASCommand& line = record_commands[frame]; output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " + WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis); } - size_t bytesWritten = Common::FS::WriteStringToFile( - Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "record.txt", + const size_t bytes_written = Common::FS::WriteStringToFile( + Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir) + "record.txt", Common::FS::FileType::TextFile, output_text); - if (bytesWritten == output_text.size()) { + if (bytes_written == output_text.size()) { LOG_INFO(Input, "TAS file written to file!"); - } - else { - LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytesWritten, + } else { + LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written, output_text.size()); } } @@ -142,30 +140,33 @@ void Tas::RecordInput(u32 buttons, const std::array, 2>& last_input = {buttons, FlipY(axes[0]), FlipY(axes[1])}; } -std::tuple Tas::GetStatus() { +std::tuple Tas::GetStatus() const { TasState state; - if (Settings::values.tas_record) { - return {TasState::RECORDING, record_commands.size(), record_commands.size()}; - } else if (Settings::values.tas_enable) { - state = TasState::RUNNING; + if (is_recording) { + return {TasState::Recording, 0, record_commands.size()}; + } + + if (is_running) { + state = TasState::Running; } else { - state = TasState::STOPPED; + state = TasState::Stopped; } return {state, current_command, script_length}; } static std::string DebugButtons(u32 buttons) { - return "{ " + TasInput::Tas::ButtonsToString(buttons) + " }"; + return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons)); } static std::string DebugJoystick(float x, float y) { - return "[ " + std::to_string(x) + "," + std::to_string(y) + " ]"; + return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y)); } static std::string DebugInput(const TasData& data) { - return "{ " + DebugButtons(data.buttons) + " , " + DebugJoystick(data.axis[0], data.axis[1]) + - " , " + DebugJoystick(data.axis[2], data.axis[3]) + " }"; + return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons), + DebugJoystick(data.axis[0], data.axis[1]), + DebugJoystick(data.axis[2], data.axis[3])); } static std::string DebugInputs(const std::array& arr) { @@ -180,66 +181,54 @@ static std::string DebugInputs(const std::array& arr) { } void Tas::UpdateThread() { - if (update_thread_running) { - if (Settings::values.pause_tas_on_load && Settings::values.is_cpu_boosted) { - for (size_t i = 0; i < PLAYER_NUMBER; i++) { - tas_data[i].buttons = 0; - tas_data[i].axis = {}; - } - } + if (!update_thread_running) { + return; + } - if (Settings::values.tas_record) { - record_commands.push_back(last_input); - } - if (!Settings::values.tas_record && !record_commands.empty()) { - WriteTasFile(); - Settings::values.tas_reset = true; - refresh_tas_fle = true; - record_commands.clear(); - } - if (Settings::values.tas_reset) { - current_command = 0; - if (refresh_tas_fle) { - LoadTasFiles(); - refresh_tas_fle = false; - } - Settings::values.tas_reset = false; + if (is_recording) { + record_commands.push_back(last_input); + } + if (!is_recording && !record_commands.empty()) { + WriteTasFile(); + needs_reset = true; + refresh_tas_fle = true; + record_commands.clear(); + } + if (needs_reset) { + current_command = 0; + if (refresh_tas_fle) { LoadTasFiles(); - LOG_DEBUG(Input, "tas_reset done"); + refresh_tas_fle = false; } - if (Settings::values.tas_enable) { - if ((signed)current_command < script_length) { - LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length); - size_t frame = current_command++; - for (size_t i = 0; i < PLAYER_NUMBER; i++) { - if (frame < commands[i].size()) { - TASCommand command = commands[i][frame]; - tas_data[i].buttons = command.buttons; - auto [l_axis_x, l_axis_y] = command.l_axis; - tas_data[i].axis[0] = l_axis_x; - tas_data[i].axis[1] = l_axis_y; - auto [r_axis_x, r_axis_y] = command.r_axis; - tas_data[i].axis[2] = r_axis_x; - tas_data[i].axis[3] = r_axis_y; - } else { - tas_data[i].buttons = 0; - tas_data[i].axis = {}; - } - } - } else { - Settings::values.tas_enable = false; - current_command = 0; - for (size_t i = 0; i < PLAYER_NUMBER; i++) { - tas_data[i].buttons = 0; - tas_data[i].axis = {}; + needs_reset = false; + LoadTasFiles(); + LOG_DEBUG(Input, "tas_reset done"); + } + if (is_running) { + if (current_command < script_length) { + LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length); + size_t frame = current_command++; + for (size_t i = 0; i < PLAYER_NUMBER; i++) { + if (frame < commands[i].size()) { + TASCommand command = commands[i][frame]; + tas_data[i].buttons = command.buttons; + auto [l_axis_x, l_axis_y] = command.l_axis; + tas_data[i].axis[0] = l_axis_x; + tas_data[i].axis[1] = l_axis_y; + auto [r_axis_x, r_axis_y] = command.r_axis; + tas_data[i].axis[2] = r_axis_x; + tas_data[i].axis[3] = r_axis_y; + } else { + tas_data[i] = {}; } } } else { - for (size_t i = 0; i < PLAYER_NUMBER; i++) { - tas_data[i].buttons = 0; - tas_data[i].axis = {}; - } + is_running = Settings::values.tas_loop; + current_command = 0; + tas_data.fill({}); } + } else { + tas_data.fill({}); } LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data)); } @@ -284,8 +273,9 @@ std::string Tas::WriteCommandAxis(TasAnalog data) const { } std::string Tas::WriteCommandButtons(u32 data) const { - if (data == 0) + if (data == 0) { return "NONE"; + } std::string line; u32 index = 0; @@ -307,6 +297,37 @@ std::string Tas::WriteCommandButtons(u32 data) const { return line; } +void Tas::StartStop() { + is_running = !is_running; +} + +void Tas::Reset() { + needs_reset = true; +} + +void Tas::Record() { + is_recording = !is_recording; +<<<<<<< HEAD +======= + return is_recording; +} + +void Tas::SaveRecording(bool overwrite_file) { + if (is_recording) { + return; + } + if (record_commands.empty()) { + return; + } + WriteTasFile("record.txt"); + if (overwrite_file) { + WriteTasFile("script0-1.txt"); + } + needs_reset = true; + record_commands.clear(); +>>>>>>> 773d268db (config: disable pause on load) +} + InputCommon::ButtonMapping Tas::GetButtonMappingForDevice( const Common::ParamPackage& params) const { // This list is missing ZL/ZR since those are not considered buttons. diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h index 8ee70bcaf..49ef10ff9 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/tas/tas_input.h @@ -14,14 +14,14 @@ namespace TasInput { -constexpr int PLAYER_NUMBER = 8; +constexpr size_t PLAYER_NUMBER = 8; using TasAnalog = std::pair; enum class TasState { - RUNNING, - RECORDING, - STOPPED, + Running, + Recording, + Stopped, }; enum class TasButton : u32 { @@ -114,8 +114,19 @@ public: void LoadTasFiles(); void RecordInput(u32 buttons, const std::array, 2>& axes); void UpdateThread(); - std::tuple GetStatus(); + void StartStop(); + void Reset(); + void Record(); + + /** + * Returns the current status values of TAS playback/recording + * @return Tuple of + * TasState indicating the current state out of Running, Recording or Stopped ; + * Current playback progress or amount of frames (so far) for Recording ; + * Total length of script file currently loaded or amount of frames (so far) for Recording + */ + std::tuple GetStatus() const; InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; [[nodiscard]] const TasData& GetTasState(std::size_t pad) const; @@ -137,9 +148,12 @@ private: std::array tas_data; bool update_thread_running{true}; bool refresh_tas_fle{false}; + bool is_recording{false}; + bool is_running{false}; + bool needs_reset{false}; std::array, PLAYER_NUMBER> commands{}; std::vector record_commands{}; - std::size_t current_command{0}; + size_t current_command{0}; TASCommand last_input{}; // only used for recording }; } // namespace TasInput diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 19ba0dbba..b6dda283d 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -108,6 +108,9 @@ add_executable(yuzu configuration/configure_system.cpp configuration/configure_system.h configuration/configure_system.ui + configuration/configure_tas.cpp + configuration/configure_tas.h + configuration/configure_tas.ui configuration/configure_touch_from_button.cpp configuration/configure_touch_from_button.h configuration/configure_touch_from_button.ui diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index 4636d476e..013de02db 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -26,8 +26,6 @@ ConfigureFilesystem::ConfigureFilesystem(QWidget* parent) [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); }); connect(ui->load_path_button, &QToolButton::pressed, this, [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); }); - connect(ui->tas_path_button, &QToolButton::pressed, this, - [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); }); connect(ui->reset_game_list_cache, &QPushButton::pressed, this, &ConfigureFilesystem::ResetMetadata); @@ -51,8 +49,6 @@ void ConfigureFilesystem::setConfiguration() { QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::DumpDir))); ui->load_path_edit->setText( QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LoadDir))); - ui->tas_path_edit->setText( - QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile))); ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted.GetValue()); ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game.GetValue()); @@ -74,11 +70,9 @@ void ConfigureFilesystem::applyConfiguration() { ui->dump_path_edit->text().toStdString()); Common::FS::SetYuzuPath(Common::FS::YuzuPath::LoadDir, ui->load_path_edit->text().toStdString()); - Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASFile, ui->tas_path_edit->text().toStdString()); Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked(); Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked(); - Settings::values.pause_tas_on_load = ui->tas_pause_on_load->isChecked(); Settings::values.dump_exefs = ui->dump_exefs->isChecked(); Settings::values.dump_nso = ui->dump_nso->isChecked(); @@ -104,9 +98,6 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) case DirectoryTarget::Load: caption = tr("Select Mod Load Directory..."); break; - case DirectoryTarget::TAS: - caption = tr("Select TAS Directory..."); - break; } QString str; diff --git a/src/yuzu/configuration/configure_filesystem.h b/src/yuzu/configuration/configure_filesystem.h index 86dab8684..2147cd405 100644 --- a/src/yuzu/configuration/configure_filesystem.h +++ b/src/yuzu/configuration/configure_filesystem.h @@ -32,7 +32,6 @@ private: Gamecard, Dump, Load, - TAS, }; void SetDirectory(DirectoryTarget target, QLineEdit* edit); diff --git a/src/yuzu/configuration/configure_filesystem.ui b/src/yuzu/configuration/configure_filesystem.ui index 8ac7250fd..62b9abc7a 100644 --- a/src/yuzu/configuration/configure_filesystem.ui +++ b/src/yuzu/configuration/configure_filesystem.ui @@ -219,55 +219,6 @@ - - - - TAS Directories - - - - - - Path - - - - - - - ... - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Maximum - - - - 60 - 20 - - - - - - - - Pause TAS execution during loads (SMO - 1.3) - - - - - - diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index e649e2169..e4383676a 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -232,7 +232,7 @@ void PlayerControlPreview::UpdateInput() { axis_values[Settings::NativeAnalog::RStick].value.x(), axis_values[Settings::NativeAnalog::RStick].value.y()}; input.button_values = button_values; - if (controller_callback.input != NULL) { + if (controller_callback.input != nullptr) { controller_callback.input(std::move(input)); } @@ -242,7 +242,7 @@ void PlayerControlPreview::UpdateInput() { } void PlayerControlPreview::SetCallBack(ControllerCallback callback_) { - controller_callback = callback_; + controller_callback = std::move(callback_); } void PlayerControlPreview::paintEvent(QPaintEvent* event) { diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp new file mode 100644 index 000000000..f2f91d84a --- /dev/null +++ b/src/yuzu/configuration/configure_tas.cpp @@ -0,0 +1,84 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "ui_configure_tas.h" +#include "yuzu/configuration/configure_tas.h" +#include "yuzu/uisettings.h" + +ConfigureTasDialog::ConfigureTasDialog(QWidget* parent) + : QDialog(parent), ui(std::make_unique()) { + + ui->setupUi(this); + + setFocusPolicy(Qt::ClickFocus); + setWindowTitle(tr("TAS Configuration")); + + connect(ui->tas_path_button, &QToolButton::pressed, this, + [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); }); + + LoadConfiguration(); +} + +ConfigureTasDialog::~ConfigureTasDialog() = default; + +void ConfigureTasDialog::LoadConfiguration() { + ui->tas_path_edit->setText( + QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir))); + ui->tas_enable->setChecked(Settings::values.tas_enable); + ui->tas_control_swap->setChecked(Settings::values.tas_swap_controllers); + ui->tas_loop_script->setChecked(Settings::values.tas_loop); + ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load); +} + +void ConfigureTasDialog::ApplyConfiguration() { + Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASDir, ui->tas_path_edit->text().toStdString()); + Settings::values.tas_enable = ui->tas_enable->isChecked(); + Settings::values.tas_swap_controllers = ui->tas_control_swap->isChecked(); + Settings::values.tas_loop = ui->tas_loop_script->isChecked(); + Settings::values.pause_tas_on_load = ui->tas_pause_on_load->isChecked(); +} + +void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) { + QString caption; + + switch (target) { + case DirectoryTarget::TAS: + caption = tr("Select TAS Load Directory..."); + break; + } + + QString str = QFileDialog::getExistingDirectory(this, caption, edit->text()); + + if (str.isNull() || str.isEmpty()) { + return; + } + + if (str.back() != QChar::fromLatin1('/')) { + str.append(QChar::fromLatin1('/')); + } + + edit->setText(str); +} + +void ConfigureTasDialog::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureTasDialog::RetranslateUI() { + ui->retranslateUi(this); +} + +void ConfigureTasDialog::HandleApplyButtonClicked() { + UISettings::values.configuration_applied = true; + ApplyConfiguration(); +} diff --git a/src/yuzu/configuration/configure_tas.h b/src/yuzu/configuration/configure_tas.h new file mode 100644 index 000000000..1546bf16f --- /dev/null +++ b/src/yuzu/configuration/configure_tas.h @@ -0,0 +1,38 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +namespace Ui { +class ConfigureTas; +} + +class ConfigureTasDialog : public QDialog { + Q_OBJECT + +public: + explicit ConfigureTasDialog(QWidget* parent); + ~ConfigureTasDialog() override; + + /// Save all button configurations to settings file + void ApplyConfiguration(); + +private: + enum class DirectoryTarget { + TAS, + }; + + void LoadConfiguration(); + + void SetDirectory(DirectoryTarget target, QLineEdit* edit); + + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + void HandleApplyButtonClicked(); + + std::unique_ptr ui; +}; diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui new file mode 100644 index 000000000..906e073ff --- /dev/null +++ b/src/yuzu/configuration/configure_tas.ui @@ -0,0 +1,143 @@ + + + ConfigureTas + + + + 0 + 0 + 800 + 300 + + + + Dialog + + + + + + + + TAS Settings + + + + + + Enable TAS features + + + + + + + false + + + Automatic controller profile swapping + + + + + + + Loop script + + + + + + + false + + + Pause execution during loads + + + + + + + + + + + + + + TAS Directories + + + + + + Path + + + + + + + ... + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 60 + 20 + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ConfigureTas + accept() + + + buttonBox + rejected() + ConfigureTas + reject() + + + diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h index 659923e1b..f2f6653f7 100644 --- a/src/yuzu/debugger/controller.h +++ b/src/yuzu/debugger/controller.h @@ -25,7 +25,6 @@ struct ControllerInput { struct ControllerCallback { std::function input; - std::function update; }; class ControllerDialog : public QWidget { diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 0ee0fd8cd..820e31fa7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -19,6 +19,7 @@ #include "common/nvidia_flags.h" #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" +#include "configuration/configure_tas.h" #include "configuration/configure_vibration.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" @@ -750,6 +751,11 @@ void GMainWindow::InitializeWidgets() { statusBar()->addPermanentWidget(label); } + tas_label = new QLabel(); + tas_label->setObjectName(QStringLiteral("TASlabel")); + tas_label->setFocusPolicy(Qt::NoFocus); + statusBar()->insertPermanentWidget(0, tas_label); + // Setup Dock button dock_status_button = new QPushButton(); dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); @@ -826,12 +832,6 @@ void GMainWindow::InitializeWidgets() { }); statusBar()->insertPermanentWidget(0, renderer_status_button); - tas_label = new QLabel(); - tas_label->setObjectName(QStringLiteral("TASlabel")); - tas_label->setText(tr("TAS not running")); - tas_label->setFocusPolicy(Qt::NoFocus); - statusBar()->insertPermanentWidget(0, tas_label); - statusBar()->setVisible(true); setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}")); } @@ -1024,18 +1024,11 @@ void GMainWindow::InitializeHotkeys() { } }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this), - &QShortcut::activated, this, [&] { - Settings::values.tas_enable = !Settings::values.tas_enable; - LOG_INFO(Frontend, "Tas enabled {}", Settings::values.tas_enable); - }); - + &QShortcut::activated, this, [&] { input_subsystem->GetTas()->StartStop(); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), - &QShortcut::activated, this, [&] { Settings::values.tas_reset = true; }); + &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), - &QShortcut::activated, this, [&] { - Settings::values.tas_record = !Settings::values.tas_record; - LOG_INFO(Frontend, "Tas recording {}", Settings::values.tas_record); - }); + &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Record(); }); } void GMainWindow::SetDefaultUIGeometry() { @@ -1154,6 +1147,7 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ); connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); + connect(ui.action_Configure_Tas, &QAction::triggered, this, &GMainWindow::OnConfigureTas); connect(ui.action_Configure_Current_Game, &QAction::triggered, this, &GMainWindow::OnConfigurePerGame); @@ -2720,6 +2714,19 @@ void GMainWindow::OnConfigure() { UpdateStatusButtons(); } +void GMainWindow::OnConfigureTas() { + const auto& system = Core::System::GetInstance(); + ConfigureTasDialog dialog(this); + const auto result = dialog.exec(); + + if (result != QDialog::Accepted && !UISettings::values.configuration_applied) { + Settings::RestoreGlobalState(system.IsPoweredOn()); + return; + } else if (result == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} + void GMainWindow::OnConfigurePerGame() { const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); OpenPerGameConfiguration(title_id, game_path.toStdString()); @@ -2898,14 +2905,14 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie static std::string GetTasStateDescription(TasInput::TasState state) { switch (state) { - case TasInput::TasState::RUNNING: - return "Running"; - case TasInput::TasState::RECORDING: - return "Recording"; - case TasInput::TasState::STOPPED: - return "Stopped"; - default: - return "INVALID STATE"; + case TasInput::TasState::Running: + return "Running"; + case TasInput::TasState::Recording: + return "Recording"; + case TasInput::TasState::Stopped: + return "Stopped"; + default: + return "INVALID STATE"; } } @@ -2915,8 +2922,16 @@ void GMainWindow::UpdateStatusBar() { return; } - auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus(); - tas_label->setText(tr("%1 TAS %2/%3").arg(tr(GetTasStateDescription(tas_status).c_str())).arg(current_tas_frame).arg(total_tas_frames)); + if (Settings::values.tas_enable) { + auto [tas_status, current_tas_frame, total_tas_frames] = + input_subsystem->GetTas()->GetStatus(); + tas_label->setText(tr("%1 TAS %2/%3") + .arg(tr(GetTasStateDescription(tas_status).c_str())) + .arg(current_tas_frame) + .arg(total_tas_frames)); + } else { + tas_label->clear(); + } auto& system = Core::System::GetInstance(); auto results = system.GetAndResetPerfStats(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index edca661ac..867a0003c 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -259,6 +259,7 @@ private slots: void OnMenuInstallToNAND(); void OnMenuRecentFile(); void OnConfigure(); + void OnConfigureTas(); void OnConfigurePerGame(); void OnLoadAmiibo(); void OnOpenYuzuFolder(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 048870687..31c1a20f3 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -72,6 +72,7 @@ + @@ -294,6 +295,11 @@ &Capture Screenshot + + + Configure &TAS... + + false -- cgit v1.2.3 From f078b15565c8cab08587b8f8629d878615705cfb Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Sun, 20 Jun 2021 00:04:34 +0200 Subject: input_common/tas: Fallback to simple update --- src/common/settings.h | 7 ++- src/core/hle/service/apm/apm_interface.cpp | 2 - src/core/hle/service/vi/vi.cpp | 3 -- src/input_common/tas/tas_input.cpp | 47 ++++++++--------- src/input_common/tas/tas_input.h | 59 ++++------------------ .../configure_input_player_widget.cpp | 3 ++ src/yuzu/debugger/controller.cpp | 16 +++--- src/yuzu/debugger/controller.h | 3 +- src/yuzu/main.cpp | 21 +++----- src/yuzu/main.h | 1 + 10 files changed, 60 insertions(+), 102 deletions(-) (limited to 'src/common') diff --git a/src/common/settings.h b/src/common/settings.h index 7333a64dc..cf12c325c 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -14,7 +14,6 @@ #include #include -#include #include "common/common_types.h" #include "common/settings_input.h" @@ -515,9 +514,9 @@ struct Values { BasicSetting pause_tas_on_load { false, "pause_tas_on_load" }; BasicSetting tas_enable{ false, "tas_enable" }; - BasicSetting tas_reset{ false, "tas_reset" }; - BasicSetting tas_record{ false, "tas_record" }; - BasicSetting is_cpu_boxted{ false, " BasicSetting is_cpu_boxted{ false, "cpuBoosted" }; + BasicSetting tas_loop{ false, "tas_loop" }; + BasicSetting tas_swap_controllers{ false, "tas_swap_controllers" }; + BasicSetting is_cpu_boosted{ false, "is_cpu_boosted" }; " }; BasicSetting mouse_panning{false, "mouse_panning"}; diff --git a/src/core/hle/service/apm/apm_interface.cpp b/src/core/hle/service/apm/apm_interface.cpp index 520ccfa88..e58bad083 100644 --- a/src/core/hle/service/apm/apm_interface.cpp +++ b/src/core/hle/service/apm/apm_interface.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include "common/logging/log.h" -#include "common/settings.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/apm/apm_controller.h" @@ -121,7 +120,6 @@ void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_APM, "called, mode={:08X}", mode); - Settings::values.is_cpu_boosted = (mode == CpuBoostMode::Full); controller.SetFromCpuBoostMode(mode); IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index f4eac0bca..8e8fc40ca 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -32,8 +32,6 @@ #include "core/hle/service/vi/vi_s.h" #include "core/hle/service/vi/vi_u.h" -#include "input_common/tas/tas_input.h" - namespace Service::VI { constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1}; @@ -597,7 +595,6 @@ private: IGBPQueueBufferResponseParcel response{1280, 720}; ctx.WriteBuffer(response.Serialize()); - Settings::values.input_subsystem->GetTas()->UpdateThread(); break; } case TransactionId::Query: { diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp index 6efa1234a..baeb18c22 100644 --- a/src/input_common/tas/tas_input.cpp +++ b/src/input_common/tas/tas_input.cpp @@ -2,13 +2,8 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include #include -#include -#include #include -#include -#include #include "common/fs/file.h" #include "common/fs/fs_types.h" @@ -43,27 +38,25 @@ constexpr std::array, 20> text_to_tas_but }; Tas::Tas() { + if (!Settings::values.tas_enable) { + return; + } LoadTasFiles(); } -Tas::~Tas() { - update_thread_running = false; -} +Tas::~Tas() = default; -void Tas::RefreshTasFile() { - refresh_tas_fle = true; -} void Tas::LoadTasFiles() { script_length = 0; - for (size_t i = 0; i < PLAYER_NUMBER; i++) { + for (size_t i = 0; i < commands.size(); i++) { LoadTasFile(i); if (commands[i].size() > script_length) { script_length = commands[i].size(); } } } + void Tas::LoadTasFile(size_t player_index) { - LOG_DEBUG(Input, "LoadTasFile()"); if (!commands[player_index].empty()) { commands[player_index].clear(); } @@ -110,7 +103,6 @@ void Tas::LoadTasFile(size_t player_index) { } void Tas::WriteTasFile() { - LOG_DEBUG(Input, "WriteTasFile()"); std::string output_text; for (size_t frame = 0; frame < record_commands.size(); frame++) { if (!output_text.empty()) { @@ -131,13 +123,13 @@ void Tas::WriteTasFile() { } } -static std::pair FlipY(std::pair old) { +std::pair Tas::FlipAxisY(std::pair old) { auto [x, y] = old; return {x, -y}; } void Tas::RecordInput(u32 buttons, const std::array, 2>& axes) { - last_input = {buttons, FlipY(axes[0]), FlipY(axes[1])}; + last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])}; } std::tuple Tas::GetStatus() const { @@ -155,21 +147,21 @@ std::tuple Tas::GetStatus() const { return {state, current_command, script_length}; } -static std::string DebugButtons(u32 buttons) { +std::string Tas::DebugButtons(u32 buttons) const { return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons)); } -static std::string DebugJoystick(float x, float y) { +std::string Tas::DebugJoystick(float x, float y) const { return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y)); } -static std::string DebugInput(const TasData& data) { +std::string Tas::DebugInput(const TasData& data) const { return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons), DebugJoystick(data.axis[0], data.axis[1]), DebugJoystick(data.axis[2], data.axis[3])); } -static std::string DebugInputs(const std::array& arr) { +std::string Tas::DebugInputs(const std::array& arr) const { std::string returns = "[ "; for (size_t i = 0; i < arr.size(); i++) { returns += DebugInput(arr[i]); @@ -180,8 +172,17 @@ static std::string DebugInputs(const std::array& arr) { return returns + "]"; } +std::string Tas::ButtonsToString(u32 button) const { + std::string returns; + for (auto [text_button, tas_button] : text_to_tas_button) { + if ((button & static_cast(tas_button)) != 0) + returns += fmt::format(", {}", text_button.substr(4)); + } + return returns.empty() ? "" : returns.substr(2); +} + void Tas::UpdateThread() { - if (!update_thread_running) { + if (!Settings::values.tas_enable) { return; } @@ -206,9 +207,9 @@ void Tas::UpdateThread() { } if (is_running) { if (current_command < script_length) { - LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length); + LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); size_t frame = current_command++; - for (size_t i = 0; i < PLAYER_NUMBER; i++) { + for (size_t i = 0; i < commands.size(); i++) { if (frame < commands[i].size()) { TASCommand command = commands[i][frame]; tas_data[i].buttons = command.buttons; diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h index 49ef10ff9..e011e559e 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/tas/tas_input.h @@ -5,8 +5,6 @@ #pragma once #include -#include -#include #include "common/common_types.h" #include "core/frontend/input.h" @@ -65,53 +63,6 @@ public: Tas(); ~Tas(); - static std::string ButtonsToString(u32 button) { - std::string returns; - if ((button & static_cast(TasInput::TasButton::BUTTON_A)) != 0) - returns += ", A"; - if ((button & static_cast(TasInput::TasButton::BUTTON_B)) != 0) - returns += ", B"; - if ((button & static_cast(TasInput::TasButton::BUTTON_X)) != 0) - returns += ", X"; - if ((button & static_cast(TasInput::TasButton::BUTTON_Y)) != 0) - returns += ", Y"; - if ((button & static_cast(TasInput::TasButton::STICK_L)) != 0) - returns += ", STICK_L"; - if ((button & static_cast(TasInput::TasButton::STICK_R)) != 0) - returns += ", STICK_R"; - if ((button & static_cast(TasInput::TasButton::TRIGGER_L)) != 0) - returns += ", TRIGGER_L"; - if ((button & static_cast(TasInput::TasButton::TRIGGER_R)) != 0) - returns += ", TRIGGER_R"; - if ((button & static_cast(TasInput::TasButton::TRIGGER_ZL)) != 0) - returns += ", TRIGGER_ZL"; - if ((button & static_cast(TasInput::TasButton::TRIGGER_ZR)) != 0) - returns += ", TRIGGER_ZR"; - if ((button & static_cast(TasInput::TasButton::BUTTON_PLUS)) != 0) - returns += ", PLUS"; - if ((button & static_cast(TasInput::TasButton::BUTTON_MINUS)) != 0) - returns += ", MINUS"; - if ((button & static_cast(TasInput::TasButton::BUTTON_LEFT)) != 0) - returns += ", LEFT"; - if ((button & static_cast(TasInput::TasButton::BUTTON_UP)) != 0) - returns += ", UP"; - if ((button & static_cast(TasInput::TasButton::BUTTON_RIGHT)) != 0) - returns += ", RIGHT"; - if ((button & static_cast(TasInput::TasButton::BUTTON_DOWN)) != 0) - returns += ", DOWN"; - if ((button & static_cast(TasInput::TasButton::BUTTON_SL)) != 0) - returns += ", SL"; - if ((button & static_cast(TasInput::TasButton::BUTTON_SR)) != 0) - returns += ", SR"; - if ((button & static_cast(TasInput::TasButton::BUTTON_HOME)) != 0) - returns += ", HOME"; - if ((button & static_cast(TasInput::TasButton::BUTTON_CAPTURE)) != 0) - returns += ", CAPTURE"; - return returns.empty() ? "" : returns.substr(2); - } - - void RefreshTasFile(); - void LoadTasFiles(); void RecordInput(u32 buttons, const std::array, 2>& axes); void UpdateThread(); @@ -137,6 +88,7 @@ private: TasAnalog l_axis{}; TasAnalog r_axis{}; }; + void LoadTasFiles(); void LoadTasFile(size_t player_index); void WriteTasFile(); TasAnalog ReadCommandAxis(const std::string& line) const; @@ -144,9 +96,16 @@ private: std::string WriteCommandButtons(u32 data) const; std::string WriteCommandAxis(TasAnalog data) const; + std::pair FlipAxisY(std::pair old); + + std::string DebugButtons(u32 buttons) const; + std::string DebugJoystick(float x, float y) const; + std::string DebugInput(const TasData& data) const; + std::string DebugInputs(const std::array& arr) const; + std::string ButtonsToString(u32 button) const; + size_t script_length{0}; std::array tas_data; - bool update_thread_running{true}; bool refresh_tas_fle{false}; bool is_recording{false}; bool is_running{false}; diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index e4383676a..b905fc73d 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -175,6 +175,9 @@ void PlayerControlPreview::ResetInputs() { } void PlayerControlPreview::UpdateInput() { + if (controller_callback.update != nullptr) { + controller_callback.update(std::move(true)); + } if (!is_enabled && !mapping_active) { return; } diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 822d033d1..a745699bf 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -41,7 +41,8 @@ void ControllerDialog::refreshConfiguration() { constexpr std::size_t player = 0; widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); widget->SetControllerType(players[player].controller_type); - ControllerCallback callback{[this](ControllerInput input) { InputController(input); }}; + ControllerCallback callback{[this](ControllerInput input) { InputController(input); }, + [this](bool update) { UpdateController(update); }}; widget->SetCallBack(callback); widget->repaint(); widget->SetConnectedStatus(players[player].connected); @@ -74,10 +75,6 @@ void ControllerDialog::hideEvent(QHideEvent* ev) { QWidget::hideEvent(ev); } -void ControllerDialog::RefreshTasFile() { - input_subsystem->GetTas()->RefreshTasFile(); -} - void ControllerDialog::InputController(ControllerInput input) { u32 buttons = 0; int index = 0; @@ -86,4 +83,11 @@ void ControllerDialog::InputController(ControllerInput input) { index++; } input_subsystem->GetTas()->RecordInput(buttons, input.axis_values); -} \ No newline at end of file +} + +void ControllerDialog::UpdateController(bool update) { + if (!update) { + return; + } + input_subsystem->GetTas()->UpdateThread(); +} diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h index f2f6653f7..448d24b67 100644 --- a/src/yuzu/debugger/controller.h +++ b/src/yuzu/debugger/controller.h @@ -25,6 +25,7 @@ struct ControllerInput { struct ControllerCallback { std::function input; + std::function update; }; class ControllerDialog : public QWidget { @@ -43,8 +44,8 @@ protected: void hideEvent(QHideEvent* ev) override; private: - void RefreshTasFile(); void InputController(ControllerInput input); + void UpdateController(bool update); QAction* toggle_view_action = nullptr; QFileSystemWatcher* watcher = nullptr; PlayerControlPreview* widget; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 820e31fa7..6c2835a2f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -196,7 +196,6 @@ GMainWindow::GMainWindow() config{std::make_unique()}, vfs{std::make_shared()}, provider{std::make_unique()} { Common::Log::Initialize(); - Settings::values.inputSubsystem = input_subsystem; LoadTranslation(); setAcceptDrops(true); @@ -2903,16 +2902,17 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie } } -static std::string GetTasStateDescription(TasInput::TasState state) { - switch (state) { +QString GMainWindow::GetTasStateDescription() const { + auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus(); + switch (tas_status) { case TasInput::TasState::Running: - return "Running"; + return tr("TAS state: Running %1/%2").arg(current_tas_frame).arg(total_tas_frames); case TasInput::TasState::Recording: - return "Recording"; + return tr("TAS state: Recording %1").arg(total_tas_frames); case TasInput::TasState::Stopped: - return "Stopped"; + return tr("TAS state: Idle %1/%2").arg(current_tas_frame).arg(total_tas_frames); default: - return "INVALID STATE"; + return tr("INVALID TAS STATE"); } } @@ -2923,12 +2923,7 @@ void GMainWindow::UpdateStatusBar() { } if (Settings::values.tas_enable) { - auto [tas_status, current_tas_frame, total_tas_frames] = - input_subsystem->GetTas()->GetStatus(); - tas_label->setText(tr("%1 TAS %2/%3") - .arg(tr(GetTasStateDescription(tas_status).c_str())) - .arg(current_tas_frame) - .arg(total_tas_frames)); + tas_label->setText(GetTasStateDescription()); } else { tas_label->clear(); } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 867a0003c..610b59ee5 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -301,6 +301,7 @@ private: void OpenURL(const QUrl& url); void LoadTranslation(); void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); + QString GetTasStateDescription() const; Ui::MainWindow ui; -- cgit v1.2.3 From e6c4bf52f0eb2c9c78e983ffbc667891463d3253 Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 27 Jun 2021 14:02:38 -0500 Subject: input_common/tas: Add swap controller --- src/common/fs/path_util.h | 2 +- src/input_common/main.h | 20 +++---- src/input_common/tas/tas_input.cpp | 63 +++++++++++++++++++--- src/input_common/tas/tas_input.h | 9 +++- src/yuzu/configuration/config.cpp | 5 ++ .../configure_input_player_widget.cpp | 34 ++++++------ src/yuzu/configuration/configure_tas.ui | 3 -- src/yuzu/main.cpp | 2 +- 8 files changed, 99 insertions(+), 39 deletions(-) (limited to 'src/common') diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 52e4670e2..0a9e3a145 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -23,7 +23,7 @@ enum class YuzuPath { ScreenshotsDir, // Where yuzu screenshots are stored. SDMCDir, // Where the emulated SDMC is stored. ShaderDir, // Where shaders are stored. - TASDir, // Where the current script file is stored. + TASDir, // Where TAS scripts are stored. }; /** diff --git a/src/input_common/main.h b/src/input_common/main.h index 1d06fc5f5..6390d3f09 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -155,28 +155,28 @@ public: /// Retrieves the underlying udp touch handler. [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const; - /// Retrieves the underlying GameCube button handler. + /// Retrieves the underlying mouse button handler. [[nodiscard]] MouseButtonFactory* GetMouseButtons(); - /// Retrieves the underlying GameCube button handler. + /// Retrieves the underlying mouse button handler. [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const; - /// Retrieves the underlying udp touch handler. + /// Retrieves the underlying mouse analog handler. [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs(); - /// Retrieves the underlying udp touch handler. + /// Retrieves the underlying mouse analog handler. [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const; - /// Retrieves the underlying udp motion handler. + /// Retrieves the underlying mouse motion handler. [[nodiscard]] MouseMotionFactory* GetMouseMotions(); - /// Retrieves the underlying udp motion handler. + /// Retrieves the underlying mouse motion handler. [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const; - /// Retrieves the underlying udp touch handler. + /// Retrieves the underlying mouse touch handler. [[nodiscard]] MouseTouchFactory* GetMouseTouch(); - /// Retrieves the underlying udp touch handler. + /// Retrieves the underlying mouse touch handler. [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const; /// Retrieves the underlying tas button handler. @@ -185,10 +185,10 @@ public: /// Retrieves the underlying tas button handler. [[nodiscard]] const TasButtonFactory* GetTasButtons() const; - /// Retrieves the underlying tas touch handler. + /// Retrieves the underlying tas analogs handler. [[nodiscard]] TasAnalogFactory* GetTasAnalogs(); - /// Retrieves the underlying tas touch handler. + /// Retrieves the underlying tas analogs handler. [[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const; /// Reloads the input devices diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp index eb3327520..aceb13adc 100644 --- a/src/input_common/tas/tas_input.cpp +++ b/src/input_common/tas/tas_input.cpp @@ -61,8 +61,8 @@ void Tas::LoadTasFile(size_t player_index) { commands[player_index].clear(); } std::string file = - Common::FS::ReadStringFromFile(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir) + - "script0-" + std::to_string(player_index + 1) + ".txt", + Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / + fmt::format("script0-{}.txt", player_index + 1), Common::FS::FileType::BinaryFile); std::stringstream command_line(file); std::string line; @@ -102,7 +102,7 @@ void Tas::LoadTasFile(size_t player_index) { 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; for (size_t frame = 0; frame < record_commands.size(); frame++) { if (!output_text.empty()) { @@ -112,8 +112,8 @@ void Tas::WriteTasFile(std::string file_name) { output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " + WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis); } - const size_t bytes_written = Common::FS::WriteStringToFile( - Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir) + file_name, + const auto bytes_written = Common::FS::WriteStringToFile( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name, Common::FS::FileType::TextFile, output_text); if (bytes_written == output_text.size()) { LOG_INFO(Input, "TAS file written to file!"); @@ -217,6 +217,9 @@ void Tas::UpdateThread() { is_running = Settings::values.tas_loop; current_command = 0; tas_data.fill({}); + if (!is_running) { + SwapToStoredController(); + } } } else { tas_data.fill({}); @@ -290,6 +293,52 @@ std::string Tas::WriteCommandButtons(u32 data) const { void Tas::StartStop() { 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(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(i)].Serialize(); + } + for (std::size_t i = 0; i < analogs.size(); ++i) { + analogs[i] = analog_mapping[static_cast(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() { @@ -308,9 +357,9 @@ void Tas::SaveRecording(bool overwrite_file) { if (record_commands.empty()) { return; } - WriteTasFile("record.txt"); + WriteTasFile(u8"record.txt"); if (overwrite_file) { - WriteTasFile("script0-1.txt"); + WriteTasFile(u8"script0-1.txt"); } needs_reset = true; record_commands.clear(); diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h index e0462e858..e1f351251 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/tas/tas_input.h @@ -7,6 +7,7 @@ #include #include "common/common_types.h" +#include "common/settings_input.h" #include "core/frontend/input.h" #include "input_common/main.h" @@ -91,7 +92,7 @@ private: }; void LoadTasFiles(); 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; u32 ReadCommandButtons(const std::string& line) const; std::string WriteCommandButtons(u32 data) const; @@ -105,6 +106,9 @@ private: std::string DebugInputs(const std::array& arr) const; std::string ButtonsToString(u32 button) const; + void SwapToTasController(); + void SwapToStoredController(); + size_t script_length{0}; std::array tas_data; bool is_recording{false}; @@ -114,5 +118,8 @@ private: std::vector record_commands{}; size_t current_command{0}; TASCommand last_input{}; // only used for recording + + // Old settings for swapping controllers + std::array player_mappings; }; } // namespace TasInput diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 99e318a8f..a0dfa06df 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -1205,6 +1205,11 @@ void Config::SaveControlValues() { WriteBasicSetting(Settings::values.emulate_analog_keyboard); 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(); } diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index b905fc73d..ba3720c03 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -175,10 +175,7 @@ void PlayerControlPreview::ResetInputs() { } void PlayerControlPreview::UpdateInput() { - if (controller_callback.update != nullptr) { - controller_callback.update(std::move(true)); - } - if (!is_enabled && !mapping_active) { + if (!is_enabled && !mapping_active && !Settings::values.tas_enable) { return; } bool input_changed = false; @@ -223,20 +220,25 @@ void PlayerControlPreview::UpdateInput() { } } - ControllerInput input{}; if (input_changed) { update(); - input.changed = true; - } - input.axis_values[Settings::NativeAnalog::LStick] = { - axis_values[Settings::NativeAnalog::LStick].value.x(), - axis_values[Settings::NativeAnalog::LStick].value.y()}; - 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)); + ControllerInput input{ + .axis_values = + {std::pair{axis_values[Settings::NativeAnalog::LStick].value.x(), + axis_values[Settings::NativeAnalog::LStick].value.y()}, + std::pair{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)); + } + } + + if (controller_callback.update != nullptr) { + controller_callback.update(std::move(true)); } if (mapping_active) { diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui index 906e073ff..445904d8f 100644 --- a/src/yuzu/configuration/configure_tas.ui +++ b/src/yuzu/configuration/configure_tas.ui @@ -31,9 +31,6 @@ - - false - Automatic controller profile swapping diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 560de89af..7d12fcca7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -2921,7 +2921,7 @@ QString GMainWindow::GetTasStateDescription() const { case TasInput::TasState::Stopped: return tr("TAS state: Idle %1/%2").arg(current_tas_frame).arg(total_tas_frames); default: - return tr("INVALID TAS STATE"); + return tr("TAS State: Invalid"); } } -- cgit v1.2.3 From 33a1d790e8a5f67c73d0eef4a141f936345f104f Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 5 Jul 2021 20:58:52 -0500 Subject: input_common/tas: Document the main class --- src/common/settings.h | 11 +-- src/input_common/tas/tas_input.cpp | 3 +- src/input_common/tas/tas_input.h | 108 +++++++++++++++++++++ src/input_common/tas/tas_poller.h | 4 +- src/yuzu/configuration/config.cpp | 39 ++++---- src/yuzu/configuration/configure_filesystem.cpp | 1 - .../configure_input_player_widget.cpp | 22 ++--- src/yuzu/configuration/configure_tas.cpp | 16 +-- 8 files changed, 153 insertions(+), 51 deletions(-) (limited to 'src/common') diff --git a/src/common/settings.h b/src/common/settings.h index cf12c325c..c53d5acc3 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -512,17 +512,14 @@ struct Values { "motion_device"}; BasicSetting udp_input_servers{"127.0.0.1:26760", "udp_input_servers"}; - BasicSetting pause_tas_on_load { false, "pause_tas_on_load" }; - BasicSetting tas_enable{ false, "tas_enable" }; - BasicSetting tas_loop{ false, "tas_loop" }; - BasicSetting tas_swap_controllers{ false, "tas_swap_controllers" }; - BasicSetting is_cpu_boosted{ false, "is_cpu_boosted" }; -" }; + BasicSetting pause_tas_on_load{true, "pause_tas_on_load"}; + BasicSetting tas_enable{false, "tas_enable"}; + BasicSetting tas_loop{false, "tas_loop"}; + BasicSetting tas_swap_controllers{true, "tas_swap_controllers"}; BasicSetting mouse_panning{false, "mouse_panning"}; BasicRangedSetting mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; BasicSetting mouse_enabled{false, "mouse_enabled"}; - std::string mouse_device; MouseButtonsRaw mouse_buttons; diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp index aceb13adc..877d35088 100644 --- a/src/input_common/tas/tas_input.cpp +++ b/src/input_common/tas/tas_input.cpp @@ -14,6 +14,7 @@ namespace TasInput { +// Supported keywords and buttons from a TAS file constexpr std::array, 20> text_to_tas_button = { std::pair{"KEY_A", TasButton::BUTTON_A}, {"KEY_B", TasButton::BUTTON_B}, @@ -214,7 +215,7 @@ void Tas::UpdateThread() { } } } else { - is_running = Settings::values.tas_loop; + is_running = Settings::values.tas_loop.GetValue(); current_command = 0; tas_data.fill({}); if (!is_running) { diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h index e1f351251..52d000db4 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/tas/tas_input.h @@ -11,6 +11,38 @@ #include "core/frontend/input.h" #include "input_common/main.h" +/* +To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below +Emulation -> Configure TAS. The file itself has normal text format and has to be called +script0-1.txt for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players). + +A script file has the same format as TAS-nx uses, so final files will look like this: + +1 KEY_B 0;0 0;0 +6 KEY_ZL 0;0 0;0 +41 KEY_ZL;KEY_Y 0;0 0;0 +43 KEY_X;KEY_A 32767;0 0;0 +44 KEY_A 32767;0 0;0 +45 KEY_A 32767;0 0;0 +46 KEY_A 32767;0 0;0 +47 KEY_A 32767;0 0;0 + +After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey +CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file +has. Playback can be started or stopped using CTRL+F5. + +However, for playback to actually work, the correct input device has to be selected: In the Controls +menu, select TAS from the device list for the controller that the script should be played on. + +Recording a new script file is really simple: Just make sure that the proper device (not TAS) is +connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke +again (CTRL+F7). The new script will be saved at the location previously selected, as the filename +record.txt. + +For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller +P1). +*/ + namespace TasInput { constexpr size_t PLAYER_NUMBER = 8; @@ -64,12 +96,26 @@ public: Tas(); ~Tas(); + // Changes the input status that will be stored in each frame void RecordInput(u32 buttons, const std::array, 2>& axes); + + // Main loop that records or executes input void UpdateThread(); + // Sets the flag to start or stop the TAS command excecution and swaps controllers profiles void StartStop(); + + // Sets the flag to reload the file and start from the begining in the next update void Reset(); + + /** + * Sets the flag to enable or disable recording of inputs + * @return Returns true if the current recording status is enabled + */ bool Record(); + + // Saves contents of record_commands on a file if overwrite is enabled player 1 will be + // overwritten with the recorded commands void SaveRecording(bool overwrite_file); /** @@ -80,7 +126,11 @@ public: * Total length of script file currently loaded or amount of frames (so far) for Recording */ std::tuple GetStatus() const; + + // Retuns an array of the default button mappings InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; + + // Retuns an array of the default analog mappings InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; [[nodiscard]] const TasData& GetTasState(std::size_t pad) const; @@ -90,23 +140,81 @@ private: TasAnalog l_axis{}; TasAnalog r_axis{}; }; + + // Loads TAS files from all players void LoadTasFiles(); + + // Loads TAS file from the specified player void LoadTasFile(size_t player_index); + + // Writes a TAS file from the recorded commands void WriteTasFile(std::u8string file_name); + + /** + * Parses a string containing the axis values with the following format "x;y" + * X and Y have a range from -32767 to 32767 + * @return Returns a TAS analog object with axis values with range from -1.0 to 1.0 + */ TasAnalog ReadCommandAxis(const std::string& line) const; + + /** + * Parses a string containing the button values with the following format "a;b;c;d..." + * Each button is represented by it's text format specified in text_to_tas_button array + * @return Returns a u32 with each bit representing the status of a button + */ u32 ReadCommandButtons(const std::string& line) const; + + /** + * Converts an u32 containing the button status into the text equivalent + * @return Returns a string with the name of the buttons to be written to the file + */ std::string WriteCommandButtons(u32 data) const; + + /** + * Converts an TAS analog object containing the axis status into the text equivalent + * @return Returns a string with the value of the axis to be written to the file + */ std::string WriteCommandAxis(TasAnalog data) const; + // Inverts the Y axis polarity std::pair FlipAxisY(std::pair old); + /** + * Converts an u32 containing the button status into the text equivalent + * @return Returns a string with the name of the buttons to be printed on console + */ std::string DebugButtons(u32 buttons) const; + + /** + * Converts an TAS analog object containing the axis status into the text equivalent + * @return Returns a string with the value of the axis to be printed on console + */ std::string DebugJoystick(float x, float y) const; + + /** + * Converts the given TAS status into the text equivalent + * @return Returns a string with the value of the TAS status to be printed on console + */ std::string DebugInput(const TasData& data) const; + + /** + * Converts the given TAS status of multiple players into the text equivalent + * @return Returns a string with the value of the status of all TAS players to be printed on + * console + */ std::string DebugInputs(const std::array& arr) const; + + /** + * Converts an u32 containing the button status into the text equivalent + * @return Returns a string with the name of the buttons + */ std::string ButtonsToString(u32 button) const; + // Stores current controller configuration and sets a TAS controller for every active controller + // to the current config void SwapToTasController(); + + // Sets the stored controller configuration to the current config void SwapToStoredController(); size_t script_length{0}; diff --git a/src/input_common/tas/tas_poller.h b/src/input_common/tas/tas_poller.h index 1bc0d173b..09e426cef 100644 --- a/src/input_common/tas/tas_poller.h +++ b/src/input_common/tas/tas_poller.h @@ -11,7 +11,7 @@ namespace InputCommon { /** - * A button device factory representing a mouse. It receives mouse events and forward them + * A button device factory representing a tas bot. It receives tas events and forward them * to all button devices it created. */ class TasButtonFactory final : public Input::Factory { @@ -29,7 +29,7 @@ private: std::shared_ptr tas_input; }; -/// An analog device factory that creates analog devices from mouse +/// An analog device factory that creates analog devices from tas class TasAnalogFactory final : public Input::Factory { public: explicit TasAnalogFactory(std::shared_ptr tas_input_); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index a0dfa06df..27b67fd9e 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -567,8 +567,10 @@ void Config::ReadControlValues() { Settings::values.mouse_panning = false; ReadBasicSetting(Settings::values.mouse_panning_sensitivity); - ReadBasicSetting(Settings::values.tas_enable = false); - ReadBasicSetting(Settings::values.tas_reset = false); + ReadBasicSetting(Settings::values.tas_enable); + ReadBasicSetting(Settings::values.tas_loop); + ReadBasicSetting(Settings::values.tas_swap_controllers); + ReadBasicSetting(Settings::values.pause_tas_on_load); ReadGlobalSetting(Settings::values.use_docked_mode); @@ -667,20 +669,16 @@ void Config::ReadDataStorageValues() { QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))) .toString() .toStdString()); - FS::SetYuzuPath( - FS::YuzuPath::TASFile, - qt_config - ->value(QStringLiteral("tas_path"), - QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASFile))) - .toString() - .toStdString()); - - ReadBasicSetting(Settings::values.pauseTasOnLoad); + FS::SetYuzuPath(FS::YuzuPath::TASDir, + qt_config + ->value(QStringLiteral("tas_directory"), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir))) + .toString() + .toStdString()); ReadBasicSetting(Settings::values.gamecard_inserted); ReadBasicSetting(Settings::values.gamecard_current_game); ReadBasicSetting(Settings::values.gamecard_path); - qt_config->endGroup(); } @@ -1205,11 +1203,11 @@ void Config::SaveControlValues() { WriteBasicSetting(Settings::values.emulate_analog_keyboard); 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); + WriteBasicSetting(Settings::values.tas_enable); + WriteBasicSetting(Settings::values.tas_loop); + WriteBasicSetting(Settings::values.tas_swap_controllers); + WriteBasicSetting(Settings::values.pause_tas_on_load); + qt_config->endGroup(); } @@ -1237,10 +1235,9 @@ void Config::SaveDataStorageValues() { WriteSetting(QStringLiteral("dump_directory"), QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)), QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); - WriteSetting(QStringLiteral("tas_path"), - QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASFile)), - QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASFile))); - WriteSetting(QStringLiteral("tas_pause_on_load"), Settings::values.pauseTasOnLoad, true); + WriteSetting(QStringLiteral("tas_directory"), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)), + QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir))); WriteBasicSetting(Settings::values.gamecard_inserted); WriteBasicSetting(Settings::values.gamecard_current_game); diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index 013de02db..9cb317822 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -52,7 +52,6 @@ void ConfigureFilesystem::setConfiguration() { ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted.GetValue()); ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game.GetValue()); - ui->tas_pause_on_load->setChecked(Settings::values.pauseTasOnLoad); ui->dump_exefs->setChecked(Settings::values.dump_exefs.GetValue()); ui->dump_nso->setChecked(Settings::values.dump_nso.GetValue()); diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index ba3720c03..38c59263c 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -222,23 +222,23 @@ void PlayerControlPreview::UpdateInput() { if (input_changed) { update(); - ControllerInput input{ - .axis_values = - {std::pair{axis_values[Settings::NativeAnalog::LStick].value.x(), - axis_values[Settings::NativeAnalog::LStick].value.y()}, - std::pair{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) { + ControllerInput input{ + .axis_values = {std::pair{ + axis_values[Settings::NativeAnalog::LStick].value.x(), + axis_values[Settings::NativeAnalog::LStick].value.y()}, + std::pair{ + axis_values[Settings::NativeAnalog::RStick].value.x(), + axis_values[Settings::NativeAnalog::RStick].value.y()}}, + .button_values = button_values, + .changed = true, + }; controller_callback.input(std::move(input)); } } if (controller_callback.update != nullptr) { - controller_callback.update(std::move(true)); + controller_callback.update(true); } if (mapping_active) { diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp index f2f91d84a..00d6c1ba5 100644 --- a/src/yuzu/configuration/configure_tas.cpp +++ b/src/yuzu/configuration/configure_tas.cpp @@ -30,18 +30,18 @@ ConfigureTasDialog::~ConfigureTasDialog() = default; void ConfigureTasDialog::LoadConfiguration() { ui->tas_path_edit->setText( QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir))); - ui->tas_enable->setChecked(Settings::values.tas_enable); - ui->tas_control_swap->setChecked(Settings::values.tas_swap_controllers); - ui->tas_loop_script->setChecked(Settings::values.tas_loop); - ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load); + ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue()); + ui->tas_control_swap->setChecked(Settings::values.tas_swap_controllers.GetValue()); + ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue()); + ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue()); } void ConfigureTasDialog::ApplyConfiguration() { Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASDir, ui->tas_path_edit->text().toStdString()); - Settings::values.tas_enable = ui->tas_enable->isChecked(); - Settings::values.tas_swap_controllers = ui->tas_control_swap->isChecked(); - Settings::values.tas_loop = ui->tas_loop_script->isChecked(); - Settings::values.pause_tas_on_load = ui->tas_pause_on_load->isChecked(); + Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked()); + Settings::values.tas_swap_controllers.SetValue(ui->tas_control_swap->isChecked()); + Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked()); + Settings::values.pause_tas_on_load.SetValue(ui->tas_pause_on_load->isChecked()); } void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) { -- cgit v1.2.3