Moar Attack Buttons

Adds a button to each weapon for faster fight initiation

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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();
})();