Greasy Fork 还支持 简体中文。

Neko's Scripts

Script for OWOP

Version vom 08.07.2022. Aktuellste Version

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

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         Neko's Scripts
// @namespace    http://tampermonkey.net/
// @version      0.12.5
// @description  Script for OWOP
// @author       Neko
// @match        https://ourworldofpixels.com/*
// @exclude      https://ourworldofpixels.com/api*
// @run-at       document-start
// @icon         https://www.google.com/s2/favicons?sz=64&domain=ourworldofpixels.com
// @grant        none
// @unwrap
// ==/UserScript==

'use strict';
/*global OWOP*/
const NS = {};
if (window) window.NS = NS;

{
  // Thanks Lapis
  NS.modules = [];
  let originalFunction = Object.defineProperty;
  Object.defineProperty = function (object, property, info) {
    let x = originalFunction(object, property, info);
    if (!object["__esModule"]) return x;
    NS.modules.push(object);
    if (NS.modules.length === 43) Object.defineProperty = originalFunction;
    return x;
  }
  0, 13, 14, 15, 16, 20, 21, 22, 23, 24, 25;
  // Thanks again Lapis
}

{
  let k = EventTarget.prototype.addEventListener;
  EventTarget._eventlists = [];
  EventTarget.prototype.addEventListener = function (r, i, e) {
    if (EventTarget._eventlists) EventTarget._eventlists.push(i);
    return k.bind(this)(...arguments)
  };

  let l = EventTarget.prototype.removeEventListener;
  EventTarget.prototype.removeEventListener = function () {
    return l.bind(this)(...arguments);
  };
}


