diff options
-rw-r--r-- | src/input_common/main.cpp | 2 | ||||
-rw-r--r-- | src/input_common/tas/tas_input.cpp | 88 | ||||
-rw-r--r-- | src/input_common/tas/tas_input.h | 48 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_tas.cpp | 2 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_tas.ui | 47 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_vibration.cpp | 2 | ||||
-rw-r--r-- | src/yuzu/debugger/controller.cpp | 2 | ||||
-rw-r--r-- | src/yuzu/main.cpp | 21 | ||||
-rw-r--r-- | src/yuzu/main.h | 2 | ||||
-rw-r--r-- | src/yuzu/main.ui | 2 |
10 files changed, 148 insertions, 68 deletions
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 3b9906b53..18d7d8817 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -117,7 +117,7 @@ struct InputSubsystem::Impl { Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, }; if (Settings::values.tas_enable) { - devices.push_back( + devices.emplace_back( Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}}); } #ifdef HAVE_SDL2 diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp index 877d35088..1598092b6 100644 --- a/src/input_common/tas/tas_input.cpp +++ b/src/input_common/tas/tas_input.cpp @@ -40,12 +40,15 @@ constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_but Tas::Tas() { if (!Settings::values.tas_enable) { + needs_reset = true; return; } LoadTasFiles(); } -Tas::~Tas() = default; +Tas::~Tas() { + Stop(); +}; void Tas::LoadTasFiles() { script_length = 0; @@ -184,6 +187,9 @@ std::string Tas::ButtonsToString(u32 button) const { void Tas::UpdateThread() { if (!Settings::values.tas_enable) { + if (is_running) { + Stop(); + } return; } @@ -196,34 +202,35 @@ void Tas::UpdateThread() { LoadTasFiles(); LOG_DEBUG(Input, "tas_reset done"); } - if (is_running) { - if (current_command < script_length) { - LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); - size_t frame = current_command++; - 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; - 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 { - is_running = Settings::values.tas_loop.GetValue(); - current_command = 0; - tas_data.fill({}); - if (!is_running) { - SwapToStoredController(); + + if (!is_running) { + tas_data.fill({}); + return; + } + if (current_command < script_length) { + LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); + size_t frame = current_command++; + 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; + 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 { + is_running = Settings::values.tas_loop.GetValue(); + current_command = 0; tas_data.fill({}); + if (!is_running) { + SwapToStoredController(); + } } LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data)); } @@ -237,8 +244,8 @@ TasAnalog Tas::ReadCommandAxis(const std::string& line) const { seglist.push_back(segment); } - const float x = std::stof(seglist.at(0)) / 32767.f; - const float y = std::stof(seglist.at(1)) / 32767.f; + const float x = std::stof(seglist.at(0)) / 32767.0f; + const float y = std::stof(seglist.at(1)) / 32767.0f; return {x, y}; } @@ -293,14 +300,22 @@ std::string Tas::WriteCommandButtons(u32 data) const { } void Tas::StartStop() { - is_running = !is_running; + if (!Settings::values.tas_enable) { + return; + } if (is_running) { - SwapToTasController(); + Stop(); } else { - SwapToStoredController(); + is_running = true; + SwapToTasController(); } } +void Tas::Stop() { + is_running = false; + SwapToStoredController(); +} + void Tas::SwapToTasController() { if (!Settings::values.tas_swap_controllers) { return; @@ -315,7 +330,8 @@ void Tas::SwapToTasController() { continue; } - auto tas_param = Common::ParamPackage{{"pad", static_cast<u8>(index)}}; + Common::ParamPackage tas_param; + tas_param.Set("pad", static_cast<u8>(index)); auto button_mapping = GetButtonMappingForDevice(tas_param); auto analog_mapping = GetAnalogMappingForDevice(tas_param); auto& buttons = player.buttons; @@ -328,25 +344,33 @@ void Tas::SwapToTasController() { analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize(); } } + is_old_input_saved = true; Settings::values.is_device_reload_pending.store(true); } void Tas::SwapToStoredController() { - if (!Settings::values.tas_swap_controllers) { + if (!is_old_input_saved) { return; } auto& players = Settings::values.players.GetValue(); for (std::size_t index = 0; index < players.size(); index++) { players[index] = player_mappings[index]; } + is_old_input_saved = false; Settings::values.is_device_reload_pending.store(true); } void Tas::Reset() { + if (!Settings::values.tas_enable) { + return; + } needs_reset = true; } bool Tas::Record() { + if (!Settings::values.tas_enable) { + return true; + } is_recording = !is_recording; return is_recording; } diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h index 52d000db4..3e2db8f00 100644 --- a/src/input_common/tas/tas_input.h +++ b/src/input_common/tas/tas_input.h @@ -13,8 +13,8 @@ /* 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). +Tools -> 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: @@ -56,26 +56,26 @@ enum class TasState { }; enum class TasButton : u32 { - BUTTON_A = 0x000001, - BUTTON_B = 0x000002, - BUTTON_X = 0x000004, - BUTTON_Y = 0x000008, - STICK_L = 0x000010, - STICK_R = 0x000020, - TRIGGER_L = 0x000040, - TRIGGER_R = 0x000080, - TRIGGER_ZL = 0x000100, - TRIGGER_ZR = 0x000200, - BUTTON_PLUS = 0x000400, - BUTTON_MINUS = 0x000800, - BUTTON_LEFT = 0x001000, - BUTTON_UP = 0x002000, - BUTTON_RIGHT = 0x004000, - BUTTON_DOWN = 0x008000, - BUTTON_SL = 0x010000, - BUTTON_SR = 0x020000, - BUTTON_HOME = 0x040000, - BUTTON_CAPTURE = 0x080000, + BUTTON_A = 1U << 0, + BUTTON_B = 1U << 1, + BUTTON_X = 1U << 2, + BUTTON_Y = 1U << 3, + STICK_L = 1U << 4, + STICK_R = 1U << 5, + TRIGGER_L = 1U << 6, + TRIGGER_R = 1U << 7, + TRIGGER_ZL = 1U << 8, + TRIGGER_ZR = 1U << 9, + BUTTON_PLUS = 1U << 10, + BUTTON_MINUS = 1U << 11, + BUTTON_LEFT = 1U << 12, + BUTTON_UP = 1U << 13, + BUTTON_RIGHT = 1U << 14, + BUTTON_DOWN = 1U << 15, + BUTTON_SL = 1U << 16, + BUTTON_SR = 1U << 17, + BUTTON_HOME = 1U << 18, + BUTTON_CAPTURE = 1U << 19, }; enum class TasAxes : u8 { @@ -105,6 +105,9 @@ public: // Sets the flag to start or stop the TAS command excecution and swaps controllers profiles void StartStop(); + // Stop the TAS and reverts any controller profile + void Stop(); + // Sets the flag to reload the file and start from the begining in the next update void Reset(); @@ -219,6 +222,7 @@ private: size_t script_length{0}; std::array<TasData, PLAYER_NUMBER> tas_data; + bool is_old_input_saved{false}; bool is_recording{false}; bool is_running{false}; bool needs_reset{false}; diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp index 00d6c1ba5..b666b175a 100644 --- a/src/yuzu/configuration/configure_tas.cpp +++ b/src/yuzu/configuration/configure_tas.cpp @@ -55,7 +55,7 @@ void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) { QString str = QFileDialog::getExistingDirectory(this, caption, edit->text()); - if (str.isNull() || str.isEmpty()) { + if (str.isEmpty()) { return; } diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui index 445904d8f..8a3ecb834 100644 --- a/src/yuzu/configuration/configure_tas.ui +++ b/src/yuzu/configuration/configure_tas.ui @@ -19,7 +19,50 @@ <item> <widget class="QGroupBox" name="groupBox"> <property name="title"> - <string>TAS Settings</string> + <string>TAS</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="1"> + <widget class="QLabel" name="label_1"> + <property name="text"> + <string>Reads controller input from scripts in the same format as TAS-nx scripts. For a more detailed explanation please consult the FAQ on the yuzu website.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0" colspan="1"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (General -> Hotkeys).</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0" colspan="1"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>WARNING: This is an experimental feature. It will not play back scripts frame perfectly with the current, imperfect syncing method.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Settings</string> </property> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0" colspan="4"> @@ -63,7 +106,7 @@ <item> <widget class="QGroupBox" name="groupBox"> <property name="title"> - <string>TAS Directories</string> + <string>Script Directory</string> </property> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp index 9d92c4949..46a0f3025 100644 --- a/src/yuzu/configuration/configure_vibration.cpp +++ b/src/yuzu/configuration/configure_vibration.cpp @@ -99,7 +99,7 @@ void ConfigureVibration::SetVibrationDevices(std::size_t player_index) { const auto guid = param.Get("guid", ""); const auto port = param.Get("port", ""); - if (engine.empty() || engine == "keyboard" || engine == "mouse") { + if (engine.empty() || engine == "keyboard" || engine == "mouse" || engine == "tas") { continue; } diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 296000ed5..5a844409b 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp @@ -78,7 +78,7 @@ void ControllerDialog::InputController(ControllerInput input) { u32 buttons = 0; int index = 0; for (bool btn : input.button_values) { - buttons += (btn ? 1 : 0) << index; + buttons |= (btn ? 1U : 0U) << index; index++; } input_subsystem->GetTas()->RecordInput(buttons, input.axis_values); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ea77caad5..3c2824362 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1022,18 +1022,25 @@ void GMainWindow::InitializeHotkeys() { } }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this), - &QShortcut::activated, this, [&] { input_subsystem->GetTas()->StartStop(); }); + &QShortcut::activated, this, [&] { + if (!emulation_running) { + return; + } + input_subsystem->GetTas()->StartStop(); + }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), &QShortcut::activated, this, [&] { + if (!emulation_running) { + return; + } bool is_recording = input_subsystem->GetTas()->Record(); if (!is_recording) { - QMessageBox::StandardButton reply; - reply = QMessageBox::question(this, tr("TAS Recording"), - tr("Overwrite file of player 1?"), - QMessageBox::Yes | QMessageBox::No); - input_subsystem->GetTas()->SaveRecording(reply == QMessageBox::Yes); + const auto res = QMessageBox::question(this, tr("TAS Recording"), + tr("Overwrite file of player 1?"), + QMessageBox::Yes | QMessageBox::No); + input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes); } }); } @@ -1487,6 +1494,8 @@ void GMainWindow::ShutdownGame() { game_list->show(); } game_list->SetFilterFocus(); + tas_label->clear(); + input_subsystem->GetTas()->Stop(); render_window->removeEventFilter(render_window); render_window->setAttribute(Qt::WA_Hover, false); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 610b59ee5..36eed6103 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -320,7 +320,7 @@ private: QLabel* emu_speed_label = nullptr; QLabel* game_fps_label = nullptr; QLabel* emu_frametime_label = nullptr; - QLabel* TASlabel; + QLabel* tas_label = nullptr; QPushButton* gpu_accuracy_button = nullptr; QPushButton* renderer_status_button = nullptr; QPushButton* dock_status_button = nullptr; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 31c1a20f3..653c010d8 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -72,7 +72,6 @@ <addaction name="action_Restart"/> <addaction name="separator"/> <addaction name="action_Configure"/> - <addaction name="action_Configure_Tas"/> <addaction name="action_Configure_Current_Game"/> </widget> <widget class="QMenu" name="menu_View"> @@ -101,6 +100,7 @@ <addaction name="action_Rederive"/> <addaction name="separator"/> <addaction name="action_Capture_Screenshot"/> + <addaction name="action_Configure_Tas"/> </widget> <widget class="QMenu" name="menu_Help"> <property name="title"> |