Accelerator

place pixels faster

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Accelerator
// @namespace    http://tampermonkey.net/
// @version      1.4.0
// @description  place pixels faster
// @author       zZedo
// @match        https://pixelplace.io/*-*
// @license      MIT
// @grant        none
// @run-at       document-start
// @require      https://update.greasyfork.org/scripts/498080/1395134/Hacktimer.js
// ==/UserScript==

(function () {
  'use strict';

  const SPEEDS = {
    slow:   { ms: 12 },
    medium: { ms: 9  },
    fast:   { ms: 5  },
  };

  const WS_INTERVAL = 1000; // ms between websocket packets

  let enabled      = true;
  let spaceHeld    = false;
  let autoMode     = false;
  let currentSpeed = 'medium';
  let dispatching  = false;
  let panelVisible = true;
  let hookedSocket = null;

  const OriginalWS = window.WebSocket;
  window.WebSocket = function (url, protocols) {
    const socket = protocols ? new OriginalWS(url, protocols) : new OriginalWS(url);
    socket.addEventListener('open', () => {
      if (url.includes('pixelplace.io')) {
        hookedSocket = socket;
        updateUI();
      }
    });
    socket.addEventListener('close', () => {
      if (hookedSocket === socket) hookedSocket = null;
      updateUI();
    });
    return socket;
  };
  window.WebSocket.prototype = OriginalWS.prototype;

  function getMouseData() {
    const coords = document.getElementById('coordinates')?.textContent || '0,0';
    const [x, y] = coords.split(',').map(c => parseInt(c.trim()));
    const color = document.querySelector('#palette-buttons a.selected')?.getAttribute('data-id');
    return { x, y, color: parseInt(color) };
  }

  const _channel = new MessageChannel();
  let _channelActive = false;
  let _channelCallback = null;

  _channel.port2.onmessage = () => {
    if (_channelActive && _channelCallback) {
      _channelCallback();
      _channel.port1.postMessage(null);
    }
  };

  function startChannel(cb) {
    _channelCallback = cb;
    _channelActive = true;
    _channel.port1.postMessage(null);
  }

  function stopChannel() {
    _channelActive = false;
    _channelCallback = null;
  }

  function kbTick() {
    dispatching = true;
    const canvas = document.querySelector('canvas');
    const target = canvas || document.body;
    const opts = {
      bubbles: true, cancelable: true,
      code: 'Space', key: ' ', keyCode: 32, which: 32, view: window,
    };
    target.dispatchEvent(new KeyboardEvent('keydown', opts));
    target.dispatchEvent(new KeyboardEvent('keyup',   opts));
    dispatching = false;
  }

  function wsTick() {
    if (!hookedSocket || hookedSocket.readyState !== 1) return;
    const { x, y, color } = getMouseData();
    if (!isNaN(x) && !isNaN(y) && !isNaN(color)) {
      hookedSocket.send(`42["p",[${x},${y},${color},1]]`);
    }
  }

  function makeLoop(tickFn, ms) {
    let timer = null;
    let expected = performance.now() + ms;

    function step() {
      if (!timer) return;
      tickFn();
      const drift = performance.now() - expected;
      expected += ms;
      timer = setTimeout(step, Math.max(0, ms - drift));
    }

    timer = setTimeout(step, ms);
    return {
      stop: () => { clearTimeout(timer); timer = null; },
      get active() { return timer !== null; }
    };
  }

  let kbRunning = false;
  let wsLoop = null;

  function startLoops() {
    if (!kbRunning) {
      kbRunning = true;
      startChannel(kbTick);
    }
    if (!wsLoop && hookedSocket) wsLoop = makeLoop(wsTick, WS_INTERVAL);
  }

  function stopLoops() {
    if (kbRunning) {
      kbRunning = false;
      stopChannel();
    }
    if (wsLoop) { wsLoop.stop(); wsLoop = null; }
  }

  function isPlacing() {
    return enabled && (spaceHeld || autoMode);
  }

  function syncPlacing() {
    isPlacing() ? startLoops() : stopLoops();
    updateUI();
  }

  window.addEventListener('keydown', (e) => {
    if (dispatching) return;
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
    if (e.code === 'Space' && !e.repeat) {
      spaceHeld = true;
      if (enabled) startLoops();
      updateUI();
    }
  }, true);

  window.addEventListener('keyup', (e) => {
    if (dispatching) return;
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
    if (e.code === 'Space') {
      spaceHeld = false;
      if (!autoMode) stopLoops();
      updateUI();
    }
    if (e.shiftKey && e.code === 'KeyK') {
      if (!enabled) return;
      autoMode = !autoMode;
      syncPlacing();
    }
    if (e.shiftKey && e.code === 'KeyN') {
      panelVisible = !panelVisible;
      panel.style.display = panelVisible ? 'block' : 'none';
    }
  }, true);

  const style = document.createElement('style');
  style.textContent = `
    @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@700&display=swap');
    #sca-panel {
      position: fixed; bottom: 24px; right: 24px; z-index: 999999;
      background: #0a0a0f; border: 1px solid #1e1e2e; border-radius: 12px;
      padding: 16px 18px; width: 200px;
      box-shadow: 0 0 30px rgba(0,0,0,0.7), 0 0 0 1px #1e1e2e;
      font-family: 'Share Tech Mono', monospace; user-select: none; cursor: default;
    }
    #sca-panel .sca-header {
      font-family: 'Orbitron', sans-serif; font-size: 11px; letter-spacing: 0.18em;
      color: #555577; text-transform: uppercase; margin-bottom: 14px;
      display: flex; align-items: center; gap: 8px;
    }
    #sca-panel .sca-dot {
      width: 7px; height: 7px; border-radius: 50%; background: #333344;
      flex-shrink: 0; transition: background 0.2s, box-shadow 0.2s;
    }
    #sca-panel .sca-dot.placing { background: #4ade80; box-shadow: 0 0 7px #4ade80; animation: sca-pulse 0.4s infinite alternate; }
    #sca-panel .sca-dot.standby { background: #facc15; box-shadow: 0 0 5px #facc1588; }
    @keyframes sca-pulse { from { opacity: 1; } to { opacity: 0.2; } }
    #sca-panel .sca-status {
      font-size: 9px; color: #333355; letter-spacing: 0.06em; margin-bottom: 10px;
      padding: 4px 8px; border: 1px solid #111122; border-radius: 5px;
      background: #0d0d18; text-align: center;
    }
    #sca-panel .sca-btn-row { display: flex; gap: 7px; margin-bottom: 12px; }
    #sca-panel .sca-toggle, #sca-panel .sca-auto {
      flex: 1; padding: 10px 0; border-radius: 8px; border: 1px solid #222233;
      background: #111122; color: #666688; font-family: 'Share Tech Mono', monospace;
      font-size: 11px; letter-spacing: 0.08em; cursor: pointer; transition: all 0.2s;
    }
    #sca-panel .sca-toggle:hover, #sca-panel .sca-auto:hover { border-color: #444466; color: #ccccee; }
    #sca-panel .sca-toggle.on { background: #0d1f12; border-color: #4ade80; color: #4ade80; box-shadow: 0 0 10px rgba(74,222,128,0.15); }
    #sca-panel .sca-auto.on { background: #1a0d1f; border-color: #c084fc; color: #c084fc; box-shadow: 0 0 10px rgba(192,132,252,0.2); animation: sca-glow 1.2s infinite alternate; }
    @keyframes sca-glow { from { box-shadow: 0 0 6px rgba(192,132,252,0.15); } to { box-shadow: 0 0 18px rgba(192,132,252,0.45); } }
    #sca-panel .sca-speeds { display: flex; flex-direction: column; gap: 6px; }
    #sca-panel .sca-speed-btn {
      width: 100%; padding: 8px 12px; border-radius: 6px; border: 1px solid #1e1e2e;
      background: transparent; color: #44445a; font-family: 'Share Tech Mono', monospace;
      font-size: 12px; letter-spacing: 0.12em; cursor: pointer;
      display: flex; justify-content: space-between; align-items: center; transition: all 0.15s;
    }
    #sca-panel .sca-speed-btn:hover { border-color: #333355; color: #7777aa; background: #111122; }
    #sca-panel .sca-speed-btn.active-slow   { border-color: #4ade80; color: #4ade80; background: #0d1f12; }
    #sca-panel .sca-speed-btn.active-medium { border-color: #facc15; color: #facc15; background: #1a1800; }
    #sca-panel .sca-speed-btn.active-fast   { border-color: #f87171; color: #f87171; background: #1f0d0d; }
    #sca-panel .sca-ms { font-size: 10px; opacity: 0.5; }
    #sca-panel .sca-drag { margin-top: 12px; font-size: 9px; color: #222233; letter-spacing: 0.08em; text-align: center; }
  `;
  document.head.appendChild(style);

  const panel = document.createElement('div');
  panel.id = 'sca-panel';
  panel.innerHTML = `
    <div class="sca-header"><div class="sca-dot" id="sca-dot"></div>PIXEL ACCELERATOR</div>
    <div class="sca-status" id="sca-status">Why Winfarm...</div>
    <div class="sca-btn-row">
      <button class="sca-toggle on" id="sca-toggle">ON</button>
      <button class="sca-auto" id="sca-auto">AUTO</button>
    </div>
    <div class="sca-speeds">
      <button class="sca-speed-btn" data-speed="slow">SLOW <span class="sca-ms">12ms</span></button>
      <button class="sca-speed-btn" data-speed="medium">MEDIUM <span class="sca-ms">9ms</span></button>
      <button class="sca-speed-btn" data-speed="fast">FAST <span class="sca-ms">5ms</span></button>
    </div>
    <div class="sca-drag">drag to move &nbsp;·&nbsp; auto: Shift+K &nbsp;·&nbsp; hide: Shift+N</div>
  `;
  document.body.appendChild(panel);

  let dragging = false, dragOffX = 0, dragOffY = 0;
  panel.addEventListener('mousedown', (e) => {
    if (e.target.tagName === 'BUTTON') return;
    dragging = true;
    dragOffX = e.clientX - panel.getBoundingClientRect().left;
    dragOffY = e.clientY - panel.getBoundingClientRect().top;
    panel.style.cursor = 'grabbing';
  });
  document.addEventListener('mousemove', (e) => {
    if (!dragging) return;
    panel.style.right = 'auto'; panel.style.bottom = 'auto';
    panel.style.left = (e.clientX - dragOffX) + 'px';
    panel.style.top  = (e.clientY - dragOffY) + 'px';
  });
  document.addEventListener('mouseup', () => { dragging = false; panel.style.cursor = 'default'; });

  function updateUI() {
    const toggleBtn = document.getElementById('sca-toggle');
    const autoBtn   = document.getElementById('sca-auto');
    const dot       = document.getElementById('sca-dot');
    const status    = document.getElementById('sca-status');
    if (!toggleBtn) return;

    toggleBtn.textContent = enabled ? 'ON' : 'OFF';
    toggleBtn.classList.toggle('on', enabled);
    autoBtn.textContent = autoMode ? 'AUTO ●' : 'AUTO';
    autoBtn.classList.toggle('on', autoMode);

    const wsReady = hookedSocket && hookedSocket.readyState === 1;
    status.style.color = wsReady ? '#38bdf8' : '#555577';

    if (!enabled) dot.className = 'sca-dot';
    else if (isPlacing()) dot.className = 'sca-dot placing';
    else dot.className = 'sca-dot standby';
  }

  function updateSpeedUI() {
    document.querySelectorAll('.sca-speed-btn').forEach(btn => {
      btn.className = 'sca-speed-btn' + (btn.dataset.speed === currentSpeed ? ` active-${btn.dataset.speed}` : '');
    });
  }

  setInterval(updateUI, 1000);

  document.getElementById('sca-toggle').addEventListener('click', (e) => {
    e.stopPropagation();
    enabled = !enabled;
    if (!enabled) { autoMode = false; spaceHeld = false; stopLoops(); }
    syncPlacing();
  });

  document.getElementById('sca-auto').addEventListener('click', (e) => {
    e.stopPropagation();
    if (!enabled) return;
    autoMode = !autoMode;
    syncPlacing();
  });

  document.querySelectorAll('.sca-speed-btn').forEach(btn => {
    btn.addEventListener('click', (e) => {
      e.stopPropagation();
      currentSpeed = btn.dataset.speed;
      updateSpeedUI();
      if (wsLoop) { wsLoop.stop(); wsLoop = null; if (isPlacing() && hookedSocket) wsLoop = makeLoop(wsTick, WS_INTERVAL); }
      updateUI();
    });
  });

  updateSpeedUI();
  updateUI();

})();