One Trillion Free Draws Accelerator and Automation

Also provides a user control interface

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         One Trillion Free Draws Accelerator and Automation
// @namespace    xfdz.OTFDAA
// @version      2.0
// @description  Also provides a user control interface
// @author       Zero (Acceleration), 销锋镝铸 (Automation), @superslacker87 (English Translator)
// @match        https://duducat.moe/gacha/*
// @match        https://gityxs.github.io/one-trillion-free-draws/*
// @grant        none
// @run-at       document-end
// @license      MIT
// @downloadURL
// @updateURL
// ==/UserScript==

(function () {
  "use strict";
  const defaultOptions = {
    speedMultiplier: 1,
    autoDraw: {
      enabled: false,
      drawOnFull: false,
    },
    autoSkill: {
      enabled: false,
      skills: {
        fire: false,
        water: false,
        leaf: false,
        sun: false,
        moon: false,
      },
    },
  };
  let options = {};

  let interval = null;
  const intervalActions = new Map();

  let accelerator = null;
  let autoDrawManager = null;
  let autoSkillManager = null;
  let uiManager = null;

  function initialize() {
    try {
      let optionsInLocalStorage = JSON.parse(
        window.localStorage.getItem("OTFDAAOptions")
      );
      if (optionsInLocalStorage) {
        try {
          options = Object.assign({}, defaultOptions, optionsInLocalStorage);
          console.log("[OTFDAA] Settings loaded from local storage");
        } catch {
          options = defaultOptions;
          console.log(
            "[OTFDAA] There was a problem with the settings in local storage, initializing with defaults"
          );
        }
      } else {
        options = defaultOptions;
        saveOptions();
        console.log("[OTFDAA] Settings not found, initializing with defaults");
      }
    } catch (e) {
      options = defaultOptions;
      console.log("[OTFDAA] Settings not found, initializing with defaults");
    }
    accelerator = new Accelerator();
    autoDrawManager = new AutoDrawManager();
    autoSkillManager = new AutoSkillManager();
    uiManager = new UIManager();
    interval = setInterval(() => {
      for (let action of intervalActions.values()) {
        action();
      }
    }, 500);
    window.addEventListener("beforeunload", () => {
      accelerator.restoreOriginalAPIs();
      clearInterval(interval);
    });
  }

  function saveOptions() {
    window.localStorage.setItem("OTFDAAOptions", JSON.stringify(options));
  }

  class Accelerator {
    originalAPIs;
    performanceNowOverridden = false;
    requestAnimationFrameOverridden = false;
    syncTimeSourcesOverridden = false;

    constructor() {
      this.originalAPIs = {
        raf: window.requestAnimationFrame.bind(window),
        performanceNow: performance.now.bind(performance),
        DateNow: Date.now,
      };
      try {
        this.initialize();
        console.log("[OTFDAA] Accelerator OTFDAA has been activated");
      } catch (error) {
        console.error("[OTFDAA] Initialization failed:", error);
        setTimeout(this.initialize, 1500); // Retry initialization
      }
    }

    initialize() {
      this.overridePerformanceNow();
      this.overrideRequestAnimationFrame();
      this.syncTimeSources();
    }

    setSpeedMultiplier(multiplier) {
      options.speedMultiplier = multiplier;
      console.log(
        `[OTFDAA] Game speed has been changed to ×${options.speedMultiplier}`
      );
      saveOptions();
    }

    // Hook performance.now
    overridePerformanceNow() {
      if (this.performanceNowOverridden) {
        return;
      }
      Object.defineProperty(performance, "now", {
        value: () =>
          this.originalAPIs.performanceNow() * options.speedMultiplier,
        configurable: true,
        writable: false,
      });
      console.log("[OTFDAA] performance.now has been hooked");
      this.performanceNowOverridden = true;
    }

    // Hook requestAnimationFrame
    overrideRequestAnimationFrame() {
      if (this.requestAnimationFrameOverridden) {
        return;
      }
      window.requestAnimationFrame = (callback) => {
        return this.originalAPIs.raf((timestamp) => {
          callback(timestamp * options.speedMultiplier);
        });
      };
      console.log("[OTFDAA] requestAnimationFrame has been hooked");
      this.requestAnimationFrameOverridden = true;
    }

    // Synchronize time sources
    syncTimeSources() {
      if (this.syncTimeSourcesOverridden) {
        return;
      }
      const baseTime = Date.now();
      Date.now = () => baseTime + (performance.now() - baseTime);
      console.log("[OTFDAA] Time sources have been synchronized");
      this.syncTimeSourcesOverridden = true;
    }

    // Restore original APIs
    restoreOriginalAPIs() {
      Object.defineProperty(performance, "now", {
        value: this.originalAPIs.performanceNow,
        configurable: true,
      });
      window.requestAnimationFrame = this.originalAPIs.raf;
      Date.now = this.originalAPIs.DateNow;
      console.log("[OTFDAA] Original APIs have been restored");
      this.performanceNowOverridden = false;
      this.requestAnimationFrameOverridden = false;
      this.syncTimeSourcesOverridden = false;
    }
  }

  class AutoDrawManager {
    constructor() {
      this.setAutoDraw(options.autoDraw.enabled, false);
    }

    setAutoDraw(on, save = true) {
      options.autoDraw.enabled = on;
      if (on) {
        if (!intervalActions.has("autoDraw")) {
          intervalActions.set("autoDraw", this.draw);
        }
      } else {
        intervalActions.delete("autoDraw");
      }
      if (save) {
        saveOptions();
      }
    }

    draw() {
      try {
        const drawButton = options.autoDraw.drawOnFull
          ? document
              .querySelector("#draw-zone > .currency.f-fire")
              ?.parentElement?.querySelector("#draw-button")
          : document.querySelector("#draw-button");
        if (drawButton) {
          drawButton.click();
        }
        const cancelButton = document.querySelector(
          ".card-list.done + .draw-result > button"
        );
        if (cancelButton) {
          cancelButton.click();
        }
      } catch {}
    }

    setDrawOnFull(on) {
      options.autoDraw.drawOnFull = on;
      saveOptions();
    }
  }

  class AutoSkillManager {
    constructor() {
      this.setAutoSkillEnabled(options.autoSkill.enabled, false);
    }

    setAutoSkillEnabled(on, save = true) {
      options.autoSkill.enabled = on;
      if (on) {
        if (!intervalActions.has("autoSkill")) {
          intervalActions.set("autoSkill", this.useSkill);
        }
      } else {
        intervalActions.delete("autoSkill");
      }
      if (save) {
        saveOptions();
      }
    }

    useSkill() {
      if (!options.autoSkill.enabled) {
        return;
      }
      const skillHolder = document.querySelector("#draw-options .skill-holder");
      if (!skillHolder) {
        return;
      }
      const skillButtons = skillHolder.children;
      for (let i = 0; i < 5; i++) {
        let skillButton = skillButtons[i];
        if (skillButton.classList.contains("disabled")) {
          continue;
        }
        for (const [skill, on] of Object.entries(options.autoSkill.skills)) {
          if (on) {
            const button = skillHolder.querySelector(
              `.f-${skill}:not(.disabled)`
            );
            if (button) {
              button.click();
            }
          }
        }
      }
    }

    setAutoSkill(skill, on) {
      switch (skill) {
        case "fire":
          options.autoSkill.skills.fire = on;
          break;
        case "water":
          options.autoSkill.skills.water = on;
          break;
        case "leaf":
          options.autoSkill.skills.leaf = on;
          break;
        case "sun":
          options.autoSkill.skills.sun = on;
          break;
        case "moon":
          options.autoSkill.skills.moon = on;
          break;
      }
      saveOptions();
    }
  }

  class UIManager {
    constructor() {
      this.insertStyle();
      this.createControlPanel();
    }

    insertStyle() {
      const style = document.createElement("style");
      style.textContent = `
#OTFDAAControlPanel {
    display: flex;
    flex-direction: column;
    gap: 8px;
    position: fixed;
    bottom: 40px;
    left: 25px;
    max-width: 248px;
    z-index: 2000001;
}
#OTFDAAControlPanel .OTFDAAOptionContainer {
    display: flex;
    gap: 12px;
    justify-content: space-between;
    align-items: center;
    min-height: 35px;
}
#OTFDAASkillPanel {
    display: flex;
    gap: 2px;
}
#OTFDAASkillPanel button{
    flex: 1;
    aspect-ratio: 1;
}`;
      document.head.appendChild(style);
    }

    createControlPanel() {
      if (document.getElementById("OTFDAAControlPanel")) {
        return;
      }
      const panelContainer = document.createElement("div");
      panelContainer.id = "OTFDAAControlPanel";
      panelContainer.classList.add("opt-container");
      const panelTitle = document.createElement("h3");
      panelTitle.textContent = "OTFDAA Control Panel";
      panelTitle.style.marginTop = "10px";
      panelContainer.appendChild(panelTitle);
      this.createCheckbox(
        "Auto Draw",
        panelContainer,
        options.autoDraw.enabled,
        (event) => {
          autoDrawManager.setAutoDraw(event.target.checked);
        }
      );
      this.createCheckbox(
        "Wait for bulk energy to be full",
        panelContainer,
        options.autoDraw.drawOnFull,
        (event) => {
          autoDrawManager.setDrawOnFull(event.target.checked);
        }
      );
      this.createCheckbox(
        "Auto Skill",
        panelContainer,
        options.autoSkill.enabled,
        (event) => {
          autoSkillManager.setAutoSkillEnabled(event.target.checked);
          const skillPanel = document.getElementById("OTFDAASkillPanel");
          if (skillPanel) {
            skillPanel.style.display = event.target.checked ? "flex" : "none";
          }
        }
      );
      this.createNumberInput(
        "Speed Multiplier",
        panelContainer,
        0.1,
        100,
        0.1,
        options.speedMultiplier,
        (event) => {
          let num = event.target.valueAsNumber;
          if (num < 0.1) {
            num = 0.1;
            event.target.value = num;
          }
          if (num > 100) {
            num = 100;
            event.target.value = num;
          }
          accelerator.setSpeedMultiplier(num);
        }
      );
      document.body.appendChild(panelContainer);
      this.createSkillButtons();
    }

    createCheckbox(title, parent, checked, onChange) {
      const container = document.createElement("div");
      container.classList.add("OTFDAAOptionContainer");
      const titleLabel = document.createElement("label");
      titleLabel.textContent = title;
      titleLabel.htmlFor = title;
      const checkbox = document.createElement("input");
      checkbox.type = "checkbox";
      checkbox.id = title;
      checkbox.checked = checked;
      checkbox.addEventListener("change", onChange);
      container.appendChild(titleLabel);
      container.appendChild(checkbox);
      parent.appendChild(container);
    }

    createNumberInput(title, parent, min, max, step, value, onChange) {
      const container = document.createElement("div");
      container.classList.add("OTFDAAOptionContainer");
      const titleLabel = document.createElement("label");
      titleLabel.textContent = title;
      titleLabel.htmlFor = title;
      const input = document.createElement("input");
      input.type = "number";
      input.id = title;
      input.min = min;
      input.max = max;
      input.step = step;
      input.value = value;
      input.addEventListener("change", onChange);
      container.appendChild(titleLabel);
      container.appendChild(input);
      parent.appendChild(container);
    }

    createSlider(title, parent, min, max, step, value, onChange) {
      const container = document.createElement("div");
      container.classList.add("OTFDAAOptionContainer");
      const titleSpan = document.createElement("span");
      titleSpan.textContent = title;
      const sliderContainer = document.createElement("div");
      const sliderInput = document.createElement("input");
      sliderInput.type = "range";
      sliderInput.id = title;
      sliderInput.min = min;
      sliderInput.max = max;
      sliderInput.step = step;
      sliderInput.value = value;
      sliderInput.style.width = "140px";
      const sliderLabel = document.createElement("label");
      sliderLabel.textContent = value.toFixed(1);
      sliderLabel.htmlFor = title;
      sliderInput.addEventListener("change", (event) => {
        sliderLabel.textContent = "×" + sliderInput.valueAsNumber.toFixed(1);
        onChange(event);
      });
      sliderContainer.appendChild(sliderLabel);
      sliderContainer.appendChild(sliderInput);
      container.appendChild(titleSpan);
      container.appendChild(sliderContainer);
      parent.appendChild(container);
    }

    createSkillButtons() {
      if (document.getElementById("OTFDAASkillPanel")) {
        return;
      }
      const skillHolder = document.querySelector("#draw-options .skill-holder");
      if (!skillHolder) {
        return;
      }
      const container = document.createElement("div");
      container.style.display = options.autoSkill.enabled ? "flex" : "none";
      container.id = "OTFDAASkillPanel";
      for (const [skill, on] of Object.entries(options.autoSkill.skills)) {
        const button = document.createElement("button");
        button.classList.add(`f-${skill}`);
        button.disabled = true;
        const checkbox = document.createElement("input");
        checkbox.type = "checkbox";
        checkbox.checked = on;
        checkbox.addEventListener("change", (event) => {
          autoSkillManager.setAutoSkill(skill, event.target.checked);
        });
        button.appendChild(checkbox);
        container.appendChild(button);
      }
      skillHolder.after(container);
    }
  }

  if (!window.OTFDAALoaded) {
    window.OTFDAALoaded = true;
    if (document.readyState === "complete") {
      initialize();
    } else {
      window.addEventListener("load", () => {
        initialize();
      });
    }
  }
})();