Powerline Wall Hugger

Powerline.io Wall Hugger

// ==UserScript==
// @name         Powerline Wall Hugger
// @version      2.1
// @description  Powerline.io Wall Hugger
// @author       Rumini - Discord: rumini
// @match        *://powerline.io/*
// @icon         https://i.imgur.com/9k4SFr0.png
// @grant        unsafeWindow
// @license      MIT
// @run-at       document-start
// @namespace https://greasyfork.org/users/1356205
// ==/UserScript==

if (window.location.href === 'https://powerline.io/') {
  window.location.href = 'https://powerline.io/maindev.html';
}

(function() {
  'use strict';

  const DIRECTION_NONE = 0;
  const DIRECTION_UP = 1;
  const DIRECTION_LEFT = 2;
  const DIRECTION_DOWN = 3;
  const DIRECTION_RIGHT = 4;

  const BASE_WALL_DISTANCE = 0.000000001;
  const WALL_DISTANCE_INCREMENT = 0.00001;

  let wallHuggerEnabled = false;
  let originalSendTurnPoint;
  let isTeleporting = false;
  let currentDirection = DIRECTION_NONE;
  let wallDistance = BASE_WALL_DISTANCE;
  let GAME_SCALE;

  const waitForGame = (callback) => {
    if (typeof Snake !== 'undefined' && typeof localPlayerID !== 'undefined' && typeof network !== 'undefined') {
        GAME_SCALE = unsafeWindow.GAME_SCALE || 10;
        callback();
    } else {
      setTimeout(() => waitForGame(callback), 100);
    }
  };

  const findNearestWall = (x, y) => {
    const halfArena = unsafeWindow.arenaWidth / 2;
    const distances = [
      { wall: 'left', distance: Math.abs(halfArena + x) },
      { wall: 'right', distance: Math.abs(halfArena - x) },
      { wall: 'top', distance: Math.abs(halfArena + y) },
      { wall: 'bottom', distance: Math.abs(halfArena - y) }
    ];
    return distances.reduce((nearest, current) =>
      current.distance < nearest.distance ? current : nearest
    );
  };

  const teleportToWall = (newDirection) => {
    if (!localPlayer || isTeleporting) return;

    isTeleporting = true;
    const nearestWall = findNearestWall(localPlayer.x, localPlayer.y);
    const halfArena = unsafeWindow.arenaWidth / 2;
    wallDistance += WALL_DISTANCE_INCREMENT * GAME_SCALE;
    let newX = localPlayer.x;
    let newY = localPlayer.y;

    switch (nearestWall.wall) {
      case 'left':
        newX = -halfArena + wallDistance;
        if (newDirection === DIRECTION_UP) {
          newY = halfArena - wallDistance;
        } else if (newDirection === DIRECTION_DOWN) {
          newY = -halfArena + wallDistance;
        }
        break;
      case 'right':
        newX = halfArena - wallDistance;
        if (newDirection === DIRECTION_UP) {
          newY = halfArena - wallDistance;
        } else if (newDirection === DIRECTION_DOWN) {
          newY = -halfArena + wallDistance;
        }
        break;
      case 'top':
        newY = halfArena - wallDistance;
        if (newDirection === DIRECTION_LEFT) {
          newX = -halfArena + wallDistance;
        } else if (newDirection === DIRECTION_RIGHT) {
          newX = halfArena - wallDistance;
        }
        break;
      case 'bottom':
        newY = -halfArena + wallDistance;
        if (newDirection === DIRECTION_LEFT) {
          newX = -halfArena + wallDistance;
        } else if (newDirection === DIRECTION_RIGHT) {
          newX = halfArena - wallDistance;
        }
        break;
    }

    localPlayer.x = newX;
    localPlayer.y = newY;
    localPlayer.direction = newDirection;

    // X and Y Axis is for some reason flipped when you are near the top and bottom wall
    if (nearestWall.wall === 'top' || nearestWall.wall === 'bottom') {
      originalSendTurnPoint.call(network, newDirection, newY / GAME_SCALE, -newX / GAME_SCALE);
    } else if (nearestWall.wall === 'left' && currentDirection === DIRECTION_UP && newDirection === DIRECTION_RIGHT) {
      originalSendTurnPoint.call(network, newDirection, -newX / GAME_SCALE, newY / GAME_SCALE);
    } else if (nearestWall.wall === 'right' && currentDirection === DIRECTION_DOWN && newDirection === DIRECTION_LEFT) {
      originalSendTurnPoint.call(network, newDirection, -newX / GAME_SCALE, newY / GAME_SCALE);
    } else {
      originalSendTurnPoint.call(network, newDirection, newX / GAME_SCALE, -newY / GAME_SCALE);
    }

    isTeleporting = false;
  };

  const toggleWallHugger = (enable) => {
    wallHuggerEnabled = enable !== undefined ? enable : !wallHuggerEnabled;
    if (wallHuggerEnabled) {
      originalSendTurnPoint = network.sendTurnPoint;
      network.sendTurnPoint = function(direction, x, y) {
        if (wallHuggerEnabled && !isTeleporting && direction !== DIRECTION_NONE) {
          teleportToWall(direction);
        } else {
          originalSendTurnPoint.call(this, direction, x, y);
        }
      };
      startDirectionTracking();
      hud.showTip("Wall Hugger Enabled", 2000);
    } else {
      if (originalSendTurnPoint) {
        network.sendTurnPoint = originalSendTurnPoint;
      }
      stopDirectionTracking();
      hud.showTip("Wall Hugger Disabled", 2000);
    }
  };

  const startDirectionTracking = () => {
    if (typeof requestAnimationFrame !== 'undefined') {
      const trackDirection = () => {
        if (wallHuggerEnabled && localPlayer) {
          currentDirection = localPlayer.direction;
        }
        if (wallHuggerEnabled) {
          requestAnimationFrame(trackDirection);
        }
      };
      requestAnimationFrame(trackDirection);
    } else {
      window.directionTrackingInterval = setInterval(() => {
        if (localPlayer) {
          currentDirection = localPlayer.direction;
        }
      }, 16);
    }
  };

  const stopDirectionTracking = () => {
    if (window.directionTrackingInterval) {
      clearInterval(window.directionTrackingInterval);
    }
  };

  const checkCollisionAndReset = () => {
    const observer = new MutationObserver(() => {
      if (localPlayerID === 0 && wallHuggerEnabled) {
        toggleWallHugger(false);
        wallDistance = BASE_WALL_DISTANCE;
      }
    });
    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
  };

  const setupKeyBindings = () => {
    document.addEventListener('keydown', (e) => {
      if (e.key.toLowerCase() === 'h') {
        toggleWallHugger();
      }
    });
  };

  waitForGame(() => {
    setupKeyBindings();
    checkCollisionAndReset();
  });
})();