Stimulation Splitter

LiveSplit Auto-Split support for Stimulation Clicker!

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        Stimulation Splitter
// @namespace   mikarific.com
// @description LiveSplit Auto-Split support for Stimulation Clicker!
// @icon        https://raw.githubusercontent.com/Mikarific/StimulationSplitter/main/assets/userscript/icon.png
// @icon64      https://raw.githubusercontent.com/Mikarific/StimulationSplitter/main/assets/userscript/icon64.png
// @version     0.1.1
// @author      Mikarific
// @match       https://neal.fun/*
// @run-at      document-start
// @noframes    
// @inject-into browser
// @sandbox     raw
// @connect     github.com
// @supportURL  https://discord.gg/Ka4ww68xnY
// @homepageURL https://discord.gg/Ka4ww68xnY
// @license     MIT
// @grant       GM.getValue
// @grant       GM.registerMenuCommand
// @grant       GM.setValue
// ==/UserScript==

(function () {
'use strict';

if (window.location.hostname === 'neal.fun') {
  let finishedLoading = false;
  window.history.pushState = new Proxy(history.pushState, {
    apply: (target, thisArg, argsList) => {
      if (argsList[2] !== undefined && finishedLoading) {
        const newURL = new URL(argsList[2], document.baseURI);
        const isStimulationClicker = newURL.hostname === 'neal.fun' && newURL.pathname === '/stimulation-clicker/';
        if (isStimulationClicker) location.replace(newURL);
      }
      return Reflect.apply(target, thisArg, argsList);
    }
  });
  window.history.replaceState = new Proxy(history.replaceState, {
    apply: (target, thisArg, argsList) => {
      if (argsList[2] !== undefined && finishedLoading) {
        const newURL = new URL(argsList[2], document.baseURI);
        const isStimulationClicker = newURL.hostname === 'neal.fun' && newURL.pathname === '/stimulation-clicker/';
        if (isStimulationClicker) location.replace(newURL);
      }
      return Reflect.apply(target, thisArg, argsList);
    }
  });
  window.addEventListener('popstate', () => {
    if (finishedLoading) {
      const newURL = new URL(window.location.href);
      const isStimulationClicker = newURL.hostname === 'neal.fun' && newURL.pathname === '/stimulation-clicker/';
      if (isStimulationClicker) location.replace(newURL);
    }
  });
  if (document.readyState === 'interactive') {
    finishedLoading = true;
  } else {
    window.addEventListener('DOMContentLoaded', () => {
      finishedLoading = true;
    }, {
      once: true
    });
  }
}

function domPromise(elem) {
  return new Promise(resolve => {
    if (window.location.hostname === 'neal.fun' && window.location.pathname === '/stimulation-clicker/') {
      if (document.readyState === 'interactive') {
        resolve(elem());
      } else {
        window.addEventListener('DOMContentLoaded', () => {
          resolve(elem());
        }, {
          once: true
        });
      }
    } else {
      resolve(null);
    }
  });
}
const container = domPromise(() => document.querySelector('.container'));
const dom = {
  container
};

function getVueState(container, resolve) {
  if (container.__vue__ !== undefined) {
    const vueState = container.__vue__.stimulation === undefined ? container.__vue__.$children.find(child => child.stimulation !== undefined) : container.__vue__;
    resolve(vueState);
  } else {
    Object.defineProperty(container, '__vue__', {
      set(vueState) {
        if (vueState !== null) {
          resolve(vueState);
          Object.defineProperty(container, '__vue__', {
            value: vueState,
            writable: true,
            configurable: true,
            enumerable: true
          });
        }
      },
      configurable: true,
      enumerable: true
    });
  }
}
const state = new Promise(resolve => {
  if (window.location.hostname === 'neal.fun' && window.location.pathname === '/stimulation-clicker/') {
    if (document.readyState === 'complete') {
      dom.container.then(async container => getVueState(await container, resolve));
    } else {
      window.addEventListener('load', () => {
        dom.container.then(async container => getVueState(await container, resolve));
      }, {
        once: true
      });
    }
  } else {
    resolve(null);
  }
});

async function startLiveSplit() {
  const livesplitServer = new WebSocket('ws://127.0.0.1:16834/livesplit');
  livesplitServer.onopen = () => {
    livesplitServer.send('reset');
  };
  function getSplitIndex() {
    return new Promise(resolve => {
      livesplitServer.onmessage = event => {
        livesplitServer.onmessage = null;
        resolve(parseInt(event.data));
      };
      livesplitServer.send('getsplitindex');
    });
  }
  async function split(splitTo) {
    let splitIndex = await getSplitIndex();
    for (let i = Math.max(splitIndex, 0); i < splitTo; i++) {
      livesplitServer.send('skipsplit');
      splitIndex++;
    }
    if (splitIndex === splitTo) livesplitServer.send('split');
  }
  const vueState = await state;

  // When stimulation is first shown, start the timer
  const {
    set: showStimulationSetter
  } = Object.getOwnPropertyDescriptor(vueState, 'showStimulation');
  Object.defineProperty(vueState, 'showStimulation', {
    set(showStimulation) {
      if (!vueState.showStimulation && showStimulation) livesplitServer.send('starttimer');
      return showStimulationSetter.call(this, showStimulation);
    },
    configurable: true,
    enumerable: true
  });

  // When Hydraulic Press is purchased, split to index 0
  const {
    set: showHydraulicPressSetter
  } = Object.getOwnPropertyDescriptor(vueState, 'showHydraulicPress');
  Object.defineProperty(vueState, 'showHydraulicPress', {
    set(showHydraulicPress) {
      if (!vueState.showHydraulicPress && showHydraulicPress) split(0);
      return showHydraulicPressSetter.call(this, showHydraulicPress);
    },
    configurable: true,
    enumerable: true
  });

  // When Levels is purchased, split to index 1
  const {
    set: showLevelsSetter
  } = Object.getOwnPropertyDescriptor(vueState, 'showLevels');
  Object.defineProperty(vueState, 'showLevels', {
    set(showLevels) {
      if (!vueState.showLevels && showLevels) split(1);
      return showLevelsSetter.call(this, showLevels);
    },
    configurable: true,
    enumerable: true
  });

  // When Stock Market is purchased, split to index 2
  const {
    set: showStockMarketSetter
  } = Object.getOwnPropertyDescriptor(vueState, 'showStockMarket');
  Object.defineProperty(vueState, 'showStockMarket', {
    set(showStockMarket) {
      if (!vueState.showStockMarket && showStockMarket) split(2);
      return showStockMarketSetter.call(this, showStockMarket);
    },
    configurable: true,
    enumerable: true
  });

  // When Email is purchased, split to index 3
  const {
    set: inboxUnlockedSetter
  } = Object.getOwnPropertyDescriptor(vueState, 'inboxUnlocked');
  Object.defineProperty(vueState, 'inboxUnlocked', {
    set(inboxUnlocked) {
      if (!vueState.inboxUnlocked && inboxUnlocked) split(3);
      return inboxUnlockedSetter.call(this, inboxUnlocked);
    },
    configurable: true,
    enumerable: true
  });

  // When Crypto is purchased, split to index 4
  const {
    set: cryptoUnlockedSetter
  } = Object.getOwnPropertyDescriptor(vueState, 'cryptoUnlocked');
  Object.defineProperty(vueState, 'cryptoUnlocked', {
    set(cryptoUnlocked) {
      if (!vueState.cryptoUnlocked && cryptoUnlocked) split(4);
      return cryptoUnlockedSetter.call(this, cryptoUnlocked);
    },
    configurable: true,
    enumerable: true
  });

  // When Leverage is purchased, split to index 5
  const {
    set: stockLeverageSetter
  } = Object.getOwnPropertyDescriptor(vueState, 'stockLeverage');
  Object.defineProperty(vueState, 'stockLeverage', {
    set(stockLeverage) {
      if (vueState.stockLeverage === 1 && stockLeverage === 2) split(5);
      return stockLeverageSetter.call(this, stockLeverage);
    },
    configurable: true,
    enumerable: true
  });

  // When Subway Surfers Wormhole is purchased, split to index 6
  vueState.startWormhole = new Proxy(vueState.startWormhole, {
    apply: (target, thisArg, argsList) => {
      split(6);
      return Reflect.apply(target, thisArg, argsList);
    }
  });

  // When Go to the Ocean is purchased, split to index 7
  vueState.endGame = new Proxy(vueState.endGame, {
    apply: (target, thisArg, argsList) => {
      split(7);
      return Reflect.apply(target, thisArg, argsList);
    }
  });
}
if (window.location.hostname === 'neal.fun' && window.location.pathname === '/stimulation-clicker/') {
  if (document.readyState === 'complete') {
    startLiveSplit();
  } else {
    window.addEventListener('load', startLiveSplit, {
      once: true
    });
  }
}

async function patchDVDs() {
  if (await GM.getValue('dvdStandardization', true)) {
    const vueState = await state;
    const bgState = vueState.$refs.bg;
    const renderer = bgState.$refs.renderer;
    renderer.style.width = '1920px';
    renderer.style.height = '1080px';
    renderer.style.position = 'fixed';
    renderer.style.top = '50%';
    renderer.style.left = '50%';
    renderer.style.transform = 'translate(-50%, -50%)';

    // We don't have direct access to updateDVDs as it's not part of vue data,
    // but we can set the size of the window to 1920x1080 before the dvd hits
    // calculation by setting them before the start of bgAnimationLoop, and
    // resetting them after the bgAnimationLoop function has executed.
    vueState.bgAnimationLoop = new Proxy(vueState.bgAnimationLoop, {
      apply: (target, thisArg, argsList) => {
        const realWidth = window.innerWidth;
        const realHeight = window.innerHeight;
        window.innerWidth = 1920;
        window.innerHeight = 1080;
        const returnValue = Reflect.apply(target, thisArg, argsList);
        window.innerWidth = realWidth;
        window.innerHeight = realHeight;
        return returnValue;
      }
    });
    // This is purely for the visuals, actually renders the DVDs at the size of the canvas (1920x1080)
    bgState.resize();
  }
}
if (window.location.hostname === 'neal.fun' && window.location.pathname === '/stimulation-clicker/') {
  if (document.readyState === 'complete') {
    patchDVDs();
  } else {
    window.addEventListener('load', patchDVDs, {
      once: true
    });
  }
}

GM.registerMenuCommand('Toggle DVD Standardization', async () => {
  const dvdStandardization = await GM.getValue('dvdStandardization', true);
  GM.setValue('dvdStandardization', !dvdStandardization);
  if (dvdStandardization) alert('DVD Standardization has been turned OFF');
  if (!dvdStandardization) alert('DVD Standardization has been turned ON');
  location.reload();
});

})();