mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
Automated PIN Macro after auto-inserting a card (#698)
## Description of change Adds a way to automatically use the PIN Macro for P1, P2 or both when Auto Card Insert is used. ### Launcher changes: - Added a checkbox to enable/disable the feature - Added a textbox for each player to set what to look for in the logs. ### How it works: The feature requires Auto Card Insert and PIN Macro to be enabled and set. It uses the Auto Card Insert value to enable the feature for P1, P2 or both, with a "master" checkbox to completely disable the feature. It then relies on the current PIN Macro code to send the PIN to the games. The games logs send a message when a new scene is loaded with the name of the scene. By hooking on the logs, we can see when a PIN is required and send the relevant macro. Games have different scene names, and sometimes one for each player, so the field is configurable per game per player. These could be hard-coded in a lookup table but I didn't want to recompile the project every time I wanted to test, and I don't have all the games to get this info from. The PIN Macro thread is used to hook to the logs and check if the defined string appears, which triggers the PIN macro just like a button press would. Setting nothing for the log trigger disables the feature for that player. It's useful if someone wants to have Auto Card Insert enabled for both players but only Auto Pin on 1 player, but is mainly there so the check won't happen if nothing is set. ## Testing I used SDVX and DDR to have a single and a two player test with the feature both enabled and disabled. I also tested multiple combinations of PIN Macro settings and Auto Card Insert settings with no apparent issues. The PIN is automatically entered as soon as the game requests it. ## Discussion I know that hooking to the logs might not be the best solution. I'm very open to discussions about this, and understand that this might be a deal breaker for this feature. I made this to use with my setup and thought I'd try opening this PR to see if the feature would be useful for others. I haven't found another way to make sure we're on the correct screen to launch the PIN Macro, and sending the macro every time the Auto Card Insert finishes had sync issues where the end of the PIN would actually be registered by the game. It would also be much more convenient for the users to hardcode a lookup table for every games' log-trigger but I don't have the games currently so I can't check the logs to see what would be best. --------- Co-authored-by: Maxime St-Pierre <maxime.stpierre@ticketmaster.com>
This commit is contained in:
@@ -918,6 +918,12 @@ int main_implementation(int argc, char *argv[]) {
|
|||||||
PIN_MACRO_ENABLED = true;
|
PIN_MACRO_ENABLED = true;
|
||||||
PIN_MACRO_VALUES[1] = options[launcher::Options::Player2PinMacro].value_text();
|
PIN_MACRO_VALUES[1] = options[launcher::Options::Player2PinMacro].value_text();
|
||||||
}
|
}
|
||||||
|
if (options[launcher::Options::AutoPinMacroTrigger0].is_active()) {
|
||||||
|
AUTO_PIN_MACRO_TRIGGER[0] = options[launcher::Options::AutoPinMacroTrigger0].value_text();
|
||||||
|
}
|
||||||
|
if (options[launcher::Options::AutoPinMacroTrigger1].is_active()) {
|
||||||
|
AUTO_PIN_MACRO_TRIGGER[1] = options[launcher::Options::AutoPinMacroTrigger1].value_text();
|
||||||
|
}
|
||||||
|
|
||||||
for (auto &reader : options[launcher::Options::ICCAReaderPort].values_text()) {
|
for (auto &reader : options[launcher::Options::ICCAReaderPort].values_text()) {
|
||||||
static int reader_id = 0;
|
static int reader_id = 0;
|
||||||
|
|||||||
@@ -2464,6 +2464,28 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
|||||||
{"both", ""},
|
{"both", ""},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// AutoPinMacroTrigger0
|
||||||
|
.title = "Auto PIN Macro Trigger Log String (P1)",
|
||||||
|
.name = "autopinmacrotrigger0",
|
||||||
|
.desc =
|
||||||
|
"Substring matched against game log output to detect when Player 1's PIN entry "
|
||||||
|
"screen is ready. When the substring appears in any log line, the PIN macro is "
|
||||||
|
"typed for Player 1 only. Leave blank to disable auto-trigger for P1.",
|
||||||
|
.type = OptionType::Text,
|
||||||
|
.category = "Network (Advanced)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// AutoPinMacroTrigger1
|
||||||
|
.title = "Auto PIN Macro Trigger Log String (P2)",
|
||||||
|
.name = "autopinmacrotrigger1",
|
||||||
|
.desc =
|
||||||
|
"Substring matched against game log output to detect when Player 2's PIN entry "
|
||||||
|
"screen is ready. When the substring appears in any log line, the PIN macro is "
|
||||||
|
"typed for Player 2 only. Leave blank to disable auto-trigger for P2.",
|
||||||
|
.type = OptionType::Text,
|
||||||
|
.category = "Network (Advanced)",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// spice2x_LowLatencySharedAudio
|
// spice2x_LowLatencySharedAudio
|
||||||
.title = "Low Latency Shared Audio",
|
.title = "Low Latency Shared Audio",
|
||||||
|
|||||||
@@ -253,6 +253,8 @@ namespace launcher {
|
|||||||
IIDXSubMonitorOverride,
|
IIDXSubMonitorOverride,
|
||||||
spice2x_IIDXEmulateSubscreenKeypadTouch,
|
spice2x_IIDXEmulateSubscreenKeypadTouch,
|
||||||
spice2x_AutoCard,
|
spice2x_AutoCard,
|
||||||
|
AutoPinMacroTrigger0,
|
||||||
|
AutoPinMacroTrigger1,
|
||||||
spice2x_LowLatencySharedAudio,
|
spice2x_LowLatencySharedAudio,
|
||||||
spice2x_TapeLedAlgorithm,
|
spice2x_TapeLedAlgorithm,
|
||||||
spice2x_NoNVAPI,
|
spice2x_NoNVAPI,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "eamuse.h"
|
#include "eamuse.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -44,6 +45,9 @@ static uint8_t AUTO_INSERT_CARD_CACHED_DATA[2][8];
|
|||||||
// pin macro
|
// pin macro
|
||||||
bool PIN_MACRO_ENABLED = false;
|
bool PIN_MACRO_ENABLED = false;
|
||||||
std::string PIN_MACRO_VALUES[2] = {"", ""};
|
std::string PIN_MACRO_VALUES[2] = {"", ""};
|
||||||
|
std::string AUTO_PIN_MACRO_TRIGGER[2];
|
||||||
|
static std::atomic_bool AUTO_PIN_MACRO_REQUEST[2] {false, false};
|
||||||
|
static bool AUTO_PIN_MACRO_PLAYER_ACTIVE[2] = {false, false};
|
||||||
static std::thread *PIN_MACRO_THREAD = nullptr;
|
static std::thread *PIN_MACRO_THREAD = nullptr;
|
||||||
static bool PIN_MACRO_THREAD_ACTIVE = false;
|
static bool PIN_MACRO_THREAD_ACTIVE = false;
|
||||||
static uint16_t PIN_MACRO_TRIGGER_KEYS[2] = {
|
static uint16_t PIN_MACRO_TRIGGER_KEYS[2] = {
|
||||||
@@ -51,6 +55,18 @@ static uint16_t PIN_MACRO_TRIGGER_KEYS[2] = {
|
|||||||
games::OverlayButtons::TriggerPinMacroP2
|
games::OverlayButtons::TriggerPinMacroP2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool pin_macro_log_hook(void *, const std::string &data, logger::Style, std::string &) {
|
||||||
|
for (int unit = 0; unit < 2; unit++) {
|
||||||
|
if (!AUTO_PIN_MACRO_PLAYER_ACTIVE[unit]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (data.find(AUTO_PIN_MACRO_TRIGGER[unit]) != std::string::npos) {
|
||||||
|
AUTO_PIN_MACRO_REQUEST[unit].store(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) {
|
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card) {
|
||||||
|
|
||||||
// get unit index
|
// get unit index
|
||||||
@@ -324,6 +340,25 @@ void eamuse_pin_macro_start_thread() {
|
|||||||
// set active
|
// set active
|
||||||
PIN_MACRO_THREAD_ACTIVE = true;
|
PIN_MACRO_THREAD_ACTIVE = true;
|
||||||
|
|
||||||
|
// a unit is eligible for auto-trigger only if all the static prerequisites are
|
||||||
|
// satisfied at startup; precomputing this lets the log hook skip per-line checks
|
||||||
|
// on values that never change at runtime
|
||||||
|
for (int unit = 0; unit < 2; unit++) {
|
||||||
|
AUTO_PIN_MACRO_PLAYER_ACTIVE[unit] =
|
||||||
|
!AUTO_PIN_MACRO_TRIGGER[unit].empty() &&
|
||||||
|
!PIN_MACRO_VALUES[unit].empty();
|
||||||
|
if(!AUTO_PIN_MACRO_TRIGGER[unit].empty() && PIN_MACRO_VALUES[unit].empty()) {
|
||||||
|
log_warning("eamuse", "Configuration error; Pin Macro empty for P{}.", unit+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register scene log hook so the macro fires on the per-game trigger string,
|
||||||
|
// but only if at least one player is eligible
|
||||||
|
if (AUTO_PIN_MACRO_PLAYER_ACTIVE[0] || AUTO_PIN_MACRO_PLAYER_ACTIVE[1]) {
|
||||||
|
logger::hook_add(pin_macro_log_hook, nullptr);
|
||||||
|
log_info("eamuse", "AUTO_PIN_MACRO enabled");
|
||||||
|
}
|
||||||
|
|
||||||
// create thread
|
// create thread
|
||||||
PIN_MACRO_THREAD = new std::thread([]() {
|
PIN_MACRO_THREAD = new std::thread([]() {
|
||||||
uint16_t keypad_overrides[] = {
|
uint16_t keypad_overrides[] = {
|
||||||
@@ -345,15 +380,20 @@ void eamuse_pin_macro_start_thread() {
|
|||||||
timeutils::PreciseSleepTimer timer;
|
timeutils::PreciseSleepTimer timer;
|
||||||
|
|
||||||
while (PIN_MACRO_THREAD_ACTIVE) {
|
while (PIN_MACRO_THREAD_ACTIVE) {
|
||||||
// wait for key press
|
// wait for key press or auto-trigger
|
||||||
if (!active_unit.has_value()) {
|
if (!active_unit.has_value()) {
|
||||||
for (int unit = 0; unit < 2; unit++) {
|
for (int unit = 0; unit < 2; unit++) {
|
||||||
if (PIN_MACRO_VALUES[unit].empty()) {
|
if (PIN_MACRO_VALUES[unit].empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (overlay_buttons &&
|
bool key_press = overlay_buttons &&
|
||||||
(!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered()) &&
|
(!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered()) &&
|
||||||
GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(PIN_MACRO_TRIGGER_KEYS[unit]))) {
|
GameAPI::Buttons::getState(RI_MGR, overlay_buttons->at(PIN_MACRO_TRIGGER_KEYS[unit]));
|
||||||
|
bool auto_request = AUTO_PIN_MACRO_REQUEST[unit].exchange(false);
|
||||||
|
if (auto_request) {
|
||||||
|
log_info("eamuse", "AUTO_PIN_MACRO_REQUEST detected for P{}", unit+1);
|
||||||
|
}
|
||||||
|
if (key_press || auto_request) {
|
||||||
active_unit = unit;
|
active_unit = unit;
|
||||||
// Reset key index
|
// Reset key index
|
||||||
pin_index[unit] = 0;
|
pin_index[unit] = 0;
|
||||||
@@ -405,6 +445,9 @@ void eamuse_pin_macro_stop_thread() {
|
|||||||
delete PIN_MACRO_THREAD;
|
delete PIN_MACRO_THREAD;
|
||||||
PIN_MACRO_THREAD = nullptr;
|
PIN_MACRO_THREAD = nullptr;
|
||||||
}
|
}
|
||||||
|
if (AUTO_PIN_MACRO_PLAYER_ACTIVE[0] || AUTO_PIN_MACRO_PLAYER_ACTIVE[1]) {
|
||||||
|
logger::hook_remove(pin_macro_log_hook, nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void eamuse_set_keypad_overrides(size_t unit, uint16_t keypad_state) {
|
void eamuse_set_keypad_overrides(size_t unit, uint16_t keypad_state) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ extern float AUTO_INSERT_CARD_COOLDOWN;
|
|||||||
|
|
||||||
extern bool PIN_MACRO_ENABLED;
|
extern bool PIN_MACRO_ENABLED;
|
||||||
extern std::string PIN_MACRO_VALUES[2];
|
extern std::string PIN_MACRO_VALUES[2];
|
||||||
|
extern std::string AUTO_PIN_MACRO_TRIGGER[2];
|
||||||
|
|
||||||
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card);
|
bool eamuse_get_card(int active_count, int unit_id, uint8_t *card);
|
||||||
bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int unit_id);
|
bool eamuse_get_card(const std::filesystem::path &path, uint8_t *card, int unit_id);
|
||||||
|
|||||||
Reference in New Issue
Block a user