aboutsummaryrefslogtreecommitdiff
path: root/src/input_common/drivers/tas_input.h
blob: 38a27a2309a49374bd9e83dafddab2d8a63d57d4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <string>
#include <vector>

#include "common/common_types.h"
#include "input_common/input_engine.h"

/*
To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below
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:

1 KEY_B 0;0 0;0
6 KEY_ZL 0;0 0;0
41 KEY_ZL;KEY_Y 0;0 0;0
43 KEY_X;KEY_A 32767;0 0;0
44 KEY_A 32767;0 0;0
45 KEY_A 32767;0 0;0
46 KEY_A 32767;0 0;0
47 KEY_A 32767;0 0;0

After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey
CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file
has. Playback can be started or stopped using CTRL+F5.

However, for playback to actually work, the correct input device has to be selected: In the Controls
menu, select TAS from the device list for the controller that the script should be played on.

Recording a new script file is really simple: Just make sure that the proper device (not TAS) is
connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke
again (CTRL+F7). The new script will be saved at the location previously selected, as the filename
record.txt.

For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller
P1).
*/

namespace InputCommon::TasInput {

constexpr size_t PLAYER_NUMBER = 10;

enum class TasButton : u64 {
    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,
};

struct TasAnalog {
    float x{};
    float y{};
};

enum class TasState {
    Running,
    Recording,
    Stopped,
};

class Tas final : public InputEngine {
public:
    explicit Tas(std::string input_engine_);
    ~Tas() override;

    /**
     * Changes the input status that will be stored in each frame
     * @param buttons    Bitfield with the status of the buttons
     * @param left_axis  Value of the left axis
     * @param right_axis Value of the right axis
     */
    void RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis);

    // Main loop that records or executes input
    void UpdateThread();

    // Sets the flag to start or stop the TAS command execution 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 beginning in the next update
    void Reset();

    /**
     * Sets the flag to enable or disable recording of inputs
     * @returns true if the current recording status is enabled
     */
    bool Record();

    /**
     * Saves contents of record_commands on a file
     * @param overwrite_file Indicates if player 1 should be overwritten
     */
    void SaveRecording(bool overwrite_file);

    /**
     * Returns the current status values of TAS playback/recording
     * @returns A Tuple of
     * TasState indicating the current state out of Running ;
     * Current playback progress ;
     * Total length of script file currently loaded or being recorded
     */
    std::tuple<TasState, size_t, size_t> GetStatus() const;

private:
    enum class TasAxis : u8;

    struct TASCommand {
        u64 buttons{};
        TasAnalog l_axis{};
        TasAnalog r_axis{};
    };

    /// Loads TAS files from all players
    void LoadTasFiles();

    /**
     * Loads TAS file from the specified player
     * @param player_index Player number to save the script
     * @param file_index   Script number of the file
     */
    void LoadTasFile(size_t player_index, size_t file_index);

    /**
     * Writes a TAS file from the recorded commands
     * @param file_name Name of the file to be written
     */
    void WriteTasFile(std::u8string_view file_name);

    /**
     * Parses a string containing the axis values. X and Y have a range from -32767 to 32767
     * @param line String containing axis values with the following format "x;y"
     * @returns A TAS analog object with axis values with range from -1.0 to 1.0
     */
    TasAnalog ReadCommandAxis(const std::string& line) const;

    /**
     * Parses a string containing the button values. Each button is represented by it's text format
     * specified in text_to_tas_button array
     * @param line string containing button name with the following format "a;b;c;d..."
     * @returns A u64 with each bit representing the status of a button
     */
    u64 ReadCommandButtons(const std::string& line) const;

    /**
     * Reset state of all players
     */
    void ClearInput();

    /**
     * Converts an u64 containing the button status into the text equivalent
     * @param buttons Bitfield with the status of the buttons
     * @returns A string with the name of the buttons to be written to the file
     */
    std::string WriteCommandButtons(u64 buttons) const;

    /**
     * Converts an TAS analog object containing the axis status into the text equivalent
     * @param analog Value of the axis
     * @returns A string with the value of the axis to be written to the file
     */
    std::string WriteCommandAxis(TasAnalog analog) const;

    /// Sets an axis for a particular pad to the given value.
    void SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value);

    size_t script_length{0};
    bool is_recording{false};
    bool is_running{false};
    bool needs_reset{false};
    std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
    std::vector<TASCommand> record_commands{};
    size_t current_command{0};
    TASCommand last_input{}; // only used for recording
};
} // namespace InputCommon::TasInput