summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar MonsterDruide12021-06-18 16:15:42 +0200
committerGravatar MonsterDruide12021-09-18 23:22:00 +0200
commitb42c3ce21db249d5e3bc04b4f73202e757da317c (patch)
treec5ccf9e311d2a675b0e2afff3d85ed4654047b5e /src
parentMerge pull request #7020 from Moonlacer/remove_audio_stretching (diff)
downloadyuzu-b42c3ce21db249d5e3bc04b4f73202e757da317c.tar.gz
yuzu-b42c3ce21db249d5e3bc04b4f73202e757da317c.tar.xz
yuzu-b42c3ce21db249d5e3bc04b4f73202e757da317c.zip
input_common/tas: Base playback & recording system
The base playback system supports up to 8 controllers (specified by `PLAYER_NUMBER` in `tas_input.h`), which all change their inputs simulataneously when `TAS::UpdateThread` is called. The recording system uses the controller debugger to read the state of the first controller and forwards that data to the TASing system for recording. Currently, this process sadly is not frame-perfect and pixel-accurate. Co-authored-by: Naii-the-Baf <sfabian200@gmail.com> Co-authored-by: Narr-the-Reg <juangerman-13@hotmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/common/settings.h7
-rw-r--r--src/input_common/CMakeLists.txt4
-rw-r--r--src/input_common/main.cpp48
-rw-r--r--src/input_common/main.h23
-rw-r--r--src/input_common/tas/tas_input.cpp340
-rw-r--r--src/input_common/tas/tas_input.h163
-rw-r--r--src/input_common/tas/tas_poller.cpp101
-rw-r--r--src/input_common/tas/tas_poller.h43
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp30
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.cpp16
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.h3
-rw-r--r--src/yuzu/debugger/controller.cpp22
-rw-r--r--src/yuzu/debugger/controller.h24
-rw-r--r--src/yuzu/main.cpp3
14 files changed, 818 insertions, 9 deletions
diff --git a/src/common/settings.h b/src/common/settings.h
index b1bddb895..29b888f10 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -14,6 +14,7 @@
14#include <utility> 14#include <utility>
15#include <vector> 15#include <vector>
16 16
17#include <input_common/main.h>
17#include "common/common_types.h" 18#include "common/common_types.h"
18#include "common/settings_input.h" 19#include "common/settings_input.h"
19 20
@@ -499,6 +500,7 @@ struct Values {
499 500
500 // Controls 501 // Controls
501 InputSetting<std::array<PlayerInput, 10>> players; 502 InputSetting<std::array<PlayerInput, 10>> players;
503 std::shared_ptr<InputCommon::InputSubsystem> inputSubsystem = NULL;
502 504
503 Setting<bool> use_docked_mode{true, "use_docked_mode"}; 505 Setting<bool> use_docked_mode{true, "use_docked_mode"};
504 506
@@ -512,9 +514,14 @@ struct Values {
512 "motion_device"}; 514 "motion_device"};
513 BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"}; 515 BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
514 516
517 BasicSetting<bool> tas_enable{false, "tas_enable"};
518 BasicSetting<bool> tas_reset{ false, "tas_reset" };
519 BasicSetting<bool> tas_record{ false, "tas_record" };
520
515 BasicSetting<bool> mouse_panning{false, "mouse_panning"}; 521 BasicSetting<bool> mouse_panning{false, "mouse_panning"};
516 BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"}; 522 BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
517 BasicSetting<bool> mouse_enabled{false, "mouse_enabled"}; 523 BasicSetting<bool> mouse_enabled{false, "mouse_enabled"};
524
518 std::string mouse_device; 525 std::string mouse_device;
519 MouseButtonsRaw mouse_buttons; 526 MouseButtonsRaw mouse_buttons;
520 527
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index c4283a952..dd13d948f 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -21,6 +21,10 @@ add_library(input_common STATIC
21 mouse/mouse_poller.h 21 mouse/mouse_poller.h
22 sdl/sdl.cpp 22 sdl/sdl.cpp
23 sdl/sdl.h 23 sdl/sdl.h
24 tas/tas_input.cpp
25 tas/tas_input.h
26 tas/tas_poller.cpp
27 tas/tas_poller.h
24 udp/client.cpp 28 udp/client.cpp
25 udp/client.h 29 udp/client.h
26 udp/protocol.cpp 30 udp/protocol.cpp
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index ff23230f0..4f170493e 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -13,6 +13,8 @@
13#include "input_common/motion_from_button.h" 13#include "input_common/motion_from_button.h"
14#include "input_common/mouse/mouse_input.h" 14#include "input_common/mouse/mouse_input.h"
15#include "input_common/mouse/mouse_poller.h" 15#include "input_common/mouse/mouse_poller.h"
16#include "input_common/tas/tas_input.h"
17#include "input_common/tas/tas_poller.h"
16#include "input_common/touch_from_button.h" 18#include "input_common/touch_from_button.h"
17#include "input_common/udp/client.h" 19#include "input_common/udp/client.h"
18#include "input_common/udp/udp.h" 20#include "input_common/udp/udp.h"
@@ -60,6 +62,12 @@ struct InputSubsystem::Impl {
60 Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion); 62 Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion);
61 mousetouch = std::make_shared<MouseTouchFactory>(mouse); 63 mousetouch = std::make_shared<MouseTouchFactory>(mouse);
62 Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch); 64 Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch);
65
66 tas = std::make_shared<TasInput::Tas>();
67 tasbuttons = std::make_shared<TasButtonFactory>(tas);
68 Input::RegisterFactory<Input::ButtonDevice>("tas", tasbuttons);
69 tasanalog = std::make_shared<TasAnalogFactory>(tas);
70 Input::RegisterFactory<Input::AnalogDevice>("tas", tasanalog);
63 } 71 }
64 72
65 void Shutdown() { 73 void Shutdown() {
@@ -94,12 +102,19 @@ struct InputSubsystem::Impl {
94 mouseanalog.reset(); 102 mouseanalog.reset();
95 mousemotion.reset(); 103 mousemotion.reset();
96 mousetouch.reset(); 104 mousetouch.reset();
105
106 Input::UnregisterFactory<Input::ButtonDevice>("tas");
107 Input::UnregisterFactory<Input::AnalogDevice>("tas");
108
109 tasbuttons.reset();
110 tasanalog.reset();
97 } 111 }
98 112
99 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { 113 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
100 std::vector<Common::ParamPackage> devices = { 114 std::vector<Common::ParamPackage> devices = {
101 Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, 115 Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
102 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, 116 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
117 Common::ParamPackage{{"display", "TAS"}, {"class", "tas"}},
103 }; 118 };
104#ifdef HAVE_SDL2 119#ifdef HAVE_SDL2
105 auto sdl_devices = sdl->GetInputDevices(); 120 auto sdl_devices = sdl->GetInputDevices();
@@ -120,6 +135,9 @@ struct InputSubsystem::Impl {
120 if (params.Get("class", "") == "gcpad") { 135 if (params.Get("class", "") == "gcpad") {
121 return gcadapter->GetAnalogMappingForDevice(params); 136 return gcadapter->GetAnalogMappingForDevice(params);
122 } 137 }
138 if (params.Get("class", "") == "tas") {
139 return tas->GetAnalogMappingForDevice(params);
140 }
123#ifdef HAVE_SDL2 141#ifdef HAVE_SDL2
124 if (params.Get("class", "") == "sdl") { 142 if (params.Get("class", "") == "sdl") {
125 return sdl->GetAnalogMappingForDevice(params); 143 return sdl->GetAnalogMappingForDevice(params);
@@ -136,6 +154,9 @@ struct InputSubsystem::Impl {
136 if (params.Get("class", "") == "gcpad") { 154 if (params.Get("class", "") == "gcpad") {
137 return gcadapter->GetButtonMappingForDevice(params); 155 return gcadapter->GetButtonMappingForDevice(params);
138 } 156 }
157 if (params.Get("class", "") == "tas") {
158 return tas->GetButtonMappingForDevice(params);
159 }
139#ifdef HAVE_SDL2 160#ifdef HAVE_SDL2
140 if (params.Get("class", "") == "sdl") { 161 if (params.Get("class", "") == "sdl") {
141 return sdl->GetButtonMappingForDevice(params); 162 return sdl->GetButtonMappingForDevice(params);
@@ -174,9 +195,12 @@ struct InputSubsystem::Impl {
174 std::shared_ptr<MouseAnalogFactory> mouseanalog; 195 std::shared_ptr<MouseAnalogFactory> mouseanalog;
175 std::shared_ptr<MouseMotionFactory> mousemotion; 196 std::shared_ptr<MouseMotionFactory> mousemotion;
176 std::shared_ptr<MouseTouchFactory> mousetouch; 197 std::shared_ptr<MouseTouchFactory> mousetouch;
198 std::shared_ptr<TasButtonFactory> tasbuttons;
199 std::shared_ptr<TasAnalogFactory> tasanalog;
177 std::shared_ptr<CemuhookUDP::Client> udp; 200 std::shared_ptr<CemuhookUDP::Client> udp;
178 std::shared_ptr<GCAdapter::Adapter> gcadapter; 201 std::shared_ptr<GCAdapter::Adapter> gcadapter;
179 std::shared_ptr<MouseInput::Mouse> mouse; 202 std::shared_ptr<MouseInput::Mouse> mouse;
203 std::shared_ptr<TasInput::Tas> tas;
180}; 204};
181 205
182InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} 206InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -207,6 +231,14 @@ const MouseInput::Mouse* InputSubsystem::GetMouse() const {
207 return impl->mouse.get(); 231 return impl->mouse.get();
208} 232}
209 233
234TasInput::Tas* InputSubsystem::GetTas() {
235 return impl->tas.get();
236}
237
238const TasInput::Tas* InputSubsystem::GetTas() const {
239 return impl->tas.get();
240}
241
210std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { 242std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
211 return impl->GetInputDevices(); 243 return impl->GetInputDevices();
212} 244}
@@ -287,6 +319,22 @@ const MouseTouchFactory* InputSubsystem::GetMouseTouch() const {
287 return impl->mousetouch.get(); 319 return impl->mousetouch.get();
288} 320}
289 321
322TasButtonFactory* InputSubsystem::GetTasButtons() {
323 return impl->tasbuttons.get();
324}
325
326const TasButtonFactory* InputSubsystem::GetTasButtons() const {
327 return impl->tasbuttons.get();
328}
329
330TasAnalogFactory* InputSubsystem::GetTasAnalogs() {
331 return impl->tasanalog.get();
332}
333
334const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const {
335 return impl->tasanalog.get();
336}
337
290void InputSubsystem::ReloadInputDevices() { 338void InputSubsystem::ReloadInputDevices() {
291 if (!impl->udp) { 339 if (!impl->udp) {
292 return; 340 return;
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 5d6f26385..1d06fc5f5 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -29,6 +29,10 @@ namespace MouseInput {
29class Mouse; 29class Mouse;
30} 30}
31 31
32namespace TasInput {
33class Tas;
34}
35
32namespace InputCommon { 36namespace InputCommon {
33namespace Polling { 37namespace Polling {
34 38
@@ -64,6 +68,8 @@ class MouseButtonFactory;
64class MouseAnalogFactory; 68class MouseAnalogFactory;
65class MouseMotionFactory; 69class MouseMotionFactory;
66class MouseTouchFactory; 70class MouseTouchFactory;
71class TasButtonFactory;
72class TasAnalogFactory;
67class Keyboard; 73class Keyboard;
68 74
69/** 75/**
@@ -103,6 +109,11 @@ public:
103 /// Retrieves the underlying mouse device. 109 /// Retrieves the underlying mouse device.
104 [[nodiscard]] const MouseInput::Mouse* GetMouse() const; 110 [[nodiscard]] const MouseInput::Mouse* GetMouse() const;
105 111
112 /// Retrieves the underlying tas device.
113 [[nodiscard]] TasInput::Tas* GetTas();
114
115 /// Retrieves the underlying tas device.
116 [[nodiscard]] const TasInput::Tas* GetTas() const;
106 /** 117 /**
107 * Returns all available input devices that this Factory can create a new device with. 118 * Returns all available input devices that this Factory can create a new device with.
108 * Each returned ParamPackage should have a `display` field used for display, a class field for 119 * Each returned ParamPackage should have a `display` field used for display, a class field for
@@ -168,6 +179,18 @@ public:
168 /// Retrieves the underlying udp touch handler. 179 /// Retrieves the underlying udp touch handler.
169 [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const; 180 [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const;
170 181
182 /// Retrieves the underlying tas button handler.
183 [[nodiscard]] TasButtonFactory* GetTasButtons();
184
185 /// Retrieves the underlying tas button handler.
186 [[nodiscard]] const TasButtonFactory* GetTasButtons() const;
187
188 /// Retrieves the underlying tas touch handler.
189 [[nodiscard]] TasAnalogFactory* GetTasAnalogs();
190
191 /// Retrieves the underlying tas touch handler.
192 [[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const;
193
171 /// Reloads the input devices 194 /// Reloads the input devices
172 void ReloadInputDevices(); 195 void ReloadInputDevices();
173 196
diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp
new file mode 100644
index 000000000..343641945
--- /dev/null
+++ b/src/input_common/tas/tas_input.cpp
@@ -0,0 +1,340 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <cstring>
7#include <functional>
8#include <random>
9#include <regex>
10#include <thread>
11#include <boost/asio.hpp>
12
13#include "common/fs/file.h"
14#include "common/fs/fs_types.h"
15#include "common/fs/path_util.h"
16#include "common/logging/log.h"
17#include "common/settings.h"
18#include "input_common/tas/tas_input.h"
19
20namespace TasInput {
21
22Tas::Tas() {
23 LoadTasFiles();
24}
25
26Tas::~Tas() {
27 update_thread_running = false;
28}
29
30void Tas::RefreshTasFile() {
31 refresh_tas_fle = true;
32}
33void Tas::LoadTasFiles() {
34 scriptLength = 0;
35 for (int i = 0; i < PLAYER_NUMBER; i++) {
36 LoadTasFile(i);
37 if (newCommands[i].size() > scriptLength)
38 scriptLength = newCommands[i].size();
39 }
40}
41void Tas::LoadTasFile(int playerIndex) {
42 LOG_DEBUG(Input, "LoadTasFile()");
43 if (!newCommands[playerIndex].empty()) {
44 newCommands[playerIndex].clear();
45 }
46 std::string file = Common::FS::ReadStringFromFile(
47 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "script0-" +
48 std::to_string(playerIndex + 1) + ".txt",
49 Common::FS::FileType::BinaryFile);
50 std::stringstream command_line(file);
51 std::string line;
52 int frameNo = 0;
53 TASCommand empty = {.buttons = 0, .l_axis = {0.f, 0.f}, .r_axis = {0.f, 0.f}};
54 while (std::getline(command_line, line, '\n')) {
55 if (line.empty())
56 continue;
57 LOG_DEBUG(Input, "Loading line: {}", line);
58 std::smatch m;
59
60 std::stringstream linestream(line);
61 std::string segment;
62 std::vector<std::string> seglist;
63
64 while (std::getline(linestream, segment, ' ')) {
65 seglist.push_back(segment);
66 }
67
68 if (seglist.size() < 4)
69 continue;
70
71 while (frameNo < std::stoi(seglist.at(0))) {
72 newCommands[playerIndex].push_back(empty);
73 frameNo++;
74 }
75
76 TASCommand command = {
77 .buttons = ReadCommandButtons(seglist.at(1)),
78 .l_axis = ReadCommandAxis(seglist.at(2)),
79 .r_axis = ReadCommandAxis(seglist.at(3)),
80 };
81 newCommands[playerIndex].push_back(command);
82 frameNo++;
83 }
84 LOG_INFO(Input, "TAS file loaded! {} frames", frameNo);
85}
86
87void Tas::WriteTasFile() {
88 LOG_DEBUG(Input, "WriteTasFile()");
89 std::string output_text = "";
90 for (int frame = 0; frame < (signed)recordCommands.size(); frame++) {
91 if (!output_text.empty())
92 output_text += "\n";
93 TASCommand line = recordCommands.at(frame);
94 output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
95 WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
96 }
97 size_t bytesWritten = Common::FS::WriteStringToFile(
98 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "record.txt",
99 Common::FS::FileType::TextFile, output_text);
100 if (bytesWritten == output_text.size())
101 LOG_INFO(Input, "TAS file written to file!");
102 else
103 LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytesWritten,
104 output_text.size());
105}
106
107void Tas::RecordInput(u32 buttons, std::array<std::pair<float, float>, 2> axes) {
108 lastInput = {buttons, flipY(axes[0]), flipY(axes[1])};
109}
110
111std::pair<float, float> Tas::flipY(std::pair<float, float> old) const {
112 auto [x, y] = old;
113 return {x, -y};
114}
115
116std::string Tas::GetStatusDescription() {
117 if (Settings::values.tas_record) {
118 return "Recording TAS: " + std::to_string(recordCommands.size());
119 }
120 if (Settings::values.tas_enable) {
121 return "Playing TAS: " + std::to_string(current_command) + "/" +
122 std::to_string(scriptLength);
123 }
124 return "TAS not running: " + std::to_string(current_command) + "/" +
125 std::to_string(scriptLength);
126}
127
128std::string debugButtons(u32 buttons) {
129 return "{ " + TasInput::Tas::buttonsToString(buttons) + " }";
130}
131
132std::string debugJoystick(float x, float y) {
133 return "[ " + std::to_string(x) + "," + std::to_string(y) + " ]";
134}
135
136std::string debugInput(TasData data) {
137 return "{ " + debugButtons(data.buttons) + " , " + debugJoystick(data.axis[0], data.axis[1]) +
138 " , " + debugJoystick(data.axis[2], data.axis[3]) + " }";
139}
140
141std::string debugInputs(std::array<TasData, PLAYER_NUMBER> arr) {
142 std::string returns = "[ ";
143 for (size_t i = 0; i < arr.size(); i++) {
144 returns += debugInput(arr[i]);
145 if (i != arr.size() - 1)
146 returns += " , ";
147 }
148 return returns + "]";
149}
150
151void Tas::UpdateThread() {
152 if (update_thread_running) {
153 if (Settings::values.pauseTasOnLoad && Settings::values.cpuBoosted) {
154 for (int i = 0; i < PLAYER_NUMBER; i++) {
155 tas_data[i].buttons = 0;
156 tas_data[i].axis = {};
157 }
158 }
159
160 if (Settings::values.tas_record) {
161 recordCommands.push_back(lastInput);
162 }
163 if (!Settings::values.tas_record && !recordCommands.empty()) {
164 WriteTasFile();
165 Settings::values.tas_reset = true;
166 refresh_tas_fle = true;
167 recordCommands.clear();
168 }
169 if (Settings::values.tas_reset) {
170 current_command = 0;
171 if (refresh_tas_fle) {
172 LoadTasFiles();
173 refresh_tas_fle = false;
174 }
175 Settings::values.tas_reset = false;
176 LoadTasFiles();
177 LOG_DEBUG(Input, "tas_reset done");
178 }
179 if (Settings::values.tas_enable) {
180 if ((signed)current_command < scriptLength) {
181 LOG_INFO(Input, "Playing TAS {}/{}", current_command, scriptLength);
182 size_t frame = current_command++;
183 for (int i = 0; i < PLAYER_NUMBER; i++) {
184 if (frame < newCommands[i].size()) {
185 TASCommand command = newCommands[i][frame];
186 tas_data[i].buttons = command.buttons;
187 auto [l_axis_x, l_axis_y] = command.l_axis;
188 tas_data[i].axis[0] = l_axis_x;
189 tas_data[i].axis[1] = l_axis_y;
190 auto [r_axis_x, r_axis_y] = command.r_axis;
191 tas_data[i].axis[2] = r_axis_x;
192 tas_data[i].axis[3] = r_axis_y;
193 } else {
194 tas_data[i].buttons = 0;
195 tas_data[i].axis = {};
196 }
197 }
198 } else {
199 Settings::values.tas_enable = false;
200 current_command = 0;
201 for (int i = 0; i < PLAYER_NUMBER; i++) {
202 tas_data[i].buttons = 0;
203 tas_data[i].axis = {};
204 }
205 }
206 } else {
207 for (int i = 0; i < PLAYER_NUMBER; i++) {
208 tas_data[i].buttons = 0;
209 tas_data[i].axis = {};
210 }
211 }
212 }
213 LOG_DEBUG(Input, "TAS inputs: {}", debugInputs(tas_data));
214}
215
216TasAnalog Tas::ReadCommandAxis(const std::string line) const {
217 std::stringstream linestream(line);
218 std::string segment;
219 std::vector<std::string> seglist;
220
221 while (std::getline(linestream, segment, ';')) {
222 seglist.push_back(segment);
223 }
224
225 const float x = std::stof(seglist.at(0)) / 32767.f;
226 const float y = std::stof(seglist.at(1)) / 32767.f;
227
228 return {x, y};
229}
230
231u32 Tas::ReadCommandButtons(const std::string data) const {
232 std::stringstream button_text(data);
233 std::string line;
234 u32 buttons = 0;
235 while (std::getline(button_text, line, ';')) {
236 for (auto [text, tas_button] : text_to_tas_button) {
237 if (text == line) {
238 buttons |= static_cast<u32>(tas_button);
239 break;
240 }
241 }
242 }
243 return buttons;
244}
245
246std::string Tas::WriteCommandAxis(TasAnalog data) const {
247 auto [x, y] = data;
248 std::string line;
249 line += std::to_string(static_cast<int>(x * 32767));
250 line += ";";
251 line += std::to_string(static_cast<int>(y * 32767));
252 return line;
253}
254
255std::string Tas::WriteCommandButtons(u32 data) const {
256 if (data == 0)
257 return "NONE";
258
259 std::string line;
260 u32 index = 0;
261 while (data > 0) {
262 if ((data & 1) == 1) {
263 for (auto [text, tas_button] : text_to_tas_button) {
264 if (tas_button == static_cast<TasButton>(1 << index)) {
265 if (line.size() > 0)
266 line += ";";
267 line += text;
268 break;
269 }
270 }
271 }
272 index++;
273 data >>= 1;
274 }
275 return line;
276}
277
278InputCommon::ButtonMapping Tas::GetButtonMappingForDevice(
279 const Common::ParamPackage& params) const {
280 // This list is missing ZL/ZR since those are not considered buttons.
281 // We will add those afterwards
282 // This list also excludes any button that can't be really mapped
283 static constexpr std::array<std::pair<Settings::NativeButton::Values, TasButton>, 20>
284 switch_to_tas_button = {
285 std::pair{Settings::NativeButton::A, TasButton::BUTTON_A},
286 {Settings::NativeButton::B, TasButton::BUTTON_B},
287 {Settings::NativeButton::X, TasButton::BUTTON_X},
288 {Settings::NativeButton::Y, TasButton::BUTTON_Y},
289 {Settings::NativeButton::LStick, TasButton::STICK_L},
290 {Settings::NativeButton::RStick, TasButton::STICK_R},
291 {Settings::NativeButton::L, TasButton::TRIGGER_L},
292 {Settings::NativeButton::R, TasButton::TRIGGER_R},
293 {Settings::NativeButton::Plus, TasButton::BUTTON_PLUS},
294 {Settings::NativeButton::Minus, TasButton::BUTTON_MINUS},
295 {Settings::NativeButton::DLeft, TasButton::BUTTON_LEFT},
296 {Settings::NativeButton::DUp, TasButton::BUTTON_UP},
297 {Settings::NativeButton::DRight, TasButton::BUTTON_RIGHT},
298 {Settings::NativeButton::DDown, TasButton::BUTTON_DOWN},
299 {Settings::NativeButton::SL, TasButton::BUTTON_SL},
300 {Settings::NativeButton::SR, TasButton::BUTTON_SR},
301 {Settings::NativeButton::Screenshot, TasButton::BUTTON_CAPTURE},
302 {Settings::NativeButton::Home, TasButton::BUTTON_HOME},
303 {Settings::NativeButton::ZL, TasButton::TRIGGER_ZL},
304 {Settings::NativeButton::ZR, TasButton::TRIGGER_ZR},
305 };
306
307 InputCommon::ButtonMapping mapping{};
308 for (const auto& [switch_button, tas_button] : switch_to_tas_button) {
309 Common::ParamPackage button_params({{"engine", "tas"}});
310 button_params.Set("pad", params.Get("pad", 0));
311 button_params.Set("button", static_cast<int>(tas_button));
312 mapping.insert_or_assign(switch_button, std::move(button_params));
313 }
314
315 return mapping;
316}
317
318InputCommon::AnalogMapping Tas::GetAnalogMappingForDevice(
319 const Common::ParamPackage& params) const {
320
321 InputCommon::AnalogMapping mapping = {};
322 Common::ParamPackage left_analog_params;
323 left_analog_params.Set("engine", "tas");
324 left_analog_params.Set("pad", params.Get("pad", 0));
325 left_analog_params.Set("axis_x", static_cast<int>(TasAxes::StickX));
326 left_analog_params.Set("axis_y", static_cast<int>(TasAxes::StickY));
327 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
328 Common::ParamPackage right_analog_params;
329 right_analog_params.Set("engine", "tas");
330 right_analog_params.Set("pad", params.Get("pad", 0));
331 right_analog_params.Set("axis_x", static_cast<int>(TasAxes::SubstickX));
332 right_analog_params.Set("axis_y", static_cast<int>(TasAxes::SubstickY));
333 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
334 return mapping;
335}
336
337const TasData& Tas::GetTasState(std::size_t pad) const {
338 return tas_data[pad];
339}
340} // namespace TasInput
diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h
new file mode 100644
index 000000000..0a152a04f
--- /dev/null
+++ b/src/input_common/tas/tas_input.h
@@ -0,0 +1,163 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <mutex>
9#include <thread>
10
11#include "common/common_types.h"
12#include "core/frontend/input.h"
13#include "input_common/main.h"
14
15#define PLAYER_NUMBER 8
16
17namespace TasInput {
18
19using TasAnalog = std::tuple<float, float>;
20
21enum class TasButton : u32 {
22 BUTTON_A = 0x000001,
23 BUTTON_B = 0x000002,
24 BUTTON_X = 0x000004,
25 BUTTON_Y = 0x000008,
26 STICK_L = 0x000010,
27 STICK_R = 0x000020,
28 TRIGGER_L = 0x000040,
29 TRIGGER_R = 0x000080,
30 TRIGGER_ZL = 0x000100,
31 TRIGGER_ZR = 0x000200,
32 BUTTON_PLUS = 0x000400,
33 BUTTON_MINUS = 0x000800,
34 BUTTON_LEFT = 0x001000,
35 BUTTON_UP = 0x002000,
36 BUTTON_RIGHT = 0x004000,
37 BUTTON_DOWN = 0x008000,
38 BUTTON_SL = 0x010000,
39 BUTTON_SR = 0x020000,
40 BUTTON_HOME = 0x040000,
41 BUTTON_CAPTURE = 0x080000,
42};
43
44static const std::array<std::pair<std::string, TasButton>, 20> text_to_tas_button = {
45 std::pair{"KEY_A", TasButton::BUTTON_A},
46 {"KEY_B", TasButton::BUTTON_B},
47 {"KEY_X", TasButton::BUTTON_X},
48 {"KEY_Y", TasButton::BUTTON_Y},
49 {"KEY_LSTICK", TasButton::STICK_L},
50 {"KEY_RSTICK", TasButton::STICK_R},
51 {"KEY_L", TasButton::TRIGGER_L},
52 {"KEY_R", TasButton::TRIGGER_R},
53 {"KEY_PLUS", TasButton::BUTTON_PLUS},
54 {"KEY_MINUS", TasButton::BUTTON_MINUS},
55 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
56 {"KEY_DUP", TasButton::BUTTON_UP},
57 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
58 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
59 {"KEY_SL", TasButton::BUTTON_SL},
60 {"KEY_SR", TasButton::BUTTON_SR},
61 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
62 {"KEY_HOME", TasButton::BUTTON_HOME},
63 {"KEY_ZL", TasButton::TRIGGER_ZL},
64 {"KEY_ZR", TasButton::TRIGGER_ZR},
65};
66
67enum class TasAxes : u8 {
68 StickX,
69 StickY,
70 SubstickX,
71 SubstickY,
72 Undefined,
73};
74
75struct TasData {
76 u32 buttons{};
77 std::array<float, 4> axis{};
78};
79
80class Tas {
81public:
82 Tas();
83 ~Tas();
84
85 static std::string buttonsToString(u32 button) {
86 std::string returns;
87 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_A)) != 0)
88 returns += ", A";
89 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_B)) != 0)
90 returns += ", B";
91 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_X)) != 0)
92 returns += ", X";
93 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_Y)) != 0)
94 returns += ", Y";
95 if ((button & static_cast<u32>(TasInput::TasButton::STICK_L)) != 0)
96 returns += ", STICK_L";
97 if ((button & static_cast<u32>(TasInput::TasButton::STICK_R)) != 0)
98 returns += ", STICK_R";
99 if ((button & static_cast<u32>(TasInput::TasButton::TRIGGER_L)) != 0)
100 returns += ", TRIGGER_L";
101 if ((button & static_cast<u32>(TasInput::TasButton::TRIGGER_R)) != 0)
102 returns += ", TRIGGER_R";
103 if ((button & static_cast<u32>(TasInput::TasButton::TRIGGER_ZL)) != 0)
104 returns += ", TRIGGER_ZL";
105 if ((button & static_cast<u32>(TasInput::TasButton::TRIGGER_ZR)) != 0)
106 returns += ", TRIGGER_ZR";
107 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_PLUS)) != 0)
108 returns += ", PLUS";
109 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_MINUS)) != 0)
110 returns += ", MINUS";
111 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_LEFT)) != 0)
112 returns += ", LEFT";
113 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_UP)) != 0)
114 returns += ", UP";
115 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_RIGHT)) != 0)
116 returns += ", RIGHT";
117 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_DOWN)) != 0)
118 returns += ", DOWN";
119 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_SL)) != 0)
120 returns += ", SL";
121 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_SR)) != 0)
122 returns += ", SR";
123 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_HOME)) != 0)
124 returns += ", HOME";
125 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_CAPTURE)) != 0)
126 returns += ", CAPTURE";
127 return returns.length() != 0 ? returns.substr(2) : "";
128 }
129
130 void RefreshTasFile();
131 void LoadTasFiles();
132 void RecordInput(u32 buttons, std::array<std::pair<float, float>, 2> axes);
133 void UpdateThread();
134 std::string GetStatusDescription();
135
136 InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
137 InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
138 [[nodiscard]] const TasData& GetTasState(std::size_t pad) const;
139
140private:
141 struct TASCommand {
142 u32 buttons{};
143 TasAnalog l_axis{};
144 TasAnalog r_axis{};
145 };
146 void LoadTasFile(int playerIndex);
147 void WriteTasFile();
148 TasAnalog ReadCommandAxis(const std::string line) const;
149 u32 ReadCommandButtons(const std::string line) const;
150 std::string WriteCommandButtons(u32 data) const;
151 std::string WriteCommandAxis(TasAnalog data) const;
152 std::pair<float, float> flipY(std::pair<float, float> old) const;
153
154 size_t scriptLength{0};
155 std::array<TasData, PLAYER_NUMBER> tas_data;
156 bool update_thread_running{true};
157 bool refresh_tas_fle{false};
158 std::array<std::vector<TASCommand>, PLAYER_NUMBER> newCommands{};
159 std::vector<TASCommand> recordCommands{};
160 std::size_t current_command{0};
161 TASCommand lastInput{}; // only used for recording
162};
163} // namespace TasInput
diff --git a/src/input_common/tas/tas_poller.cpp b/src/input_common/tas/tas_poller.cpp
new file mode 100644
index 000000000..15810d6b0
--- /dev/null
+++ b/src/input_common/tas/tas_poller.cpp
@@ -0,0 +1,101 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <mutex>
6#include <utility>
7
8#include "common/settings.h"
9#include "common/threadsafe_queue.h"
10#include "input_common/tas/tas_input.h"
11#include "input_common/tas/tas_poller.h"
12
13namespace InputCommon {
14
15class TasButton final : public Input::ButtonDevice {
16public:
17 explicit TasButton(u32 button_, u32 pad_, const TasInput::Tas* tas_input_)
18 : button(button_), pad(pad_), tas_input(tas_input_) {}
19
20 bool GetStatus() const override {
21 return (tas_input->GetTasState(pad).buttons & button) != 0;
22 }
23
24private:
25 const u32 button;
26 const u32 pad;
27 const TasInput::Tas* tas_input;
28};
29
30TasButtonFactory::TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_)
31 : tas_input(std::move(tas_input_)) {}
32
33std::unique_ptr<Input::ButtonDevice> TasButtonFactory::Create(const Common::ParamPackage& params) {
34 const auto button_id = params.Get("button", 0);
35 const auto pad = params.Get("pad", 0);
36
37 return std::make_unique<TasButton>(button_id, pad, tas_input.get());
38}
39
40class TasAnalog final : public Input::AnalogDevice {
41public:
42 explicit TasAnalog(u32 pad_, u32 axis_x_, u32 axis_y_, const TasInput::Tas* tas_input_)
43 : pad(pad_), axis_x(axis_x_), axis_y(axis_y_), tas_input(tas_input_) {}
44
45 float GetAxis(u32 axis) const {
46 std::lock_guard lock{mutex};
47 return tas_input->GetTasState(pad).axis.at(axis);
48 }
49
50 std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
51 float x = GetAxis(analog_axis_x);
52 float y = GetAxis(analog_axis_y);
53
54 // Make sure the coordinates are in the unit circle,
55 // otherwise normalize it.
56 float r = x * x + y * y;
57 if (r > 1.0f) {
58 r = std::sqrt(r);
59 x /= r;
60 y /= r;
61 }
62
63 return {x, y};
64 }
65
66 std::tuple<float, float> GetStatus() const override {
67 return GetAnalog(axis_x, axis_y);
68 }
69
70 Input::AnalogProperties GetAnalogProperties() const override {
71 return {0.0f, 1.0f, 0.5f};
72 }
73
74private:
75 const u32 pad;
76 const u32 axis_x;
77 const u32 axis_y;
78 const TasInput::Tas* tas_input;
79 mutable std::mutex mutex;
80};
81
82/// An analog device factory that creates analog devices from GC Adapter
83TasAnalogFactory::TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_)
84 : tas_input(std::move(tas_input_)) {}
85
86/**
87 * Creates analog device from joystick axes
88 * @param params contains parameters for creating the device:
89 * - "port": the nth gcpad on the adapter
90 * - "axis_x": the index of the axis to be bind as x-axis
91 * - "axis_y": the index of the axis to be bind as y-axis
92 */
93std::unique_ptr<Input::AnalogDevice> TasAnalogFactory::Create(const Common::ParamPackage& params) {
94 const auto pad = static_cast<u32>(params.Get("pad", 0));
95 const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
96 const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
97
98 return std::make_unique<TasAnalog>(pad, axis_x, axis_y, tas_input.get());
99}
100
101} // namespace InputCommon
diff --git a/src/input_common/tas/tas_poller.h b/src/input_common/tas/tas_poller.h
new file mode 100644
index 000000000..1bc0d173b
--- /dev/null
+++ b/src/input_common/tas/tas_poller.h
@@ -0,0 +1,43 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/tas/tas_input.h"
10
11namespace InputCommon {
12
13/**
14 * A button device factory representing a mouse. It receives mouse events and forward them
15 * to all button devices it created.
16 */
17class TasButtonFactory final : public Input::Factory<Input::ButtonDevice> {
18public:
19 explicit TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_);
20
21 /**
22 * Creates a button device from a button press
23 * @param params contains parameters for creating the device:
24 * - "code": the code of the key to bind with the button
25 */
26 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
27
28private:
29 std::shared_ptr<TasInput::Tas> tas_input;
30};
31
32/// An analog device factory that creates analog devices from mouse
33class TasAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
34public:
35 explicit TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_);
36
37 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
38
39private:
40 std::shared_ptr<TasInput::Tas> tas_input;
41};
42
43} // namespace InputCommon
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 7527c068b..88f4bf388 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -124,6 +124,19 @@ QString ButtonToText(const Common::ParamPackage& param) {
124 return GetKeyName(param.Get("code", 0)); 124 return GetKeyName(param.Get("code", 0));
125 } 125 }
126 126
127 if (param.Get("engine", "") == "tas") {
128 if (param.Has("axis")) {
129 const QString axis_str = QString::fromStdString(param.Get("axis", ""));
130
131 return QObject::tr("TAS Axis %1").arg(axis_str);
132 }
133 if (param.Has("button")) {
134 const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
135 return QObject::tr("TAS Btn %1").arg(button_str);
136 }
137 return GetKeyName(param.Get("code", 0));
138 }
139
127 if (param.Get("engine", "") == "cemuhookudp") { 140 if (param.Get("engine", "") == "cemuhookudp") {
128 if (param.Has("pad_index")) { 141 if (param.Has("pad_index")) {
129 const QString motion_str = QString::fromStdString(param.Get("pad_index", "")); 142 const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
@@ -187,7 +200,8 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir)
187 const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); 200 const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
188 const bool invert_x = param.Get("invert_x", "+") == "-"; 201 const bool invert_x = param.Get("invert_x", "+") == "-";
189 const bool invert_y = param.Get("invert_y", "+") == "-"; 202 const bool invert_y = param.Get("invert_y", "+") == "-";
190 if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse") { 203 if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse" ||
204 engine_str == "tas") {
191 if (dir == "modifier") { 205 if (dir == "modifier") {
192 return QObject::tr("[unused]"); 206 return QObject::tr("[unused]");
193 } 207 }
@@ -926,9 +940,9 @@ void ConfigureInputPlayer::UpdateUI() {
926 940
927 int slider_value; 941 int slider_value;
928 auto& param = analogs_param[analog_id]; 942 auto& param = analogs_param[analog_id];
929 const bool is_controller = param.Get("engine", "") == "sdl" || 943 const bool is_controller =
930 param.Get("engine", "") == "gcpad" || 944 param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad" ||
931 param.Get("engine", "") == "mouse"; 945 param.Get("engine", "") == "mouse" || param.Get("engine", "") == "tas";
932 946
933 if (is_controller) { 947 if (is_controller) {
934 if (!param.Has("deadzone")) { 948 if (!param.Has("deadzone")) {
@@ -1045,8 +1059,12 @@ int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType ty
1045void ConfigureInputPlayer::UpdateInputDevices() { 1059void ConfigureInputPlayer::UpdateInputDevices() {
1046 input_devices = input_subsystem->GetInputDevices(); 1060 input_devices = input_subsystem->GetInputDevices();
1047 ui->comboDevices->clear(); 1061 ui->comboDevices->clear();
1048 for (auto device : input_devices) { 1062 for (auto& device : input_devices) {
1049 ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); 1063 const std::string display = device.Get("display", "Unknown");
1064 ui->comboDevices->addItem(QString::fromStdString(display), {});
1065 if (display == "TAS") {
1066 device.Set("pad", static_cast<u8>(player_index));
1067 }
1050 } 1068 }
1051} 1069}
1052 1070
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index 9c890ed5d..e649e2169 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -220,8 +220,20 @@ void PlayerControlPreview::UpdateInput() {
220 } 220 }
221 } 221 }
222 222
223 ControllerInput input{};
223 if (input_changed) { 224 if (input_changed) {
224 update(); 225 update();
226 input.changed = true;
227 }
228 input.axis_values[Settings::NativeAnalog::LStick] = {
229 axis_values[Settings::NativeAnalog::LStick].value.x(),
230 axis_values[Settings::NativeAnalog::LStick].value.y()};
231 input.axis_values[Settings::NativeAnalog::RStick] = {
232 axis_values[Settings::NativeAnalog::RStick].value.x(),
233 axis_values[Settings::NativeAnalog::RStick].value.y()};
234 input.button_values = button_values;
235 if (controller_callback.input != NULL) {
236 controller_callback.input(std::move(input));
225 } 237 }
226 238
227 if (mapping_active) { 239 if (mapping_active) {
@@ -229,6 +241,10 @@ void PlayerControlPreview::UpdateInput() {
229 } 241 }
230} 242}
231 243
244void PlayerControlPreview::SetCallBack(ControllerCallback callback_) {
245 controller_callback = callback_;
246}
247
232void PlayerControlPreview::paintEvent(QPaintEvent* event) { 248void PlayerControlPreview::paintEvent(QPaintEvent* event) {
233 QFrame::paintEvent(event); 249 QFrame::paintEvent(event);
234 QPainter p(this); 250 QPainter p(this);
diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h
index f4a6a5e1b..f4bbfa528 100644
--- a/src/yuzu/configuration/configure_input_player_widget.h
+++ b/src/yuzu/configuration/configure_input_player_widget.h
@@ -9,6 +9,7 @@
9#include <QPointer> 9#include <QPointer>
10#include "common/settings.h" 10#include "common/settings.h"
11#include "core/frontend/input.h" 11#include "core/frontend/input.h"
12#include "yuzu/debugger/controller.h"
12 13
13class QLabel; 14class QLabel;
14 15
@@ -33,6 +34,7 @@ public:
33 void BeginMappingAnalog(std::size_t button_id); 34 void BeginMappingAnalog(std::size_t button_id);
34 void EndMapping(); 35 void EndMapping();
35 void UpdateInput(); 36 void UpdateInput();
37 void SetCallBack(ControllerCallback callback_);
36 38
37protected: 39protected:
38 void paintEvent(QPaintEvent* event) override; 40 void paintEvent(QPaintEvent* event) override;
@@ -181,6 +183,7 @@ private:
181 using StickArray = 183 using StickArray =
182 std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>; 184 std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>;
183 185
186 ControllerCallback controller_callback;
184 bool is_enabled{}; 187 bool is_enabled{};
185 bool mapping_active{}; 188 bool mapping_active{};
186 int blink_counter{}; 189 int blink_counter{};
diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp
index c1fc69578..822d033d1 100644
--- a/src/yuzu/debugger/controller.cpp
+++ b/src/yuzu/debugger/controller.cpp
@@ -6,10 +6,13 @@
6#include <QLayout> 6#include <QLayout>
7#include <QString> 7#include <QString>
8#include "common/settings.h" 8#include "common/settings.h"
9#include "input_common/main.h"
10#include "input_common/tas/tas_input.h"
9#include "yuzu/configuration/configure_input_player_widget.h" 11#include "yuzu/configuration/configure_input_player_widget.h"
10#include "yuzu/debugger/controller.h" 12#include "yuzu/debugger/controller.h"
11 13
12ControllerDialog::ControllerDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) { 14ControllerDialog::ControllerDialog(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_)
15 : QWidget(parent, Qt::Dialog), input_subsystem{input_subsystem_} {
13 setObjectName(QStringLiteral("Controller")); 16 setObjectName(QStringLiteral("Controller"));
14 setWindowTitle(tr("Controller P1")); 17 setWindowTitle(tr("Controller P1"));
15 resize(500, 350); 18 resize(500, 350);
@@ -38,6 +41,9 @@ void ControllerDialog::refreshConfiguration() {
38 constexpr std::size_t player = 0; 41 constexpr std::size_t player = 0;
39 widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); 42 widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs);
40 widget->SetControllerType(players[player].controller_type); 43 widget->SetControllerType(players[player].controller_type);
44 ControllerCallback callback{[this](ControllerInput input) { InputController(input); }};
45 widget->SetCallBack(callback);
46 widget->repaint();
41 widget->SetConnectedStatus(players[player].connected); 47 widget->SetConnectedStatus(players[player].connected);
42} 48}
43 49
@@ -67,3 +73,17 @@ void ControllerDialog::hideEvent(QHideEvent* ev) {
67 widget->SetConnectedStatus(false); 73 widget->SetConnectedStatus(false);
68 QWidget::hideEvent(ev); 74 QWidget::hideEvent(ev);
69} 75}
76
77void ControllerDialog::RefreshTasFile() {
78 input_subsystem->GetTas()->RefreshTasFile();
79}
80
81void ControllerDialog::InputController(ControllerInput input) {
82 u32 buttons = 0;
83 int index = 0;
84 for (bool btn : input.button_values) {
85 buttons += (btn ? 1 : 0) << index;
86 index++;
87 }
88 input_subsystem->GetTas()->RecordInput(buttons, input.axis_values);
89} \ No newline at end of file
diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h
index c54750070..659923e1b 100644
--- a/src/yuzu/debugger/controller.h
+++ b/src/yuzu/debugger/controller.h
@@ -4,18 +4,36 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <QFileSystemWatcher>
7#include <QWidget> 8#include <QWidget>
9#include "common/settings.h"
8 10
9class QAction; 11class QAction;
10class QHideEvent; 12class QHideEvent;
11class QShowEvent; 13class QShowEvent;
12class PlayerControlPreview; 14class PlayerControlPreview;
13 15
16namespace InputCommon {
17class InputSubsystem;
18}
19
20struct ControllerInput {
21 std::array<std::pair<float, float>, Settings::NativeAnalog::NUM_STICKS_HID> axis_values{};
22 std::array<bool, Settings::NativeButton::NumButtons> button_values{};
23 bool changed{};
24};
25
26struct ControllerCallback {
27 std::function<void(ControllerInput)> input;
28 std::function<void()> update;
29};
30
14class ControllerDialog : public QWidget { 31class ControllerDialog : public QWidget {
15 Q_OBJECT 32 Q_OBJECT
16 33
17public: 34public:
18 explicit ControllerDialog(QWidget* parent = nullptr); 35 explicit ControllerDialog(QWidget* parent = nullptr,
36 InputCommon::InputSubsystem* input_subsystem_ = nullptr);
19 37
20 /// Returns a QAction that can be used to toggle visibility of this dialog. 38 /// Returns a QAction that can be used to toggle visibility of this dialog.
21 QAction* toggleViewAction(); 39 QAction* toggleViewAction();
@@ -26,6 +44,10 @@ protected:
26 void hideEvent(QHideEvent* ev) override; 44 void hideEvent(QHideEvent* ev) override;
27 45
28private: 46private:
47 void RefreshTasFile();
48 void InputController(ControllerInput input);
29 QAction* toggle_view_action = nullptr; 49 QAction* toggle_view_action = nullptr;
50 QFileSystemWatcher* watcher = nullptr;
30 PlayerControlPreview* widget; 51 PlayerControlPreview* widget;
52 InputCommon::InputSubsystem* input_subsystem;
31}; 53};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index f4e49001d..22489231b 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -193,6 +193,7 @@ GMainWindow::GMainWindow()
193 config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, 193 config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
194 provider{std::make_unique<FileSys::ManualContentProvider>()} { 194 provider{std::make_unique<FileSys::ManualContentProvider>()} {
195 Common::Log::Initialize(); 195 Common::Log::Initialize();
196 Settings::values.inputSubsystem = input_subsystem;
196 LoadTranslation(); 197 LoadTranslation();
197 198
198 setAcceptDrops(true); 199 setAcceptDrops(true);
@@ -841,7 +842,7 @@ void GMainWindow::InitializeDebugWidgets() {
841 waitTreeWidget->hide(); 842 waitTreeWidget->hide();
842 debug_menu->addAction(waitTreeWidget->toggleViewAction()); 843 debug_menu->addAction(waitTreeWidget->toggleViewAction());
843 844
844 controller_dialog = new ControllerDialog(this); 845 controller_dialog = new ControllerDialog(this, input_subsystem.get());
845 controller_dialog->hide(); 846 controller_dialog->hide();
846 debug_menu->addAction(controller_dialog->toggleViewAction()); 847 debug_menu->addAction(controller_dialog->toggleViewAction());
847 848