Moar Attack Buttons

Adds a button to each weapon for faster fight initiation

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Moar Attack Buttons
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Adds a button to each weapon for faster fight initiation
// @author       xentac
// @match        https://www.torn.com/page.php?sid=attack*
// @grant        none
// @run-at document-end
// ==/UserScript==

(function () {
  "use strict";

  // Constants for script identification and selection
  const CUSTOM_BTN_CLASS = "script-bridge-trigger";
  const COMBAT_BTN_SELECTOR =
    '[class*="modal____"] [class*="dialogWrapper___"] button[type="submit"]';

  /**
   * Finds the primary target button and triggers its click handler,
   * then cleans up all script-generated buttons.
   */
  function executeCombatSequence() {
    const targetButton = document.querySelector(COMBAT_BTN_SELECTOR);
    for (const c of targetButton.classList) {
      if (c.startsWith("btnTimer__")) {
        console.warn(
          '[Bridge Script] Target button ("Start fight" or "Join Fight") has a timer.',
        );
        return;
      }
    }

    if (targetButton) {
      targetButton.click();
    } else {
      console.warn(
        '[Bridge Script] Target button ("Start fight" or "Join Fight") was not found in the DOM.',
      );
      return;
    }

    // Cleanup: Purge all buttons created by this script
    document
      .querySelectorAll(`.${CUSTOM_BTN_CLASS}`)
      .forEach((btn) => btn.remove());
  }

  /**
   * Iterates through available figure containers and injects the trigger overlay.
   */
  async function injectOverlays() {
    // Target weapon figures safely by their built-in prefix naming convention
    const found = await waitForElement(
      '[aria-describedby*="label_attacker_"] [class*="weaponImage___"]',
      10_000,
    );

    if (!found) {
      return;
    }

    const figures = document.querySelectorAll(
      '[aria-describedby*="label_attacker_"] [class*="weaponImage___"]',
    );

    figures.forEach((figure) => {
      // Avoid duplicate injections if the DOM re-evaluates
      if (figure.querySelector(`.${CUSTOM_BTN_CLASS}`)) return;

      // Ensure the parent container supports relative positioning context
      if (window.getComputedStyle(figure).position === "static") {
        figure.style.position = "relative";
      }

      // Construct structural overlay button
      const overlayBtn = document.createElement("button");
      overlayBtn.className = CUSTOM_BTN_CLASS;
      overlayBtn.textContent = "Fight";

      // Inline styling to ensure visual layout visibility directly over the figure asset
      Object.assign(overlayBtn.style, {
        position: "absolute",
        top: "0",
        left: "0",
        width: "100%",
        height: "100%",
        zIndex: "9999",
        padding: "4px 8px",
        fontSize: "11px",
        color: "#fff",
        border: "1px solid #666",
        borderRadius: "3px",
        cursor: "pointer",
        backgroundColor: "rgba(51, 51, 51, 0.4)",
      });

      overlayBtn.addEventListener("click", (e) => {
        //e.stopPropagation(); // Stop event bubbling to underlying React elements
        executeCombatSequence();
      });

      figure.appendChild(overlayBtn);
    });
  }

  function waitForElement(selector, timeoutMs = 15_000) {
    return new Promise((resolve) => {
      const existing = document.querySelector(selector);
      if (existing) {
        return resolve(existing);
      }

      const observer = new MutationObserver((_, obs) => {
        const el = document.querySelector(selector);
        if (el) {
          obs.disconnect();
          resolve(el);
        }
      });

      observer.observe(document.documentElement, {
        childList: true,
        subtree: true,
      });

      if (timeoutMs > 0) {
        setTimeout(() => {
          observer.disconnect();
          log.debug(`Timeout waiting for element selector: '${selector}'`);
          resolve(null);
        }, timeoutMs);
      }
    });
  }

  // Initial check
  injectOverlays();
})();