function install() {
  "use strict";

  class Point {
    constructor(x, y) {
      this.x = x;
      this.y = y;
    }
    static distance(p1, p2) {
      if (p1 instanceof Point && p2 instanceof Point) return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
    }
  }

  class BPoint extends Point {
    constructor(x, y) {
      super(x, y);
      this.bottom = false;
      this.right = false;
    }
    static check(bp1, bp2, direction) {
      let p1 = NS.PM.queue[`${bp1.x},${bp1.y}`];
      let p2 = NS.PM.queue[`${bp2.x},${bp2.y}`];
      let d = false;
      if ((!!p1 && !p2) || (!p1 && !!p2)) d = true;
      return bp1[direction] = d;
    }
  }

  class Color {
    static compare(c1, c2) {
      return (c1[0] == c2[0] && c1[1] == c2[1] && c1[2] == c2[2]);
    }
  }

  class Pixel extends Point {
    constructor(x, y, c, o = false) {
      super(x, y);
      this.c = c;
      this.o = o;
      this.placed = false;
    }
    static compare(p1, p2) {
      return (p1.x == p2.x && p1.y == p2.y) && Color.compare(p1.c, p2.c);
    }
  }

  class Action {
    constructor(p1, p2) {
      this.x = p1.x;
      this.y = p1.y;
      this.before_color = p1.c;
      this.after_color = p2.c;
    }
    undo() {
      return this.before_color;
    }
    redo() {
      return this.after_color;
    }
  }

  class PixelManager {
    constructor() {
      this.undoStack = [];
      this.redoStack = [];
      this.actionStack = {};
      this.record = false;
      this.queue = {};
      this.chunkQueueTemp = {};
      this.border = {};
      this.borderCheck = true;
      this.renderBorder = false;
      this.autoMove = false;
      this.whitelist = {};
      this.enabled = true;
      this.extra = {};
      this.extra.placeData = [];
      let p1 = new Point(0, 0);
      for (let y = -47; y < 47; y++) {
        for (let x = -47; x < 47; x++) {
          let p2 = new Point(x, y);
          let d = Point.distance(p1, p2);
          // d = Math.random();
          this.extra.placeData.push([d, p2]);
        }
      }
      this.extra.placeData.sort((a, b) => a[0] - b[0]);
      NS.M14.eventSys.addListener(NS.M13.EVENTS.tick, function () { this.enabled ? this.placePixel() : void 0 }.bind(this));
      NS.M14.eventSys.addListener(NS.M13.EVENTS.net.world.tilesUpdated, function (message) {
        for (let i = 0; i < message.length; i++) {
          let p = message[i];
          let placedColor = [(p.rgb & (255 << 0)) >> 0, (p.rgb & (255 << 8)) >> 8, (p.rgb & (255 << 16)) >> 16];
          if (Object.keys(this.whitelist).includes(`${p.id}`)) this.setPixel(p.x, p.y, placedColor);
          let pixel = this.queue[`${p.x},${p.y}`];
          if (pixel) (this.borderCheck = true, pixel.placed = false, this.chunkQueueTemp[`${Math.floor(p.x / 16)},${Math.floor(p.y / 16)}`] = true, this.updateBorder(p.x, p.y));
        }
      }.bind(this));
      NS.M14.eventSys.addListener(NS.M13.EVENTS.net.world.leave, function () {
        OWOP.sounds.play(OWOP.sounds.launch);
        this.disable();
        // this.border = {};
        console.log(arguments, "leave");
      }.bind(this));
      NS.M14.eventSys.addListener(NS.M13.EVENTS.net.world.join, function () {
        this.enable();
        console.log(arguments, "join");
      }.bind(this));
    }
    moveToNext() {
      if (!this.autoMove) return;
      if (!this.borderCheck) return;
      for (let e in this.chunkQueueTemp) {
        if (this.chunkQueueTemp[e]) {
          let [x, y] = e.split(",");
          for (let i = 0; i < 16; i++) {
            for (let j = 0; j < 16; j++) {
              if (this.queue[`${x * 16 + i},${y * 16 + j}`]?.placed === false) return NS.M20.centerCameraTo(x * 16 + i, y * 16 + j);
            }
          }
          this.chunkQueueTemp[e] = false;
        }
      }
      this.borderCheck = false;
    }
    updateBorder(x, y) {
      let p = this.border[`${x},${y}`];
      if (!p) p = this.border[`${x},${y}`] = new BPoint(x, y);

      let t = this.border[`${x},${y - 1}`];
      let l = this.border[`${x - 1},${y}`];
      let b = this.border[`${x},${y + 1}`];
      let r = this.border[`${x + 1},${y}`];

      if (!t) t = new BPoint(x, y - 1);
      if (!l) l = new BPoint(x - 1, y);
      if (!b) b = new BPoint(x, y + 1);
      if (!r) r = new BPoint(x + 1, y);
      if (BPoint.check(t, p, "bottom") && (t.bottom || t.right)) this.border[`${x},${y - 1}`] = t;
      if (BPoint.check(l, p, "right") && (l.bottom || l.right)) this.border[`${x - 1},${y}`] = l;
      if (BPoint.check(p, b, "bottom") && (b.bottom || b.right)) this.border[`${x},${y + 1}`] = b;
      if (BPoint.check(p, r, "right") && (r.bottom || r.right)) this.border[`${x + 1},${y}`] = r;
    }
    undo() {
      if (!this.enabled) return;
      if (!this.undoStack.length) return;
      let action = this.undoStack.pop();
      for (let e in action) {
        let e2 = action[e];
        if (!this.queue[`${e2.x},${e2.y}`] && (delete action[e], true)) continue;
        this.setPixel(e2.x, e2.y, e2.undo());
        // console.log(e2.x, e2.y, e2.undo());
      }
      if (!Object.keys(action).length) {
        this.undo();
        return;
      }
      this.redoStack.push(action);
    }
    redo() {
      if (!this.enabled) return;
      if (!this.redoStack.length) return;
      let action = this.redoStack.pop();
      for (let e in action) {
        let e2 = action[e];
        if (!this.queue[`${e2.x},${e2.y}`] && (delete action[e], true)) continue;
        this.setPixel(e2.x, e2.y, e2.redo());
        // console.log(e2.x, e2.y, e2.redo());
      }
      if (!Object.keys(action).length) {
        this.redo();
        return;
      }
      this.undoStack.push(action);
    }
    startHistory() {
      this.record = true;
    }
    endHistory() {
      if (!this.record) return;
      this.record = false;
      if (Object.keys(this.actionStack).length) this.undoStack.push(this.actionStack);
      this.actionStack = {};
      this.redoStack = [];
    }
    enable() {
      this.enabled = true;
    }
    disable() {
      this.enabled = false;
    }
    clearQueue() {
      this.queue = {};
      this.chunkQueueTemp = {};
      this.border = {};
    }
    unsetPixel(x, y) {
      let p = new Point(x, y);
      this.deletePixels(p);
      return true;
    }
    deletePixels() {
      for (let i = 0; i < arguments.length; i++) {
        if (Array.isArray(arguments[i])) this.deletePixels(arguments[i]);
        else if (arguments[i] instanceof Point) {
          delete this.queue[`${arguments[i].x},${arguments[i].y}`];
          let x = Math.floor(arguments[i].x / 16);
          let y = Math.floor(arguments[i].y / 16);
          let found = false;
          for (let i = 0; i < 16; i++) {
            for (let j = 0; j < 16; j++) {
              if (this.queue[`${x * 16 + i},${y * 16 + j}`]) {
                found = true;
                break;
              }
            }
            if (found) break;
          }
          if (!found) delete this.chunkQueueTemp[`${x},${y}`];
          this.updateBorder(arguments[i].x, arguments[i].y);
        }
      }
    }
    setPixel(x, y, c, placeOnce = false) { // make checks for all variables coming in to make sure nothing is incorrectly set and c 4th element is either undefined or 255 otherwise drop the set
      if (!this.enabled) {
        OWOP.world.setPixel(x, y, c);
        return;
      }
      if (!Number.isInteger(x) || !Number.isInteger(y)) return false;
      if (!Array.isArray(c) || c.length < 3 || c.length > 4) return false;
      if (c.length == 4) c.pop();
      if (c.find(e => !Number.isInteger(e) || e < 0 || e > 255) !== undefined) return false;
      let p = new Pixel(x, y, c);
      if (placeOnce) p.o = true;
      let xchunk = Math.floor(p.x / 16);
      let ychunk = Math.floor(p.y / 16);
      if (NS.M0.misc.world.protectedChunks[`${xchunk},${ychunk}`]) return false;
      if (this.record) {
        let stackE = this.actionStack[`${x},${y}`];
        if (!(stackE instanceof Action)) {
          let bp = new Pixel(x, y, this.getPixel(x, y, 1));
          if (bp.c !== p.c) this.actionStack[`${x},${y}`] = new Action(bp, p);
        } else if (stackE.after_color !== c) {
          stackE.after_color = c;
        }
      }
      this.addPixels(p);
      return true;
    }
    getPixel(x, y, a = 1) {
      if (!Number.isInteger(x) || !Number.isInteger(y)) return console.error("There is no inputs in \"getPixel\" on PixelManager instance.");
      // if (!Object.keys(OWOP.world).includes("_getPixel")) return undefined;
      if (a && this.queue[`${x},${y}`]) return this.queue[`${x},${y}`].c;
      try {
        OWOP.world.getPixel;
      } catch (e) {
        return undefined;
      }
      return OWOP.world.getPixel(x, y);
    }
    addPixels() {
      for (let i = 0; i < arguments.length; i++) {
        if (Array.isArray(arguments[i])) this.addPixels(arguments[i]);
        else if (arguments[i] instanceof Pixel) {
          this.queue[`${arguments[i].x},${arguments[i].y}`] = arguments[i];
          let x = Math.floor(arguments[i].x / 16);
          let y = Math.floor(arguments[i].y / 16);
          this.chunkQueueTemp[`${x},${y}`] = true;
          this.updateBorder(arguments[i].x, arguments[i].y);
          this.borderCheck = true;
        }
      }
    }
    placePixel() {
      let totalPlaced = 0;
      for (let i = 0; i < this.extra.placeData.length; i++) {
        let e = this.extra.placeData[i][1];
        let tX = OWOP.mouse.tileX;
        let tY = OWOP.mouse.tileY;
        let pixel = this.queue[`${tX + e.x},${tY + e.y}`];
        if (!pixel) continue;
        let xchunk = Math.floor(pixel.x / 16);
        let ychunk = Math.floor(pixel.y / 16);
        if (NS.M0.misc.world.protectedChunks[`${xchunk},${ychunk}`]) continue;
        let xcc = Math.floor(tX / 16) * 16;
        let ycc = Math.floor(tY / 16) * 16;
        if (pixel.x < (xcc - 31) || pixel.y < (ycc - 31) || pixel.x > (xcc + 46) || pixel.y > (ycc + 46)) continue;
        let c = this.getPixel(pixel.x, pixel.y, 0);
        if (!c) continue;
        if (!Color.compare(pixel.c, c)) {
          if (!OWOP.world.setPixel(pixel.x, pixel.y, pixel.c) || (++totalPlaced === 5, pixel.placed = true, false)) return;
        } else if ((pixel.o && this.deletePixels(pixel), pixel.placed = true, true)) continue;
      }
      this.moveToNext();
    }
  }

  const modulo = (i, m) => {
    return i - m * Math.floor(i / m);
  }

  const line = (x1, y1, x2, y2, m, e, plot) => {
    if (x1 === undefined || y1 === undefined || x2 === undefined || y2 === undefined) return console.error();
    var dx = Math.abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
    var dy = -Math.abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
    var err = dx + dy, e2;

    if (e?.type == "mousemove") {
      if (x1 == x2 && y1 == y2) return;
      e2 = 2 * err;
      if (e2 >= dy) { err += dy; x1 += sx; }
      if (e2 <= dx) { err += dx; y1 += sy; }
    }
    var i = 0;
    while (true) {
      plot(x1, y1, i);
      i++;
      if (x1 == x2 && y1 == y2) break;
      e2 = 2 * err;
      if (e2 >= dy) { err += dy; x1 += sx; }
      if (e2 <= dx) { err += dx; y1 += sy; }
    }
    return [i];
  }

  NS.modulo = modulo;
  NS.line = line;
  NS.Point = Point;
  NS.Color = Color;
  NS.Pixel = Pixel;
  NS.PixelManager = PixelManager;
  const PM = new PixelManager();
  NS.PM = PM;

  OWOP.OPM = false;
  if (OWOP.misc) OWOP.OPM = true;
  if (!OWOP.OPM) {
    OWOP.misc = NS.M0.misc;
    OWOP.tool = OWOP.tools;
  }
  if (localStorage.options) {
    let o = JSON.parse(localStorage.options);
    if (o?.enableSounds) OWOP.options.enableSounds = o.enableSounds;
  }

  const windows = {};
  NS.windows = windows;

  if (!NS.localStorage.cursors) {
    let l = NS.localStorage.cursors = {
      cursor: { hotspot: [7, 2] },
      move: { hotspot: [18, 18] },
      pipette: { hotspot: [3, 31] },
      zoom: { hotspot: [22, 13] },
      export: { hotspot: [0, 3] }, // needs better hotspot
      fill: { hotspot: [6, 32] },
      line: { hotspot: [6, 6] },
      paste: { hotspot: [5, 2] }, // this too
      copy: { hotspot: [5, 5] }, // and this
      write: { hotspot: [17, 8] } // fix hotspot
    };

    l.cursor.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAA00lEQVRYw+3YMQ6DMAwFUPPFhTpCRxaW3qYzQ9f2Nl1YGIGRI8ENShz7W6mEZxwe34kUIXKVoR63+x79TpSGQmlJobTxJYHe8xiGSk4oCgXNwxEoaBvYKOQ0MVHIbew/AwWVDeqaloKCdQFvlAnUNa07Cl5Re6HguSE9UPA+tlaUO8iKMoGmdXFHURKyoJCTxLQuP9OxoGrLeMbnS31H/25zRRmZFpN686ytmLMvpp8yJkYNYmNUoAiMiEil3YBMjHpkbIwKFIFJBkVhivz7cdXf1QFBsW2mhPMCDAAAAABJRU5ErkJggg=="
    l.move.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABgklEQVRYw+2YvU7EMAzH/055CYZj6IJ0243Q8ZYs9zbcWgErfZtbTkgdGNqOtyM2Ft7imHKK0g/nw4Ui4S1K7Pzi2q4TQEB2m+K82xRnCVskAWOPD6eGfg3IwLw0RwDAvtDJUCQFYyQViiRhJKBIGiYVilICmPNQTKCrEKCYTxCqo+bcIOYACguTvwMk9SsIta2mFDiofaFRd+1lXHdtL8NCbVOKZ3zTPiTwVUzRAwBdlVFz7mFcB1BMBfYB9l3rVvTMhtFViXy1mjSQrXN2TcjabJ3j4/UNt9c3j+9fn09kwwDA9u7+R9PcJMXx4RkAcDW2YG4wOzt7MeR6iRNf2LFNXTHeOZwaUnZAmQkf5ZCNfGF6dcj2FGdMV+Wop+qu9dJ3YRZZGIn71wy1EPa87SnXM5zu0Lyaoub6GZMEdddeAphLDM62kmyuJBq3/wZNHCgkC2OaPDUXTKxO9EWRy6ahojf7VXoMKhZG5LHBhUqBEXuOMVCpMIt8sBK7Z0nd474B40/4pg5xCDEAAAAASUVORK5CYII="
    l.pipette.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABAUlEQVRYw+3XvQ6CMBQF4FPiCzHQRDu6uPA2zg6u+jYuLI5ogoOPhFOTpnL7Y3ovDj0jIeRLzyEEoKaGTt+auW/NHLpHSUH8a7f3qFYBuZhOawDAa5pIlJLAWIgbCrXhrmYJI7KhHEyosqYUyH14p3X2ybBtKLSb2OkUB/m1+agYhmVDh+sJADAcz8mvevENWcxlHLDf7uDCLCQ1TUkMANyfD3I37KAQxlYWq6jYhnIxfWvmFJz6l5P5GcSJyQZxY7JAEphkkBQmCSSJiYKkMUHQGhgS5H8o3XBisj8d3JhFkF+VJOarMhdjN2MhEhiysrUw5Aml/GGKgHyUNKampkQ+wWnd9v+Wv9QAAAAASUVORK5CYII="
    l.zoom.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABMUlEQVRYw+3Wuw6CMBQG4L+NL+SIjgyy+CSuujo4u/ISri4wdFQTHFx9CScS1zrVNFCgdzThJCRcGvrlHHooMMWfBfH5svV8yVX3z48LiQrqgtjAiE9M14Q6Y7yVSEyWs5I/33XryFnJVeODYVQQGSFHzkoeBCW/VAdigqIuMBnAblds0hWJ3idU2dHJjG6WqCvQd2acQb7LRPFjQV3LZRNpsggDsimX6QIwXmmm8XzX4fpQiOxQm+zI57olyFnJ02SB3TLr/ckSG0x23AMAiu0B8rXqmxIQAIMYI1AT8zoVAIB7VX3HiGfN1SQgOtsP4oJpgqJs0HQwYqLgW1gTTPBN/hiYTtBYGCVoTMxgY4yNaWVIzs4YGACY9WVG7jExMJ2gZrOLhen9qGNDppjCV3wAmp0jyCb9nC4AAAAASUVORK5CYII="
    l.export.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABCklEQVRYw+2YMQ6CQBBFP4QL2Ql02mDhadQWg+3iaSzcRrsVO4+k1RCNhB3RHdZkfzUJBW//Z4YBIOjPFAHAcpLfpW54uJmoF0gShgOVULHYbwEAs2kKADhfm5/Xm7ywR0YOKaNxvjZOnTmuKqtDsVRM5JJNLZBrd7iKPz3BUHEPHCILkXkdGc0dryIjKG8iU0bjuKr86jJlNOs9Jtplymi/ugwAirqEbf8SHYzzNLNCiUXGdSruGmAE9209BMr5ClvUJeZpBgA4NZe363q9e1nanC/55ESfnqEiVyB0yC4gAhi97W0wh5uJkjFgvFjyOTBiQFyYtstcPtTcT2hRh7gwzjXGf4MgcT0AtrSSgUtEfWEAAAAASUVORK5CYII="
    l.fill.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABjUlEQVRYw+2YsW7DIBCGAfWFMhgpZezCkreJVw9Z7bfpwpLRiYSHPlI6XYSviLszWG2lnJQhGJNP//3HQZR6xT8LzZ14OrgHHvv8mvWvAOVg9oLTXBg/DavxcL7sAqU5MOMcVO/86lln7er7EmMTKC2FwSAYqBbKtICBgLRyPMcG2grTCspwJlEwS4wr09dAaUodjmfgnev9lq1EiaeMRJklxufHT8NTCT8N6nq/qY/je3X63jiTQI3cXpSOAVSqlJ8GFc4XdTq4B0cpQ4FgNVKYcQ4/3qlVylAVg0EwDP7xWqgsUO+8GueQbQ+QpjQtLaGMpCRznmkNpUtdHW+OufRhGFz68Jy7JZhSt4bUlWC2KiVOGRg69VPOU62hWB7qnVedtaqzdncokamXGKuhqJZipDCcKEHlTF+lEO7sUiiq4YqAqGqjoDjdnw0Ei5T8U4KC/YxqsCKFtkJxYUignMRSKOkhTewhCVTzE+NWqHRXl16JNHV1phYsHSW23M+KF0Xughhqjz8hXvFn4xvHckf64m/UhgAAAABJRU5ErkJggg=="
    l.line.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABEUlEQVRYw+2XvRGEIBCFkbEDKzGQxAZITK3E2OBiKzG9hAYuweAqsQYvWofjVOB4GPESHRdmP/cHV8aysrLSqrgydnW70f3z/Sp8bUmATIc+QkFx1wI5jUxO48/zRgjWCAFPWem78AhqnRUciLtSoIbHoX2dFVu0vg/IpaqXXykLrTk4EEHJadzBEFBlzGY7bYhO4662p7e3C1gNDziMd8ooEgRlwlH33VZDi9Z7JOxrIwRbZwWFcrb91dlDYEiooLzb3y+7zqpe7ufWvzUVvKmr2810hoaCdAYSCjY2oKBgQCgoKBACCg4UC5UEKAYqGdARlHmYnkHxlEDkNGSQSxqhs+EN+ZcSBYWaCLKysmL0AfZSsN1jloZVAAAAAElFTkSuQmCC"
    l.paste.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABQ0lEQVRYw+2Yvw6CMBDGr4QXYpBA2Jx04GmMs3E2Po2DTmwEggOPhFOTUtvSa6/FGC4xacuf/vjuu1IE2CJg1Fk11Vk1Ud6T+cCI/cfYslWARJDj/QIAAM/TlQyM+cBwELHtC8VsU2JSxTSOhUwwMCoAuY19QDlS08Fdns/672HQQmGucwZSTaRLiQ4GGyn2At8JnTy0ZqAUwnghWspu7Qt1/rk6/F7KeDXalH40D9lCpTFg5KWizqpJt3J7ATV9BwAA+6L86vP2rX3Nxpc85QXEQVR9XZu8ykQlVOrICpkegAxIdWOTWkHXIZ1XVIAqJYMrZJrIRaXEVSH+k/0T/dVB6Zf/e9uryl5n4KbvZuPRTL2URmw6yRRa3UP7oiQFsVLItEOkLPVFoMfYMtO+JYQy6E9p6n85dPsh52/7EDBb2MQHK+ObZIdb/JMAAAAASUVORK5CYII="
    l.copy.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABS0lEQVRYw+2XMQ6CMBSGfwxH8CIOEg2bkx2cOYhxJs6Ggzg74ORGNGXwIt5BpxrAAn3PVzWGPzEpaMLH199CgSFD/iwB5cerSXznXORwLQJxIC4MFYoMpLKUBJKvtySo0JeRaqZRhFJrrCbxvQ8qkIZpGszXW0yjCABQat1rqhNoVxxJMJt4+QJVnTKXToXSf1uVpTUrKktx2+cAgHGiWgFNRj7WEpWlKLXGOFFPEDNml5qbqoGqKQMmBnS6nAEAi9n85diMd8Wxdn4TL53NkIEMiO24bQyg1h8RIHPHNjtNQzZgcUPNO+6z9W5CSndsJmzfvQNIMtR1ISlLIxdD5tPsj0tu+/xZbC8dopqgltrLSv2RUncV+HQ5185/rNR90yhRbJahr05Z2wrNediKAXFAuC/8rA753AaFLq+kP7FRlNp1UOwMGfKXeQBOJJ69p18KUgAAAABJRU5ErkJggg=="
    l.write.icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABTUlEQVRYw+2XPW6DQBCFHyNu4JOktF26oUnrk7hO4TY5CW2abbY0SEvhk3AGp8miMcGEmVmMLPEa/0iGx3xvZsfAqlU2ZdYLvL/tb/zz9/WSLWaobyaFqcxq5vPiAAC+rgAA7nQ2maIU3KOZFKKUZtrSLWOoj8udzh2q4utjNF+zVsjXVWcEAJoQlkcWA2xtd7UhjmsoM23pTNjIgmsIkRWb2tBYR1m6jbS4YiV4duL7JgQ1NrLOntQiC66hzorfabGRBdeYtNhyCy7tNJ59MD59QeO4fF3hsN3dVeyw3d299iVZSXINrv5NfV2hLR38b5g3x6IL9eZYiCokzhA/TP8ohp2FvhAaIgkuTStLz7ZcgmtoOv/3ME0IoiqRZomf44+BqO05Lsnew6d2xJZkDj1aNaZK8luaGkyrpl6DJE+oWVP5SvKSR8eqVVb9AMXs1T0X1uPeAAAAAElFTkSuQmCC"

    localStorage.NS = JSON.stringify(NS.localStorage);
  }

  {
    function holeify(img) {
      let canvas = document.createElement("canvas");
      var shadowcolor = 0xFF3B314D;
      var backgroundcolor = 0xFF5C637E;
      canvas.width = img.width;
      canvas.height = img.height;
      var ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0);
      var idat = ctx.getImageData(0, 0, canvas.width, canvas.height);
      var u32dat = new Uint32Array(idat.data.buffer);
      var clr = (x, y) => {
        return (x < 0 || y < 0 || x >= idat.width || y >= idat.height) ? 0 : u32dat[y * idat.width + x];
      };
      for (var i = u32dat.length; i--;) {
        if (u32dat[i] !== 0) {
          u32dat[i] = backgroundcolor;
        }
      }
      for (var y = idat.height; y--;) {
        for (var x = idat.width; x--;) {
          if (clr(x, y) === backgroundcolor && (!clr(x, y - 1) || !clr(x - 1, y)) && !clr(x - 1, y - 1)) {
            u32dat[y * idat.width + x] = shadowcolor;
          }
        }
      }
      for (var y = idat.height; y--;) {
        for (var x = idat.width; x--;) {
          if (clr(x, y - 1) === shadowcolor && clr(x - 1, y) === shadowcolor) {
            u32dat[y * idat.width + x] = shadowcolor;
          }
        }
      }
      ctx.putImageData(idat, 0, 0);
      return canvas.toDataURL();
    }

    let iconStyler = document.createElement("style");
    document.getElementById("toole-container").parentElement.appendChild(iconStyler);
    for (let cursor in NS.localStorage.cursors) {
      let cursorURL = NS.localStorage.cursors[cursor].icon;
      // cursorURL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAACeElEQVRYw+2XsWsaYRiHH6VRSyGBEwepi5nsIFToWKUKDoVkyl/QIQhZRMdCJqGj0kUoGRw6FqcIGY4qnI6BBBwiFCqBKzcUDxoieMlgB/NdYtN6Z7yzGfyBy3eIj8/3vu/3HayyQGKvPoyX/ZvexwblfWymLIEMrb1UKFuGQk+PMLT2UqC8Vnb0UReAyOblUqBmAumjLlIgDkClpZhQ0edvx/9ty/zhJACdoWFC+cNJ10zNBJICcbR+FYDXz/wU0il2GrKrNeW1siO2rDM0AKhvZam0FPD1XIHy2ml5YUikkE6RPz0DX8/xkWBpSHSZMFRpKaapL9++YwwOHDU1E0jrV80tE+kMDXYa8j1TTkFZFrUwJKIcqrd1BHx8+YJESXYMynIOhaN7f31WSKdMqJP97FRNLTKnbLe9SGo7MgWVKMmmKSlTxhgcLDSnLA3d3SqAz8WjyfmWqxHK1UhtR0wovVk0obR+9UFQnlkPhfrI5iU7DZn6VpZQrsZ5q4veLP7ze4mSzMl+Fq5i6KMuvwaqx3GgUK7Gz0/vqLQUCumUaenPnLdurerNIlzF8IeT9I7fexYG2ghGxlIgDr4e+dMz6ltZAFRVRcqUZ1qammfBXbR+1ZYp222vHKqo6uSTKMnmv9cvrtEvruEqNrER3DXXxZqhtQlH92zVlOVpLwaj2AopU+Zr/s0E5OZ6IsD1Ude8Q4lxIaa9obVtFbplDemjLtL6GlKmPFUXIv0fRx4czBN7F7TePZh5u8eRLtsIRsYA0vraZOGmjaVA3HEzc9fQ3ZpxC8a+oTsnvpswtl+DROu7DTPXli0DxrYhN7rpQRE1tMoqM/Ibi49haAcCegQAAAAASUVORK5CYII=";

      iconStyler.innerHTML += `#tool-${cursor}:not(.selected) div { background-image: url("${cursorURL}") !important } `
      let img = new Image();
      img.onload = function () {
        iconStyler.innerHTML += `#tool-${cursor}.selected div { background-image: url("${holeify(img)}") !important } `
      }
      img.src = cursorURL;
    }
  }

  (function () {
    var camera = OWOP.camera; // NS.M20.camera
    var renderer = OWOP.renderer; // NS.M20.renderer
    var GUIWindow = OWOP.windowSys.class.window;
    const mkHTML = OWOP.util.mkHTML; // NS.
    const isSame = (a, b) => a && b && a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
    var drawText = (t, e, n, r, o) => {
      t.strokeStyle = "#000000";
      t.fillStyle = "#FFFFFF";
      t.lineWidth = 2.5;
      t.globalAlpha = 0.5;
      o && (n -= t.measureText(e).width >> 1);
      t.strokeText(e, n, r);
      t.globalAlpha = 1;
      t.fillText(e, n, r);
    };
    var setColor = (cursor, color) => {
      if (!color) return;
      if (cursor === 1) {
        OWOP.player.selectedColor = color;
      } else if (cursor === 2) {
        OWOP.player.rightSelectedColor = color;
        localStorage.setItem("rSC", JSON.stringify(OWOP.player.rightSelectedColor));
      }
    };
    NS.renderPlayer = function (fx, ctx, time) {
      (function (fx, ctx, time) {
        if (!NS.PM.renderPlayerRings) return;
        let t = 1;
        let e = 5;
        let i = fx.extra.player.x;
        let a = fx.extra.player.y;
        let defaultLine = ctx.lineWidth;
        let s = ((i / (16 * t) + 0.5) * t - OWOP.camera.x) * OWOP.camera.zoom;
        let u = ((a / (16 * t) + 0.5) * t - OWOP.camera.y) * OWOP.camera.zoom;
        new NS.Point(OWOP.mouse.worldX, OWOP.mouse.worldY);
        ctx.globalAlpha = 1;
        ctx.lineWidth = 16;
        ctx.beginPath();
        ctx.strokeStyle = "#000000";
        ctx.arc(s, u, OWOP.camera.zoom * t * e, 0, Math.PI * 2, false);
        ctx.stroke();
        ctx.closePath();
        ctx.lineWidth = 15;
        ctx.beginPath();
        ctx.strokeStyle = "#FFFFFF";
        ctx.arc(s, u, OWOP.camera.zoom * t * e, Math.PI, Math.PI * 3, false);
        ctx.stroke();
        ctx.setLineDash([0]);
        ctx.closePath();
        ctx.lineWidth = 11;
        ctx.beginPath();
        ctx.setLineDash([]);
        ctx.strokeStyle = fx.extra.player.htmlRgb;
        ctx.arc(s, u, OWOP.camera.zoom * t * e, 0, Math.PI * 2, false);
        ctx.stroke();
        ctx.closePath();
        ctx.lineWidth = defaultLine;
        ctx.globalAlpha = .8;
        // let canvas = document.getElementById('canvas4');
        // let ctx = canvas.getContext('2d');
        // ctx.strokeStyle = '#b668ff';
        // ctx.lineWidth = 4;
      })(fx, ctx, time);
      return 1;
    }
    NS.renderBorder = function (fx, ctx, time) {
      (function (fx, ctx, time) {
        if (!NS.PM.renderBorder) return;
        let t = "#00FF00";
        let e = 1;
        let l = NS.M20;
        ctx.globalAlpha = 1;
        ctx.strokeStyle = t || fx.extra.player.htmlRgb;
        // ctx.strokeRect(i, j, l.camera.zoom * e, l.camera.zoom * e);
        let oldWidth = ctx.lineWidth;
        ctx.lineWidth = 5;
        for (let k in NS.PM.border) {
          if (NS.PM.border[k].right || NS.PM.border[k].bottom) {
            let x = NS.PM.border[k].x;
            let y = NS.PM.border[k].y;
            if (Point.distance(new Point(OWOP.mouse.tileX, OWOP.mouse.tileY), new Point(x, y)) > (16 * 25)) continue;
            let i = (Math.floor(x / (e)) * e - l.camera.x) * l.camera.zoom;
            let j = (Math.floor(y / (e)) * e - l.camera.y) * l.camera.zoom;
            ctx.beginPath();
            if (NS.PM.border[k].bottom) {
              ctx.moveTo(i, j + l.camera.zoom);
              ctx.lineTo(i + l.camera.zoom, j + l.camera.zoom);
              ctx.stroke();
            }
            if (NS.PM.border[k].right) {
              ctx.moveTo(i + l.camera.zoom, j);
              ctx.lineTo(i + l.camera.zoom, j + l.camera.zoom);
              ctx.stroke();
            }
          } else delete NS.PM.border[k];
        }
        ctx.lineWidth = oldWidth;
        return 1;
      })(fx, ctx, time);
      return 0;
    }
    // var C = OWOP.require('util/color').colorUtils;
    // var C = NS.colorUtils;

    if (!localStorage["rSC"]) localStorage.setItem("rSC", JSON.stringify([255, 255, 255]));
    OWOP.player.rightSelectedColor = JSON.parse(localStorage.getItem("rSC"));
    let someRenderer = ((fx, ctx, time, defaultFx) => {
      if (!fx.extra.isLocalPlayer) {
        if (fx.visible) return NS.renderPlayer(fx, ctx, time);
        return defaultFx(fx, ctx, time);
      }
      return NS.renderBorder(fx, ctx, time);
    });
    function setTools() {
      let mouseStyler = document.createElement("style");
      document.getElementById("viewport").appendChild(mouseStyler);
      {
        let i = NS.localStorage.cursors[OWOP.player.tool.id];
        // i = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAACeElEQVRYw+2XsWsaYRiHH6VRSyGBEwepi5nsIFToWKUKDoVkyl/QIQhZRMdCJqGj0kUoGRw6FqcIGY4qnI6BBBwiFCqBKzcUDxoieMlgB/NdYtN6Z7yzGfyBy3eIj8/3vu/3HayyQGKvPoyX/ZvexwblfWymLIEMrb1UKFuGQk+PMLT2UqC8Vnb0UReAyOblUqBmAumjLlIgDkClpZhQ0edvx/9ty/zhJACdoWFC+cNJ10zNBJICcbR+FYDXz/wU0il2GrKrNeW1siO2rDM0AKhvZam0FPD1XIHy2ml5YUikkE6RPz0DX8/xkWBpSHSZMFRpKaapL9++YwwOHDU1E0jrV80tE+kMDXYa8j1TTkFZFrUwJKIcqrd1BHx8+YJESXYMynIOhaN7f31WSKdMqJP97FRNLTKnbLe9SGo7MgWVKMmmKSlTxhgcLDSnLA3d3SqAz8WjyfmWqxHK1UhtR0wovVk0obR+9UFQnlkPhfrI5iU7DZn6VpZQrsZ5q4veLP7ze4mSzMl+Fq5i6KMuvwaqx3GgUK7Gz0/vqLQUCumUaenPnLdurerNIlzF8IeT9I7fexYG2ghGxlIgDr4e+dMz6ltZAFRVRcqUZ1qammfBXbR+1ZYp222vHKqo6uSTKMnmv9cvrtEvruEqNrER3DXXxZqhtQlH92zVlOVpLwaj2AopU+Zr/s0E5OZ6IsD1Ude8Q4lxIaa9obVtFbplDemjLtL6GlKmPFUXIv0fRx4czBN7F7TePZh5u8eRLtsIRsYA0vraZOGmjaVA3HEzc9fQ3ZpxC8a+oTsnvpswtl+DROu7DTPXli0DxrYhN7rpQRE1tMoqM/Ibi49haAcCegQAAAAASUVORK5CYII=";
        if (i) mouseStyler.innerHTML = `#viewport { cursor: url("${i.icon}") ${i.hotspot[0]} ${i.hotspot[1]}, pointer !important; }`;
        else mouseStyler.innerHTML = `#viewport { }`;
      }
      let oldFunction = Object.getOwnPropertyDescriptor(OWOP.player, "tool").set;
      Object.defineProperty(OWOP.player, 'tool', {
        set: function (x) {
          let i = NS.localStorage.cursors[x];
          // i = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAACeElEQVRYw+2XsWsaYRiHH6VRSyGBEwepi5nsIFToWKUKDoVkyl/QIQhZRMdCJqGj0kUoGRw6FqcIGY4qnI6BBBwiFCqBKzcUDxoieMlgB/NdYtN6Z7yzGfyBy3eIj8/3vu/3HayyQGKvPoyX/ZvexwblfWymLIEMrb1UKFuGQk+PMLT2UqC8Vnb0UReAyOblUqBmAumjLlIgDkClpZhQ0edvx/9ty/zhJACdoWFC+cNJ10zNBJICcbR+FYDXz/wU0il2GrKrNeW1siO2rDM0AKhvZam0FPD1XIHy2ml5YUikkE6RPz0DX8/xkWBpSHSZMFRpKaapL9++YwwOHDU1E0jrV80tE+kMDXYa8j1TTkFZFrUwJKIcqrd1BHx8+YJESXYMynIOhaN7f31WSKdMqJP97FRNLTKnbLe9SGo7MgWVKMmmKSlTxhgcLDSnLA3d3SqAz8WjyfmWqxHK1UhtR0wovVk0obR+9UFQnlkPhfrI5iU7DZn6VpZQrsZ5q4veLP7ze4mSzMl+Fq5i6KMuvwaqx3GgUK7Gz0/vqLQUCumUaenPnLdurerNIlzF8IeT9I7fexYG2ghGxlIgDr4e+dMz6ltZAFRVRcqUZ1qammfBXbR+1ZYp222vHKqo6uSTKMnmv9cvrtEvruEqNrER3DXXxZqhtQlH92zVlOVpLwaj2AopU+Zr/s0E5OZ6IsD1Ude8Q4lxIaa9obVtFbplDemjLtL6GlKmPFUXIv0fRx4czBN7F7TePZh5u8eRLtsIRsYA0vraZOGmjaVA3HEzc9fQ3ZpxC8a+oTsnvpswtl+DROu7DTPXli0DxrYhN7rpQRE1tMoqM/Ibi49haAcCegQAAAAASUVORK5CYII=";
          if (i) mouseStyler.innerHTML = `#viewport { cursor: url("${i.icon}") ${i.hotspot[0]} ${i.hotspot[1]}, pointer !important; }`;
          else mouseStyler.innerHTML = `#viewport { }`;
          oldFunction.bind(this)(...arguments);
        }
      });
      OWOP.tool.addToolObject(new OWOP.tool.class('Cursor', OWOP.cursors.cursor, null, 1, tool => {
        // render protected chunks
        tool.setFxRenderer((fx, ctx, time) => {
          let defaultFx = OWOP.fx.player.RECT_SELECT_ALIGNED(1);
          if (someRenderer(fx, ctx, time, defaultFx)) return;

          if (tool.extra.state.chunkize) defaultFx = OWOP.fx.player.RECT_SELECT_ALIGNED(16);
          defaultFx(fx, ctx, time);
          return;
          if (!fx.extra.isLocalPlayer) return 1;
          var x = fx.extra.player.x;
          var y = fx.extra.player.y;
          var fxx = (Math.floor(x / 16) - camera.x) * camera.zoom;
          var fxy = (Math.floor(y / 16) - camera.y) * camera.zoom;
          var oldlinew = ctx.lineWidth;
          ctx.lineWidth = 1;
          if (tool.extra.end) {
            var s = tool.extra.start;
            var e = tool.extra.end;
            var x = (s[0] - camera.x) * camera.zoom + 0.5;
            var y = (s[1] - camera.y) * camera.zoom + 0.5;
            var w = e[0] - s[0];
            var h = e[1] - s[1];
            ctx.beginPath();
            ctx.rect(x, y, w * camera.zoom, h * camera.zoom);
            ctx.globalAlpha = 1;
            ctx.strokeStyle = "#FFFFFF";
            ctx.stroke();
            ctx.setLineDash([3, 4]);
            ctx.strokeStyle = "#000000";
            ctx.stroke();
            ctx.globalAlpha = 0.25 + Math.sin(time / 500) / 4;
            ctx.fillStyle = renderer.patterns.unloaded;
            ctx.fill();
            ctx.setLineDash([]);
            var oldfont = ctx.font;
            ctx.font = "16px sans-serif";
            var txt = `${!tool.extra.clicking ? "Right click to screenshot " : ""}(${Math.abs(w)}x${Math.abs(h)})`;
            var txtx = window.innerWidth >> 1;
            var txty = window.innerHeight >> 1;
            txtx = Math.max(x, Math.min(txtx, x + w * camera.zoom));
            txty = Math.max(y, Math.min(txty, y + h * camera.zoom));

            drawText(ctx, txt, txtx, txty, true);
            ctx.font = oldfont;
            ctx.lineWidth = oldlinew;
            return 0;
          } else {
            ctx.beginPath();
            ctx.moveTo(0, fxy + 0.5);
            ctx.lineTo(window.innerWidth, fxy + 0.5);
            ctx.moveTo(fxx + 0.5, 0);
            ctx.lineTo(fxx + 0.5, window.innerHeight);

            //ctx.lineWidth = 1;
            ctx.globalAlpha = 1;
            ctx.strokeStyle = "#FFFFFF";
            ctx.stroke();
            ctx.setLineDash([3]);
            ctx.strokeStyle = "#000000";
            ctx.stroke();

            ctx.setLineDash([]);
            ctx.lineWidth = oldlinew;
            return 1;
          }
        });
        // cursor functionality
        tool.extra.state = {
          scalar: "1",
          rainbow: false,
          chunkize: false,
          perfect: false
        };
        tool.extra.lastX;
        tool.extra.lastY;
        tool.extra.last1PX;
        tool.extra.last1PY;
        tool.extra.last2PX;
        tool.extra.last2PY;
        tool.extra.start;
        tool.extra.c = 0;
        tool.setEvent('mousedown mousemove', (mouse, event) => {
          if (mouse.buttons !== 2 && mouse.buttons !== 1) return 3;
          if (tool.extra.lastX == mouse.tileX && tool.extra.lastY == mouse.tileY) return 3;
          if (event?.ctrlKey) return setColor(mouse.buttons, PM.getPixel(mouse.tileX, mouse.tileY));
          let c = mouse.buttons === 1 ? OWOP.player.selectedColor : OWOP.player.rightSelectedColor;
          if (isNaN(tool.extra.lastX) || isNaN(tool.extra.lastY)) {
            tool.extra.lastX = mouse.tileX;
            tool.extra.lastY = mouse.tileY;
            tool.extra.last1PX = mouse.tileX;
            tool.extra.last1PY = mouse.tileY;
            tool.extra.last2PX = mouse.tileX;
            tool.extra.last2PY = mouse.tileY;
            tool.extra.start = true;
          }
          PM.startHistory();
          line(tool.extra.lastX, tool.extra.lastY, mouse.tileX, mouse.tileY, undefined, event, (x, y) => {
            let tempx = x;
            let tempy = y;
            if (tool.extra.state.perfect) {
              let place = false;
              // check to place
              if (tool.extra.start) {
                tool.extra.start = false;
                place = true;
              } else {
                if (tool.extra.last1PX == x && tool.extra.last1PY == y) {
                  tool.extra.last2PX = tool.extra.last1PX;
                  tool.extra.last2PY = tool.extra.last1PY;
                  tool.extra.last1PX = x;
                  tool.extra.last1PY = y;
                  return;
                }
              }
              if (!place) {
                for (let x1 = -1; x1 < 2; x1++) {
                  for (let y1 = -1; y1 < 2; y1++) {
                    if (x1 == 0 && y1 == 0) continue;
                    if (tool.extra.last2PX === (x + x1) && tool.extra.last2PY === (y + y1)) {
                      tool.extra.last1PX = x;
                      tool.extra.last1PY = y;
                      return;
                    }
                  }
                }
              }
              tempx = tool.extra.last1PX;
              tempy = tool.extra.last1PY;
            }
            let size = Number(tool.extra.state.scalar);
            if (tool.extra.state.chunkize) size = 16;
            let offset = Math.floor(size / 2);
            if (tool.extra.state.chunkize) {
              tempx = Math.floor(tempx / 16) * 16;
              tempy = Math.floor(tempy / 16) * 16;
              offset = 0;
            }
            for (let x1 = 0; x1 < size; x1++) {
              for (let y1 = 0; y1 < size; y1++) {
                if (tool.extra.state.rainbow) {
                  let pixel;
                  if ((pixel = PM.getPixel(tempx + x1 - offset, tempy + y1 - offset), !pixel)) continue;
                  c = mouse.buttons === 1 ? hue((tempx + x1 - offset) - (tempy + y1 - offset), 8) : hue(tool.extra.c++, 8);
                  if (Color.compare(pixel, c)) continue;
                }
                PM.setPixel(tempx + x1 - offset, tempy + y1 - offset, c);
                //[Math.round(OWOP.mouse.worldX/16)-0.5, Math.round(OWOP.mouse.worldY/16)-0.5]
              }
            }
            if (tool.extra.state.perfect) {
              tool.extra.last2PX = tool.extra.last1PX;
              tool.extra.last2PY = tool.extra.last1PY;
              tool.extra.last1PX = x;
              tool.extra.last1PY = y;
            }
          });
          tool.extra.lastX = mouse.tileX;
          tool.extra.lastY = mouse.tileY;
          return 3;
        });
        tool.setEvent('mouseup deselect', () => {
          PM.endHistory();
          tool.extra.lastX = undefined;
          tool.extra.lastY = undefined;
          tool.extra.last1PX = undefined;
          tool.extra.last1PY = undefined;
          tool.extra.last2PX = undefined;
          tool.extra.last2PY = undefined;
        });
        // change color positions
        tool.setEvent('keydown', keys => {
          if ((keys["87"] && keys["83"]) || !keys["16"]) return;
          if (keys["87"]) { // w
            let i1 = OWOP.player.paletteIndex;
            let i2 = modulo(i1 - 1, OWOP.player.palette.length);
            if (i2 == OWOP.player.palette.length - 1) {
              OWOP.player.palette.push(OWOP.player.palette.shift());
            } else {
              [OWOP.player.palette[i1], OWOP.player.palette[i2]] = [OWOP.player.palette[i2], OWOP.player.palette[i1]];
            }
            OWOP.player.paletteIndex = i2;
          }
          if (keys["83"]) { // s
            let i1 = OWOP.player.paletteIndex;
            let i2 = modulo(i1 + 1, OWOP.player.palette.length);
            if (i2 === 0) {
              OWOP.player.palette.unshift(OWOP.player.palette.pop());
            } else {
              [OWOP.player.palette[i1], OWOP.player.palette[i2]] = [OWOP.player.palette[i2], OWOP.player.palette[i1]];
            }
            OWOP.player.paletteIndex = i2;
          }
        });
      }));
      OWOP.tool.addToolObject(new OWOP.tool.class('Pipette', OWOP.cursors.pipette, null, 0, tool => {
        tool.extra.state = {};
        tool.setEvent('mousedown mousemove', mouse => {
          var c = PM.getPixel(mouse.tileX, mouse.tileY);
          if (!c) return mouse.buttons;
          switch (mouse.buttons) {
            case 1:
              OWOP.player.selectedColor = c;
              break;
            case 2:
              OWOP.player.rightSelectedColor = c;
              localStorage.setItem("rSC", JSON.stringify(OWOP.player.rightSelectedColor));
              break;
          }
          return mouse.buttons;
        });
      }));
      OWOP.tool.addToolObject(new OWOP.tool.class('Export', OWOP.cursors.select, null, 0, tool => {
        tool.extra.state = {
          type: "export",
          rainbow: false,
          chunkize: false
        };
        tool.setFxRenderer((fx, ctx, time) => {
          if (someRenderer(fx, ctx, time, () => 1)) return;

          var x = fx.extra.player.x;
          var y = fx.extra.player.y;
          var fxx = (Math.floor(x / 16) - camera.x) * camera.zoom;
          var fxy = (Math.floor(y / 16) - camera.y) * camera.zoom;
          var oldlinew = ctx.lineWidth;
          ctx.lineWidth = 1;
          if (tool.extra.end) {
            var s = tool.extra.start;
            var e = tool.extra.end;
            var x = s[0];
            var y = s[1];
            var w = e[0];
            var h = e[1];
            if (s[0] > e[0]) [w, x] = [x, w];
            if (s[1] > e[1]) [h, y] = [y, h];
            if (tool.extra.state.chunkize) {
              x = Math.floor(x / 16) * 16;
              y = Math.floor(y / 16) * 16;
              w = Math.floor(w / 16) * 16 + 16;
              h = Math.floor(h / 16) * 16 + 16;
            }
            w = w - x;
            h = h - y;
            x = (x - camera.x) * camera.zoom + 0.5;
            y = (y - camera.y) * camera.zoom + 0.5;

            ctx.beginPath();
            ctx.rect(x, y, w * camera.zoom, h * camera.zoom);
            ctx.globalAlpha = 1;
            ctx.strokeStyle = "#FFFFFF";
            ctx.stroke();
            ctx.setLineDash([3, 4]);
            ctx.strokeStyle = "#000000";
            ctx.stroke();
            ctx.globalAlpha = 0.25 + Math.sin(time / 500) / 4;
            ctx.fillStyle = renderer.patterns.unloaded;
            ctx.fill();
            ctx.setLineDash([]);
            var oldfont = ctx.font;
            ctx.font = "16px sans-serif";
            var txt = `${!tool.extra.clicking ? "Right click " : ""}(${Math.abs(w)}x${Math.abs(h)})`;
            var txtx = window.innerWidth >> 1;
            var txty = window.innerHeight >> 1;
            txtx = Math.max(x, Math.min(txtx, x + w * camera.zoom));
            txty = Math.max(y, Math.min(txty, y + h * camera.zoom));

            drawText(ctx, txt, txtx, txty, true);
            ctx.font = oldfont;
            ctx.lineWidth = oldlinew;
            return 0;
          } else {
            var x = fx.extra.player.x;
            var y = fx.extra.player.y;
            var fxx = Math.floor(x / 16);
            var fxy = Math.floor(y / 16);
            if (tool.extra.state.chunkize) {
              fxx = Math.floor(fxx / 16) * 16;
              fxy = Math.floor(fxy / 16) * 16;
            }
            fxx -= camera.x;
            fxy -= camera.y;
            fxx *= camera.zoom;
            fxy *= camera.zoom;
            ctx.beginPath();
            ctx.moveTo(0, fxy + 0.5);
            ctx.lineTo(window.innerWidth, fxy + 0.5);
            ctx.moveTo(fxx + 0.5, 0);
            ctx.lineTo(fxx + 0.5, window.innerHeight);

            //ctx.lineWidth = 1;
            ctx.globalAlpha = 1;
            ctx.strokeStyle = "#FFFFFF";
            ctx.stroke();
            ctx.setLineDash([3]);
            ctx.strokeStyle = "#000000";
            ctx.stroke();

            ctx.setLineDash([]);
            ctx.lineWidth = oldlinew;
            return 1;
          }
        });

        tool.extra.start = undefined;
        tool.extra.end = undefined;
        tool.extra.clicking = false;

        tool.setEvent('mousedown', (mouse, event) => {
          var s = tool.extra.start;
          var e = tool.extra.end;
          const isInside = () => {
            var x = s[0];
            var y = s[1];
            var w = e[0];
            var h = e[1];
            if (tool.extra.state.chunkize) {
              x = Math.floor(x / 16) * 16;
              y = Math.floor(y / 16) * 16;
              w = Math.floor(w / 16) * 16 + 16;
              h = Math.floor(h / 16) * 16 + 16;
            }
            return mouse.tileX >= x && mouse.tileX < w && mouse.tileY >= y && mouse.tileY < h;
          }
          if (mouse.buttons === 1 && !tool.extra.end) {
            tool.extra.start = [mouse.tileX, mouse.tileY];
            tool.extra.clicking = true;
            tool.setEvent('mousemove', (mouse, event) => {
              if (tool.extra.start && mouse.buttons === 1) {
                tool.extra.end = [mouse.tileX, mouse.tileY];
                return 1;
              }
            });
            const finish = () => {
              tool.setEvent('mousemove mouseup deselect', null);
              tool.extra.clicking = false;
              var s = tool.extra.start;
              var e = tool.extra.end;
              var tmp = undefined;
              if (e) {
                if (s[0] === e[0] || s[1] === e[1]) {
                  tool.extra.start = undefined;
                  tool.extra.end = undefined;
                }
                if (s[0] > e[0]) {
                  tmp = e[0];
                  e[0] = s[0];
                  s[0] = tmp;
                }
                if (s[1] > e[1]) {
                  tmp = e[1];
                  e[1] = s[1];
                  s[1] = tmp;
                }
              }
              renderer.render(renderer.rendertype.FX);
            }
            tool.setEvent('deselect', finish);
            tool.setEvent('mouseup', (mouse, event) => {
              if (!(mouse.buttons & 1)) {
                finish();
              }
            });
          } else if (mouse.buttons === 1 && tool.extra.end) {
            if (isInside()) {
              var offx = mouse.tileX;
              var offy = mouse.tileY;
              tool.setEvent('mousemove', (mouse, event) => {
                var dx = mouse.tileX - offx;
                var dy = mouse.tileY - offy;
                tool.extra.start = [s[0] + dx, s[1] + dy];
                tool.extra.end = [e[0] + dx, e[1] + dy];
              });
              const end = () => {
                tool.setEvent('mouseup deselect mousemove', null);
              }
              tool.setEvent('deselect', end);
              tool.setEvent('mouseup', (mouse, event) => {
                if (!(mouse.buttons & 1)) {
                  end();
                }
              });
            } else {
              tool.extra.start = undefined;
              tool.extra.end = undefined;
            }
          } else if (mouse.buttons === 2 && tool.extra.end && isInside()) {
            tool.extra.start = undefined;
            tool.extra.end = undefined;
            var x = s[0];
            var y = s[1];
            var w = e[0];
            var h = e[1];
            if (tool.extra.state.chunkize) {
              x = Math.floor(x / 16) * 16;
              y = Math.floor(y / 16) * 16;
              w = Math.floor(w / 16) * 16 + 16;
              h = Math.floor(h / 16) * 16 + 16;
            }
            w -= x;
            h -= y;
            let warn = false;
            switch (tool.extra.state.type) {
              case "export": {
                ((x, y, w, h, onblob) => {
                  var c = document.createElement('canvas');
                  c.width = w;
                  c.height = h;
                  var ctx = c.getContext('2d');
                  var d = ctx.createImageData(w, h);
                  for (var i = y; i < y + h; i++) {
                    for (var j = x; j < x + w; j++) {
                      let pix;
                      let tempPix = PM.queue[`${j},${i}`];
                      if (!tempPix) {
                        if ((pix = PM.getPixel(j, i), !pix)) {
                          warn = true;
                          pix = [255, 255, 255];
                        }
                      } else {
                        pix = tempPix.c;
                      }
                      d.data[4 * ((i - y) * w + (j - x))] = pix[0];
                      d.data[4 * ((i - y) * w + (j - x)) + 1] = pix[1];
                      d.data[4 * ((i - y) * w + (j - x)) + 2] = pix[2];
                      d.data[4 * ((i - y) * w + (j - x)) + 3] = 255;
                    }
                  }
                  ctx.putImageData(d, 0, 0);
                  c.toBlob(onblob);
                })(x, y, w, h, b => {
                  var url = URL.createObjectURL(b);
                  var img = new Image();
                  img.onload = () => {
                    if (OWOP.windowSys.windows['Resulting image']) OWOP.windowSys.delWindow(OWOP.windowSys.windows['Resulting image']);
                    OWOP.windowSys.addWindow(new GUIWindow("Resulting image", {
                      centerOnce: true,
                      closeable: true
                    }, win => {
                      var props = ['width', 'height'];
                      if (img.width > img.height) {
                        props.reverse();
                      }
                      var r = img[props[0]] / img[props[1]];
                      var shownSize = img[props[1]] >= 128 ? 256 : 128;
                      img[props[0]] = r * shownSize;
                      img[props[1]] = shownSize;
                      //win.container.classList.add('centeredChilds');
                      //setTooltip(img, "Right click to copy/save!");
                      var p1 = document.createElement("p");
                      img.style = "display:block; margin-left: auto; margin-right: auto; padding-bottom:15px;";
                      p1.appendChild(img);
                      //p1.appendChild(document.createElement("br"));
                      var closeButton = mkHTML("button", {
                        innerHTML: "CLOSE",
                        style: "width: 100%; height: 30px; margin: auto; padding-left: 10%;",
                        onclick: () => {
                          img.remove();
                          URL.revokeObjectURL(url);
                          win.getWindow().close();
                        }
                      });
                      var saveButton = mkHTML("button", {
                        innerHTML: "SAVE",
                        style: "width: 100%; height: 30px; margin: auto; padding-left: 10%;"
                      });
                      saveButton.onclick = () => {
                        var a = document.createElement('a');
                        a.download = `${Base64.fromNumber(Date.now())} OWOP_${OWOP.world.name} at ${s[0]} ${s[1]}.png`;
                        a.href = img.src;
                        a.click();
                      }
                      var scalar = document.createElement("input");
                      scalar.id = "scalar";
                      scalar.type = "range";
                      scalar.style = "width: 100%; margin: auto;";
                      scalar.value = "1";
                      scalar.min = "1";
                      scalar.max = "10";
                      //<p id="scalar-num" style="margin: auto;top: -8px; right: 12px; user-select: none; color: white;">1</p>
                      scalar.oninput = () => {
                        // scalarNum.textContent = scalar.value;
                      }
                      p1.appendChild(saveButton);
                      p1.appendChild(closeButton);
                      p1.appendChild(scalar);
                      var image = win.addObj(p1);
                    }));
                  }
                  img.src = url;
                });
              } break;
              case "color": {
                let test = false;
                let totalAdded = 0;
                let limit = 50;
                for (var i = x; i < x + w; i++) {
                  for (var j = y; j < y + h; j++) {
                    if (totalAdded >= limit) continue;
                    var pix = PM.getPixel(i, j);
                    if (!pix) continue;
                    for (let k = 0; k < OWOP.player.palette.length; k++) {
                      var c = OWOP.player.palette[k];
                      if (isSame(c, pix)) {
                        test = true;
                        break;
                      }
                    }
                    if (test) {
                      test = false;
                      continue;
                    }
                    OWOP.player.palette.push(pix);
                    totalAdded++;
                  }
                }
                OWOP.player.paletteIndex = OWOP.player.palette.length - 1;
                if (totalAdded >= limit) OWOP.chat.local(`total colors added limit has been reached (${limit} added)`);
              } break;
              case "adder": {
                for (var i = x; i < x + w; i++) {
                  for (var j = y; j < y + h; j++) {
                    var pix = PM.getPixel(i, j);
                    if (pix && !PM.queue[`${i},${j}`]) PM.setPixel(i, j, pix);
                  }
                }
              } break;
              case "filler": {
                var pix = OWOP.player.selectedColor;
                PM.startHistory();
                for (var i = x; i < x + w; i++) {
                  for (var j = y; j < y + h; j++) {
                    PM.setPixel(i, j, pix);
                  }
                }
                PM.endHistory();
              } break;
              case "clearer": {
                for (var i = x; i < x + w; i++) {
                  for (var j = y; j < y + h; j++) {
                    PM.unsetPixel(i, j);
                  }
                }
              } break;
            }
            if (warn) console.warn("Well something happened, you probably tried getting an area outside of loaded chunks.");
          }
        });
      }));
      OWOP.tool.addToolObject(new OWOP.tool.class('Fill', OWOP.cursors.fill, null, 1, tool => {
        tool.extra.state = {
          rainbow: false,
          checkered: false,
          dither: false,
          dither2: false,
          dither3: false,
          dither4: false,
          dither5: false,
          dither6: false
        }
        tool.extra.usedQueue = {};
        tool.extra.queue = {};
        tool.extra.fillingColor = undefined;
        tool.extra.button = 0;
        tool.extra.checkered = 0;
        const isFillColor = (x, y) => isSame(PM.getPixel(x, y), tool.extra.fillingColor) && (!tool.extra.usedQueue[`${x},${y}`]) && (tool.extra.queue[`${x},${y}`] = { x: x, y: y }, true);

        function tick() {
          var selClr = tool.extra.button === 1 ? OWOP.player.selectedColor : OWOP.player.rightSelectedColor;
          for (let current in tool.extra.queue) {
            current = tool.extra.queue[current];
            var x = current.x;
            var y = current.y;
            if (tool.extra.state.rainbow) selClr = hue(x - y, 8);
            var thisClr = PM.getPixel(x, y);
            if (isSame(thisClr, tool.extra.fillingColor) && !isSame(thisClr, selClr)) {
              if (tool.extra.state.checkered) {
                let pattern = [
                  [1, 0],
                  [0, 1]
                ];
                if (pattern[modulo(x, 2)][modulo(y, 2)]) PM.setPixel(x, y, selClr);
              } else if (tool.extra.state.dither) {
                let pattern = [
                  [1, 0, 1, 0],
                  [0, 1, 0, 0],
                  [1, 0, 1, 0],
                  [0, 0, 0, 1]
                ];
                if (pattern[modulo(x, 4)][modulo(y, 4)]) PM.setPixel(x, y, selClr);
              } else if (tool.extra.state.dither2) {
                let pattern = [
                  [0, 0],
                  [0, 1],
                  [0, 0],
                  [1, 0]
                ];
                if (pattern[modulo(x, 4)][modulo(y, 2)]) PM.setPixel(x, y, selClr);
              } else if (tool.extra.state.dither3) {
                let pattern = [
                  [1, 0, 0, 0, 1, 0, 1, 0],
                  [0, 1, 0, 1, 0, 1, 0, 0],
                  [1, 0, 1, 0, 0, 0, 1, 0],
                  [0, 0, 0, 1, 0, 1, 0, 1],
                  [1, 0, 1, 0, 1, 0, 0, 0],
                  [0, 1, 0, 0, 0, 1, 0, 1],
                  [0, 0, 1, 0, 1, 0, 1, 0],
                  [0, 1, 0, 1, 0, 0, 0, 1]
                ];
                if (pattern[modulo(x, 8)][modulo(y, 8)]) PM.setPixel(x, y, selClr);
              } else if (tool.extra.state.dither4) {
                let pattern = [
                  [0, 1, 0, 0],
                  [1, 1, 0, 0],
                  [0, 0, 1, 1],
                  [0, 0, 1, 0]
                ];
                if (pattern[modulo(x, 4)][modulo(y, 4)]) PM.setPixel(x, y, selClr);
              } else if (tool.extra.state.dither5) {
                let pattern = [
                  [0, 1, 0, 0, 0, 0, 1, 0],
                  [1, 1, 0, 0, 0, 0, 1, 1],
                  [0, 0, 1, 1, 1, 1, 0, 0],
                  [0, 0, 1, 0, 0, 1, 0, 0],
                  [0, 0, 1, 0, 0, 1, 0, 0],
                  [0, 0, 1, 1, 1, 1, 0, 0],
                  [1, 1, 0, 0, 0, 0, 1, 1],
                  [0, 1, 0, 0, 0, 0, 1, 0]
                ];
                if (pattern[modulo(x, 8)][modulo(y, 8)]) PM.setPixel(x, y, selClr);
              } else if (tool.extra.state.dither6) {
                let pattern = [
                  [0, 1, 1, 0, 0],
                  [1, 1, 1, 1, 0],
                  [1, 0, 1, 0, 0],
                  [1, 0, 1, 1, 0],
                  [0, 0, 0, 0, 0]
                ];
                if (pattern[modulo(x, 5)][modulo(y, 5)]) PM.setPixel(x, y, selClr);
              } else if (tool.extra.state.dither7) {
                let pattern = [
                  [1, 1, 1, 0, 1, 1, 1, 0, 1, 0],
                  [0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
                  [1, 0, 1, 0, 1, 1, 1, 0, 1, 1],
                  [1, 0, 0, 0, 0, 0, 0, 0, 1, 0],
                  [1, 0, 1, 1, 1, 0, 1, 0, 1, 1],
                  [0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
                  [1, 0, 1, 1, 1, 0, 1, 1, 1, 0],
                  [0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
                  [1, 1, 1, 0, 1, 0, 1, 1, 1, 0],
                  [1, 0, 1, 0, 0, 0, 0, 0, 0, 0]
                ];
                if (pattern[modulo(x, 10)][modulo(y, 10)]) PM.setPixel(x, y, selClr);
              } else if (tool.extra.state.dither8) {
                let pattern = [
                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                  [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
                  [0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0],
                  [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
                  [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
                  [0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0],
                  [0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1],
                  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
                  [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                  [0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0],
                  [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0]
                ];
                if (pattern[modulo(x, 10)][modulo(y, 10)]) PM.setPixel(x, y, selClr);
              } else {
                PM.setPixel(x, y, selClr);
              }
              var t = isFillColor(x, y - 1);
              var b = isFillColor(x, y + 1);
              var l = isFillColor(x - 1, y);
              var r = isFillColor(x + 1, y);

              t && l && isFillColor(x - 1, y - 1);
              t && r && isFillColor(x + 1, y - 1);
              b && l && isFillColor(x - 1, y + 1);
              b && r && isFillColor(x + 1, y + 1);
            }
            delete tool.extra.queue[`${x},${y}`];
            tool.extra.usedQueue[`${x},${y}`] = true;
          }
        }
        tool.setFxRenderer((fx, ctx, time) => {
          let defaultFx = OWOP.fx.player.RECT_SELECT_ALIGNED(1);
          if (someRenderer(fx, ctx, time, defaultFx)) return;

          ctx.globalAlpha = 0.8;
          ctx.strokeStyle = rgb(...(tool.extra.button === 1 ? OWOP.player.selectedColor : OWOP.player.rightSelectedColor));
          var z = OWOP.camera.zoom;
          if (!tool.extra.fillingColor) return defaultFx(fx, ctx, time);
          ctx.beginPath();
          for (let current in tool.extra.queue) {
            current = tool.extra.queue[current];
            if (tool.extra.state.rainbow) ctx.strokeStyle = rgb(...hue(current.x - current.y, 8));
            let x = current.x
            let y = current.y;
            // if (tool.extra.state.checkered) {
            //   if ((x + y) - 2 * Math.floor((x + y) / 2) == tool.extra.checkered) ctx.rect((x - OWOP.camera.x) * z, (y - OWOP.camera.y) * z, z, z);
            // } else {
            ctx.rect((x - OWOP.camera.x) * z, (y - OWOP.camera.y) * z, z, z);
            // }
          }
          ctx.stroke();
        });
        tool.setEvent("mousedown", (mouse, event) => {
          if (event.which !== 1 && event.which !== 3) return;
          tool.extra.button = event.which;
          tool.extra.fillingColor = PM.getPixel(mouse.tileX, mouse.tileY);
          tool.extra.queue[`${mouse.tileX},${mouse.tileY}`] = { x: mouse.tileX, y: mouse.tileY };
          tool.extra.checkered = (mouse.tileX + mouse.tileY) - 2 * Math.floor((mouse.tileX + mouse.tileY) / 2);
          PM.startHistory();
          tool.setEvent("tick", tick);
        });
        tool.setEvent("mouseup deselect", mouse => {
          PM.endHistory();
          tool.extra.usedQueue = {};
          tool.extra.queue = {};
          tool.extra.fillingColor = undefined;
          tool.extra.button = 0;
          tool.extra.checkered = 0;
          tool.setEvent("tick", null);
          return mouse && 1 & mouse.buttons;
        });
      }));
      OWOP.tool.addToolObject(new OWOP.tool.class('Line', OWOP.cursors.wand, null, 1, tool => {
        tool.extra.state = {
          rainbow: false,
          gradient: false
        };
        tool.extra.start = undefined;
        tool.extra.end = undefined;
        tool.extra.lineLength = 0;
        tool.extra.c = 0;
        tool.setFxRenderer((fx, ctx, time) => {
          let defaultFx = OWOP.fx.player.RECT_SELECT_ALIGNED(1);
          if (someRenderer(fx, ctx, time, defaultFx)) return;

          ctx.globalAlpha = 0.8;
          ctx.strokeStyle = rgb(...(tool.extra.button === 1 ? OWOP.player.selectedColor : OWOP.player.rightSelectedColor));
          if (tool.extra.state.rainbow) ctx.strokeStyle = rgb(...hue(~~(time / 100), 8));
          if ((!tool.extra.start || !tool.extra.end) && (defaultFx(fx, ctx, time), true)) return;
          tool.extra.lineLength = line(tool.extra.start[0], tool.extra.start[1], tool.extra.end[0], tool.extra.end[1], undefined, undefined, (x, y, i) => {
            ctx.beginPath();
            if (tool.extra.state.rainbow) ctx.strokeStyle = rgb(...hue(~~(time / 100) + i, 8));
            ctx.rect((x - camera.x) * camera.zoom, (y - camera.y) * camera.zoom, camera.zoom, camera.zoom);
            ctx.stroke();
          })[0];
        });
        tool.setEvent('mousedown mouseup', (mouse, event) => {
          if (event.which !== 1 && event.which !== 3) return;
          tool.extra.button = event.which;
          if (event.type === "mousedown" && !tool.extra.start) return tool.extra.start = [mouse.tileX, mouse.tileY];
          if (!tool.extra.start) return;
          tool.extra.end = [mouse.tileX, mouse.tileY];
          if (event.type === "mouseup" && tool.extra.start[0] === tool.extra.end[0] && tool.extra.start[1] === tool.extra.end[1]) return;
          PM.startHistory();
          let sc = PM.getPixel(tool.extra.start[0], tool.extra.start[1]);
          line(tool.extra.start[0], tool.extra.start[1], tool.extra.end[0], tool.extra.end[1], undefined, undefined, (x, y, i) => {
            let c = event.which === 1 ? OWOP.player.selectedColor : OWOP.player.rightSelectedColor;
            if (tool.extra.state.gradient) {
              let divisor = (tool.extra.lineLength - 1);
              let r = sc[0] - ((sc[0] - c[0]) / divisor) * i;
              let g = sc[1] - ((sc[1] - c[1]) / divisor) * i;
              let b = sc[2] - ((sc[2] - c[2]) / divisor) * i;
              c = [~~r, ~~g, ~~b];
              if (i == 0) c = sc;
            } else if (tool.extra.state.rainbow) c = event.which === 1 ? hue(x - y, 8) : hue(tool.extra.c++, 8);
            PM.setPixel(x, y, c);
          });
          PM.endHistory();
          tool.extra.start = undefined;
          tool.extra.end = undefined;
        });
        tool.setEvent('mousemove', mouse => {
          if (tool.extra.start) tool.extra.end = [mouse.tileX, mouse.tileY];
        });
        tool.setEvent('deselect', () => {
          PM.endHistory();
          tool.extra.start = undefined;
          tool.extra.end = undefined;
          tool.extra.c = 0;
        });
      }));
      OWOP.tool.addToolObject(new OWOP.tool.class('Paste', OWOP.cursors.paste, null, 1, tool => {
        tool.extra.state = {
          chunkize: false,
          rc: () => tool.extra.renderData(0b00),
          rcc: () => tool.extra.renderData(0b01),
          fh: () => tool.extra.renderData(0b10),
          fv: () => tool.extra.renderData(0b11)
        };
        tool.extra.img = undefined;
        tool.extra.data = undefined;
        tool.extra.renderData = function (type) {
          let transpose3 = function (m) {
            let result = new Array(m[0].length);
            for (let i = 0; i < m[0].length; i++) {
              result[i] = new Array(m.length - 1);
              for (let j = m.length - 1; j > -1; j--) {
                result[i][j] = m[j][i];
              }
            }
            return result;
          };

          let reverseRows = function (m) {
            return m.reverse();
          };

          let reverseCols = function (m) {
            for (let i = 0; i < m.length; i++) {
              m[i].reverse();
            }
            return m;
          };

          let rotateCc = m => transpose3(m).reverse();
          let rotateCw = m => transpose3(m.reverse());
          switch (type) {
            case 0: {
              tool.extra.data = rotateCw(tool.extra.data);
            } break;
            case 1: {
              tool.extra.data = rotateCc(tool.extra.data);
            } break;
            case 2: {
              reverseCols(tool.extra.data);
            } break;
            case 3: {
              reverseRows(tool.extra.data);
            } break;
          }
          ((arr, onblob) => {
            let c = document.createElement('canvas');
            let w = arr[0].length;
            let h = arr.length;
            c.width = w;
            c.height = h;
            let ctx = c.getContext('2d');
            let d = ctx.createImageData(w, h);
            for (let j = 0; j < h; j++) {
              for (let i = 0; i < w; i++) {
                let pix = arr[j][i];
                d.data[4 * (j * w + i)] = pix[0];
                d.data[4 * (j * w + i) + 1] = pix[1];
                d.data[4 * (j * w + i) + 2] = pix[2];
                d.data[4 * (j * w + i) + 3] = 255;
              }
            }
            ctx.putImageData(d, 0, 0);
            c.toBlob(onblob);
          })(tool.extra.data, b => {
            let url = URL.createObjectURL(b);
            let img = new Image();
            img.onload = () => tool.extra.img = img;
            img.src = url;
          });
        }
        tool.setFxRenderer((fx, ctx, time) => {
          let defaultFx = OWOP.fx.player.RECT_SELECT_ALIGNED(1);
          if (someRenderer(fx, ctx, time, defaultFx)) return;

          let p9 = OWOP.camera.zoom;
          let pp = OWOP.mouse.tileX;
          let pD = OWOP.mouse.tileY;

          if (tool.extra.state.chunkize) {
            pp = Math.floor(pp / 16) * 16;
            pD = Math.floor(pD / 16) * 16;
          }

          pp -= OWOP.camera.x;
          pD -= OWOP.camera.y;

          // if (p2.length) {
          //   ctx.globalAlpha = 0.8;

          //for (let pS = 0; pS < p2.length; pS++) {
          //    ctx.strokeStyle = C.toHTML(p2[pS][2]);
          //    ctx.strokeRect((p2[pS][0] - OWOP.camera.x) * p9, (p2[pS][1] - OWOP.camera.y) * p9, p9, p9);
          //}

          //   return 0;
          // }
          if (tool.extra.img) {
            ctx.globalAlpha = 0.5 + Math.sin(time / 500) / 4;
            ctx.strokeStyle = '#000000';
            ctx.scale(p9, p9);
            ctx.drawImage(tool.extra.img, pp, pD);
            ctx.scale(1 / p9, 1 / p9);
            ctx.globalAlpha = 0.8;
            ctx.strokeRect(pp * p9, pD * p9, tool.extra.img.width * p9, tool.extra.img.height * p9);
            return 0;
          }
        });
        tool.setEvent('select', () => {
          if (tool.extra.k) {
            if (tool.extra.k instanceof Image) tool.extra.k = NS.getImageData(tool.extra.k);
            tool.extra.data = tool.extra.k;
            tool.extra.renderData();
            delete tool.extra.k;
            return;
          }
          let p6 = document.createElement('input');
          p6.type = 'file';
          p6.accept = 'image/*';
          p6.addEventListener('change', () => {
            if (!p6.files || !p6.files[0]) return;
            let p7 = new FileReader();
            p7.addEventListener('load', () => {
              let p8 = new Image();
              p8.addEventListener('load', () => {
                tool.extra.data = NS.getImageData(p8);
                tool.extra.renderData();
              });
              p8.src = p7.result;
            });
            p7.readAsDataURL(p6.files[0]);
          });
          p6.click();
        });
        tool.setEvent('mousedown', (mouse, event) => {
          if (!(mouse.buttons & 1)) return;
          if (!tool.extra.data) return;
          let x = mouse.tileX;
          let y = mouse.tileY;
          let data = tool.extra.data;
          let fix = (p6, p7, p8) => Math.floor(p6 * (1 - p8) + p7 * p8);

          if (tool.extra.state.chunkize) {
            x = Math.floor(x / 16) * 16;
            y = Math.floor(y / 16) * 16;
          }
          PM.startHistory();
          for (let j = 0; j < data.length; j++) {
            for (let i = 0; i < data[0].length; i++) {
              let d = data[j][i];
              let color = PM.getPixel(i + x, j + y);
              if (!color) continue;
              let pH = !isNaN(d[3]) ? d[3] / 255 : 1;
              // let pH = 1;
              // color = [fix(color[0], data[pD + 0], pH), fix(color[1], data[pD + 1], pH), fix(color[2], data[pD + 2], pH)];
              color = [fix(color[0], d[0], pH), fix(color[1], d[1], pH), fix(color[2], d[2], pH)];
              // use this when color is checked against being alpha color cause this is stupid
              // var pix = PM.getPixel(i, j);
              // if (!PM.queue[`${i},${j}`]) PM.setPixel(i, j, pix);
              PM.setPixel(i + x, j + y, color);
            }
          }
          PM.endHistory();
        });
        tool.setEvent('mousemove', (mouse, event) => {
          if (!OWOP.OPM) return;
          if (mouse.buttons !== 0) {
            ((x, y, startX, startY) => {
              OWOP.require("canvas_renderer").moveCameraBy((startX - x) / 16, (startY - y) / 16);
            })(mouse.worldX, mouse.worldY, mouse.mouseDownWorldX, mouse.mouseDownWorldY);
            return mouse.buttons;
          }
        });
      }));
      OWOP.tool.addToolObject(new OWOP.tool.class('Copy', OWOP.cursors.copy, null, 1, tool => {
        tool.extra.state = {
          margin: false
        };
        function shrinkMargin(s, e) {
          // for () {

          // }
          return [s2, e2];
        }
        tool.setFxRenderer((fx, ctx, time) => {
          if (someRenderer(fx, ctx, time, () => 1)) return;

          var x = fx.extra.player.x;
          var y = fx.extra.player.y;
          var fxx = (Math.floor(x / 16) - camera.x) * camera.zoom;
          var fxy = (Math.floor(y / 16) - camera.y) * camera.zoom;
          var oldlinew = ctx.lineWidth;
          ctx.lineWidth = 1;
          if (tool.extra.end) {
            var s = tool.extra.start;
            var e = tool.extra.end;
            var x = s[0];
            var y = s[1];
            var w = e[0];
            var h = e[1];
            if (s[0] > e[0]) [w, x] = [x, w];
            if (s[1] > e[1]) [h, y] = [y, h];
            if (NS.chunkize) {
              x = Math.floor(x / 16) * 16;
              y = Math.floor(y / 16) * 16;
              w = Math.floor(w / 16) * 16 + 16;
              h = Math.floor(h / 16) * 16 + 16;
            }
            w = w - x;
            h = h - y;
            x = (x - camera.x) * camera.zoom + 0.5;
            y = (y - camera.y) * camera.zoom + 0.5;

            ctx.beginPath();
            ctx.rect(x, y, w * camera.zoom, h * camera.zoom);
            ctx.globalAlpha = 1;
            ctx.strokeStyle = "#FFFFFF";
            ctx.stroke();
            ctx.setLineDash([3, 4]);
            ctx.strokeStyle = "#000000";
            ctx.stroke();
            ctx.globalAlpha = 0.25 + Math.sin(time / 500) / 4;
            ctx.fillStyle = renderer.patterns.unloaded;
            ctx.fill();
            ctx.setLineDash([]);
            var oldfont = ctx.font;
            ctx.font = "16px sans-serif";
            var txt = `${!tool.extra.clicking ? "Right click to copy area " : ""}(${Math.abs(w)}x${Math.abs(h)})`;
            var txtx = window.innerWidth >> 1;
            var txty = window.innerHeight >> 1;
            txtx = Math.max(x, Math.min(txtx, x + w * camera.zoom));
            txty = Math.max(y, Math.min(txty, y + h * camera.zoom));

            drawText(ctx, txt, txtx, txty, true);
            ctx.font = oldfont;
            ctx.lineWidth = oldlinew;
            return 0;
          } else {
            ctx.beginPath();
            ctx.moveTo(0, fxy + 0.5);
            ctx.lineTo(window.innerWidth, fxy + 0.5);
            ctx.moveTo(fxx + 0.5, 0);
            ctx.lineTo(fxx + 0.5, window.innerHeight);

            //ctx.lineWidth = 1;
            ctx.globalAlpha = 1;
            ctx.strokeStyle = "#FFFFFF";
            ctx.stroke();
            ctx.setLineDash([3]);
            ctx.strokeStyle = "#000000";
            ctx.stroke();

            ctx.setLineDash([]);
            ctx.lineWidth = oldlinew;
            return 1;
          }
        });

        tool.extra.start = undefined;
        tool.extra.end = undefined;
        tool.extra.clicking = false;
        tool.extra.tempCallback = undefined;

        tool.setEvent('mousedown', (mouse, event) => {
          var s = tool.extra.start;
          var e = tool.extra.end;
          const isInside = () => {
            var x = s[0];
            var y = s[1];
            var w = e[0];
            var h = e[1];
            if (NS.chunkize) {
              x = Math.floor(x / 16) * 16;
              y = Math.floor(y / 16) * 16;
              w = Math.floor(w / 16) * 16 + 16;
              h = Math.floor(h / 16) * 16 + 16;
            }
            return mouse.tileX >= x && mouse.tileX < w && mouse.tileY >= y && mouse.tileY < h;
          }
          if (mouse.buttons === 1 && !tool.extra.end) {
            tool.extra.start = [mouse.tileX, mouse.tileY];
            tool.extra.clicking = true;
            tool.setEvent('mousemove', (mouse, event) => {
              if (tool.extra.start && mouse.buttons === 1) {
                tool.extra.end = [mouse.tileX, mouse.tileY];
                return 1;
              }
            });
            tool.setEvent('mouseup', (mouse, event) => {
              if (!(mouse.buttons & 1)) {
                tool.setEvent('mousemove mouseup', null);
                tool.extra.clicking = false;
                var s = tool.extra.start;
                var e = tool.extra.end;
                if (e) {
                  if (s[0] === e[0] || s[1] === e[1]) {
                    tool.extra.start = undefined;
                    tool.extra.end = undefined;
                  }
                  if (s[0] > e[0]) {
                    var tmp = e[0];
                    e[0] = s[0];
                    s[0] = tmp;
                  }
                  if (s[1] > e[1]) {
                    var tmp = e[1];
                    e[1] = s[1];
                    s[1] = tmp;
                  }
                }
                renderer.render(renderer.rendertype.FX);
              }
            });
          } else if (mouse.buttons === 1 && tool.extra.end) {
            if (isInside()) {
              var offx = mouse.tileX;
              var offy = mouse.tileY;
              tool.setEvent('mousemove', (mouse, event) => {
                var dx = mouse.tileX - offx;
                var dy = mouse.tileY - offy;
                tool.extra.start = [s[0] + dx, s[1] + dy];
                tool.extra.end = [e[0] + dx, e[1] + dy];
              });
              tool.setEvent('mouseup', (mouse, event) => { if (!(mouse.buttons & 1)) tool.setEvent('mousemove mouseup', null) });
            } else {
              tool.extra.start = undefined;
              tool.extra.end = undefined;
            }
          } else if (mouse.buttons === 2 && tool.extra.end && isInside()) {
            tool.extra.start = undefined;
            tool.extra.end = undefined;
            let x = s[0];
            let y = s[1];
            let w = e[0];
            let h = e[1];
            if (tool.extra.state.chunkize) {
              x = Math.floor(x / 16) * 16;
              y = Math.floor(y / 16) * 16;
              w = Math.floor(w / 16) * 16 + 16;
              h = Math.floor(h / 16) * 16 + 16;
            }
            w -= x;
            h -= y;
            let data = [];
            for (let j = 0; j < h; j++) {
              data.push([]);
              for (let i = 0; i < w; i++) {
                let pix = PM.getPixel(x + i, y + j);
                if (pix) data[j].push(pix);
              }
            }
            if (tool.extra.tempCallback) {
              if (tool.extra.tempCallback(data)) {
                tool.extra.tempCallback = undefined;
                OWOP.player.tool = "move";
              }
            } else {
              OWOP.tool.allTools.paste.extra.k = data;
              OWOP.player.tool = "paste";
            }
          }
        });
        tool.setEvent('deselect', () => {
          tool.setEvent('mousemove mouseup', null);
          if (!tool.extra.end) {
            tool.extra.clicking = false;
            var s = tool.extra.start;
            var e = tool.extra.end;
            if (e) {
              if (s[0] === e[0] || s[1] === e[1]) {
                tool.extra.start = undefined;
                tool.extra.end = undefined;
              }
              if (s[0] > e[0]) {
                var tmp = e[0];
                e[0] = s[0];
                s[0] = tmp;
              }
              if (s[1] > e[1]) {
                var tmp = e[1];
                e[1] = s[1];
                s[1] = tmp;
              }
            }
          }
          tool.extra.tempCallback = undefined;
        });
      }));
      OWOP.tool.addToolObject(new OWOP.tool.class('Write', OWOP.cursors.write, null, 1, tool => {
        tool.extra.state = {
          rainbow: false
        };
        tool.extra.text = "";
        tool.extra.newText = {
          data: {
            gap: 1,
            space: 1,
            height: 8,
            bottom: 6
          },
          " ": {
            width: 1,
            height: 8,
            skip: 0,
            text: "00000000"
          },
          a: {
            width: 3,
            height: 3,
            skip: 3,
            text: "011101011"
          },
          b: {
            width: 3,
            height: 6,
            skip: 0,
            text: `100100100110101110`
          },
          c: {
            width: 3,
            height: 3,
            skip: 3,
            text: `011100011`
          },
          d: {
            width: 3,
            height: 5,
            skip: 1,
            text: `001001011101011`
          },
          e: {
            width: 3,
            height: 3,
            skip: 3,
            text: `010110011`
          },
          f: {
            width: 2,
            height: 5,
            skip: 1,
            text: `0110111010`
          },
          g: {
            width: 3,
            height: 5,
            skip: 3,
            text: `011101011001110`
          },
          h: {
            width: 3,
            height: 5,
            skip: 1,
            text: `100100110101101`
          },
          i: {
            width: 1,
            height: 5,
            skip: 1,
            text: `10111`
          },
          j: {
            width: 2,
            height: 7,
            skip: 1,
            text: `01000101010110`
          },
          k: {
            width: 3,
            height: 5,
            skip: 1,
            text: `100100101110101`
          },
          l: {
            width: 2,
            height: 5,
            skip: 1,
            text: `1010101001`
          },
          m: {
            width: 5,
            height: 3,
            skip: 3,
            text: `111101010110101`
          },
          n: {
            width: 3,
            height: 3,
            skip: 3,
            text: `110101101`
          },
          o: {
            width: 3,
            height: 3,
            skip: 3,
            text: `010101010`
          },
          p: {
            width: 3,
            height: 5,
            skip: 3,
            text: `110101110100100`
          },
          q: {
            width: 3,
            height: 5,
            skip: 3,
            text: `011101011001001`
          },
          r: {
            width: 2,
            height: 3,
            skip: 3,
            text: `111010`
          },
          s: {
            width: 3,
            height: 3,
            skip: 3,
            text: `011010110`
          },
          t: {
            width: 2,
            height: 5,
            skip: 1,
            text: `1010111001`
          },
          u: {
            width: 3,
            height: 3,
            skip: 3,
            text: `101101011`
          },
          v: {
            width: 3,
            height: 3,
            skip: 3,
            text: `101101010`
          },
          w: {
            width: 5,
            height: 3,
            skip: 3,
            text: `101011010101010`
          },
          x: {
            width: 3,
            height: 3,
            skip: 3,
            text: `101010101`
          },
          y: {
            width: 3,
            height: 5,
            skip: 3,
            text: `101101011001010`
          },
          z: {
            width: 3,
            height: 3,
            skip: 3,
            text: `110010011`
          }
        }
        tool.extra.position = 0;
        tool.extra.start = undefined;
        tool.extra.end = undefined;
        function setText(t, pos, func) {
          let localPos = [...pos];
          let furthestPos = [...pos];
          function setLetter(letter, pos, func) {
            if (letter === "\n") return 1;
            if (!tool.extra.newText[letter]) return 0;
            let data = tool.extra.newText.data;
            let letterData = tool.extra.newText[letter];
            for (let x = 0; x < letterData.width; x++) {
              for (let y = 0; y < letterData.height; y++) {
                if (letterData.text[x + y * letterData.width] !== "0") func(pos[0] + x, pos[1] + y + letterData.skip);
              }
            }
            return letterData;
          }
          for (let p5 = 0; p5 < t.length; p5++) {
            let l = setLetter(t[p5].toLowerCase(), localPos, func);
            if (l === 0) continue;
            if (l === 1) {
              localPos[0] = pos[0];
              localPos[1] = localPos[1] + tool.extra.newText.data.height + 1;
            } else {
              localPos[0] += l.width + tool.extra.newText.data.gap;
            }
            if (localPos[0] > furthestPos[0]) furthestPos[0] = localPos[0];
            if (localPos[1] > furthestPos[1]) furthestPos[1] = localPos[1];
          }
          return furthestPos;
        }
        tool.setFxRenderer((fx, ctx, time) => {
          if (someRenderer(fx, ctx, time, () => 1)) return;

          let camera = OWOP.camera;
          // let x = fx.extra.player.x;
          // let y = fx.extra.player.y;
          // let fxx = (Math.floor(x / 16) - camera.x) * camera.zoom;
          // let fxy = (Math.floor(y / 16) - camera.y) * camera.zoom;
          let oldlinew = ctx.lineWidth;
          ctx.lineWidth = 2;
          let s, e;
          if (!tool.extra.start) {
            s = [OWOP.mouse.tileX, OWOP.mouse.tileY];
            ctx.strokeStyle = "#00FF00";
          } else {
            s = tool.extra.start;
            ctx.strokeStyle = "#FF0000";
          }
          let oldFillstyle = ctx.fillStyle;
          ctx.fillStyle = OWOP.player.htmlRgb;
          let tempEnd = setText(tool.extra.text, [...s], (x, y) => {
            let x1 = (x - camera.x) * camera.zoom + 0.5;
            let y1 = (y - camera.y) * camera.zoom + 0.5;
            ctx.fillStyle = tool.extra.state.rainbow ? rgb(...hue(x - y, 8)) : OWOP.player.htmlRgb;
            ctx.fillRect(x1, y1, camera.zoom, camera.zoom);
          });
          e = [tempEnd[0] + 1, tempEnd[1] + 8]
          if (tool.extra.end) tool.extra.end = e;
          let x = (s[0] - camera.x) * camera.zoom + 0.5;
          let y = (s[1] - camera.y) * camera.zoom + 0.5;
          let w = e[0] - s[0];
          let h = e[1] - s[1];
          ctx.beginPath();
          ctx.rect(x, y, w * camera.zoom, h * camera.zoom);
          ctx.stroke();
          ctx.lineWidth = oldlinew;
          ctx.fillStyle = oldFillstyle;
          return 0;
        });
        tool.setEvent('mousedown', (mouse, event) => {
          var s = tool.extra.start;
          var e = tool.extra.end;
          const isInside = () => mouse.tileX >= s[0] && mouse.tileX < e[0] && mouse.tileY >= s[1] && mouse.tileY < e[1];
          if (mouse.buttons === 1 && !tool.extra.end) {
            tool.extra.start = [mouse.tileX, mouse.tileY];
            tool.extra.end = [mouse.tileX + 1, mouse.tileY + 7];
            tool.setEvent('keydown', (keysDown, event) => {
              if (event.key.length > 1) {
                switch (event.key) {
                  case "Enter": {
                    tool.extra.text += "\n";
                  } break;
                  case "Backspace": {
                    let t = tool.extra.text.split("");
                    t.pop();
                    tool.extra.text = t.join("");
                  } break;
                }
                return;
              }
              tool.extra.text += event.key;
              return 1;
            });
          } else if (mouse.buttons === 1 && tool.extra.end) {
            if (isInside()) {
              var offx = mouse.tileX;
              var offy = mouse.tileY;
              tool.setEvent('mousemove', (mouse, event) => {
                var dx = mouse.tileX - offx;
                var dy = mouse.tileY - offy;
                tool.extra.start = [s[0] + dx, s[1] + dy];
                tool.extra.end = [e[0] + dx, e[1] + dy];
              });
              tool.setEvent('mouseup', () => tool.setEvent('mouseup mousemove', null));
            } else {
              tool.extra.start = undefined;
              tool.extra.end = undefined;
            }
          } else if (mouse.buttons === 2 && tool.extra.end && isInside()) {
            PM.startHistory();
            setText(tool.extra.text, [...tool.extra.start], (x, y) => PM.setPixel(x, y, tool.extra.state.rainbow ? hue(x - y, 8) : OWOP.player.selectedColor));
            PM.endHistory();
            /*
              for (let p9 = 0; p9 < 7; p9++) {
                for (let pp = 0; pp < 7; pp++) {
                  let pD = (p9 * 189 + pp + offsetx * 7);
                  // console.log(tool.extra.text[pD]);
                  // let color = [p8[pD + 0], p8[pD + 1], p8[pD + 2], p8[pD + 3]];
                  let c = [OWOP.player.selectedColor, undefined, undefined];
                  let pos = [...tool.extra.start];
                  pos[0] = pos[0] + pp + p6 * 7;
                  pos[1] = pos[1] + p9 + row * 7;
                  let color = c[tool.extra.text[pD]];
                  if (color) PM.setPixel(...pos, color);
                }
              }
            }
            */
            return true;
          }
        });
        tool.setEvent('deselect', () => {
          tool.extra.position = 0;
          tool.extra.start = undefined;
          tool.extra.end = undefined;
          // tool.extra.text = "";
          tool.setEvent('keydown mouseup mousemove', null);
        });
        tool.setEvent('keyup', () => 1);
      }));
      if (OWOP?.tool?.allTools?.pipette) OWOP.tool.allTools.pipette.fxRenderer = someRenderer;
      if (OWOP?.tool?.allTools?.move) OWOP.tool.allTools.move.fxRenderer = someRenderer; // make fucking sure the move tool fucking exists gahhhhh
      if (OWOP?.tool?.allTools?.zoom) OWOP.tool.allTools.zoom.fxRenderer = someRenderer;
      OWOP.tool.updateToolbar();
      if (document.domain && !NS.OPM) {
        let r = 0;
        for (let e in OWOP.tool.allTools) {
          e = OWOP.tool.allTools[e];
          if (e.rankRequired < 2) r++;
        }
        document.getElementById("toole-container").style.maxWidth = 40 * Math.ceil(r / 8) + "px";
      }
      makeOptionsWindow();
    }
    function x() {
      setTimeout(() => {
        if (OWOP?.tool !== undefined && OWOP?.player?.tool?.id !== undefined) setTools();
        else setTimeout(x, 1.5e3);
      }, 1.5e3);
    }
    x();
  })();

  if (document.domain && !NS.OPM) {
    let originalFunction = OWOP.chat.sendModifier;
    NS.privateMessageID = void 0;
    OWOP.chat.sendModifier = function () {
      let command = arguments[0].slice(1).split(' ');
      if (arguments[0].startsWith('/')) {
        switch (command[0]) {
          case "commands":
          case "help": {
            OWOP.chat.local(`Commands: /tp, /whitelist, /msg`);
          } break;
          case "tp": {
            let x, y;
            if (command.length === 3) {
              [x, y] = [Number(command[1]), Number(command[2])];
            }
            if (command.length === 2) {
              if (isNaN(Number(command[1]))) break;
              let p = NS.M0.misc.world.players[command[1]];
              if (!p) {
                OWOP.chat.local(`Player ${command[1]} doesn't exist.`);
                break;
              }
              ({ tileX: x, tileY: y } = p);
            }
            if (isNaN(x) || isNaN(y)) {
              NS.teleport.camera = {};
              break;
            }
            if (Math.abs(x) > 0xFFFFFF || Math.abs(y) > 0xFFFFFF) break;
            if (Math.abs(x) < 5e5 && Math.abs(y) < 5e5) {
              NS.teleport.camera = {};
              NS.M20.centerCameraTo(x, y);
              break;
            }
            NS.teleport.camera = { x: x, y: y };
            NS.M20.camera.zoom = 32;
            if (!NS.teleport.teleporting) {
              OWOP.chat.local("Press Esc to cancel teleport OR send \"/tp\" in chat.");
              NS.teleport();
            }
          } break;
          case "chat": {
            // if (isNaN(Number(command[1]))) break;
            if (command[1] === "all") {
              NS.privateMessageID = void 0;
              OWOP.chat.local(`Chat set to Mode: All.`);
            } else if (isNaN(Number(command[1]))) {

            } else {
              OWOP.chat.local(`Chat set to Mode: Private Messaging\nID: ${command[1]}`);
            }
            NS.privateMessageID = Number(command[1]);
            command[0] = "tell";
            arguments[0] = "/" + command.join(" ");
          } break;
          case "pm":
          case "message":
          case "msg": {
            command[0] = "tell";
            arguments[0] = "/" + command.join(" ");
          } break;
          case "wl":
          case "whitelist": {
            if (!command[1]) {
              OWOP.chat.local(`Whitelist: ${Object.keys(PM.whitelist).join(", ")}`);
              console.log(1);
              break;
            }
            // if (!isNaN(Number(command[1]))) [command[1], command[2], command[3]] = ["add", command[1], command[2]];
            // else {
            //   OWOP.chat.local(`Syntax: /whitelist [add/remove] [id] -[super: true/false]-`);
            //   console.log(2);
            //   break;
            // }
            switch (command[1]) {
              case "add": {
                if (isNaN(Number(command[2]))) {
                  OWOP.chat.local(`Syntax: /whitelist [add/remove] [id] -[super: true/false]-`);
                  console.log(3);
                  break;
                }
                if (PM.whitelist[command[2]]) {
                  OWOP.chat.local(`Player ${command[2]} is already whitelisted.`);
                  console.log(4);
                  break;
                }
                if (!Object.keys(NS.M0.playerList).includes(command[2])) {
                  OWOP.chat.local(`Player ${command[2]} doesn't exist.`);
                  console.log(5);
                  break;
                }
                let _super = false;
                if (command[3] === "true") _super = true;
                PM.whitelist[command[2]] = { super: _super };
                OWOP.chat.local(`Player ${command[2]} added to whitelist.`);
                console.log(6);
              } break;
              case "remove": {
                if (isNaN(Number(command[2]))) {
                  OWOP.chat.local(`Syntax: /whitelist [add/remove] [id] -[super: true/false]-`);
                  console.log(7);
                  break;
                }
                if (!PM.whitelist[command[2]]) {
                  OWOP.chat.local(`Player ${command[2]} is not on the whitelist.`);
                  console.log(8);
                  break;
                }
                delete PM.whitelist[command[2]];
                OWOP.chat.local(`Player ${command[2]} removed from whitelist.`);
                console.log(9);
              } break;
              default: {
              }
            }
          } break;
        }
      } else if (NS.privateMessageID) {
        command[0] = "tell";
        arguments[0] = "/" + command.join(" ");
      }
      return originalFunction.bind(this)(...arguments);
    }
    console.log("non opm completed");
  }

  if (document.domain && NS.OPM) {
    let element1 = document.createElement("span");
    element1.className = "top-bar";
    element1.style.float = "right";
    element1.style.width = "34px";
    element1.style.height = "17px";
    element1.style.background = "#FFFFFF";
    if (localStorage.rSC) {
      let arr = JSON.parse(localStorage.rSC);
      element1.style.background = `#${arr[0].toString(16).padStart(2, "0")}${arr[1].toString(16).padStart(2, "0")}${arr[2].toString(16).padStart(2, "0")}`;
    }
    let element2 = document.createElement("span");
    element2.className = "top-bar";
    element2.style.float = "right";
    element2.textContent = "Right Color:";
    let element3 = document.createElement("span");
    element3.className = "top-bar";
    element3.style.float = "right";
    element3.textContent = "Your ID: null";
    OWOP.elements.topBar.appendChild(element1);
    OWOP.elements.topBar.appendChild(element2);
    OWOP.elements.topBar.appendChild(element3);
    document.getElementById("playercount-display").style.marginRight = "45px";
    setInterval(() => {
      let arr = OWOP.player.rightSelectedColor;
      element1.style.background = rgb(...arr);
      element3.textContent = `Your ID:${OWOP.player.id}`;
    }, 10);

    // teleport detector
    OWOP.playerList = {};
    function tick() {
      let players = OWOP.require("main").playerList;
      let playersFixed = {};
      playersFixed[OWOP.player.id] = {
        id: OWOP.player.id,
        x: OWOP.mouse.tileX,
        y: OWOP.mouse.tileY
      };
      for (let player in players) {
        let n = players[player].childNodes;
        playersFixed[n[0].innerHTML] = {
          id: ~~n[0].innerHTML,
          x: ~~n[1].innerHTML,
          y: ~~n[2].innerHTML
        }
      }
      players = playersFixed;
      // check if the local copy has a disconnected player
      for (let p1 in OWOP.playerList) {
        let test = false;
        for (let p2 in players) {
          if (p1 === p2) {
            test = true;
            break;
          }
        }
        if (!test) {
          delete OWOP.playerList[p1];
          //OWOP.chat.local(`${p1} has left.`);
        }
      }
      // check if the main copy has new players
      for (let p1 in players) {
        let test = false;
        for (let p2 in OWOP.playerList) {
          if (p1 === p2) {
            test = true;
            break;
          }
        }
        if (!test) {
          let p = players[p1];
          OWOP.playerList[p.id] = {
            id: p.id,
            x: p.x,
            y: p.y
          }
          //OWOP.chat.local(`${p1} has joined.`);
        }
      }
      // check for a teleport
      var banlist = [];
      for (let player in players) {
        let p1 = OWOP.playerList[player];
        let p2 = players[player];

        if (Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) > 2000) {
          //console.log("someone teleported", p2.id,  p2.x, p2.y);
          p1.tp = !isNaN(p1.tp) ? p1.tp + 1 : 1;
          //if (!p1.ban && p1.tp < 10) OWOP.chat.local(`${player} Teleported from ${p1.x} ${p1.y} to ${p2.x} ${p2.y}`);
        }
        p1.x = p2.x;
        p1.y = p2.y;
      }
      return players;
    }
    setInterval(tick, 10);
    setInterval(() => {
      for (let player in OWOP.playerList) {
        let p1 = OWOP.playerList[player];
        if (p1.tp >= 10) p1.ban = true;
        p1.tp = 0;
      }
    }, 10 * 1000);


    // it gets in the way of reading chat, im not trying to be mean to arc.
    { let e = document.querySelector("div[id='arc-widget-container']"); e ? e.parentElement.removeChild(e) : void 0; }
    console.log("opm completed");
  }

  {
    setInterval(() => {
      let k = document.getElementById("chat-messages").children;
      for (let i = 0; i < k.length; i++) {
        let t = k[i].innerHTML;
        let id = OWOP.player.id;
        var hasClass = t.classList !== undefined ? Array.from(t.classList).indexOf('nK') > -1 : false;
        if (!t.match(`(\\[${id}\\]: )|(${id}: )`) && t.match(`${id}`) && !hasClass) k[i].style = "background: #FF404059;";
      }
    }, 100);// change this to mutation observer
    OWOP.windowSys.class.window.prototype.move = (function (t, e) { document.getElementById('windows').appendChild(this.frame); document.getElementById('windows').appendChild(OWOP.windowSys.windows.Tools.frame); return this.opt.immobile || (this.frame.style.transform = "translate(" + t + "px," + e + "px)", this.x = t, this.y = e), this });
    Object.keys(OWOP.windowSys.windows).forEach(e => OWOP.windowSys.windows[e].move = (function (t, e) { document.getElementById('windows').appendChild(this.frame); document.getElementById('windows').appendChild(OWOP.windowSys.windows.Tools.frame); return this.opt.immobile || (this.frame.style.transform = "translate(" + t + "px," + e + "px)", this.x = t, this.y = e), this }));

    if (NS.et) {
      clearInterval(OWOP.misc.tickInterval);
      OWOP.misc.tickIntervalNS = setInterval(O, 1e3 / OWOP.options.tickSpeed);
      function O() {
        NS.M14.eventSys.emit(OWOP.events.tick, t);
        if (null !== OWOP.player.tool && null !== OWOP.misc.world) OWOP.player.tool.call("tick");
        if (OWOP.player.tool == OWOP.tool.allTools.write) return;
        var t = ++OWOP.misc.tick;
        var e = Math.max(Math.min(OWOP.options.movementSpeed, 64), 0);
        var n = 0;
        var r = 0;
        (NS.keysdown[38] || (NS.keysdown[87] && !NS.keysdown[16])) && (r -= e);
        (NS.keysdown[37] || (NS.keysdown[65] && !NS.keysdown[16])) && (n -= e);
        (NS.keysdown[40] || (NS.keysdown[83] && !NS.keysdown[16])) && (r += e);
        (NS.keysdown[39] || (NS.keysdown[68] && !NS.keysdown[16])) && (n += e);
        if (0 !== n || 0 !== r) {
          // OWOP.require("canvas_renderer").moveCameraBy(n, r);
          NS.M20.moveCameraBy(n, r);
          A(null, "mousemove", OWOP.mouse.x, OWOP.mouse.y);
        }
      }
      function A(t, e, n, r) {
        OWOP.mouse.x = n;
        OWOP.mouse.y = r;
        let o = 0;
        if (null !== OWOP.misc.world) OWOP.mouse.validTile = OWOP.misc.world.validMousePos(OWOP.mouse.tileX, OWOP.mouse.tileY);
        if (null !== OWOP.player.tool) o = OWOP.player.tool.call(e, [OWOP.mouse, t]);
        if (F(OWOP.mouse.tileX, OWOP.mouse.tileY)) NS.M21.updateClientFx();
        return o;
      }
      function F(t, e) {
        return (OWOP.misc.lastXYDisplay[0] !== t || OWOP.misc.lastXYDisplay[1] !== e) && (OWOP.misc.lastXYDisplay = [t, e],
          OWOP.options.hexCoords && (t = (t < 0 ? "-" : "") + "0x" + Math.abs(t).toString(16),
            e = (e < 0 ? "-" : "") + "0x" + Math.abs(e).toString(16)),
          OWOP.elements.xyDisplay.innerHTML = "X: " + t + ", Y: " + e,
          !0)
      }
    }
    console.log("all completed");
  }

  // window setup

  // palette saver
  !(function () {
    let windowName = "Palette Saver";
    let options = {
      closeable: false
    }

    let paletteJson = {}

    function windowFunc(thisWindow) {
      var divwindow = document.createElement("div");
      divwindow.style = "width: 300px; overflow-y: scroll; overflow-x: scroll; max-height: 165px;"
      divwindow.innerHTML = `<input id="pName" type="text" style="max-width: 100px; border: 0px;" placeholder="Name"></input>
        <button id="addPalette" >Save Current Palette</button> <table id="paletteTable" style="overflow-x: hidden; overflow-y: scroll;"></table>`;
      thisWindow.addObj(divwindow);
    }

    var windowClass = OWOP.windowSys.addWindow(new OWOP.windowSys.class.window(windowName, options, windowFunc))
      .move(window.innerWidth / 3, window.innerHeight / 3);
    windows[windowName] = windowClass;
    NS.windows[windowName].frame.style.visibility = "hidden";

    var pName = document.getElementById("pName");

    pName.oninput = () => {
      if (pName.value.length > 25) pName.style.backgroundColor = "rgb(255 148 129)";
      else pName.style.backgroundColor = "rgb(255, 255, 255)";
    }

    document.getElementById("addPalette").onclick = () => {

      if (pName.value.length > 25) return alert("Your max name length is 25 characters.");
      if (pName.value.length == 0) return alert("Invalid Name");

      if (!localStorage.getItem("paletteJson")) {
        paletteJson[pName.value] = OWOP.player.palette;
        localStorage.setItem("paletteJson", JSON.stringify(paletteJson));
      } else {
        paletteJson = JSON.parse(localStorage.getItem("paletteJson"));
        if (paletteJson[pName.value]) {
          pName.value = "";
          return alert("You already have a palette with this name.");
        }
        paletteJson[pName.value] = OWOP.player.palette;
        localStorage.setItem("paletteJson", JSON.stringify(paletteJson));
      }

      var divPalette = document.createElement("tr");
      let pN = pName.value;
      divPalette.id = `im-busy${pN}`;
      divPalette.innerHTML = `<td id="palette-${pN}" style="cursor: pointer; padding: 5px; border: 1px solid white; border-radius: 5px; color: white;">${pN}</td> <td id="useT1-${pN}"><button id="useB1-${pN}">Use</button></td> <td id="useT2-${pN}"><button id="useB2-${pN}">Replace</button></td> <td id="useT3-${pN}"><button id="useB3-${pN}">Delete</button></td>`;
      document.getElementById("paletteTable").appendChild(divPalette);
      document.getElementById(`useB1-${pN}`).onclick = () => {
        let paletteJson = JSON.parse(localStorage.getItem("paletteJson"));
        OWOP.player.palette.splice(0);
        OWOP.player.palette.push(...paletteJson[pN]);
        OWOP.player.paletteIndex = OWOP.player.paletteIndex;
      }
      document.getElementById(`useB2-${pN}`).onclick = () => {
        if (!confirm(`Are you sure you want to REPLACE the palette ${pN}?`)) return;
        let paletteJson = JSON.parse(localStorage.getItem("paletteJson"));
        paletteJson[`${pN}`] = OWOP.player.palette;
        localStorage.setItem('paletteJson', JSON.stringify(paletteJson));
      }
      document.getElementById(`useB3-${pN}`).onclick = () => {
        if (!confirm(`Are you sure you want to DELETE the palette ${pN}?`)) return;
        let paletteJson = JSON.parse(localStorage.getItem("paletteJson"));
        document.getElementById(`palette-${pN}`).outerHTML = '';
        document.getElementById(`im-busy${pN}`).outerHTML = '';
        delete paletteJson[pN];
        localStorage.setItem('paletteJson', JSON.stringify(paletteJson));
      }

      pName.style.backgroundColor = "rgb(255 255 255)";

      pName.value = "";
    }

    if (localStorage.getItem("paletteJson")) {

      var gettedJson = JSON.parse(localStorage.getItem("paletteJson"));
      var obj = Object.keys(gettedJson);
      for (var i = 0; i < obj.length; i++) {
        let pN = obj[i];
        var divPalette = document.createElement("tr");
        divPalette.id = `im-busy${pN}`;
        divPalette.innerHTML = `<td id="palette-${pN}" style="cursor: pointer; padding: 5px; border: 1px solid white; border-radius: 5px; color: white;">${pN}</td> <td id="useT1-${pN}"><button id="useB1-${pN}">Use</button></td> <td id="useT2-${pN}"><button id="useB2-${pN}">Replace</button></td> <td id="useT3-${pN}"><button id="useB3-${pN}">Delete</button></td>`;
        document.getElementById("paletteTable").appendChild(divPalette);
        document.getElementById(`useB1-${pN}`).onclick = () => {
          let paletteJson = JSON.parse(localStorage.getItem("paletteJson"));
          OWOP.player.palette.splice(0);
          OWOP.player.palette.push(...paletteJson[`${pN}`]);
          OWOP.player.paletteIndex = OWOP.player.paletteIndex;
        }
        document.getElementById(`useB2-${pN}`).onclick = () => {
          if (!confirm(`Are you sure you want to REPLACE the palette ${pN}?`)) return;
          let paletteJson = JSON.parse(localStorage.getItem("paletteJson"));
          paletteJson[`${pN}`] = OWOP.player.palette;
          localStorage.setItem('paletteJson', JSON.stringify(paletteJson));
        }
        document.getElementById(`useB3-${pN}`).onclick = () => {
          if (!confirm(`Are you sure you want to DELETE the palette ${pN}?`)) return;
          let paletteJson = JSON.parse(localStorage.getItem("paletteJson"));
          document.getElementById(`palette-${pN}`).outerHTML = '';
          document.getElementById(`im-busy${pN}`).outerHTML = '';
          delete paletteJson[pN];
          localStorage.setItem('paletteJson', JSON.stringify(paletteJson));
        }
      }
    }
  })();

  // icons
  !(function () {
    let windowName = "Icons";
    let options = {
      closeable: false
    }

    function windowFunc(thisWindow) {
      let content = `
      <style>
        .NSspan1 {
          display: flex;
          flex-direction: column;
          max-height: 200px;
          width: 300px;
        }
        .NSspan2 {
          display: flex;
          justify-content: space-between;
          padding: 4px 0px;
        }
        .NSspan3 {
          display: flex;
          background: #0003;
          border-radius: 10px;
          padding: 5px;
        }
        .NSspan4 {
          display: flex;
          flex-direction: column;
          align-items: center;
          width: 75px;
        }
        .NSspan5 {
          display: flex;
          flex-direction: column;
          justify-content: space-evenly;
        }
        .NSspan6 {
          padding: 5px 0px;
        }
        .NSdiv1 {
          background-image: url("https://ourworldofpixels.com/img/toolset.png");
          width: 36px;
          height: 36px;
        }
        .NSdiv2 {
          background: #aba389;
          color: #7e635c;
          border-radius: 6px;
          border: initial;
          padding: 4px;
          text-shadow: 1px 1px #4d313b;
        }
      </style>
      <span class="NSspan1">
        <span class="NSspan2">
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: 0px 0px;"></div>
              </span>
              <div class="NSdiv2">Cursor</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('cursor')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('cursor')">Paste</button>
            </span>
          </span>
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: -36px 0px;"></div>
              </span>
              <div class="NSdiv2">Move</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('move')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('move')">Paste</button>
            </span>
          </span>
        </span>
        <span class="NSspan2">
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: 0px -36px;"></div>
              </span>
              <div class="NSdiv2">Pipette</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('pipette')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('pipette')">Paste</button>
            </span>
          </span>
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: -36px -72px;"></div>
              </span>
              <div class="NSdiv2">Zoom</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('zoom')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('zoom')">Paste</button>
            </span>
          </span>
        </span>
        <span class="NSspan2">
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: -72px 0px;"></div>
              </span>
              <div class="NSdiv2">Select</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('export')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('export')">Paste</button>
            </span>
          </span>
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: -36px -36px;"></div>
              </span>
              <div class="NSdiv2">Bucket</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('fill')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('fill')">Paste</button>
            </span>
          </span>
        </span>
        <span class="NSspan2">
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: -108px -108px;"></div>
              </span>
              <div class="NSdiv2">Wand</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('line')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('line')">Paste</button>
            </span>
          </span>
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: -108px -36px;"></div>
              </span>
              <div class="NSdiv2">Paste</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('paste')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('paste')">Paste</button>
            </span>
          </span>
        </span>
        <span class="NSspan2">
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: -108px 0px;"></div>
              </span>
              <div class="NSdiv2">Copy</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('copy')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('copy')">Paste</button>
            </span>
          </span>
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: -36px -108px;"></div>
              </span>
              <div class="NSdiv2">Text</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('write')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('write')">Paste</button>
            </span>
          </span>
        </span>
        <!--
        <span class="NSspan2">
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: -108px 0px;"></div>
              </span>
              <div class="NSdiv2">Copy</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('copy')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('copy')">Paste</button>
            </span>
          </span>
          <span class="NSspan3">
            <span class="NSspan4">
              <span class="NSspan6">
                <div class="NSdiv1" style="background-position: -36px -108px;"></div>
              </span>
              <div class="NSdiv2">Text</div>
            </span>
            <span class="NSspan5">
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconSelect('write')">Select</button>
              <button class="optionButton" style="max-height: 25px;" onclick="NS.iconPaste('write')">Paste</button>
            </span>
          </span>
        </span>
        -->
      </span>
        `;
      thisWindow.container.innerHTML = content;

      NS.iconSelect = function (iconsName) {
        OWOP.tool.allTools.copy.extra.tempCallback = function (data) {
          if (data.length !== 36 || data[0].length !== 36) {
            OWOP.chat.local("The size needs to be (36 by 36).");
            return false;
          }
          NS.localStorage.cursors[iconsName].icon = NS.setImageData(data).toDataURL();
          localStorage.NS = JSON.stringify(NS.localStorage);
          return true;
        }
        OWOP.player.tool = "copy";
      }
      NS.iconPaste = function (iconsName) {
        let offset = [0, 0];
        let icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAA00lEQVRYw+3YMQ6DMAwFUPPFhTpCRxaW3qYzQ9f2Nl1YGIGRI8ENShz7W6mEZxwe34kUIXKVoR63+x79TpSGQmlJobTxJYHe8xiGSk4oCgXNwxEoaBvYKOQ0MVHIbew/AwWVDeqaloKCdQFvlAnUNa07Cl5Re6HguSE9UPA+tlaUO8iKMoGmdXFHURKyoJCTxLQuP9OxoGrLeMbnS31H/25zRRmZFpN686ytmLMvpp8yJkYNYmNUoAiMiEil3YBMjHpkbIwKFIFJBkVhivz7cdXf1QFBsW2mhPMCDAAAAABJRU5ErkJggg==";
        switch (iconsName) {
          case "cursor": {
            icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAA00lEQVRYw+3YMQ6DMAwFUPPFhTpCRxaW3qYzQ9f2Nl1YGIGRI8ENShz7W6mEZxwe34kUIXKVoR63+x79TpSGQmlJobTxJYHe8xiGSk4oCgXNwxEoaBvYKOQ0MVHIbew/AwWVDeqaloKCdQFvlAnUNa07Cl5Re6HguSE9UPA+tlaUO8iKMoGmdXFHURKyoJCTxLQuP9OxoGrLeMbnS31H/25zRRmZFpN686ytmLMvpp8yJkYNYmNUoAiMiEil3YBMjHpkbIwKFIFJBkVhivz7cdXf1QFBsW2mhPMCDAAAAABJRU5ErkJggg==";
          } break;
          case "move": {
            icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABgklEQVRYw+2YvU7EMAzH/055CYZj6IJ0243Q8ZYs9zbcWgErfZtbTkgdGNqOtyM2Ft7imHKK0g/nw4Ui4S1K7Pzi2q4TQEB2m+K82xRnCVskAWOPD6eGfg3IwLw0RwDAvtDJUCQFYyQViiRhJKBIGiYVilICmPNQTKCrEKCYTxCqo+bcIOYACguTvwMk9SsIta2mFDiofaFRd+1lXHdtL8NCbVOKZ3zTPiTwVUzRAwBdlVFz7mFcB1BMBfYB9l3rVvTMhtFViXy1mjSQrXN2TcjabJ3j4/UNt9c3j+9fn09kwwDA9u7+R9PcJMXx4RkAcDW2YG4wOzt7MeR6iRNf2LFNXTHeOZwaUnZAmQkf5ZCNfGF6dcj2FGdMV+Wop+qu9dJ3YRZZGIn71wy1EPa87SnXM5zu0Lyaoub6GZMEdddeAphLDM62kmyuJBq3/wZNHCgkC2OaPDUXTKxO9EWRy6ahojf7VXoMKhZG5LHBhUqBEXuOMVCpMIt8sBK7Z0nd474B40/4pg5xCDEAAAAASUVORK5CYII=";
          } break;
          case "pipette": {
            icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABAUlEQVRYw+3XvQ6CMBQF4FPiCzHQRDu6uPA2zg6u+jYuLI5ogoOPhFOTpnL7Y3ovDj0jIeRLzyEEoKaGTt+auW/NHLpHSUH8a7f3qFYBuZhOawDAa5pIlJLAWIgbCrXhrmYJI7KhHEyosqYUyH14p3X2ybBtKLSb2OkUB/m1+agYhmVDh+sJADAcz8mvevENWcxlHLDf7uDCLCQ1TUkMANyfD3I37KAQxlYWq6jYhnIxfWvmFJz6l5P5GcSJyQZxY7JAEphkkBQmCSSJiYKkMUHQGhgS5H8o3XBisj8d3JhFkF+VJOarMhdjN2MhEhiysrUw5Aml/GGKgHyUNKampkQ+wWnd9v+Wv9QAAAAASUVORK5CYII=";
          } break;
          case "zoom": {
            icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABMUlEQVRYw+3Wuw6CMBQG4L+NL+SIjgyy+CSuujo4u/ISri4wdFQTHFx9CScS1zrVNFCgdzThJCRcGvrlHHooMMWfBfH5svV8yVX3z48LiQrqgtjAiE9M14Q6Y7yVSEyWs5I/33XryFnJVeODYVQQGSFHzkoeBCW/VAdigqIuMBnAblds0hWJ3idU2dHJjG6WqCvQd2acQb7LRPFjQV3LZRNpsggDsimX6QIwXmmm8XzX4fpQiOxQm+zI57olyFnJ02SB3TLr/ckSG0x23AMAiu0B8rXqmxIQAIMYI1AT8zoVAIB7VX3HiGfN1SQgOtsP4oJpgqJs0HQwYqLgW1gTTPBN/hiYTtBYGCVoTMxgY4yNaWVIzs4YGACY9WVG7jExMJ2gZrOLhen9qGNDppjCV3wAmp0jyCb9nC4AAAAASUVORK5CYII=";
          } break;
          case "export": {
            icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABCklEQVRYw+2YMQ6CQBBFP4QL2Ql02mDhadQWg+3iaSzcRrsVO4+k1RCNhB3RHdZkfzUJBW//Z4YBIOjPFAHAcpLfpW54uJmoF0gShgOVULHYbwEAs2kKADhfm5/Xm7ywR0YOKaNxvjZOnTmuKqtDsVRM5JJNLZBrd7iKPz3BUHEPHCILkXkdGc0dryIjKG8iU0bjuKr86jJlNOs9Jtplymi/ugwAirqEbf8SHYzzNLNCiUXGdSruGmAE9209BMr5ClvUJeZpBgA4NZe363q9e1nanC/55ESfnqEiVyB0yC4gAhi97W0wh5uJkjFgvFjyOTBiQFyYtstcPtTcT2hRh7gwzjXGf4MgcT0AtrSSgUtEfWEAAAAASUVORK5CYII=";
          } break;
          case "fill": {
            icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABjUlEQVRYw+2YsW7DIBCGAfWFMhgpZezCkreJVw9Z7bfpwpLRiYSHPlI6XYSviLszWG2lnJQhGJNP//3HQZR6xT8LzZ14OrgHHvv8mvWvAOVg9oLTXBg/DavxcL7sAqU5MOMcVO/86lln7er7EmMTKC2FwSAYqBbKtICBgLRyPMcG2grTCspwJlEwS4wr09dAaUodjmfgnev9lq1EiaeMRJklxufHT8NTCT8N6nq/qY/je3X63jiTQI3cXpSOAVSqlJ8GFc4XdTq4B0cpQ4FgNVKYcQ4/3qlVylAVg0EwDP7xWqgsUO+8GueQbQ+QpjQtLaGMpCRznmkNpUtdHW+OufRhGFz68Jy7JZhSt4bUlWC2KiVOGRg69VPOU62hWB7qnVedtaqzdncokamXGKuhqJZipDCcKEHlTF+lEO7sUiiq4YqAqGqjoDjdnw0Ei5T8U4KC/YxqsCKFtkJxYUignMRSKOkhTewhCVTzE+NWqHRXl16JNHV1phYsHSW23M+KF0Xughhqjz8hXvFn4xvHckf64m/UhgAAAABJRU5ErkJggg==";
          } break;
          case "line": {
            icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABEUlEQVRYw+2XvRGEIBCFkbEDKzGQxAZITK3E2OBiKzG9hAYuweAqsQYvWofjVOB4GPESHRdmP/cHV8aysrLSqrgydnW70f3z/Sp8bUmATIc+QkFx1wI5jUxO48/zRgjWCAFPWem78AhqnRUciLtSoIbHoX2dFVu0vg/IpaqXXykLrTk4EEHJadzBEFBlzGY7bYhO4662p7e3C1gNDziMd8ooEgRlwlH33VZDi9Z7JOxrIwRbZwWFcrb91dlDYEiooLzb3y+7zqpe7ufWvzUVvKmr2810hoaCdAYSCjY2oKBgQCgoKBACCg4UC5UEKAYqGdARlHmYnkHxlEDkNGSQSxqhs+EN+ZcSBYWaCLKysmL0AfZSsN1jloZVAAAAAElFTkSuQmCC";
          } break;
          case "paste": {
            icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABQ0lEQVRYw+2Yvw6CMBDGr4QXYpBA2Jx04GmMs3E2Po2DTmwEggOPhFOTUtvSa6/FGC4xacuf/vjuu1IE2CJg1Fk11Vk1Ud6T+cCI/cfYslWARJDj/QIAAM/TlQyM+cBwELHtC8VsU2JSxTSOhUwwMCoAuY19QDlS08Fdns/672HQQmGucwZSTaRLiQ4GGyn2At8JnTy0ZqAUwnghWspu7Qt1/rk6/F7KeDXalH40D9lCpTFg5KWizqpJt3J7ATV9BwAA+6L86vP2rX3Nxpc85QXEQVR9XZu8ykQlVOrICpkegAxIdWOTWkHXIZ1XVIAqJYMrZJrIRaXEVSH+k/0T/dVB6Zf/e9uryl5n4KbvZuPRTL2URmw6yRRa3UP7oiQFsVLItEOkLPVFoMfYMtO+JYQy6E9p6n85dPsh52/7EDBb2MQHK+ObZIdb/JMAAAAASUVORK5CYII=";
          } break;
          case "copy": {
            icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABS0lEQVRYw+2XMQ6CMBSGfwxH8CIOEg2bkx2cOYhxJs6Ggzg74ORGNGXwIt5BpxrAAn3PVzWGPzEpaMLH199CgSFD/iwB5cerSXznXORwLQJxIC4MFYoMpLKUBJKvtySo0JeRaqZRhFJrrCbxvQ8qkIZpGszXW0yjCABQat1rqhNoVxxJMJt4+QJVnTKXToXSf1uVpTUrKktx2+cAgHGiWgFNRj7WEpWlKLXGOFFPEDNml5qbqoGqKQMmBnS6nAEAi9n85diMd8Wxdn4TL53NkIEMiO24bQyg1h8RIHPHNjtNQzZgcUPNO+6z9W5CSndsJmzfvQNIMtR1ISlLIxdD5tPsj0tu+/xZbC8dopqgltrLSv2RUncV+HQ5185/rNR90yhRbJahr05Z2wrNediKAXFAuC/8rA753AaFLq+kP7FRlNp1UOwMGfKXeQBOJJ69p18KUgAAAABJRU5ErkJggg==";
          } break;
          case "write": {
            icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAABTUlEQVRYw+2XPW6DQBCFHyNu4JOktF26oUnrk7hO4TY5CW2abbY0SEvhk3AGp8miMcGEmVmMLPEa/0iGx3xvZsfAqlU2ZdYLvL/tb/zz9/WSLWaobyaFqcxq5vPiAAC+rgAA7nQ2maIU3KOZFKKUZtrSLWOoj8udzh2q4utjNF+zVsjXVWcEAJoQlkcWA2xtd7UhjmsoM23pTNjIgmsIkRWb2tBYR1m6jbS4YiV4duL7JgQ1NrLOntQiC66hzorfabGRBdeYtNhyCy7tNJ59MD59QeO4fF3hsN3dVeyw3d299iVZSXINrv5NfV2hLR38b5g3x6IL9eZYiCokzhA/TP8ohp2FvhAaIgkuTStLz7ZcgmtoOv/3ME0IoiqRZomf44+BqO05Lsnew6d2xJZkDj1aNaZK8luaGkyrpl6DJE+oWVP5SvKSR8eqVVb9AMXs1T0X1uPeAAAAAElFTkSuQmCC";
          } break;
        }
        let img = document.createElement("img");
        img.src = icon;
        img.onload = function () {
          ((onblob) => {
            let c1 = document.createElement('canvas');
            let ctx1 = c1.getContext('2d');
            ctx1.drawImage(img, 0, 0);
            let iconImageData = ctx1.getImageData(0, 0, 36, 36);
            let c2 = document.createElement('canvas');
            c2.width = 38;
            c2.height = 38;
            let ctx2 = c2.getContext('2d');
            ctx2.fillStyle = "#000000";
            ctx2.fillRect(0, 0, c2.width, c2.height);
            for (let y = 0; y < 36; y++) {
              for (let x = 0; x < 36; x++) {
                let pix = iconImageData.data[4 * (y * 36 + x) + 3];
                if (pix < 255) {
                  iconImageData.data[4 * (y * 36 + x)] = 255;
                  iconImageData.data[4 * (y * 36 + x) + 1] = 255;
                  iconImageData.data[4 * (y * 36 + x) + 2] = 255;
                  iconImageData.data[4 * (y * 36 + x) + 3] = 255;
                }
              }
            }
            ctx2.putImageData(iconImageData, 1, 1);
            c2.toBlob(onblob);
          })(b => {
            let url = URL.createObjectURL(b);
            let image = new Image();
            image.onload = () => {
              OWOP.tool.allTools.paste.extra.k = image;
              OWOP.player.tool = "move";
              OWOP.player.tool = "paste";
            }
            image.src = url;
          });
        }
      }
    }

    var windowClass = OWOP.windowSys.addWindow(new OWOP.windowSys.class.window(windowName, options, windowFunc)
      .move(OWOP.windowSys.windows['Tools'].realw + 15, 32));
    windows[windowName] = windowClass;
    windowClass.frame.style.visibility = "hidden";
  })()

  // assets
  !(function () {
    let windowName = "Assets";
    let options = {
      closeable: false
    }

    var mkHTML = OWOP.util.mkHTML;
    let G = r => document.getElementById(r);

    function windowFunc(thisWindow) {
      thisWindow.frame.style.width = "500px";
      let innerFrame = document.createElement("div");
      let realAssetsCont = mkHTML("div", {
        id: "real-assets-cont"
      });
      let p = mkHTML("p");
      p.style["margin-block"] = "auto";
      p.style["display"] = "flex";
      p.style["justify-content"] = "space-evenly";

      let button1 = mkHTML("button", {
        id: "NSoptions",
        innerHTML: "Add"
      });

      let button2 = mkHTML("button", {
        id: "NSoptions",
        innerHTML: "Paste"
      });

      let button3 = mkHTML("button", {
        id: "NSoptions",
        innerHTML: "Delete"
      });

      let button4 = mkHTML("button", {
        id: "NSoptions",
        innerHTML: "Reload"
      });

      button1.onclick = async () => {
        OWOP.sounds.play(OWOP.sounds.click);
        let pE = localStorage.MB_Assets;
        if (!pE) pE = [];
        else pE = JSON.parse(pE);
        var _imgTotal = 0;
        {
          var _lsTotal = 0,
            _xLen, _x;
          for (_x in localStorage) {
            if (!localStorage.hasOwnProperty(_x)) {
              continue;
            }
            _xLen = ((localStorage[_x].length + _x.length) * 2);
            _lsTotal += _xLen;
          };
          //console.log("Total = " + (_lsTotal / 1024).toFixed(2) + " KB");
          if ((_lsTotal / 1024) > 3000) return OWOP.chat.local(`Storage limit reached (3MB), remove images to add more.`);
          _imgTotal = _lsTotal;
        }
        {
          var _imageData = await X('image/*');
          var _lsTotal = 0, _x;
          _x = JSON.stringify(_imageData);
          _lsTotal = _x.length * 2;
          //console.log("Total = " + (_lsTotal / 1024).toFixed(2) + " KB");
          if ((_lsTotal / 1024) > 500) {
            if (((_lsTotal + _imgTotal) / 1024) > 3000) return OWOP.chat.local(`Image being added is more than Storage limit (3KB)`);
            if (!confirm(`Are you sure you want to add a image with ${(_lsTotal / 1024).toFixed(2)} KB`)) return;
          }
        }
        pE.push(_imageData);
        localStorage.MB_Assets = JSON.stringify(pE);
        J();
      };

      button2.onclick = () => {
        OWOP.sounds.play(OWOP.sounds.click);
        var img = new Image();
        img.onload = () => {
          OWOP.tool.allTools.paste.extra.k = img;
          OWOP.player.tool = "move";
          OWOP.player.tool = "paste";
        }
        img.src = NS.selectedAsset;
      };

      button3.onclick = () => {
        OWOP.sounds.play(OWOP.sounds.click);
        if (!NS.selectedAssetIndex) return;
        if (confirm("Do you want to delete the selected asset?")) NS.assets.splice(NS.selectedAssetIndex, 1);
        else return;
        localStorage.MB_Assets = JSON.stringify(NS.assets);
        J();
      };

      button4.onclick = () => {
        OWOP.sounds.play(OWOP.sounds.click);
        J();
      };

      let J = () => {
        NS.assets = localStorage.MB_Assets;
        if (!NS.assets) NS.assets = [];
        else NS.assets = JSON.parse(NS.assets);
        let y = G("real-assets-cont");
        y.innerHTML = '';
        for (let p0 in NS.assets) {
          let p1 = new Image();

          p1.onload = () => {
            p1.style.width = '48px';
            p1.style.height = '48px';
            p1.style.border = 'solid 1px';

            p1.onclick = () => {
              for (let p4 in G('real-assets-cont').children) {
                if (typeof G('real-assets-cont').children[p4] !== 'object') break;
                G('real-assets-cont').children[p4].style.border = 'solid 1px';
              }
              if (NS.selectedImg) {
                NS.selectedImg.style.width = '48px';
                NS.selectedImg.style.height = '48px';
              }
              NS.selectedAsset = NS.assets[p0];
              NS.selectedAssetIndex = p0;
              NS.selectedImg = p1;
              p1.style.width = '40px';
              p1.style.height = '40px';
              p1.style.border = 'solid 5px black';
            };

            p1.oncontextmenu = p3 => {
              p3.preventDefault();
              NS.assets.splice(p0, 1);
              localStorage.MB_Assets = JSON.stringify(NS.assets);
              J();
            };

            y.append(p1);
          };

          p1.src = NS.assets[p0];
        }
      };

      let X = (r = '*') => new Promise(Q => {
        let c = document.createElement('input');
        c.type = 'file';
        c.accept = r;

        c.onchange = () => {
          let N = new FileReader();

          N.onloadend = () => {
            Q(N.result);
          };

          N.readAsDataURL(c.files[0]);
        };

        c.onclick = () => void 0;

        c.click();
      });

      button2.addEventListener("click", function () {
        OWOP.sounds.play(OWOP.sounds.click)
      });
      p.appendChild(button1);
      p.appendChild(button2);
      p.appendChild(button3);
      p.appendChild(button4);
      innerFrame.appendChild(p);
      innerFrame.appendChild(realAssetsCont);
      thisWindow.addObj(innerFrame);
    }

    var windowClass = OWOP.windowSys.addWindow(new OWOP.windowSys.class.window(windowName, options, windowFunc)
      .move(window.innerWidth / 3, window.innerHeight / 3));
    windows[windowName] = windowClass;
    windowClass.frame.style.visibility = "hidden";
  })();

  // patterns
  !(function () {
    return 1;
    let windowName = "Patterns";
    let options = {
      closeable: false
    }

    function windowFunc(thisWindow) {
      let content = `
          <span style="display: flex;">
            <style>
              #div1, #div2, #div3 {
                float: left;
                width: 75px;
                height: 75px;
                margin: 10px;
                border: 1px solid black;
              }
            </style>
            <div style="display: flex;margin: 0px;border-radius: 5px;background-color: #7e635c;box-shadow: inset 3px 2px 0px 0px #4d313b;align-content: space-around;">
              <span style="border: 10px #0000 solid;display: flex;flex-direction: column;">
                <span style="display: flex;">
                  <div id="div1" ondrop="NS.drop(event)" ondragover="NS.allowDrop(event)">
                    <img src="https://opm.dimden.dev/client/img/gui.png" draggable="true" ondragstart="NS.drag(event)" id="drag1" width="75" height="75">
                  </div>
                  <div style="display: flex;flex-direction: column;justify-content: center;">
                    <button class="tablinks1">
                      Filter Type:\nOR
                    </button>
                    <button class="tablinks1">
                      Flip
                    </button>
                  </div>
                </span>
                <span style="display: flex;">
                  <div id="div2" ondrop="NS.drop(event)" ondragover="NS.allowDrop(event)">
                    <img src="https://opm.dimden.dev/client/img/gui.png" draggable="true" ondragstart="NS.drag(event)" id="drag2" width="75" height="75">
                  </div>
                  <div style="display: flex;flex-direction: column;justify-content: center;">
                    <button class="tablinks1">
                      Filter Type:\nOR
                    </button>
                    <button class="tablinks1">
                      Flip
                    </button>
                  </div>
                </span>
                <span style="display: flex;">
                  <div id="div3" ondrop="NS.drop(event)" ondragover="NS.allowDrop(event)"></div>
                  <div style="display: flex;flex-direction: column;justify-content: center;">
                    <button class="tablinks1">
                      Filter Type:\nOR
                    </button>
                    <button class="tablinks1">
                      Flip
                    </button>
                  </div>
                </span>
              </span>
            </div>
            <div style="width: 5px;"></div>
            <div style="display: flex;margin: 0px;border-radius: 5px;background-color: #7e635c;box-shadow: inset 3px 2px 0px 0px #4d313b;align-content: space-around;">
              <span style="border: 10px #0000 solid;">
                <div>something 2
                </div>
              </span>
            </div>
          </span>
        `;
      let container = thisWindow.container;
      container.style = "margin: 0px -5px -5px -5px;";
      container.className = "optionsDiv";
      container.innerHTML = content;
      function allowDrop(ev) {
        ev.preventDefault();
      }

      function drag(ev) {
        ev.dataTransfer.setData("text", ev.target.id);
      }

      function drop(ev) {
        ev.preventDefault();
        var data = ev.dataTransfer.getData("text");
        ev.target.appendChild(document.getElementById(data));
      }
      NS.allowDrop = allowDrop;
      NS.drag = drag;
      NS.drop = drop;
      return;
      const root = [...container.childNodes].find(e => e.nodeType !== Node.TEXT_NODE);
      let mkHTML = OWOP.util.mkHTML;
      let display = root.querySelector("#display");
      for (let w in OWOP.windowSys.windows) {
        w = OWOP.windowSys.windows[w];
        if (w.title === "Options" || w.title === "Resulting image") continue;
        if (w.title !== "Tools") {
          w.move(window.innerWidth / 3, window.innerHeight / 3);
          w.frame.style.visibility = "hidden";
          let b = w.frame.querySelectorAll(".windowCloseButton");
          if (b.length) w.frame.removeChild(b[0]);
        }
        let v = w.frame.style;
        let current = mkHTML("p");
        current.className = "tabp";
        current.appendChild(mkHTML("p", { innerHTML: w.title }));
        let button = mkHTML("button");
        current.appendChild(button);
        let isVisible = v.visibility === "visible" || v.visibility === "" ? true : false;
        button.id = button.innerHTML = isVisible ? "on" : "off";
        button.onclick = function () {
          isVisible = !isVisible;
          button.id = button.innerHTML = isVisible ? "on" : "off";
          v.visibility = isVisible ? "visible" : "hidden";
        }
        display.appendChild(current);
      }
      let options = root.querySelector("#options");

      if (window) window.openCity = function (evt, cityName, s, button) {
        var i, tabcontent, tablinks;
        tabcontent = document.getElementsByClassName("tabcontent" + s);
        for (i = 0; i < tabcontent.length; i++) {
          tabcontent[i].style.display = "none";
        }
        tablinks = document.getElementsByClassName(button.className);
        for (i = 0; i < tablinks.length; i++) {
          tablinks[i].id = "";
        }
        button.id = "on";
        document.getElementById(cityName).style.display = "block";
      }
      NS.optionbutton = function (button) {
        let parent1 = button.parentElement;
        let parent2 = parent1.parentElement;
        let s = button.id === "on" ? false : true;
        button.innerHTML = button.id = s ? "on" : "off";
        if (parent2.className === "tabcontentright") OWOP.tool.allTools[`${parent2.id}`].extra.state[`${parent1.id}`] = s;
      }
    }

    var windowClass = OWOP.windowSys.addWindow(new OWOP.windowSys.class.window(windowName, options, windowFunc)
      .move(window.innerWidth / 3, window.innerHeight / 3));
    windows[windowName] = windowClass;
    windowClass.frame.style.visibility = "hidden";
  })();

  function makeOptionsWindow() {
    if (OWOP.windowSys.windows['Options']) OWOP.windowSys.delWindow(OWOP.windowSys.windows['Options']);
    let windowName = "Options";
    let options = {
      closeable: false
    }

    function windowFunc(thisWindow) {
      let content = `
          <span>
            <span id="optionsMinimize" style="display: flex;">
              <style>
                p {
                  margin-block: auto;
                  color: white;
                  font-family: Arial;
                }
                .tabcontentleft, .tabcontentright {
                  display: none;
                  margin-block: auto;
                  text-align: center;
                }
                .tabp {
                  display: flex;
                  justify-content: space-between;
                  margin: 1px 0px;
                }
                .tabButton, .optionButton {
                  border-style: none !important;
                  border-image: none !important;
                  border: initial;
                  border-radius: 6px;
                  padding: 5px 8px;
                }
                .optionButton {
                  padding: 5px 12px;
                }
                .tabButton {
                  margin: 0px 1px;
                }
                button.on {
                  background: #9a937b;
                }
              </style>
              <div style="display: flex;margin: 0px;border-radius: 5px;background-color: #7e635c;box-shadow: inset 3px 2px 0px 0px #4d313b;align-content: space-around;">
                <span style="border: 10px #0000 solid;">
                  <div class="tab">
                    <div style="align-content: center;margin: 0px 0px 5px 0px;display: flex;justify-content: space-between;">
                      <button class="tabButton olt on" onclick="NS.switchTabs(event, 'display', 'olt', this)">
                        Window Display
                      </button>
                      <button class="tabButton olt" onclick="NS.switchTabs(event, 'options', 'olt', this)">
                        Options
                      </button>
                    </div>
                    <div id="display" class="tabcontentolt" style="display: block;"></div>
                    <div id="options" class="tabcontentolt" style="display: none;"></div>
                  </div>
                </span>
              </div>
              <div style="width: 5px;"></div>
              <div style="display: flex;margin: 0px;border-radius: 5px;background-color: #7e635c;box-shadow: inset 3px 2px 0px 0px #4d313b;align-content: space-around;">
                <span style="border: 10px #0000 solid;">
                  <div class="tab">
                    <span>
                      <div style="align-content: center;margin: 0px 0px 5px 0px;display: flex;justify-content: space-between;">
                        <button class="tabButton ort on" onclick="NS.switchTabs(event, 'cursor', 'ort', this)">
                          Cursor
                        </button>
                        <button class="tabButton ort" onclick="NS.switchTabs(event, 'line', 'ort', this)">
                          Line
                        </button>
                        <button class="tabButton ort" onclick="NS.switchTabs(event, 'fill', 'ort', this)">
                          Bucket
                        </button>
                        <button class="tabButton ort" onclick="NS.switchTabs(event, 'export', 'ort', this)">
                          Select
                        </button>
                      </div>
                      <div style="align-content: center;margin: 0px 0px 5px 0px;display: flex;justify-content: space-between;">
                        <button class="tabButton ort" onclick="NS.switchTabs(event, 'copy', 'ort', this)">
                          Copy
                        </button>
                        <button class="tabButton ort" onclick="NS.switchTabs(event, 'paste', 'ort', this)">
                          Paste
                        </button>
                        <button class="tabButton ort" onclick="NS.switchTabs(event, 'write', 'ort', this)">
                          Write
                        </button>
                        <button class="tabButton ort" onclick="NS.switchTabs(event, 'all', 'ort', this)">
                          All
                        </button>
                      </div>
                    </span>
                    <div id="cursor" class="tabcontentort" style="display: block;">
                      <div class="tabp">
                        <p>Scale</p>
                        <input type="range" style="margin: 0px 15px;" value="1" min="1" max="16" oninput="OWOP.tool.allTools.cursor.extra.state.scalar = this.value;document.getElementById('cursorspan').innerHTML = this.value;"></input>
                        <span style="padding: 4px 0px 5px 0px;" id="cursorspan">1</span>
                      </div>
                      <div class="tabp">
                        <p>Chunkize</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'chunkize')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Rainbow</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'rainbow')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Pixel Perfect</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'perfect')">off</button>
                      </div>
                    </div>
                    <div id="line" class="tabcontentort" style="display: none;">
                      <div class="tabp">
                        <p>Rainbow</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'rainbow')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Gradient</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'gradient')">off</button>
                      </div>
                    </div>
                    <div id="fill" class="tabcontentort" style="display: none;">
                      <div class="tabp">
                        <p>Rainbow</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'rainbow')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Checkered</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'checkered')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Pattern 1</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'dither')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Pattern 2</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'dither2')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Pattern 3</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'dither3')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Pattern 4</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'dither4')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Pattern 5</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'dither5')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Pattern 6</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'dither6')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Pattern 7</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'dither7')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Pattern 8</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'dither8')">off</button>
                      </div>
                    </div>
                    <div id="export" class="tabcontentort" style="display: none;">
                      <div class="tabp">
                        <p>Type</p>
                        <select style="padding: 3px 0px;background: #aba389;" class="exportselect" oninput="OWOP.tool.allTools.export.extra.state.type=this.value">
                          <option value="export">Export</option>
                          <option value="color">Palette Color Adder</option>
                          <option value="adder">Queue Adder</option>
                          <option value="filler">Queue Filler</option>
                          <option value="clearer">Queue Clearer</option>
                        </select>
                      </div>
                      <div class="tabp">
                        <p>Chunkize</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'chunkize')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Rainbow</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'rainbow')">off</button>
                      </div>
                    </div>
                    <div id="copy" class="tabcontentort" style="display: none;">
                    <!--
                      <div class="tabp">
                        <p>Shrink Margins</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'margin')">off</button>
                      </div>
                    -->
                    </div>
                    <div id="paste" class="tabcontentort" style="display: none;">
                      <div class="tabp">
                        <p>Chunkize</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'chunkize')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Rotate Clockwise</p>
                        <button class="optionButton click" onclick="NS.optionbutton(this, 'rc')">&nbsp;&nbsp;&nbsp;&nbsp;</button>
                      </div>
                      <div class="tabp">
                        <p>Rotate the other way</p>
                        <button class="optionButton click" onclick="NS.optionbutton(this, 'rcc')">&nbsp;&nbsp;&nbsp;&nbsp;</button>
                      </div>
                      <div class="tabp">
                        <p>Flip Horizontally</p>
                        <button class="optionButton click" onclick="NS.optionbutton(this, 'fh')">&nbsp;&nbsp;&nbsp;&nbsp;</button>
                      </div>
                      <div class="tabp">
                        <p>Flip Vertically</p>
                        <button class="optionButton click" onclick="NS.optionbutton(this, 'fv')">&nbsp;&nbsp;&nbsp;&nbsp;</button>
                      </div>
                    </div>
                    <div id="write" class="tabcontentort" style="display: none;">
                      <div class="tabp">
                        <p>Rainbow</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'rainbow')">off</button>
                      </div>
                      <!--
                      <div class="tabp">
                        <p>Font Size</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'gradient')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Font Type</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'gradient')">off</button>
                      </div>
                      -->
                    </div>
                    <!--
                    <div id="all" class="tabcontentort" style="display: none;">
                      <div class="tabp">
                        <p>Rainbow</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'rainbow')">off</button>
                      </div>
                      <div class="tabp">
                        <p>Some color stuff</p>
                        <button class="optionButton switch" onclick="NS.optionbutton(this, 'gradient')">off</button>
                      </div>
                    </div>
                    -->
                  </div>
                </span>
              </div>
            </span>
            <span id="optionsMaximize" style="display: none;">
              <div style="display: flex;margin: 0px;border-radius: 5px;background-color: #7e635c;box-shadow: inset 3px 2px 0px 0px #4d313b;align-content: space-around;">
                <span style="border: 10px #0000 solid;">
                  <button class="optionButton" onclick="NS.minimizeOptions(false)">Maximize</button>
                </span>
              </div>
            </span>
          </span>
        `;
      let container = thisWindow.container;
      container.style = "margin: 0px -5px -5px -5px;";
      container.className = "optionsDiv";
      container.innerHTML = content;
      const root = [...container.childNodes].find(e => e.nodeType !== Node.TEXT_NODE);
      let mkHTML = OWOP.util.mkHTML;
      let display = root.querySelector("#display");
      for (let w in OWOP.windowSys.windows) {
        w = OWOP.windowSys.windows[w];
        if (w.title === "Options" || w.title === "Resulting image") continue;
        if (w.title !== "Tools") {
          w.move(window.innerWidth / 3, window.innerHeight / 3);
          w.frame.style.visibility = "hidden";
          let b = w.frame.querySelectorAll(".windowCloseButton");
          if (b.length) w.frame.removeChild(b[0]);
        }
        let v = w.frame.style;
        let current = mkHTML("p");
        current.className = "tabp";
        current.appendChild(mkHTML("p", { innerHTML: w.title }));
        let button = mkHTML("button");
        button.classList.add("optionButton");
        current.appendChild(button);
        v.visibility === "visible" || v.visibility === "" ? button.classList.toggle("on") : void 0;
        button.innerHTML = button.classList.contains("on") ? "on" : "off";
        button.onclick = function () {
          button.classList.toggle("on");
          let s = button.classList.contains("on");
          button.innerHTML = s ? "on" : "off";
          v.visibility = s ? "visible" : "hidden";
        }
        display.appendChild(current);
      }
      let options = root.querySelector("#options");

      function optionmaker(name, inputType, checked, onclick) {
        let current = mkHTML("div");
        current.className = "tabp";
        current.appendChild(mkHTML("p", { innerHTML: name }));
        let button = mkHTML("button");
        button.classList.add("optionButton");
        current.appendChild(button);
        if (inputType === "button") button.innerHTML = checked ? "on" : "off";
        else if (inputType === "select") button.innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;";
        button.onclick = function () {
          if (inputType === "button") {
            button.classList.toggle("on");
            button.innerHTML = button.classList.contains("on") ? "on" : "off";
          }
          onclick();
        }
        if (checked) button.classList.toggle("on");
        return current;
      }

      options.appendChild(optionmaker("Disable PM", "button", !NS.PM.enabled, () => NS.PM.enabled = !NS.PM.enabled));
      options.appendChild(optionmaker("Clear PM", "select", void 0, () => NS.PM.clearQueue()));
      options.appendChild(optionmaker("Render PM", "button", NS.PM.renderBorder, () => NS.PM.renderBorder = !NS.PM.renderBorder));
      options.appendChild(optionmaker("Render Rings", "button", NS.PM.renderPlayerRings, () => NS.PM.renderPlayerRings = !NS.PM.renderPlayerRings));
      options.appendChild(optionmaker("AutoFix", "button", NS.PM.autoMove, () => NS.PM.autoMove = !NS.PM.autoMove));
      options.appendChild(optionmaker("Mute", "button", !OWOP.options.enableSounds, () => { OWOP.options.enableSounds = !OWOP.options.enableSounds; localStorage.setItem("options", JSON.stringify({ enableSounds: OWOP.options.enableSounds })) }));
      options.appendChild(optionmaker("Undo", "select", void 0, () => PM.undo()));
      options.appendChild(optionmaker("Redo", "select", void 0, () => PM.redo()));
      options.appendChild(optionmaker("Minimize Options", "select", void 0, () => NS.minimizeOptions(true)));

      NS.switchTabs = function (evt, cityName, s, button) {
        var i, tabcontent, tablinks;
        tabcontent = document.getElementsByClassName("tabcontent" + s);
        for (i = 0; i < tabcontent.length; i++) {
          tabcontent[i].style.display = "none";
        }
        tablinks = document.getElementsByClassName(s);
        for (i = 0; i < tablinks.length; i++) {
          tablinks[i].classList.remove("on");
        }
        button.classList.add("on");
        document.getElementById(cityName).style.display = "block";
      }
      NS.optionbutton = function (button, state) {
        let type = "unknown";
        let s = true;
        if (button.classList.contains("switch")) type = "switch";
        if (button.classList.contains("click")) type = "click";
        if (type === "unknown") return;
        let parent1 = button.parentElement;
        let parent2 = parent1.parentElement;
        if (type === "switch") {
          button.classList.toggle("on");
          s = button.classList.contains("on");
          button.innerHTML = s ? "on" : "off";
        }
        let extraState = OWOP.tool.allTools[parent2.id].extra.state;
        if (typeof extraState[state] === "function") {
          if (parent2.className === "tabcontentort" && parent2.id !== "all") extraState[state](s);
        } else {
          if (parent2.className === "tabcontentort" && parent2.id !== "all") extraState[state] = s;
        }
      }
      NS.minimizeOptions = function (minimize) {
        let max = document.getElementById("optionsMaximize");
        let min = document.getElementById("optionsMinimize");
        if (minimize) {
          max.style = "display: flex;";
          min.style = "display: none;";
        } else {
          max.style = "display: none;";
          min.style = "display: flex;";
        }
      }
    }

    var windowClass = OWOP.windowSys.addWindow(new OWOP.windowSys.class.window(windowName, options, windowFunc)
      .move(OWOP.windowSys.windows['Tools'].realw + 15, 32));
    windows[windowName] = windowClass;
    windowClass.frame.style.visibility = "visible";
  }

  {
    // please make a better checking statement
    function x() {
      if (OWOP.OPM && OWOP?.windowSys?.windows?.['Coordinates Saver']) makeOptionsWindow();
      else if (OWOP.OPM) setTimeout(x, 1000);
    }
    // x();
  }
  console.log("windows completed");


  function hue(d, b = 1) {
    let a = 256 / b; // 1   2   4  8  16 32 64 128 256
    //let b = mul; // 256 128 64 32 16 8  4  2   1
    d = Math.floor(d);
    // d = d % (b * 6); m_{F}\left(a,b\right)=a-b\operatorname{floor}\left(\frac{a}{b}\right)
    d = (Math.abs(Math.floor(d / (b * 6)) * ((b * 6))) + (d % (b * 6))) % (b * 6);
    // d = d - (b * 6) * ~~(d/(b * 6));
    let nD = Math.floor(Math.abs(d / b));
    let output;
    if (nD < 1) {
      output = [255, 0, (d % b) * a];
    } else if (nD < 2) {
      output = [255 - ((d % b) * a), 0, 255];
    } else if (nD < 3) {
      output = [0, (d % b) * a, 255];
    } else if (nD < 4) {
      output = [0, 255, 255 - ((d % b) * a)];
    } else if (nD < 5) {
      output = [(d % b) * a, 255, 0];
    } else if (nD < 6) {
      output = [255, 255 - ((d % b) * a), 0];
    }
    // console.log(d);
    // console.log(nD);
    // console.log(output);
    return output;
  }

  var rangeMap = (a, b) => s => {
    const [a1, a2] = a;
    const [b1, b2] = b;
    // Scaling up an order, and then down, to bypass a potential,
    // precision issue with negative numbers.
    return (((((b2 - b1) * (s - a1)) / (a2 - a1)) * 10) + (10 * b1)) / 10;
  };
  var clamp = v => Math.round(Math.max(Math.min(v, 255), 0));
  var degToRad = d => d * (Math.PI / 180);
  var radToDeg = r => r / (Math.PI / 180);

  class RGBRotate {
    constructor(degrees) {
      this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
      this.set_hue_rotation(degrees);
    }
    set_hue_rotation(degrees) {
      let cosA = Math.cos(degToRad(degrees));
      let sinA = Math.sin(degToRad(degrees));
      this.matrix[0][0] = cosA + (1 - cosA) / 3;
      this.matrix[0][1] = 1 / 3 * (1 - cosA) - Math.sqrt(1 / 3) * sinA;
      this.matrix[0][2] = 1 / 3 * (1 - cosA) + Math.sqrt(1 / 3) * sinA;
      this.matrix[1][0] = 1 / 3 * (1 - cosA) + Math.sqrt(1 / 3) * sinA;
      this.matrix[1][1] = cosA + 1 / 3 * (1 - cosA);
      this.matrix[1][2] = 1 / 3 * (1 - cosA) - Math.sqrt(1 / 3) * sinA;
      this.matrix[2][0] = 1 / 3 * (1 - cosA) - Math.sqrt(1 / 3) * sinA;
      this.matrix[2][1] = 1 / 3 * (1 - cosA) + Math.sqrt(1 / 3) * sinA;
      this.matrix[2][2] = cosA + 1 / 3 * (1 - cosA);
    }
    apply(r, g, b) {
      let rx = r * this.matrix[0][0] + g * this.matrix[0][1] + b * this.matrix[0][2];
      let gx = r * this.matrix[1][0] + g * this.matrix[1][1] + b * this.matrix[1][2];
      let bx = r * this.matrix[2][0] + g * this.matrix[2][1] + b * this.matrix[2][2];
      return [clamp(rx), clamp(gx), clamp(bx)];
    }
  }

  function rgb(r, g, b) {
    return "#" + [r, g, b].map(v => {
      return ('0' +
        Math.min(Math.max(parseInt(v), 0), 255)
          .toString(16)
      ).slice(-2);
    }).join('');
  }

  const Base64 = {
    // sourced from https://stackoverflow.com/questions/6213227/fastest-way-to-convert-a-number-to-radix-64-in-javascript
    // coded by https://stackoverflow.com/users/520997/reb-cabin

    _Rixits:
      //   0       8       16      24      32      40      48      56     63
      //   v       v       v       v       v       v       v       v      v
      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/",
    // You have the freedom, here, to choose the glyphs you want for
    // representing your base-64 numbers. The ASCII encoding guys usually
    // choose a set of glyphs beginning with ABCD..., but, looking at
    // your update #2, I deduce that you want glyphs beginning with
    // 0123..., which is a fine choice and aligns the first ten numbers
    // in base 64 with the first ten numbers in decimal.

    // This cannot handle negative numbers and only works on the
    //     integer part, discarding the fractional part.
    // Doing better means deciding on whether you're just representing
    // the subset of javascript numbers of twos-complement 32-bit integers
    // or going with base-64 representations for the bit pattern of the
    // underlying IEEE floating-point number, or representing the mantissae
    // and exponents separately, or some other possibility. For now, bail
    fromBigInt: function (bigint) {
      if (typeof bigint !== "bigint")
        throw "The input is not valid";

      var rixit; // like 'digit', only in some non-decimal radix
      var residual = bigint;
      var result = '';
      while (true) {
        rixit = residual % 64n;
        result = this._Rixits.charAt(Number(rixit)) + result;
        residual = residual / 64n;

        if (residual == 0n)
          break;
      }
      return result;
    },
    fromNumber: function (number) {
      if (isNaN(Number(number)) || number === null ||
        number === Number.POSITIVE_INFINITY)
        throw "The input is not valid";
      if (number < 0)
        throw "Can't represent negative numbers now";

      var rixit; // like 'digit', only in some non-decimal radix
      var residual = Math.floor(number);
      var result = '';
      while (true) {
        rixit = residual % 64;
        result = this._Rixits.charAt(rixit) + result;
        residual = Math.floor(residual / 64);

        if (residual == 0)
          break;
      }
      return result;
    },

    toNumber: function (rixits) {
      let result = 0;
      rixits = rixits.split('');
      for (let e of rixits) {
        result = (result * 64) + this._Rixits.indexOf(e);
      }
      return result;
    },
    toBigInt: function (rixits) {
      let result = 0n;
      rixits = rixits.split('');
      for (let e of rixits) {
        result = (result * 64n) + BigInt(this._Rixits.indexOf(e));
      }
      return result;
    }
  }

  const Base256 = {
    // sourced from https://stackoverflow.com/questions/6213227/fastest-way-to-convert-a-number-to-radix-64-in-javascript
    // coded by https://stackoverflow.com/users/520997/reb-cabin
    // modified by NekoNoka
    _Rixits:
      //   0       8       16      24      32      40      48      56     63
      //   v       v       v       v       v       v       v       v      v
      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝÞßàáâãäåçèéêëìíîïðñòóôõöùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžǞǟǠǡǦǧǨǩǪǫǬǭ",
    // You have the freedom, here, to choose the glyphs you want for
    // representing your base-64 numbers. The ASCII encoding guys usually
    // choose a set of glyphs beginning with ABCD..., but, looking at
    // your update #2, I deduce that you want glyphs beginning with
    // 0123..., which is a fine choice and aligns the first ten numbers
    // in base 64 with the first ten numbers in decimal.

    // This cannot handle negative numbers and only works on the
    //     integer part, discarding the fractional part.
    // Doing better means deciding on whether you're just representing
    // the subset of javascript numbers of twos-complement 32-bit integers
    // or going with base-64 representations for the bit pattern of the
    // underlying IEEE floating-point number, or representing the mantissae
    // and exponents separately, or some other possibility. For now, bail
    fromBigInt: function (bigint) {
      if (typeof bigint !== "bigint")
        throw "The input is not valid";
      if (bigint < 0)
        throw "Can't represent negative numbers now";

      var rixit; // like 'digit', only in some non-decimal radix
      var residual = bigint;
      var result = '';
      var l = BigInt(this._Rixits.length);
      while (true) {
        rixit = residual % l;
        result = this._Rixits.charAt(Number(rixit)) + result;
        residual = residual / l;

        if (residual == 0n)
          break;
      }
      return result;
    },
    fromNumber: function (number) {
      if (isNaN(Number(number)) || number === null ||
        number === Number.POSITIVE_INFINITY)
        throw "The input is not valid";
      if (number < 0)
        throw "Can't represent negative numbers now";

      var rixit; // like 'digit', only in some non-decimal radix
      var residual = Math.floor(number);
      var result = '';
      while (true) {
        rixit = residual % 256;
        // console.log("rixit : " + rixit);
        // console.log("result before : " + result);
        result = this._Rixits.charAt(rixit) + result;
        // console.log("result after : " + result);
        // console.log("residual before : " + residual);
        residual = Math.floor(residual / 256);
        // console.log("residual after : " + residual);

        if (residual == 0)
          break;
      }
      return result;
    },
    toNumber: function (rixits) {
      var result = 0;
      // console.log("rixits : " + rixits);
      // console.log("rixits.split('') : " + rixits.split(''));
      rixits = rixits.split('');
      for (var e = 0; e < rixits.length; e++) {
        // console.log("_Rixits.indexOf(" + rixits[e] + ") : " +
        // this._Rixits.indexOf(rixits[e]));
        // console.log("result before : " + result);
        result = (result * 256) + this._Rixits.indexOf(rixits[e]);
        // console.log("result after : " + result);
      }
      return result;
    },
    toBigInt: function (rixits) {
      var result = 0n;
      rixits = rixits.split('');
      for (var e = 0; e < rixits.length; e++) {
        result = (result * 256n) + BigInt(this._Rixits.indexOf(rixits[e]));
      }
      return result;
    }
  }

  NS.getImageData = function (image) {
    let c = document.createElement('canvas');
    let ctx = c.getContext('2d');
    if (image instanceof HTMLCanvasElement) {
      c = image;
    }
    else if (image instanceof Image) {
      c.width = image.width;
      c.height = image.height;
      ctx.drawImage(image, 0, 0);
    }
    else return false;
    let w = c.width
    let h = c.height;
    let d = ctx.getImageData(0, 0, w, h);
    let data = [];
    for (let j = 0; j < h; j++) {
      data.push([]);
      for (let i = 0; i < w; i++) {
        let c = [];
        c.push(d.data[4 * (j * w + i)]);
        c.push(d.data[4 * (j * w + i) + 1]);
        c.push(d.data[4 * (j * w + i) + 2]);
        c.push(d.data[4 * (j * w + i) + 3]);
        data[j].push(c);
      }
    }
    return data;
  }

  NS.setImageData = function (data) {
    let c = document.createElement('canvas');
    let w = data[0].length;
    let h = data.length;
    c.width = w;
    c.height = h;
    let ctx = c.getContext('2d');
    let d = ctx.createImageData(w, h);
    for (let j = 0; j < h; j++) {
      for (let i = 0; i < w; i++) {
        let r = data[j][i][0];
        let g = data[j][i][1];
        let b = data[j][i][2];
        let a = r === 255 && g === 255 && b === 255 ? 0 : 255;
        d.data[4 * (j * w + i)] = r;
        d.data[4 * (j * w + i) + 1] = g;
        d.data[4 * (j * w + i) + 2] = b;
        d.data[4 * (j * w + i) + 3] = a;
      }
    }
    ctx.putImageData(d, 0, 0);
    return c;
  }

  NS.teleport = function () {
    let { x, y } = NS.teleport.camera;
    if (isNaN(x) || isNaN(y)) {
      NS.teleport.camera = {};
      NS.teleport.teleporting = false;
      return;
    }
    let dx = x - NS.M20.camera.x;
    let dy = y - NS.M20.camera.y;
    // let distanceX = Math.abs(dx) < 10000 ? -window.innerWidth / OWOP.camera.zoom / 2 + dx : Math.sign(dx) * 10000;
    // let distanceY = Math.abs(dy) < 10000 ? -window.innerHeight / OWOP.camera.zoom / 2 + dy : Math.sign(dy) * 10000;
    NS.M20.camera.zoom = 32;

    let p = 9952;
    let d = Math.sqrt((x - OWOP.mouse.tileX) ** 2 + (y - OWOP.mouse.tileY) ** 2) * (1 / p);
    let xdirection = (x - OWOP.mouse.tileX) / d;
    let ydirection = (y - OWOP.mouse.tileY) / d;

    let p1 = new Point(x, y);
    let p2 = new Point(OWOP.mouse.tileX, OWOP.mouse.tileY);
    let distance = Point.distance(p1, p2);

    let tempx = Math.min(Math.max(x, -480000), 480000);
    let tempy = Math.min(Math.max(y, -480000), 480000);
    let p3 = new Point(tempx, tempy);

    if (Point.distance(p2, p3) > 100 && Point.distance(p1, p3) < distance) {
      NS.M20.centerCameraTo(tempx, tempy);
      setTimeout(() => NS.teleport(), 250);
      return;
    }

    if (distance < p) {
      if (distance > 100) {
        setTimeout(() => NS.teleport(), 2000);
      } else {
        NS.teleport.camera = {};
        NS.teleport.teleporting = false;
      }
      NS.M20.moveCameraBy(Math.round(-window.innerWidth / OWOP.camera.zoom / 2 + dx), Math.round(-window.innerHeight / OWOP.camera.zoom / 2 + dy));
      return;
    }
    NS.M20.moveCameraBy(Math.round(xdirection), Math.round(ydirection));
    // if (Math.abs(dx) > 1000) {
    //   NS.M20.moveCameraBy(distanceX, 0);
    //   teleported = true;
    // } else if (Math.abs(dy) > 1000) {
    //   NS.M20.moveCameraBy(0, distanceY);
    //   teleported = true;
    // }
    NS.teleport.teleporting = true;
    setTimeout(() => NS.teleport(), 150);
  }

  NS.defaultCursors = {
    "area protect": 1,// selectprotect (no space between)
    copy: 1,
    cursor: 1,
    eraser: 1,// erase
    export: 1,// select
    fill: 1,// bucket*
    line: 1,// wand
    move: 1,
    paste: 1,
    pipette: 1,
    protect: 1,// shield
    write: 1,// text*
    zoom: 1
  };
  NS.teleport.camera = {};
  NS.teleport.teleporting = false;
  NS.rangeMap = rangeMap;
  NS.hue = hue;
  NS.rgb = rgb;
  NS.Base64 = Base64;
  NS.Base256 = Base256;
  NS.clamp = clamp;
  NS.degToRad = degToRad;
  NS.radToDeg = radToDeg;
  NS.RGBRotate = RGBRotate;
  NS.chunkize = false;

  console.log("Neko's Scripts Loaded.");
  console.timeEnd("Neko");
}

function init() {
  let x = document.getElementById("load-scr");
  if (x?.style?.transform) {
    console.time("Neko");
    console.log("Loading Neko's Scripts.");
    if (document.getElementById("dev-chat")) document.getElementById("dev-chat").parentNode.removeChild(document.getElementById("dev-chat")); // im so pissed at devchat for screaming at me every time i press a single letter while typing out something in the console its so annoying. thats why its the first thing i delete when initializing.
    NS.OPM = !!OWOP.misc;
    if (!NS.OPM) {
      NS.localStorage = localStorage.NS;
      if (!NS.localStorage) {
        localStorage.NS = JSON.stringify({});
        NS.localStorage = localStorage.NS;
      }
      NS.localStorage = JSON.parse(NS.localStorage);

      NS.modules.forEach(e => {
        0, 13, 14, 15, 16, 20, 22, 21, 23, 24, 25; // module numbers
        if (e.misc) NS.M0 = e;
        if (e.EVENTS) NS.M13 = e;
        if (e.eventSys) NS.M14 = e;
        if (e.camera) NS.M20 = e;
        if (e.updateClientFx) NS.M21 = e;
      });
      if (!NS.M0 || !NS.M13 || !NS.M14 || !NS.M20 || !NS.M21) {
        // im gonna make a localstorage check to make sure the reload doesnt happen indefinitely, and if the check happens at least 3 times then it will resume code execution without anymore reloads and provide a warning that the script wasnt loaded correctly, for now there wont be anything (this is a pre-release version currently).

        if (!NS.localStorage.reloadCheck) NS.localStorage.reloadCheck = 1;
        if (NS.localStorage.reloadCheck === 3) {
          delete NS.localStorage.reloadCheck;
          localStorage.NS = JSON.stringify(NS.localStorage);
          OWOP.chat.local("Neko's Script was not loaded correctly, please reload the tab.");
        } else {
          NS.localStorage.reloadCheck++;
          localStorage.NS = JSON.stringify(NS.localStorage);
          location.reload();
        }
        return;
      } else {
        let l = JSON.parse(localStorage.NS);
        if (l.reloadCheck) delete l.reloadCheck;
        localStorage.NS = JSON.stringify(l);
      }
      NS.keysdown = [];
      NS.extra = {};
      NS.extra.log = false;
      function keydown(event) {
        var e = event.which || event.keyCode;
        if ("TEXTAREA" !== document.activeElement.tagName && "INPUT" !== document.activeElement.tagName) {
          NS.keysdown[e] = !0;
          var n = OWOP.player.tool;
          if (undefined !== OWOP?.world && n?.isEventDefined("keydown") && n?.call("keydown", [NS.keysdown, event])) return !1;
          switch (e) {
            case 80:
              OWOP.player.tool = "pipette";
              break;
            case 77:
            case 81:
              OWOP.player.tool = "move";
              break;
            case 79:
              OWOP.player.tool = "cursor";
              break;
            case 70:
              break;
            case 69:
              //OWOP.player.tool = "neko eraser";
              break;
            case 66:
              //OWOP.player.tool = "fill";
              break;
            case 72:
              // make options window open/close
              // options window will include options to switch the behavior of the tools, the game, and open/close all windows
              break;
            case 71:
              OWOP.renderer.showGrid(!OWOP.renderer.gridShown);
              break;
            case 90:
              if (!event.ctrlKey) break;
              NS.PM.undo(event.shiftKey);
              event.preventDefault();
              break;
            case 89:
              if (!event.ctrlKey) break;
              NS.PM.redo(event.shiftKey);
              event.preventDefault();
              break;
            case 112: // f1
              event.preventDefault();
              break;
            case 107:
            case 187:
              ++OWOP.camera.zoom;
              break;
            case 109:
            case 189:
              --OWOP.camera.zoom;
              break;
            case 76:
              NS.extra.log = !NS.extra.log;
              break;
            case 27:
              NS.teleport.camera = {};
              break;
          }
          (NS.extra.log && console.log(event));
        }
      }
      function keyup(event) {
        var e = event.which || event.keyCode;
        if (delete NS.keysdown[e], "INPUT" !== document.activeElement.tagName) {
          var n = OWOP.player.tool;
          if (undefined !== OWOP?.world && n?.isEventDefined("keyup") && n?.call("keyup", [NS.keysdown, event])) return !1;
          switch (event.key) {
            case "Enter":
            case "`":
              document.getElementById("chat-input").focus();
              break;
          }
        }
      }
      let t = EventTarget._eventlists;
      let down = [/Custom color\\nType three values separated by a comma: r,g,b\\n\(\.\.\.or the hex string: #RRGGBB\)\\nYou can add multiple colors at a time separating them with a space\./];
      let up = [/function\(.\)\{var .=.\.which\|\|.\.keyCode;if\(delete .\[.\],"INPUT"!==document\.activeElement\.tagName\){var .=.\.player\.tool;if\(null!==.&&null!==.\.world&&.\.isEventDefined\("keyup"\)&&.\.call\("keyup",\[.,.\]\)\)return!1;13==.\?.\.chatInput\.focus\(\):16==.&&\(.\.player\.tool="cursor"\)}}/];
      NS.etdown = false;
      NS.etup = false;
      NS.et = false;
      if (NS.OPM) {
        up.push('function(t) {\n              var e = t.which || t.keyCode;\n              if (delete b[e], "INPUT" !== document.activeElement.tagName) {\n                  var n = f.player.tool;\n                  if (null !== n && null !== E.world && n.isEventDefined("keyup") && n.call("keyup", [b, t])) return !1;\n                  13 == e ? k.chatInput.focus() : 16 == e && (f.player.tool = "cursor")\n              }\n          }');
        up.push('function(t){var e=t.which||t.keyCode;if(delete b[e],"INPUT"!==document.activeElement.tagName){var n=f.player.tool;if(null!==n&&null!==E.world&&n.isEventDefined("keyup")&&n.call("keyup",[b,t]))return!1;13==e?k.chatInput.focus():16==e&&(f.player.tool="cursor")}}');
      } else {
        up.push('function(e){var t=e.which||e.keyCode;if(delete w[t],"INPUT"!==document.activeElement.tagName){var n=d.player.tool;if(null!==n&&null!==x.world&&n.isEventDefined("keyup")&&n.call("keyup",[w,e]))return!1;13==t?k.chatInput.focus():16==t&&(d.player.tool="cursor")}}');
      }
      for (let e of t) {
        if (NS.etdown !== true) for (let d of down) {
          if ((d instanceof RegExp && d.test(String(e))) || String(e) === d) {
            NS.etdown = true;
            NS.tempdown = e;
            console.log("found down");
          }
          if (NS.etdown === true) break;
        }
        if (NS.etup !== true) for (let u of up) {
          if (String(e) === u) {
            NS.etup = true
            NS.tempup = e;
            console.log("found up");
          }
          if (NS.etup === true) break;
        }
        if (NS.etdown === true && NS.etup === true) break;
      }
      if (NS.etdown !== true) console.warn("down was not found");
      if (NS.etup !== true) console.warn("up was not found");
      if (NS.etdown === true && NS.etup === true) {
        NS.et = true;
        window.removeEventListener("keydown", NS.tempdown);
        window.removeEventListener("keyup", NS.tempup);
        window.addEventListener("keydown", keydown);
        window.addEventListener("keyup", keyup);
      }
      delete EventTarget._eventlists;
      new MutationObserver(function (mutationList, observer) {
        for (const mutation of mutationList) {
          if (mutation.type === 'attributes') {
            if (mutation.attributeName === "style") {
              if (mutation.target.style.transform) {
                console.log(observer, mutation);
                setTimeout(() => NS.M0.showPlayerList(true), 5e3);
              }
            }
          }
        }
      }).observe(document.getElementById("load-scr"), { attributes: true });
      NS.M0.showPlayerList(true);

      install();
    } else {
      let modal = document.createElement("div");
      modal.id = "notiModal";
      modal.className = "modal";
      let s = document.createElement("style");
      s.innerHTML = `
      .modal {
        position: fixed;
        z-index: 1;
        padding-top: 100px;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        overflow: auto;
        background-color: rgb(0, 0, 0);
        background-color: rgba(0, 0, 0, 0.4);
      }

      .modal-content {
        background-color: #aba389;
        margin: auto;
        padding: 20px;
        width: 80%;
        border-radius: 6px;
        border: 3px solid #7e635c;
      }
    `;
      modal.appendChild(s);
      let mc = document.createElement("div");
      mc.className = "modal-content";
      mc.innerHTML = `
      <p style="font-size: 20px;color: #7e635c;text-shadow: 1px 1px #4d313b;">
      This is a notification telling you that Neko's Scripts can no longer run with OPM 2 enabled
      <br>
      it's unfortunate but as my script keeps updating more features that is present in OPM 2 will appear in my script.
      <br>
      If you want to keep using this script you must disable OPM 2 from tampermonkey.
      <br>
      <a href="https://github.com/NekoNoka/Neko-OWOP-Scripts">Neko's Scripts</a>
      </p>
    `;
      modal.appendChild(mc);
      document.body.appendChild(modal);
      window.onclick = function (event) {
        let notiModal = document.getElementById('notiModal');
        if (event.target == notiModal) notiModal.style.display = "none";
      }
    }
  } else {
    setTimeout(init, 100);
  }
}

init();