diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index e1b243c..69709b2 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -918,6 +918,12 @@ int main_implementation(int argc, char *argv[]) { PIN_MACRO_ENABLED = true; 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()) { static int reader_id = 0; diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index e4672b3..d417b66 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -2464,6 +2464,28 @@ static const std::vector OPTION_DEFINITIONS = { {"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 .title = "Low Latency Shared Audio", diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index dfddf57..9d3ff4a 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -253,6 +253,8 @@ namespace launcher { IIDXSubMonitorOverride, spice2x_IIDXEmulateSubscreenKeypadTouch, spice2x_AutoCard, + AutoPinMacroTrigger0, + AutoPinMacroTrigger1, spice2x_LowLatencySharedAudio, spice2x_TapeLedAlgorithm, spice2x_NoNVAPI, diff --git a/src/spice2x/misc/eamuse.cpp b/src/spice2x/misc/eamuse.cpp index 467d812..0d24ef1 100644 --- a/src/spice2x/misc/eamuse.cpp +++ b/src/spice2x/misc/eamuse.cpp @@ -1,5 +1,6 @@ #include "eamuse.h" +#include #include #include @@ -44,6 +45,9 @@ static uint8_t AUTO_INSERT_CARD_CACHED_DATA[2][8]; // pin macro bool PIN_MACRO_ENABLED = false; 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 bool PIN_MACRO_THREAD_ACTIVE = false; static uint16_t PIN_MACRO_TRIGGER_KEYS[2] = { @@ -51,6 +55,18 @@ static uint16_t PIN_MACRO_TRIGGER_KEYS[2] = { 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) { // get unit index @@ -324,6 +340,25 @@ void eamuse_pin_macro_start_thread() { // set active 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 PIN_MACRO_THREAD = new std::thread([]() { uint16_t keypad_overrides[] = { @@ -345,15 +380,20 @@ void eamuse_pin_macro_start_thread() { timeutils::PreciseSleepTimer timer; while (PIN_MACRO_THREAD_ACTIVE) { - // wait for key press + // wait for key press or auto-trigger if (!active_unit.has_value()) { for (int unit = 0; unit < 2; unit++) { if (PIN_MACRO_VALUES[unit].empty()) { continue; } - if (overlay_buttons && + bool key_press = overlay_buttons && (!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; // Reset key index pin_index[unit] = 0; @@ -405,6 +445,9 @@ void eamuse_pin_macro_stop_thread() { delete PIN_MACRO_THREAD; 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) { diff --git a/src/spice2x/misc/eamuse.h b/src/spice2x/misc/eamuse.h index 4f94bfa..8c5e532 100644 --- a/src/spice2x/misc/eamuse.h +++ b/src/spice2x/misc/eamuse.h @@ -38,6 +38,7 @@ extern float AUTO_INSERT_CARD_COOLDOWN; extern bool PIN_MACRO_ENABLED; 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(const std::filesystem::path &path, uint8_t *card, int unit_id);