summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/input_common/main.cpp2
-rw-r--r--src/input_common/tas/tas_input.cpp88
-rw-r--r--src/input_common/tas/tas_input.h48
-rw-r--r--src/yuzu/configuration/configure_tas.cpp2
-rw-r--r--src/yuzu/configuration/configure_tas.ui47
-rw-r--r--src/yuzu/configuration/configure_vibration.cpp2
-rw-r--r--src/yuzu/debugger/controller.cpp2
-rw-r--r--src/yuzu/main.cpp21
-rw-r--r--src/yuzu/main.h2
-rw-r--r--src/yuzu/main.ui2
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 {
117 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, 117 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
118 }; 118 };
119 if (Settings::values.tas_enable) { 119 if (Settings::values.tas_enable) {
120 devices.push_back( 120 devices.emplace_back(
121 Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}}); 121 Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}});
122 } 122 }
123#ifdef HAVE_SDL2 123#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
40 40
41Tas::Tas() { 41Tas::Tas() {
42 if (!Settings::values.tas_enable) { 42 if (!Settings::values.tas_enable) {
43 needs_reset = true;
43 return; 44 return;
44 } 45 }
45 LoadTasFiles(); 46 LoadTasFiles();
46} 47}
47 48
48Tas::~Tas() = default; 49Tas::~Tas() {
50 Stop();
51};
49 52
50void Tas::LoadTasFiles() { 53void Tas::LoadTasFiles() {
51 script_length = 0; 54 script_length = 0;
@@ -184,6 +187,9 @@ std::string Tas::ButtonsToString(u32 button) const {
184 187
185void Tas::UpdateThread() { 188void Tas::UpdateThread() {
186 if (!Settings::values.tas_enable) { 189 if (!Settings::values.tas_enable) {
190 if (is_running) {
191 Stop();
192 }
187 return; 193 return;
188 } 194 }
189 195
@@ -196,34 +202,35 @@ void Tas::UpdateThread() {
196 LoadTasFiles(); 202 LoadTasFiles();
197 LOG_DEBUG(Input, "tas_reset done"); 203 LOG_DEBUG(Input, "tas_reset done");
198 } 204 }
199 if (is_running) { 205
200 if (current_command < script_length) { 206 if (!is_running) {
201 LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); 207 tas_data.fill({});
202 size_t frame = current_command++; 208 return;
203 for (size_t i = 0; i < commands.size(); i++) { 209 }
204 if (frame < commands[i].size()) { 210 if (current_command < script_length) {
205 TASCommand command = commands[i][frame]; 211 LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
206 tas_data[i].buttons = command.buttons; 212 size_t frame = current_command++;
207 auto [l_axis_x, l_axis_y] = command.l_axis; 213 for (size_t i = 0; i < commands.size(); i++) {
208 tas_data[i].axis[0] = l_axis_x; 214 if (frame < commands[i].size()) {
209 tas_data[i].axis[1] = l_axis_y; 215 TASCommand command = commands[i][frame];
210 auto [r_axis_x, r_axis_y] = command.r_axis; 216 tas_data[i].buttons = command.buttons;
211 tas_data[i].axis[2] = r_axis_x; 217 auto [l_axis_x, l_axis_y] = command.l_axis;
212 tas_data[i].axis[3] = r_axis_y; 218 tas_data[i].axis[0] = l_axis_x;
213 } else { 219 tas_data[i].axis[1] = l_axis_y;
214 tas_data[i] = {}; 220 auto [r_axis_x, r_axis_y] = command.r_axis;
215 } 221 tas_data[i].axis[2] = r_axis_x;
216 } 222 tas_data[i].axis[3] = r_axis_y;
217 } else { 223 } else {
218 is_running = Settings::values.tas_loop.GetValue(); 224 tas_data[i] = {};
219 current_command = 0;
220 tas_data.fill({});
221 if (!is_running) {
222 SwapToStoredController();
223 } 225 }
224 } 226 }
225 } else { 227 } else {
228 is_running = Settings::values.tas_loop.GetValue();
229 current_command = 0;
226 tas_data.fill({}); 230 tas_data.fill({});
231 if (!is_running) {
232 SwapToStoredController();
233 }
227 } 234 }
228 LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data)); 235 LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data));
229} 236}
@@ -237,8 +244,8 @@ TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
237 seglist.push_back(segment); 244 seglist.push_back(segment);
238 } 245 }
239 246
240 const float x = std::stof(seglist.at(0)) / 32767.f; 247 const float x = std::stof(seglist.at(0)) / 32767.0f;
241 const float y = std::stof(seglist.at(1)) / 32767.f; 248 const float y = std::stof(seglist.at(1)) / 32767.0f;
242 249
243 return {x, y}; 250 return {x, y};
244} 251}
@@ -293,14 +300,22 @@ std::string Tas::WriteCommandButtons(u32 data) const {
293} 300}
294 301
295void Tas::StartStop() { 302void Tas::StartStop() {
296 is_running = !is_running; 303 if (!Settings::values.tas_enable) {
304 return;
305 }
297 if (is_running) { 306 if (is_running) {
298 SwapToTasController(); 307 Stop();
299 } else { 308 } else {
300 SwapToStoredController(); 309 is_running = true;
310 SwapToTasController();
301 } 311 }
302} 312}
303 313
314void Tas::Stop() {
315 is_running = false;
316 SwapToStoredController();
317}
318
304void Tas::SwapToTasController() { 319void Tas::SwapToTasController() {
305 if (!Settings::values.tas_swap_controllers) { 320 if (!Settings::values.tas_swap_controllers) {
306 return; 321 return;
@@ -315,7 +330,8 @@ void Tas::SwapToTasController() {
315 continue; 330 continue;
316 } 331 }
317 332
318 auto tas_param = Common::ParamPackage{{"pad", static_cast<u8>(index)}}; 333 Common::ParamPackage tas_param;
334 tas_param.Set("pad", static_cast<u8>(index));
319 auto button_mapping = GetButtonMappingForDevice(tas_param); 335 auto button_mapping = GetButtonMappingForDevice(tas_param);
320 auto analog_mapping = GetAnalogMappingForDevice(tas_param); 336 auto analog_mapping = GetAnalogMappingForDevice(tas_param);
321 auto& buttons = player.buttons; 337 auto& buttons = player.buttons;
@@ -328,25 +344,33 @@ void Tas::SwapToTasController() {
328 analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize(); 344 analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize();
329 } 345 }
330 } 346 }
347 is_old_input_saved = true;
331 Settings::values.is_device_reload_pending.store(true); 348 Settings::values.is_device_reload_pending.store(true);
332} 349}
333 350
334void Tas::SwapToStoredController() { 351void Tas::SwapToStoredController() {
335 if (!Settings::values.tas_swap_controllers) { 352 if (!is_old_input_saved) {
336 return; 353 return;
337 } 354 }
338 auto& players = Settings::values.players.GetValue(); 355 auto& players = Settings::values.players.GetValue();
339 for (std::size_t index = 0; index < players.size(); index++) { 356 for (std::size_t index = 0; index < players.size(); index++) {
340 players[index] = player_mappings[index]; 357 players[index] = player_mappings[index];
341 } 358 }
359 is_old_input_saved = false;
342 Settings::values.is_device_reload_pending.store(true); 360 Settings::values.is_device_reload_pending.store(true);
343} 361}
344 362
345void Tas::Reset() { 363void Tas::Reset() {
364 if (!Settings::values.tas_enable) {
365 return;
366 }
346 needs_reset = true; 367 needs_reset = true;
347} 368}
348 369
349bool Tas::Record() { 370bool Tas::Record() {
371 if (!Settings::values.tas_enable) {
372 return true;
373 }
350 is_recording = !is_recording; 374 is_recording = !is_recording;
351 return is_recording; 375 return is_recording;
352} 376}
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 @@
13 13
14/* 14/*
15To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below 15To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below
16Emulation -> Configure TAS. The file itself has normal text format and has to be called 16Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt
17script0-1.txt for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players). 17for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players).
18 18
19A script file has the same format as TAS-nx uses, so final files will look like this: 19A script file has the same format as TAS-nx uses, so final files will look like this:
20 20
@@ -56,26 +56,26 @@ enum class TasState {
56}; 56};
57 57
58enum class TasButton : u32 { 58enum class TasButton : u32 {
59 BUTTON_A = 0x000001, 59 BUTTON_A = 1U << 0,
60 BUTTON_B = 0x000002, 60 BUTTON_B = 1U << 1,
61 BUTTON_X = 0x000004, 61 BUTTON_X = 1U << 2,
62 BUTTON_Y = 0x000008, 62 BUTTON_Y = 1U << 3,
63 STICK_L = 0x000010, 63 STICK_L = 1U << 4,
64 STICK_R = 0x000020, 64 STICK_R = 1U << 5,
65 TRIGGER_L = 0x000040, 65 TRIGGER_L = 1U << 6,
66 TRIGGER_R = 0x000080, 66 TRIGGER_R = 1U << 7,
67 TRIGGER_ZL = 0x000100, 67 TRIGGER_ZL = 1U << 8,
68 TRIGGER_ZR = 0x000200, 68 TRIGGER_ZR = 1U << 9,
69 BUTTON_PLUS = 0x000400, 69 BUTTON_PLUS = 1U << 10,
70 BUTTON_MINUS = 0x000800, 70 BUTTON_MINUS = 1U << 11,
71 BUTTON_LEFT = 0x001000, 71 BUTTON_LEFT = 1U << 12,
72 BUTTON_UP = 0x002000, 72 BUTTON_UP = 1U << 13,
73 BUTTON_RIGHT = 0x004000, 73 BUTTON_RIGHT = 1U << 14,
74 BUTTON_DOWN = 0x008000, 74 BUTTON_DOWN = 1U << 15,
75 BUTTON_SL = 0x010000, 75 BUTTON_SL = 1U << 16,
76 BUTTON_SR = 0x020000, 76 BUTTON_SR = 1U << 17,
77 BUTTON_HOME = 0x040000, 77 BUTTON_HOME = 1U << 18,
78 BUTTON_CAPTURE = 0x080000, 78 BUTTON_CAPTURE = 1U << 19,
79}; 79};
80 80
81enum class TasAxes : u8 { 81enum class TasAxes : u8 {
@@ -105,6 +105,9 @@ public:
105 // Sets the flag to start or stop the TAS command excecution and swaps controllers profiles 105 // Sets the flag to start or stop the TAS command excecution and swaps controllers profiles
106 void StartStop(); 106 void StartStop();
107 107
108 // Stop the TAS and reverts any controller profile
109 void Stop();
110
108 // Sets the flag to reload the file and start from the begining in the next update 111 // Sets the flag to reload the file and start from the begining in the next update
109 void Reset(); 112 void Reset();
110 113
@@ -219,6 +222,7 @@ private:
219 222
220 size_t script_length{0}; 223 size_t script_length{0};
221 std::array<TasData, PLAYER_NUMBER> tas_data; 224 std::array<TasData, PLAYER_NUMBER> tas_data;
225 bool is_old_input_saved{false};
222 bool is_recording{false}; 226 bool is_recording{false};
223 bool is_running{false}; 227 bool is_running{false};
224 bool needs_reset{false}; 228 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) {
55 55
56 QString str = QFileDialog::getExistingDirectory(this, caption, edit->text()); 56 QString str = QFileDialog::getExistingDirectory(this, caption, edit->text());
57 57
58 if (str.isNull() || str.isEmpty()) { 58 if (str.isEmpty()) {
59 return; 59 return;
60 } 60 }
61 61
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 @@
19 <item> 19 <item>
20 <widget class="QGroupBox" name="groupBox"> 20 <widget class="QGroupBox" name="groupBox">
21 <property name="title"> 21 <property name="title">
22 <string>TAS Settings</string> 22 <string>TAS</string>
23 </property>
24 <layout class="QGridLayout" name="gridLayout">
25 <item row="0" column="0" colspan="1">
26 <widget class="QLabel" name="label_1">
27 <property name="text">
28 <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>
29 </property>
30 <property name="wordWrap">
31 <bool>true</bool>
32 </property>
33 </widget>
34 </item>
35 <item row="1" column="0" colspan="1">
36 <widget class="QLabel" name="label_2">
37 <property name="text">
38 <string>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (General -> Hotkeys).</string>
39 </property>
40 <property name="wordWrap">
41 <bool>true</bool>
42 </property>
43 </widget>
44 </item>
45 <item row="2" column="0" colspan="1">
46 <widget class="QLabel" name="label_2">
47 <property name="text">
48 <string>WARNING: This is an experimental feature. It will not play back scripts frame perfectly with the current, imperfect syncing method.</string>
49 </property>
50 <property name="wordWrap">
51 <bool>true</bool>
52 </property>
53 </widget>
54 </item>
55 </layout>
56 </widget>
57 </item>
58 </layout>
59 </item>
60 <item>
61 <layout class="QHBoxLayout" name="horizontalLayout">
62 <item>
63 <widget class="QGroupBox" name="groupBox">
64 <property name="title">
65 <string>Settings</string>
23 </property> 66 </property>
24 <layout class="QGridLayout" name="gridLayout"> 67 <layout class="QGridLayout" name="gridLayout">
25 <item row="0" column="0" colspan="4"> 68 <item row="0" column="0" colspan="4">
@@ -63,7 +106,7 @@
63 <item> 106 <item>
64 <widget class="QGroupBox" name="groupBox"> 107 <widget class="QGroupBox" name="groupBox">
65 <property name="title"> 108 <property name="title">
66 <string>TAS Directories</string> 109 <string>Script Directory</string>
67 </property> 110 </property>
68 <layout class="QGridLayout" name="gridLayout"> 111 <layout class="QGridLayout" name="gridLayout">
69 <item row="0" column="0"> 112 <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) {
99 const auto guid = param.Get("guid", ""); 99 const auto guid = param.Get("guid", "");
100 const auto port = param.Get("port", ""); 100 const auto port = param.Get("port", "");
101 101
102 if (engine.empty() || engine == "keyboard" || engine == "mouse") { 102 if (engine.empty() || engine == "keyboard" || engine == "mouse" || engine == "tas") {
103 continue; 103 continue;
104 } 104 }
105 105
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) {
78 u32 buttons = 0; 78 u32 buttons = 0;
79 int index = 0; 79 int index = 0;
80 for (bool btn : input.button_values) { 80 for (bool btn : input.button_values) {
81 buttons += (btn ? 1 : 0) << index; 81 buttons |= (btn ? 1U : 0U) << index;
82 index++; 82 index++;
83 } 83 }
84 input_subsystem->GetTas()->RecordInput(buttons, input.axis_values); 84 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() {
1022 } 1022 }
1023 }); 1023 });
1024 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this), 1024 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this),
1025 &QShortcut::activated, this, [&] { input_subsystem->GetTas()->StartStop(); }); 1025 &QShortcut::activated, this, [&] {
1026 if (!emulation_running) {
1027 return;
1028 }
1029 input_subsystem->GetTas()->StartStop();
1030 });
1026 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), 1031 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this),
1027 &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); }); 1032 &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); });
1028 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), 1033 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this),
1029 &QShortcut::activated, this, [&] { 1034 &QShortcut::activated, this, [&] {
1035 if (!emulation_running) {
1036 return;
1037 }
1030 bool is_recording = input_subsystem->GetTas()->Record(); 1038 bool is_recording = input_subsystem->GetTas()->Record();
1031 if (!is_recording) { 1039 if (!is_recording) {
1032 QMessageBox::StandardButton reply; 1040 const auto res = QMessageBox::question(this, tr("TAS Recording"),
1033 reply = QMessageBox::question(this, tr("TAS Recording"), 1041 tr("Overwrite file of player 1?"),
1034 tr("Overwrite file of player 1?"), 1042 QMessageBox::Yes | QMessageBox::No);
1035 QMessageBox::Yes | QMessageBox::No); 1043 input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
1036 input_subsystem->GetTas()->SaveRecording(reply == QMessageBox::Yes);
1037 } 1044 }
1038 }); 1045 });
1039} 1046}
@@ -1487,6 +1494,8 @@ void GMainWindow::ShutdownGame() {
1487 game_list->show(); 1494 game_list->show();
1488 } 1495 }
1489 game_list->SetFilterFocus(); 1496 game_list->SetFilterFocus();
1497 tas_label->clear();
1498 input_subsystem->GetTas()->Stop();
1490 1499
1491 render_window->removeEventFilter(render_window); 1500 render_window->removeEventFilter(render_window);
1492 render_window->setAttribute(Qt::WA_Hover, false); 1501 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:
320 QLabel* emu_speed_label = nullptr; 320 QLabel* emu_speed_label = nullptr;
321 QLabel* game_fps_label = nullptr; 321 QLabel* game_fps_label = nullptr;
322 QLabel* emu_frametime_label = nullptr; 322 QLabel* emu_frametime_label = nullptr;
323 QLabel* TASlabel; 323 QLabel* tas_label = nullptr;
324 QPushButton* gpu_accuracy_button = nullptr; 324 QPushButton* gpu_accuracy_button = nullptr;
325 QPushButton* renderer_status_button = nullptr; 325 QPushButton* renderer_status_button = nullptr;
326 QPushButton* dock_status_button = nullptr; 326 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 @@
72 <addaction name="action_Restart"/> 72 <addaction name="action_Restart"/>
73 <addaction name="separator"/> 73 <addaction name="separator"/>
74 <addaction name="action_Configure"/> 74 <addaction name="action_Configure"/>
75 <addaction name="action_Configure_Tas"/>
76 <addaction name="action_Configure_Current_Game"/> 75 <addaction name="action_Configure_Current_Game"/>
77 </widget> 76 </widget>
78 <widget class="QMenu" name="menu_View"> 77 <widget class="QMenu" name="menu_View">
@@ -101,6 +100,7 @@
101 <addaction name="action_Rederive"/> 100 <addaction name="action_Rederive"/>
102 <addaction name="separator"/> 101 <addaction name="separator"/>
103 <addaction name="action_Capture_Screenshot"/> 102 <addaction name="action_Capture_Screenshot"/>
103 <addaction name="action_Configure_Tas"/>
104 </widget> 104 </widget>
105 <widget class="QMenu" name="menu_Help"> 105 <widget class="QMenu" name="menu_Help">
106 <property name="title"> 106 <property name="title">