summaryrefslogtreecommitdiff
path: root/src/input_common/drivers/tas_input.cpp
diff options
context:
space:
mode:
authorGravatar Feng Chen2021-12-18 13:57:14 +0800
committerGravatar GitHub2021-12-18 13:57:14 +0800
commite49184e6069a9d791d2df3c1958f5c4b1187e124 (patch)
treeb776caf722e0be0e680f67b0ad0842628162ef1c /src/input_common/drivers/tas_input.cpp
parentImplement convert legacy to generic (diff)
parentMerge pull request #7570 from ameerj/favorites-expanded (diff)
downloadyuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.tar.gz
yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.tar.xz
yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.zip
Merge branch 'yuzu-emu:master' into convert_legacy
Diffstat (limited to 'src/input_common/drivers/tas_input.cpp')
-rw-r--r--src/input_common/drivers/tas_input.cpp320
1 files changed, 320 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..5bdd5dac3
--- /dev/null
+++ b/src/input_common/drivers/tas_input.cpp
@@ -0,0 +1,320 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <fmt/format.h>
7
8#include "common/fs/file.h"
9#include "common/fs/fs_types.h"
10#include "common/fs/path_util.h"
11#include "common/logging/log.h"
12#include "common/settings.h"
13#include "input_common/drivers/tas_input.h"
14
15namespace InputCommon::TasInput {
16
17enum class Tas::TasAxis : u8 {
18 StickX,
19 StickY,
20 SubstickX,
21 SubstickY,
22 Undefined,
23};
24
25// Supported keywords and buttons from a TAS file
26constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
27 std::pair{"KEY_A", TasButton::BUTTON_A},
28 {"KEY_B", TasButton::BUTTON_B},
29 {"KEY_X", TasButton::BUTTON_X},
30 {"KEY_Y", TasButton::BUTTON_Y},
31 {"KEY_LSTICK", TasButton::STICK_L},
32 {"KEY_RSTICK", TasButton::STICK_R},
33 {"KEY_L", TasButton::TRIGGER_L},
34 {"KEY_R", TasButton::TRIGGER_R},
35 {"KEY_PLUS", TasButton::BUTTON_PLUS},
36 {"KEY_MINUS", TasButton::BUTTON_MINUS},
37 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
38 {"KEY_DUP", TasButton::BUTTON_UP},
39 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
40 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
41 {"KEY_SL", TasButton::BUTTON_SL},
42 {"KEY_SR", TasButton::BUTTON_SR},
43 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
44 {"KEY_HOME", TasButton::BUTTON_HOME},
45 {"KEY_ZL", TasButton::TRIGGER_ZL},
46 {"KEY_ZR", TasButton::TRIGGER_ZR},
47};
48
49Tas::Tas(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
50 for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) {
51 PadIdentifier identifier{
52 .guid = Common::UUID{},
53 .port = player_index,
54 .pad = 0,
55 };
56 PreSetController(identifier);
57 }
58 ClearInput();
59 if (!Settings::values.tas_enable) {
60 needs_reset = true;
61 return;
62 }
63 LoadTasFiles();
64}
65
66Tas::~Tas() {
67 Stop();
68}
69
70void Tas::LoadTasFiles() {
71 script_length = 0;
72 for (size_t i = 0; i < commands.size(); i++) {
73 LoadTasFile(i, 0);
74 if (commands[i].size() > script_length) {
75 script_length = commands[i].size();
76 }
77 }
78}
79
80void Tas::LoadTasFile(size_t player_index, size_t file_index) {
81 commands[player_index].clear();
82
83 std::string file = Common::FS::ReadStringFromFile(
84 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
85 fmt::format("script{}-{}.txt", file_index, player_index + 1),
86 Common::FS::FileType::BinaryFile);
87 std::istringstream command_line(file);
88 std::string line;
89 int frame_no = 0;
90 while (std::getline(command_line, line, '\n')) {
91 if (line.empty()) {
92 continue;
93 }
94
95 std::vector<std::string> seg_list;
96 {
97 std::istringstream line_stream(line);
98 std::string segment;
99 while (std::getline(line_stream, segment, ' ')) {
100 seg_list.push_back(std::move(segment));
101 }
102 }
103
104 if (seg_list.size() < 4) {
105 continue;
106 }
107
108 const auto num_frames = std::stoi(seg_list[0]);
109 while (frame_no < num_frames) {
110 commands[player_index].emplace_back();
111 frame_no++;
112 }
113
114 TASCommand command = {
115 .buttons = ReadCommandButtons(seg_list[1]),
116 .l_axis = ReadCommandAxis(seg_list[2]),
117 .r_axis = ReadCommandAxis(seg_list[3]),
118 };
119 commands[player_index].push_back(command);
120 frame_no++;
121 }
122 LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
123}
124
125void Tas::WriteTasFile(std::u8string_view file_name) {
126 std::string output_text;
127 for (size_t frame = 0; frame < record_commands.size(); frame++) {
128 const TASCommand& line = record_commands[frame];
129 output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons),
130 WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis));
131 }
132
133 const auto tas_file_name = Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name;
134 const auto bytes_written =
135 Common::FS::WriteStringToFile(tas_file_name, 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 SetTasAxis(identifier, TasAxis::StickX, command.l_axis.x);
209 SetTasAxis(identifier, TasAxis::StickY, command.l_axis.y);
210 SetTasAxis(identifier, TasAxis::SubstickX, command.r_axis.x);
211 SetTasAxis(identifier, TasAxis::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::vector<std::string> seg_list;
228 {
229 std::istringstream line_stream(line);
230 std::string segment;
231 while (std::getline(line_stream, segment, ';')) {
232 seg_list.push_back(std::move(segment));
233 }
234 }
235
236 const float x = std::stof(seg_list.at(0)) / 32767.0f;
237 const float y = std::stof(seg_list.at(1)) / 32767.0f;
238
239 return {x, y};
240}
241
242u64 Tas::ReadCommandButtons(const std::string& line) const {
243 std::istringstream button_text(line);
244 std::string button_line;
245 u64 buttons = 0;
246 while (std::getline(button_text, button_line, ';')) {
247 for (const auto& [text, tas_button] : text_to_tas_button) {
248 if (text == button_line) {
249 buttons |= static_cast<u64>(tas_button);
250 break;
251 }
252 }
253 }
254 return buttons;
255}
256
257std::string Tas::WriteCommandButtons(u64 buttons) const {
258 std::string returns;
259 for (const auto& [text_button, tas_button] : text_to_tas_button) {
260 if ((buttons & static_cast<u64>(tas_button)) != 0) {
261 returns += fmt::format("{};", text_button);
262 }
263 }
264 return returns.empty() ? "NONE" : returns;
265}
266
267std::string Tas::WriteCommandAxis(TasAnalog analog) const {
268 return fmt::format("{};{}", analog.x * 32767, analog.y * 32767);
269}
270
271void Tas::SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value) {
272 SetAxis(identifier, static_cast<int>(axis), value);
273}
274
275void Tas::StartStop() {
276 if (!Settings::values.tas_enable) {
277 return;
278 }
279 if (is_running) {
280 Stop();
281 } else {
282 is_running = true;
283 }
284}
285
286void Tas::Stop() {
287 is_running = false;
288}
289
290void Tas::Reset() {
291 if (!Settings::values.tas_enable) {
292 return;
293 }
294 needs_reset = true;
295}
296
297bool Tas::Record() {
298 if (!Settings::values.tas_enable) {
299 return true;
300 }
301 is_recording = !is_recording;
302 return is_recording;
303}
304
305void Tas::SaveRecording(bool overwrite_file) {
306 if (is_recording) {
307 return;
308 }
309 if (record_commands.empty()) {
310 return;
311 }
312 WriteTasFile(u8"record.txt");
313 if (overwrite_file) {
314 WriteTasFile(u8"script0-1.txt");
315 }
316 needs_reset = true;
317 record_commands.clear();
318}
319
320} // namespace InputCommon::TasInput