core: Hacky TAS syncing & load pausing
To keep the TAS inputs synced to the game speed even through lag spikes and loading zones, deeper access is required. First, the `TAS::UpdateThread` has to be executed exactly once per frame. This is done by connecting it to the service method the game calls to pass parameters to the GPU: `Service::VI::QueueBuffer`. Second, the loading time of new subareas and/or kingdoms (SMO) can vary. To counteract that, the `CPU_BOOST_MODE` can be detected: In the `APM`-interface, the call to enabling/disabling the boost mode can be caught and forwarded to the TASing system, which can pause the script execution if neccessary and enabled in the settings.
This commit is contained in:
parent
3a7b37238b
commit
4297d2fea2
|
@ -21,6 +21,7 @@
|
||||||
#define SCREENSHOTS_DIR "screenshots"
|
#define SCREENSHOTS_DIR "screenshots"
|
||||||
#define SDMC_DIR "sdmc"
|
#define SDMC_DIR "sdmc"
|
||||||
#define SHADER_DIR "shader"
|
#define SHADER_DIR "shader"
|
||||||
|
#define TAS_DIR "scripts"
|
||||||
|
|
||||||
// yuzu-specific files
|
// yuzu-specific files
|
||||||
|
|
||||||
|
|
|
@ -116,8 +116,7 @@ private:
|
||||||
GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
|
GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
|
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
|
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
|
||||||
|
GenerateYuzuPath(YuzuPath::TASFile, yuzu_path / TAS_DIR);
|
||||||
GenerateYuzuPath(YuzuPath::TASFile, fs::path{""});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~PathManagerImpl() = default;
|
~PathManagerImpl() = default;
|
||||||
|
|
|
@ -500,7 +500,6 @@ struct Values {
|
||||||
|
|
||||||
// Controls
|
// Controls
|
||||||
InputSetting<std::array<PlayerInput, 10>> players;
|
InputSetting<std::array<PlayerInput, 10>> players;
|
||||||
std::shared_ptr<InputCommon::InputSubsystem> inputSubsystem = NULL;
|
|
||||||
|
|
||||||
Setting<bool> use_docked_mode{true, "use_docked_mode"};
|
Setting<bool> use_docked_mode{true, "use_docked_mode"};
|
||||||
|
|
||||||
|
@ -514,9 +513,12 @@ struct Values {
|
||||||
"motion_device"};
|
"motion_device"};
|
||||||
BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
|
BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
|
||||||
|
|
||||||
BasicSetting<bool> tas_enable{false, "tas_enable"};
|
BasicSetting<bool> pause_tas_on_load { false, "pause_tas_on_load" };
|
||||||
|
BasicSetting<bool> tas_enable{ false, "tas_enable" };
|
||||||
BasicSetting<bool> tas_reset{ false, "tas_reset" };
|
BasicSetting<bool> tas_reset{ false, "tas_reset" };
|
||||||
BasicSetting<bool> tas_record{ false, "tas_record" };
|
BasicSetting<bool> tas_record{ false, "tas_record" };
|
||||||
|
BasicSetting<bool> is_cpu_boxted{ false, " BasicSetting<bool> is_cpu_boxted{ false, "cpuBoosted" };
|
||||||
|
" };
|
||||||
|
|
||||||
BasicSetting<bool> mouse_panning{false, "mouse_panning"};
|
BasicSetting<bool> mouse_panning{false, "mouse_panning"};
|
||||||
BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
|
BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
|
||||||
|
@ -550,9 +552,6 @@ struct Values {
|
||||||
BasicSetting<bool> gamecard_current_game{false, "gamecard_current_game"};
|
BasicSetting<bool> gamecard_current_game{false, "gamecard_current_game"};
|
||||||
BasicSetting<std::string> gamecard_path{std::string(), "gamecard_path"};
|
BasicSetting<std::string> gamecard_path{std::string(), "gamecard_path"};
|
||||||
|
|
||||||
// TAS
|
|
||||||
bool pauseTasOnLoad;
|
|
||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
bool record_frame_times;
|
bool record_frame_times;
|
||||||
BasicSetting<bool> use_gdbstub{false, "use_gdbstub"};
|
BasicSetting<bool> use_gdbstub{false, "use_gdbstub"};
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/settings.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
#include "core/hle/service/apm/apm.h"
|
#include "core/hle/service/apm/apm.h"
|
||||||
#include "core/hle/service/apm/apm_controller.h"
|
#include "core/hle/service/apm/apm_controller.h"
|
||||||
|
@ -120,6 +121,7 @@ void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) {
|
||||||
|
|
||||||
LOG_DEBUG(Service_APM, "called, mode={:08X}", mode);
|
LOG_DEBUG(Service_APM, "called, mode={:08X}", mode);
|
||||||
|
|
||||||
|
Settings::values.is_cpu_boosted = (static_cast<u32>(mode) == 1);
|
||||||
controller.SetFromCpuBoostMode(mode);
|
controller.SetFromCpuBoostMode(mode);
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2};
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
#include "core/hle/service/vi/vi_s.h"
|
#include "core/hle/service/vi/vi_s.h"
|
||||||
#include "core/hle/service/vi/vi_u.h"
|
#include "core/hle/service/vi/vi_u.h"
|
||||||
|
|
||||||
|
#include "input_common/tas/tas_input.h"
|
||||||
|
|
||||||
namespace Service::VI {
|
namespace Service::VI {
|
||||||
|
|
||||||
constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1};
|
constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1};
|
||||||
|
@ -595,6 +597,7 @@ private:
|
||||||
|
|
||||||
IGBPQueueBufferResponseParcel response{1280, 720};
|
IGBPQueueBufferResponseParcel response{1280, 720};
|
||||||
ctx.WriteBuffer(response.Serialize());
|
ctx.WriteBuffer(response.Serialize());
|
||||||
|
Settings::values.input_subsystem->GetTas()->UpdateThread();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TransactionId::Query: {
|
case TransactionId::Query: {
|
||||||
|
|
|
@ -19,6 +19,29 @@
|
||||||
|
|
||||||
namespace TasInput {
|
namespace TasInput {
|
||||||
|
|
||||||
|
constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
|
||||||
|
std::pair{"KEY_A", TasButton::BUTTON_A},
|
||||||
|
{"KEY_B", TasButton::BUTTON_B},
|
||||||
|
{"KEY_X", TasButton::BUTTON_X},
|
||||||
|
{"KEY_Y", TasButton::BUTTON_Y},
|
||||||
|
{"KEY_LSTICK", TasButton::STICK_L},
|
||||||
|
{"KEY_RSTICK", TasButton::STICK_R},
|
||||||
|
{"KEY_L", TasButton::TRIGGER_L},
|
||||||
|
{"KEY_R", TasButton::TRIGGER_R},
|
||||||
|
{"KEY_PLUS", TasButton::BUTTON_PLUS},
|
||||||
|
{"KEY_MINUS", TasButton::BUTTON_MINUS},
|
||||||
|
{"KEY_DLEFT", TasButton::BUTTON_LEFT},
|
||||||
|
{"KEY_DUP", TasButton::BUTTON_UP},
|
||||||
|
{"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
|
||||||
|
{"KEY_DDOWN", TasButton::BUTTON_DOWN},
|
||||||
|
{"KEY_SL", TasButton::BUTTON_SL},
|
||||||
|
{"KEY_SR", TasButton::BUTTON_SR},
|
||||||
|
{"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
|
||||||
|
{"KEY_HOME", TasButton::BUTTON_HOME},
|
||||||
|
{"KEY_ZL", TasButton::TRIGGER_ZL},
|
||||||
|
{"KEY_ZR", TasButton::TRIGGER_ZR},
|
||||||
|
};
|
||||||
|
|
||||||
Tas::Tas() {
|
Tas::Tas() {
|
||||||
LoadTasFiles();
|
LoadTasFiles();
|
||||||
}
|
}
|
||||||
|
@ -31,29 +54,31 @@ void Tas::RefreshTasFile() {
|
||||||
refresh_tas_fle = true;
|
refresh_tas_fle = true;
|
||||||
}
|
}
|
||||||
void Tas::LoadTasFiles() {
|
void Tas::LoadTasFiles() {
|
||||||
scriptLength = 0;
|
script_length = 0;
|
||||||
for (int i = 0; i < PLAYER_NUMBER; i++) {
|
for (size_t i = 0; i < PLAYER_NUMBER; i++) {
|
||||||
LoadTasFile(i);
|
LoadTasFile(i);
|
||||||
if (newCommands[i].size() > scriptLength)
|
if (commands[i].size() > script_length) {
|
||||||
scriptLength = newCommands[i].size();
|
script_length = commands[i].size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Tas::LoadTasFile(int playerIndex) {
|
void Tas::LoadTasFile(size_t player_index) {
|
||||||
LOG_DEBUG(Input, "LoadTasFile()");
|
LOG_DEBUG(Input, "LoadTasFile()");
|
||||||
if (!newCommands[playerIndex].empty()) {
|
if (!commands[player_index].empty()) {
|
||||||
newCommands[playerIndex].clear();
|
commands[player_index].clear();
|
||||||
}
|
}
|
||||||
std::string file = Common::FS::ReadStringFromFile(
|
std::string file = Common::FS::ReadStringFromFile(
|
||||||
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "script0-" +
|
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "script0-" +
|
||||||
std::to_string(playerIndex + 1) + ".txt",
|
std::to_string(player_index + 1) + ".txt",
|
||||||
Common::FS::FileType::BinaryFile);
|
Common::FS::FileType::BinaryFile);
|
||||||
std::stringstream command_line(file);
|
std::stringstream command_line(file);
|
||||||
std::string line;
|
std::string line;
|
||||||
int frameNo = 0;
|
int frameNo = 0;
|
||||||
TASCommand empty = {.buttons = 0, .l_axis = {0.f, 0.f}, .r_axis = {0.f, 0.f}};
|
TASCommand empty = {.buttons = 0, .l_axis = {0.f, 0.f}, .r_axis = {0.f, 0.f}};
|
||||||
while (std::getline(command_line, line, '\n')) {
|
while (std::getline(command_line, line, '\n')) {
|
||||||
if (line.empty())
|
if (line.empty()) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
LOG_DEBUG(Input, "Loading line: {}", line);
|
LOG_DEBUG(Input, "Loading line: {}", line);
|
||||||
std::smatch m;
|
std::smatch m;
|
||||||
|
|
||||||
|
@ -65,11 +90,12 @@ void Tas::LoadTasFile(int playerIndex) {
|
||||||
seglist.push_back(segment);
|
seglist.push_back(segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seglist.size() < 4)
|
if (seglist.size() < 4) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
while (frameNo < std::stoi(seglist.at(0))) {
|
while (frameNo < std::stoi(seglist.at(0))) {
|
||||||
newCommands[playerIndex].push_back(empty);
|
commands[player_index].push_back(empty);
|
||||||
frameNo++;
|
frameNo++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +104,7 @@ void Tas::LoadTasFile(int playerIndex) {
|
||||||
.l_axis = ReadCommandAxis(seglist.at(2)),
|
.l_axis = ReadCommandAxis(seglist.at(2)),
|
||||||
.r_axis = ReadCommandAxis(seglist.at(3)),
|
.r_axis = ReadCommandAxis(seglist.at(3)),
|
||||||
};
|
};
|
||||||
newCommands[playerIndex].push_back(command);
|
commands[player_index].push_back(command);
|
||||||
frameNo++;
|
frameNo++;
|
||||||
}
|
}
|
||||||
LOG_INFO(Input, "TAS file loaded! {} frames", frameNo);
|
LOG_INFO(Input, "TAS file loaded! {} frames", frameNo);
|
||||||
|
@ -87,84 +113,89 @@ void Tas::LoadTasFile(int playerIndex) {
|
||||||
void Tas::WriteTasFile() {
|
void Tas::WriteTasFile() {
|
||||||
LOG_DEBUG(Input, "WriteTasFile()");
|
LOG_DEBUG(Input, "WriteTasFile()");
|
||||||
std::string output_text = "";
|
std::string output_text = "";
|
||||||
for (int frame = 0; frame < (signed)recordCommands.size(); frame++) {
|
for (int frame = 0; frame < (signed)record_commands.size(); frame++) {
|
||||||
if (!output_text.empty())
|
if (!output_text.empty()) {
|
||||||
output_text += "\n";
|
output_text += "\n";
|
||||||
TASCommand line = recordCommands.at(frame);
|
}
|
||||||
|
TASCommand line = record_commands.at(frame);
|
||||||
output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
|
output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
|
||||||
WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
|
WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
|
||||||
}
|
}
|
||||||
size_t bytesWritten = Common::FS::WriteStringToFile(
|
size_t bytesWritten = Common::FS::WriteStringToFile(
|
||||||
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "record.txt",
|
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "record.txt",
|
||||||
Common::FS::FileType::TextFile, output_text);
|
Common::FS::FileType::TextFile, output_text);
|
||||||
if (bytesWritten == output_text.size())
|
if (bytesWritten == output_text.size()) {
|
||||||
LOG_INFO(Input, "TAS file written to file!");
|
LOG_INFO(Input, "TAS file written to file!");
|
||||||
else
|
}
|
||||||
|
else {
|
||||||
LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytesWritten,
|
LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytesWritten,
|
||||||
output_text.size());
|
output_text.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tas::RecordInput(u32 buttons, std::array<std::pair<float, float>, 2> axes) {
|
static std::pair<float, float> FlipY(std::pair<float, float> old) {
|
||||||
lastInput = {buttons, flipY(axes[0]), flipY(axes[1])};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<float, float> Tas::flipY(std::pair<float, float> old) const {
|
|
||||||
auto [x, y] = old;
|
auto [x, y] = old;
|
||||||
return {x, -y};
|
return {x, -y};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Tas::GetStatusDescription() {
|
void Tas::RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes) {
|
||||||
|
last_input = {buttons, FlipY(axes[0]), FlipY(axes[1])};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<TasState, size_t, size_t> Tas::GetStatus() {
|
||||||
|
TasState state;
|
||||||
if (Settings::values.tas_record) {
|
if (Settings::values.tas_record) {
|
||||||
return "Recording TAS: " + std::to_string(recordCommands.size());
|
return {TasState::RECORDING, record_commands.size(), record_commands.size()};
|
||||||
|
} else if (Settings::values.tas_enable) {
|
||||||
|
state = TasState::RUNNING;
|
||||||
|
} else {
|
||||||
|
state = TasState::STOPPED;
|
||||||
}
|
}
|
||||||
if (Settings::values.tas_enable) {
|
|
||||||
return "Playing TAS: " + std::to_string(current_command) + "/" +
|
return {state, current_command, script_length};
|
||||||
std::to_string(scriptLength);
|
|
||||||
}
|
|
||||||
return "TAS not running: " + std::to_string(current_command) + "/" +
|
|
||||||
std::to_string(scriptLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string debugButtons(u32 buttons) {
|
static std::string DebugButtons(u32 buttons) {
|
||||||
return "{ " + TasInput::Tas::buttonsToString(buttons) + " }";
|
return "{ " + TasInput::Tas::ButtonsToString(buttons) + " }";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string debugJoystick(float x, float y) {
|
static std::string DebugJoystick(float x, float y) {
|
||||||
return "[ " + std::to_string(x) + "," + std::to_string(y) + " ]";
|
return "[ " + std::to_string(x) + "," + std::to_string(y) + " ]";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string debugInput(TasData data) {
|
static std::string DebugInput(const TasData& data) {
|
||||||
return "{ " + debugButtons(data.buttons) + " , " + debugJoystick(data.axis[0], data.axis[1]) +
|
return "{ " + DebugButtons(data.buttons) + " , " + DebugJoystick(data.axis[0], data.axis[1]) +
|
||||||
" , " + debugJoystick(data.axis[2], data.axis[3]) + " }";
|
" , " + DebugJoystick(data.axis[2], data.axis[3]) + " }";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string debugInputs(std::array<TasData, PLAYER_NUMBER> arr) {
|
static std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) {
|
||||||
std::string returns = "[ ";
|
std::string returns = "[ ";
|
||||||
for (size_t i = 0; i < arr.size(); i++) {
|
for (size_t i = 0; i < arr.size(); i++) {
|
||||||
returns += debugInput(arr[i]);
|
returns += DebugInput(arr[i]);
|
||||||
if (i != arr.size() - 1)
|
if (i != arr.size() - 1) {
|
||||||
returns += " , ";
|
returns += " , ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return returns + "]";
|
return returns + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tas::UpdateThread() {
|
void Tas::UpdateThread() {
|
||||||
if (update_thread_running) {
|
if (update_thread_running) {
|
||||||
if (Settings::values.pauseTasOnLoad && Settings::values.cpuBoosted) {
|
if (Settings::values.pause_tas_on_load && Settings::values.is_cpu_boosted) {
|
||||||
for (int i = 0; i < PLAYER_NUMBER; i++) {
|
for (size_t i = 0; i < PLAYER_NUMBER; i++) {
|
||||||
tas_data[i].buttons = 0;
|
tas_data[i].buttons = 0;
|
||||||
tas_data[i].axis = {};
|
tas_data[i].axis = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings::values.tas_record) {
|
if (Settings::values.tas_record) {
|
||||||
recordCommands.push_back(lastInput);
|
record_commands.push_back(last_input);
|
||||||
}
|
}
|
||||||
if (!Settings::values.tas_record && !recordCommands.empty()) {
|
if (!Settings::values.tas_record && !record_commands.empty()) {
|
||||||
WriteTasFile();
|
WriteTasFile();
|
||||||
Settings::values.tas_reset = true;
|
Settings::values.tas_reset = true;
|
||||||
refresh_tas_fle = true;
|
refresh_tas_fle = true;
|
||||||
recordCommands.clear();
|
record_commands.clear();
|
||||||
}
|
}
|
||||||
if (Settings::values.tas_reset) {
|
if (Settings::values.tas_reset) {
|
||||||
current_command = 0;
|
current_command = 0;
|
||||||
|
@ -177,12 +208,12 @@ void Tas::UpdateThread() {
|
||||||
LOG_DEBUG(Input, "tas_reset done");
|
LOG_DEBUG(Input, "tas_reset done");
|
||||||
}
|
}
|
||||||
if (Settings::values.tas_enable) {
|
if (Settings::values.tas_enable) {
|
||||||
if ((signed)current_command < scriptLength) {
|
if ((signed)current_command < script_length) {
|
||||||
LOG_INFO(Input, "Playing TAS {}/{}", current_command, scriptLength);
|
LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length);
|
||||||
size_t frame = current_command++;
|
size_t frame = current_command++;
|
||||||
for (int i = 0; i < PLAYER_NUMBER; i++) {
|
for (size_t i = 0; i < PLAYER_NUMBER; i++) {
|
||||||
if (frame < newCommands[i].size()) {
|
if (frame < commands[i].size()) {
|
||||||
TASCommand command = newCommands[i][frame];
|
TASCommand command = commands[i][frame];
|
||||||
tas_data[i].buttons = command.buttons;
|
tas_data[i].buttons = command.buttons;
|
||||||
auto [l_axis_x, l_axis_y] = command.l_axis;
|
auto [l_axis_x, l_axis_y] = command.l_axis;
|
||||||
tas_data[i].axis[0] = l_axis_x;
|
tas_data[i].axis[0] = l_axis_x;
|
||||||
|
@ -198,22 +229,22 @@ void Tas::UpdateThread() {
|
||||||
} else {
|
} else {
|
||||||
Settings::values.tas_enable = false;
|
Settings::values.tas_enable = false;
|
||||||
current_command = 0;
|
current_command = 0;
|
||||||
for (int i = 0; i < PLAYER_NUMBER; i++) {
|
for (size_t i = 0; i < PLAYER_NUMBER; i++) {
|
||||||
tas_data[i].buttons = 0;
|
tas_data[i].buttons = 0;
|
||||||
tas_data[i].axis = {};
|
tas_data[i].axis = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < PLAYER_NUMBER; i++) {
|
for (size_t i = 0; i < PLAYER_NUMBER; i++) {
|
||||||
tas_data[i].buttons = 0;
|
tas_data[i].buttons = 0;
|
||||||
tas_data[i].axis = {};
|
tas_data[i].axis = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_DEBUG(Input, "TAS inputs: {}", debugInputs(tas_data));
|
LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
TasAnalog Tas::ReadCommandAxis(const std::string line) const {
|
TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
|
||||||
std::stringstream linestream(line);
|
std::stringstream linestream(line);
|
||||||
std::string segment;
|
std::string segment;
|
||||||
std::vector<std::string> seglist;
|
std::vector<std::string> seglist;
|
||||||
|
@ -228,7 +259,7 @@ TasAnalog Tas::ReadCommandAxis(const std::string line) const {
|
||||||
return {x, y};
|
return {x, y};
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 Tas::ReadCommandButtons(const std::string data) const {
|
u32 Tas::ReadCommandButtons(const std::string& data) const {
|
||||||
std::stringstream button_text(data);
|
std::stringstream button_text(data);
|
||||||
std::string line;
|
std::string line;
|
||||||
u32 buttons = 0;
|
u32 buttons = 0;
|
||||||
|
@ -262,8 +293,9 @@ std::string Tas::WriteCommandButtons(u32 data) const {
|
||||||
if ((data & 1) == 1) {
|
if ((data & 1) == 1) {
|
||||||
for (auto [text, tas_button] : text_to_tas_button) {
|
for (auto [text, tas_button] : text_to_tas_button) {
|
||||||
if (tas_button == static_cast<TasButton>(1 << index)) {
|
if (tas_button == static_cast<TasButton>(1 << index)) {
|
||||||
if (line.size() > 0)
|
if (line.size() > 0) {
|
||||||
line += ";";
|
line += ";";
|
||||||
|
}
|
||||||
line += text;
|
line += text;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,17 @@
|
||||||
#include "core/frontend/input.h"
|
#include "core/frontend/input.h"
|
||||||
#include "input_common/main.h"
|
#include "input_common/main.h"
|
||||||
|
|
||||||
#define PLAYER_NUMBER 8
|
|
||||||
|
|
||||||
namespace TasInput {
|
namespace TasInput {
|
||||||
|
|
||||||
using TasAnalog = std::tuple<float, float>;
|
constexpr int PLAYER_NUMBER = 8;
|
||||||
|
|
||||||
|
using TasAnalog = std::pair<float, float>;
|
||||||
|
|
||||||
|
enum class TasState {
|
||||||
|
RUNNING,
|
||||||
|
RECORDING,
|
||||||
|
STOPPED,
|
||||||
|
};
|
||||||
|
|
||||||
enum class TasButton : u32 {
|
enum class TasButton : u32 {
|
||||||
BUTTON_A = 0x000001,
|
BUTTON_A = 0x000001,
|
||||||
|
@ -41,29 +47,6 @@ enum class TasButton : u32 {
|
||||||
BUTTON_CAPTURE = 0x080000,
|
BUTTON_CAPTURE = 0x080000,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::array<std::pair<std::string, TasButton>, 20> text_to_tas_button = {
|
|
||||||
std::pair{"KEY_A", TasButton::BUTTON_A},
|
|
||||||
{"KEY_B", TasButton::BUTTON_B},
|
|
||||||
{"KEY_X", TasButton::BUTTON_X},
|
|
||||||
{"KEY_Y", TasButton::BUTTON_Y},
|
|
||||||
{"KEY_LSTICK", TasButton::STICK_L},
|
|
||||||
{"KEY_RSTICK", TasButton::STICK_R},
|
|
||||||
{"KEY_L", TasButton::TRIGGER_L},
|
|
||||||
{"KEY_R", TasButton::TRIGGER_R},
|
|
||||||
{"KEY_PLUS", TasButton::BUTTON_PLUS},
|
|
||||||
{"KEY_MINUS", TasButton::BUTTON_MINUS},
|
|
||||||
{"KEY_DLEFT", TasButton::BUTTON_LEFT},
|
|
||||||
{"KEY_DUP", TasButton::BUTTON_UP},
|
|
||||||
{"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
|
|
||||||
{"KEY_DDOWN", TasButton::BUTTON_DOWN},
|
|
||||||
{"KEY_SL", TasButton::BUTTON_SL},
|
|
||||||
{"KEY_SR", TasButton::BUTTON_SR},
|
|
||||||
{"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
|
|
||||||
{"KEY_HOME", TasButton::BUTTON_HOME},
|
|
||||||
{"KEY_ZL", TasButton::TRIGGER_ZL},
|
|
||||||
{"KEY_ZR", TasButton::TRIGGER_ZR},
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class TasAxes : u8 {
|
enum class TasAxes : u8 {
|
||||||
StickX,
|
StickX,
|
||||||
StickY,
|
StickY,
|
||||||
|
@ -82,7 +65,7 @@ public:
|
||||||
Tas();
|
Tas();
|
||||||
~Tas();
|
~Tas();
|
||||||
|
|
||||||
static std::string buttonsToString(u32 button) {
|
static std::string ButtonsToString(u32 button) {
|
||||||
std::string returns;
|
std::string returns;
|
||||||
if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_A)) != 0)
|
if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_A)) != 0)
|
||||||
returns += ", A";
|
returns += ", A";
|
||||||
|
@ -124,14 +107,14 @@ public:
|
||||||
returns += ", HOME";
|
returns += ", HOME";
|
||||||
if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_CAPTURE)) != 0)
|
if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_CAPTURE)) != 0)
|
||||||
returns += ", CAPTURE";
|
returns += ", CAPTURE";
|
||||||
return returns.length() != 0 ? returns.substr(2) : "";
|
return returns.empty() ? "" : returns.substr(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RefreshTasFile();
|
void RefreshTasFile();
|
||||||
void LoadTasFiles();
|
void LoadTasFiles();
|
||||||
void RecordInput(u32 buttons, std::array<std::pair<float, float>, 2> axes);
|
void RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes);
|
||||||
void UpdateThread();
|
void UpdateThread();
|
||||||
std::string GetStatusDescription();
|
std::tuple<TasState, size_t, size_t> GetStatus();
|
||||||
|
|
||||||
InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
|
InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
|
||||||
InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
|
InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
|
||||||
|
@ -143,21 +126,20 @@ private:
|
||||||
TasAnalog l_axis{};
|
TasAnalog l_axis{};
|
||||||
TasAnalog r_axis{};
|
TasAnalog r_axis{};
|
||||||
};
|
};
|
||||||
void LoadTasFile(int playerIndex);
|
void LoadTasFile(size_t player_index);
|
||||||
void WriteTasFile();
|
void WriteTasFile();
|
||||||
TasAnalog ReadCommandAxis(const std::string line) const;
|
TasAnalog ReadCommandAxis(const std::string& line) const;
|
||||||
u32 ReadCommandButtons(const std::string line) const;
|
u32 ReadCommandButtons(const std::string& line) const;
|
||||||
std::string WriteCommandButtons(u32 data) const;
|
std::string WriteCommandButtons(u32 data) const;
|
||||||
std::string WriteCommandAxis(TasAnalog data) const;
|
std::string WriteCommandAxis(TasAnalog data) const;
|
||||||
std::pair<float, float> flipY(std::pair<float, float> old) const;
|
|
||||||
|
|
||||||
size_t scriptLength{0};
|
size_t script_length{0};
|
||||||
std::array<TasData, PLAYER_NUMBER> tas_data;
|
std::array<TasData, PLAYER_NUMBER> tas_data;
|
||||||
bool update_thread_running{true};
|
bool update_thread_running{true};
|
||||||
bool refresh_tas_fle{false};
|
bool refresh_tas_fle{false};
|
||||||
std::array<std::vector<TASCommand>, PLAYER_NUMBER> newCommands{};
|
std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
|
||||||
std::vector<TASCommand> recordCommands{};
|
std::vector<TASCommand> record_commands{};
|
||||||
std::size_t current_command{0};
|
std::size_t current_command{0};
|
||||||
TASCommand lastInput{}; // only used for recording
|
TASCommand last_input{}; // only used for recording
|
||||||
};
|
};
|
||||||
} // namespace TasInput
|
} // namespace TasInput
|
||||||
|
|
|
@ -78,7 +78,7 @@ void ConfigureFilesystem::applyConfiguration() {
|
||||||
|
|
||||||
Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
|
Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
|
||||||
Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked();
|
Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked();
|
||||||
Settings::values.pauseTasOnLoad = ui->tas_pause_on_load->isChecked();
|
Settings::values.pause_tas_on_load = ui->tas_pause_on_load->isChecked();
|
||||||
Settings::values.dump_exefs = ui->dump_exefs->isChecked();
|
Settings::values.dump_exefs = ui->dump_exefs->isChecked();
|
||||||
Settings::values.dump_nso = ui->dump_nso->isChecked();
|
Settings::values.dump_nso = ui->dump_nso->isChecked();
|
||||||
|
|
||||||
|
|
|
@ -826,11 +826,11 @@ void GMainWindow::InitializeWidgets() {
|
||||||
});
|
});
|
||||||
statusBar()->insertPermanentWidget(0, renderer_status_button);
|
statusBar()->insertPermanentWidget(0, renderer_status_button);
|
||||||
|
|
||||||
TASlabel = new QLabel();
|
tas_label = new QLabel();
|
||||||
TASlabel->setObjectName(QStringLiteral("TASlabel"));
|
tas_label->setObjectName(QStringLiteral("TASlabel"));
|
||||||
TASlabel->setText(tr("TAS not running"));
|
tas_label->setText(tr("TAS not running"));
|
||||||
TASlabel->setFocusPolicy(Qt::NoFocus);
|
tas_label->setFocusPolicy(Qt::NoFocus);
|
||||||
statusBar()->insertPermanentWidget(0, TASlabel);
|
statusBar()->insertPermanentWidget(0, tas_label);
|
||||||
|
|
||||||
statusBar()->setVisible(true);
|
statusBar()->setVisible(true);
|
||||||
setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}"));
|
setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}"));
|
||||||
|
@ -2896,13 +2896,28 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string GetTasStateDescription(TasInput::TasState state) {
|
||||||
|
switch (state) {
|
||||||
|
case TasInput::TasState::RUNNING:
|
||||||
|
return "Running";
|
||||||
|
case TasInput::TasState::RECORDING:
|
||||||
|
return "Recording";
|
||||||
|
case TasInput::TasState::STOPPED:
|
||||||
|
return "Stopped";
|
||||||
|
default:
|
||||||
|
return "INVALID STATE";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::UpdateStatusBar() {
|
void GMainWindow::UpdateStatusBar() {
|
||||||
if (emu_thread == nullptr) {
|
if (emu_thread == nullptr) {
|
||||||
status_bar_update_timer.stop();
|
status_bar_update_timer.stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TASlabel->setText(tr(input_subsystem->GetTas()->GetStatusDescription().c_str()));
|
auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus();
|
||||||
|
tas_label->setText(tr("%1 TAS %2/%3").arg(tr(GetTasStateDescription(tas_status).c_str())).arg(current_tas_frame).arg(total_tas_frames));
|
||||||
|
|
||||||
auto& system = Core::System::GetInstance();
|
auto& system = Core::System::GetInstance();
|
||||||
auto results = system.GetAndResetPerfStats();
|
auto results = system.GetAndResetPerfStats();
|
||||||
auto& shader_notify = system.GPU().ShaderNotify();
|
auto& shader_notify = system.GPU().ShaderNotify();
|
||||||
|
|
Reference in New Issue