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 ++ 2 files changed, 4 insertions(+) (limited to 'src/common/fs') 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. }; /** -- 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/fs') 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/fs') 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 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/fs') 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