summaryrefslogtreecommitdiff
path: root/src/input_common/drivers/tas_input.cpp
diff options
context:
space:
mode:
authorGravatar Fernando S2021-11-27 11:52:08 +0100
committerGravatar GitHub2021-11-27 11:52:08 +0100
commit564f10527745f870621c08bbb5d16badee0ed861 (patch)
treee8ac8dee60086facf1837393882865f5df18c95e /src/input_common/drivers/tas_input.cpp
parentMerge pull request #7431 from liushuyu/fix-linux-decoding (diff)
parentconfig: Remove vibration configuration (diff)
downloadyuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.gz
yuzu-564f10527745f870621c08bbb5d16badee0ed861.tar.xz
yuzu-564f10527745f870621c08bbb5d16badee0ed861.zip
Merge pull request #7255 from german77/kraken
Project Kraken: Input rewrite
Diffstat (limited to 'src/input_common/drivers/tas_input.cpp')
-rw-r--r--src/input_common/drivers/tas_input.cpp315
1 files changed, 315 insertions, 0 deletions
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp
new file mode 100644
index 000000000..0e01fb0d9
--- /dev/null
+++ b/src/input_common/drivers/tas_input.cpp
@@ -0,0 +1,315 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <regex>
7#include <fmt/format.h>
8
9#include "common/fs/file.h"
10#include "common/fs/fs_types.h"
11#include "common/fs/path_util.h"
12#include "common/logging/log.h"
13#include "common/settings.h"
14#include "input_common/drivers/tas_input.h"
15
16namespace InputCommon::TasInput {
17
18enum TasAxes : u8 {
19 StickX,
20 StickY,
21 SubstickX,
22 SubstickY,
23 Undefined,
24};
25
26// Supported keywords and buttons from a TAS file
27constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
28 std::pair{"KEY_A", TasButton::BUTTON_A},
29 {"KEY_B", TasButton::BUTTON_B},
30 {"KEY_X", TasButton::BUTTON_X},
31 {"KEY_Y", TasButton::BUTTON_Y},
32 {"KEY_LSTICK", TasButton::STICK_L},
33 {"KEY_RSTICK", TasButton::STICK_R},
34 {"KEY_L", TasButton::TRIGGER_L},
35 {"KEY_R", TasButton::TRIGGER_R},
36 {"KEY_PLUS", TasButton::BUTTON_PLUS},
37 {"KEY_MINUS", TasButton::BUTTON_MINUS},
38 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
39 {"KEY_DUP", TasButton::BUTTON_UP},
40 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
41 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
42 {"KEY_SL", TasButton::BUTTON_SL},
43 {"KEY_SR", TasButton::BUTTON_SR},
44 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
45 {"KEY_HOME", TasButton::BUTTON_HOME},
46 {"KEY_ZL", TasButton::TRIGGER_ZL},
47 {"KEY_ZR", TasButton::TRIGGER_ZR},
48};
49
50Tas::Tas(const std::string& input_engine_) : InputCommon::InputEngine(input_engine_) {
51 for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) {
52 PadIdentifier identifier{
53 .guid = Common::UUID{},
54 .port = player_index,
55 .pad = 0,
56 };
57 PreSetController(identifier);
58 }
59 ClearInput();
60 if (!Settings::values.tas_enable) {
61 needs_reset = true;
62 return;
63 }
64 LoadTasFiles();
65}
66
67Tas::~Tas() {
68 Stop();
69};
70
71void Tas::LoadTasFiles() {
72 script_length = 0;
73 for (size_t i = 0; i < commands.size(); i++) {
74 LoadTasFile(i, 0);
75 if (commands[i].size() > script_length) {
76 script_length = commands[i].size();
77 }
78 }
79}
80
81void Tas::LoadTasFile(size_t player_index, size_t file_index) {
82 if (!commands[player_index].empty()) {
83 commands[player_index].clear();
84 }
85 std::string file = Common::FS::ReadStringFromFile(
86 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
87 fmt::format("script{}-{}.txt", file_index, player_index + 1),
88 Common::FS::FileType::BinaryFile);
89 std::stringstream command_line(file);
90 std::string line;
91 int frame_no = 0;
92 while (std::getline(command_line, line, '\n')) {
93 if (line.empty()) {
94 continue;
95 }
96 std::smatch m;
97
98 std::stringstream linestream(line);
99 std::string segment;
100 std::vector<std::string> seglist;
101
102 while (std::getline(linestream, segment, ' ')) {
103 seglist.push_back(segment);
104 }
105
106 if (seglist.size() < 4) {
107 continue;
108 }
109
110 while (frame_no < std::stoi(seglist.at(0))) {
111 commands[player_index].push_back({});
112 frame_no++;
113 }
114
115 TASCommand command = {
116 .buttons = ReadCommandButtons(seglist.at(1)),
117 .l_axis = ReadCommandAxis(seglist.at(2)),
118 .r_axis = ReadCommandAxis(seglist.at(3)),
119 };
120 commands[player_index].push_back(command);
121 frame_no++;
122 }
123 LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
124}
125
126void Tas::WriteTasFile(std::u8string file_name) {
127 std::string output_text;
128 for (size_t frame = 0; frame < record_commands.size(); frame++) {
129 const TASCommand& line = record_commands[frame];
130 output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons),
131 WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis));
132 }
133 const auto bytes_written = Common::FS::WriteStringToFile(
134 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
135 Common::FS::FileType::TextFile, output_text);
136 if (bytes_written == output_text.size()) {
137 LOG_INFO(Input, "TAS file written to file!");
138 } else {
139 LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
140 output_text.size());
141 }
142}
143
144void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) {
145 last_input = {
146 .buttons = buttons,
147 .l_axis = left_axis,
148 .r_axis = right_axis,
149 };
150}
151
152std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
153 TasState state;
154 if (is_recording) {
155 return {TasState::Recording, 0, record_commands.size()};
156 }
157
158 if (is_running) {
159 state = TasState::Running;
160 } else {
161 state = TasState::Stopped;
162 }
163
164 return {state, current_command, script_length};
165}
166
167void Tas::UpdateThread() {
168 if (!Settings::values.tas_enable) {
169 if (is_running) {
170 Stop();
171 }
172 return;
173 }
174
175 if (is_recording) {
176 record_commands.push_back(last_input);
177 }
178 if (needs_reset) {
179 current_command = 0;
180 needs_reset = false;
181 LoadTasFiles();
182 LOG_DEBUG(Input, "tas_reset done");
183 }
184
185 if (!is_running) {
186 ClearInput();
187 return;
188 }
189 if (current_command < script_length) {
190 LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
191 const size_t frame = current_command++;
192 for (size_t player_index = 0; player_index < commands.size(); player_index++) {
193 TASCommand command{};
194 if (frame < commands[player_index].size()) {
195 command = commands[player_index][frame];
196 }
197
198 PadIdentifier identifier{
199 .guid = Common::UUID{},
200 .port = player_index,
201 .pad = 0,
202 };
203 for (std::size_t i = 0; i < sizeof(command.buttons) * 8; ++i) {
204 const bool button_status = (command.buttons & (1LLU << i)) != 0;
205 const int button = static_cast<int>(i);
206 SetButton(identifier, button, button_status);
207 }
208 SetAxis(identifier, TasAxes::StickX, command.l_axis.x);
209 SetAxis(identifier, TasAxes::StickY, command.l_axis.y);
210 SetAxis(identifier, TasAxes::SubstickX, command.r_axis.x);
211 SetAxis(identifier, TasAxes::SubstickY, command.r_axis.y);
212 }
213 } else {
214 is_running = Settings::values.tas_loop.GetValue();
215 LoadTasFiles();
216 current_command = 0;
217 ClearInput();
218 }
219}
220
221void Tas::ClearInput() {
222 ResetButtonState();
223 ResetAnalogState();
224}
225
226TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
227 std::stringstream linestream(line);
228 std::string segment;
229 std::vector<std::string> seglist;
230
231 while (std::getline(linestream, segment, ';')) {
232 seglist.push_back(segment);
233 }
234
235 const float x = std::stof(seglist.at(0)) / 32767.0f;
236 const float y = std::stof(seglist.at(1)) / 32767.0f;
237
238 return {x, y};
239}
240
241u64 Tas::ReadCommandButtons(const std::string& data) const {
242 std::stringstream button_text(data);
243 std::string line;
244 u64 buttons = 0;
245 while (std::getline(button_text, line, ';')) {
246 for (auto [text, tas_button] : text_to_tas_button) {
247 if (text == line) {
248 buttons |= static_cast<u64>(tas_button);
249 break;
250 }
251 }
252 }
253 return buttons;
254}
255
256std::string Tas::WriteCommandButtons(u64 buttons) const {
257 std::string returns = "";
258 for (auto [text_button, tas_button] : text_to_tas_button) {
259 if ((buttons & static_cast<u64>(tas_button)) != 0) {
260 returns += fmt::format("{};", text_button);
261 }
262 }
263 return returns.empty() ? "NONE" : returns;
264}
265
266std::string Tas::WriteCommandAxis(TasAnalog analog) const {
267 return fmt::format("{};{}", analog.x * 32767, analog.y * 32767);
268}
269
270void Tas::StartStop() {
271 if (!Settings::values.tas_enable) {
272 return;
273 }
274 if (is_running) {
275 Stop();
276 } else {
277 is_running = true;
278 }
279}
280
281void Tas::Stop() {
282 is_running = false;
283}
284
285void Tas::Reset() {
286 if (!Settings::values.tas_enable) {
287 return;
288 }
289 needs_reset = true;
290}
291
292bool Tas::Record() {
293 if (!Settings::values.tas_enable) {
294 return true;
295 }
296 is_recording = !is_recording;
297 return is_recording;
298}
299
300void Tas::SaveRecording(bool overwrite_file) {
301 if (is_recording) {
302 return;
303 }
304 if (record_commands.empty()) {
305 return;
306 }
307 WriteTasFile(u8"record.txt");
308 if (overwrite_file) {
309 WriteTasFile(u8"script0-1.txt");
310 }
311 needs_reset = true;
312 record_commands.clear();
313}
314
315} // namespace InputCommon::TasInput