Openfront Multitab cheat

Cheat GUI, teaming up bot, control with emoji, auto spawn, auto factory, auto give troops and gold, auto alliance.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

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         Openfront Multitab cheat
// @namespace    Openfront Multitab cheat
// @version      1.2.2
// @description  Cheat GUI, teaming up bot, control with emoji, auto spawn, auto factory, auto give troops and gold, auto alliance.
// @author       EcoMaxer
// @match        *://openfront.io/*
// @match        *://*.openfront.io/*
// @allFrames    true
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-body
// @license      MIT
// ==/UserScript==

const storage_key = "multi_cheat";
const UI_KEY = "multi_cheat_ui";

const default_ui = {
  x: 20,
  y: 20,
  collapsed: false
};

const default_setting = {
  targetName: "",
  autoAlliance: true,
  autoTroops: true,
  autoGold: false,
  autoFactory: true,
  maxFactories: 12
};

let style = `#cheat-panel{--panel:#1c2027;--panel-raised:#23272f;--line:#2e333c;--text:#e8e6df;--text-dim:#8b909c;--amber:#e0a84e;--amber-dim:#6b552c;--green:#6ea87a;--red:#d16b6b;--mono:"IBM Plex Mono","SF Mono",Menlo,Consolas,monospace;--sans:"Inter","Helvetica Neue",Arial,sans-serif;background-color:var(--panel);color:var(--text);font-family:var(--sans);box-sizing:border-box;overflow:hidden;width:320px;position:fixed;top:0;left:0;z-index:999999999999;border:1px solid var(--line);border-radius:10px;display:flex;flex-direction:column}#header{border-bottom:1px solid var(--line);padding:18px 20px;font-size:15px;font-weight:600;letter-spacing:.01em;display:flex;flex-direction:row;align-items:center;justify-content:space-between}#body{flex-grow:1}#footer{padding:14px 20px;display:flex;align-items:center;justify-content:space-between;background:var(--panel-raised)}.row{display:flex;align-items:center;justify-content:space-between;padding:9px 0}.group{padding:18px 20px;border-bottom:1px solid var(--line)}.group-label{font-family:var(--mono);font-size:12px;text-transform:uppercase;letter-spacing:.1em;color:var(--text-dim);margin-bottom:12px}.row-text{display:flex;flex-direction:column;gap:2px;max-width:280px}.row-label{font-size:14px}.row-hint{font-size:12px;color:var(--text-dim)}.text-field{background:var(--panel-raised);border:1px solid var(--line);border-radius:6px;color:var(--text);font-family:var(--mono);font-size:12.5px;padding:6px 10px;width:120px;text-align:right}.text-field:focus{outline:0;border-color:var(--amber)}.switch{position:relative;display:inline-block;width:38px;height:22px}.switch input{opacity:0;width:0;height:0}.slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:var(--panel-raised);box-sizing:content-box;border:1px solid var(--line);transition:.4s;border-radius:34px}.slider::before{position:absolute;content:"";height:16px;width:16px;left:2px;bottom:2px;background-color:var(--text-dim);transition:.4s;border-radius:50%}input:checked+.slider{border-color:var(--amber);background-color:var(--amber-dim)}input:checked+.slider::before{background-color:var(--amber);transform:translateX(16px)}.stepper{display:flex;align-items:center;gap:0;border:1px solid var(--line);border-radius:6px;overflow:hidden;background:var(--panel-raised)}.stepper button{background:0 0;border:none;color:var(--text-dim);width:26px;height:26px;cursor:pointer;font-family:var(--mono);font-size:14px}.stepper input{width:42px;text-align:center;background:0 0;border:none;border-left:1px solid var(--line);border-right:1px solid var(--line);color:var(--text);font-family:var(--mono);font-size:12.5px;height:26px}.stepper input:focus{outline:0}.save-note{font-family:var(--mono);font-size:12px;color:var(--text-dim);transition:opacity .3s ease}.reset-btn{background:0 0;border:1px solid var(--line);color:var(--text-dim);font-family:var(--mono);font-size:12px;padding:6px 12px;border-radius:6px;cursor:pointer;letter-spacing:.03em}.reset-btn:hover{color:var(--red);border-color:#6b3535}.status-light{display:flex;align-items:center;gap:6px;font-family:var(--mono);font-size:11px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.06em}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}.dot{width:7px;height:7px;border-radius:50%;background:var(--red);box-shadow:0 0 6px var(--red);animation:pulse 2.4s ease-in-out infinite}.dot.green{background:var(--green);box-shadow:0 0 6px var(--green)}`
let panel = `<div id=cheat-panel><div id=header><span class=header-title>Multitab cheat</span><div class=status-light><span class=dot></span><span id=status-text>Disconnected</span></div></div><div id=body><div class=group><div class=group-label>Identity</div><div class=row><div class=row-text><span class=row-label>Target name</span> <span class=row-hint>In game username</span></div><input id=targetName class=text-field></div></div><div class=group><div class=group-label>Bot behaviour</div><div class=row><div class=row-text><span class=row-label>Auto alliance</span> <span class=row-hint>Accept alliance only from you</span></div><label class=switch><input id=autoAlliance type=checkbox> <span class=slider></span></label></div><div class=row><div class=row-text><span class=row-label>Auto troops</span> <span class=row-hint>Send reinforcements below threshold</span></div><label class=switch><input id=autoTroops type=checkbox> <span class=slider></span></label></div><div class=row><div class=row-text><span class=row-label>Auto gold</span> <span class=row-hint>Send 40% of it's income on interval</span></div><label class=switch><input id=autoGold type=checkbox> <span class=slider></span></label></div><div class=row><div class=row-text><span class=row-label>Auto factory</span> <span class=row-hint>Build factory automatically</span></div><label class=switch><input id=autoFactory type=checkbox> <span class=slider></span></label></div></div><div class=group><div class=group-label>Limit</div><div class=row><div class=row-text><span class=row-label>Max factory</span> <span class=row-hint>Max number of factory</span></div><div class=stepper><button type=button>–</button> <input id=maxFactories inputmode=numeric> <button type=button>+</button></div></div></div></div><div id=footer><span class=save-note id=save-note>Changes save instantly</span> <button class=reset-btn id=reset-btn>Reset</button></div></div>`

let toggled = true;
let isDragging = false;
let setting = null;
let uiState = null;
let hasListeners = false;

let styleElement = document.createElement("style");
styleElement.textContent = style;
document.head.appendChild(styleElement);
document.body.insertAdjacentHTML("afterend", panel);

function saveUIState() {
  const panel = document.getElementById("cheat-panel");
  if (!panel) return;

  uiState = {
    x: parseInt(panel.style.left) || 20,
    y: parseInt(panel.style.top) || 20,
    collapsed: !toggled
  };
  GM_setValue(UI_KEY, uiState);
}

function loadUIState() {
  uiState = GM_getValue(UI_KEY, {...default_ui});

  const panel = document.getElementById("cheat-panel");
  if (panel) {
    panel.style.left = uiState.x + "px";
    panel.style.top = uiState.y + "px";
    toggled = !uiState.collapsed;

    document.getElementById("body").style.display = toggled ? "block" : "none";
    document.getElementById("footer").style.display = toggled ? "block" : "none";
  }
}

function saveSetting() {
  if (!setting) return;

  document.getElementById("save-note").textContent = "Saving...";
  for (const key in setting) {
    const element = document.getElementById(key);
    if (!element) continue;
    if (element.type === "checkbox") {
      setting[key] = element.checked;
    } else {
      setting[key] = element.inputMode === "numeric" ?
        parseInt(element.value) || 0 :
        element.value;
    }
  }
  GM_setValue(storage_key, JSON.stringify(setting));
  document.getElementById("save-note").textContent = "Saved!";
}

function loadSetting() {
  const saved = GM_getValue(storage_key);
  setting = saved ? JSON.parse(saved) : {...default_setting};

  for (const [key, value] of Object.entries(setting)) {
    const element = document.getElementById(key);
    if (!element) continue;

    if (element.type === "checkbox") {
      element.checked = Boolean(value);
      if (!hasListeners) element.addEventListener("change", saveSetting);
    } else {
      element.value = value;
      if (!hasListeners) element.addEventListener("input", saveSetting);
    }
  }
  hasListeners = true;
}

function makeDraggable() {
  const panelEl = document.getElementById("cheat-panel");
  const header = document.getElementById("header");
  let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;

  header.addEventListener("mousedown", (e) => {
    if (e.target.tagName === "INPUT" || e.target.tagName === "BUTTON") return;
    isDragging = false;
    e.preventDefault();
    pos3 = e.clientX;
    pos4 = e.clientY;

    document.addEventListener("mousemove", elementDrag);
    document.addEventListener("mouseup", closeDrag);
  });

  function elementDrag(e) {
    e.preventDefault();
    isDragging = true;
    pos1 = pos3 - e.clientX;
    pos2 = pos4 - e.clientY;
    pos3 = e.clientX;
    pos4 = e.clientY;

    panelEl.style.top = (panelEl.offsetTop - pos2) + "px";
    panelEl.style.left = (panelEl.offsetLeft - pos1) + "px";
  }

  function closeDrag() {
    document.removeEventListener("mousemove", elementDrag);
    document.removeEventListener("mouseup", closeDrag);
    saveUIState();
  }
}

function setupSteppers() {
  const input = document.getElementById("maxFactories");
  if (!input) return;
  const minus = input.previousElementSibling;
  const plus = input.nextElementSibling;

  minus.addEventListener("click", () => {
    let val = parseInt(input.value) || 0;
    input.value = Math.max(0, val - 1);
    saveSetting();
  });

  plus.addEventListener("click", () => {
    let val = parseInt(input.value) || 0;
    input.value = val + 1;
    saveSetting();
  });
}

document.getElementById("header").addEventListener("click", (e) => {
  if (e.target.closest("input, button") || isDragging) {
    isDragging = false;
    return;
  }

  toggled = !toggled;
  document.getElementById("body").style.display = toggled ? "block" : "none";
  document.getElementById("footer").style.display = toggled ? "block" : "none";

  saveUIState();
});

document.getElementById("reset-btn").addEventListener("click", () => {
  if (confirm("Reset all settings to default?")) {
    setting = {...default_setting};
    GM_setValue(storage_key, JSON.stringify(setting));
    loadSetting();
  }
});

setInterval(loadSetting, 2000);
loadUIState();
makeDraggable();
setupSteppers();

const statusElement = document.querySelector(".dot")
const statusText = document.getElementById("status-text")

const NativeWS = window.WebSocket;
const NativeSend = NativeWS.prototype.send;
const WS_OPEN = NativeWS.OPEN || 1;
let activeSocket = null;
let lastKnownTile = null;
let myUsername = null;
let lastDonate = 0;
let lastKnownGold = 0;
let builtFactories = 0;
let factory_id = null;
let targetPlayerSpawnTile = null;

function getSettings() {
  return {
    targetName: setting.targetName || "",
    autoAlliance: setting.autoAlliance,
    autoTroops: setting.autoTroops,
    autoGold: setting.autoGold,
    autoFactory: setting.autoFactory,
    maxFactories: parseInt(setting.maxFactories) || 12
  };
}

let emojiControl = true;

let emoji_table = {
  "😀":0, "😊":1, "🥰":2, "😇":3, "😎":4,
  "😞":5, "🥺":6, "😭":7, "😱":8, "😡":9,
  "😈":10, "🤡":11, "🥱":12, "🫡":13, "🖕":14,
  "👋":15, "👏":16, "✋":17, "🙏":18, "💪":19,
  "👍":20, "👎":21, "🫴":22, "🤌":23, "🤦‍♂️":24,
  "🤝":25, "🆘":26, "🕊️":27, "🏳️":28, "⌛":29,
  "🔥":30, "💥":31, "💀":32, "☢️":33, "⚠️":34,
  "↖️":35, "⬆️":36, "↗️":37, "👑":38, "🥇":39,
  "⬅️":40, "🎯":41, "➡️":42, "🥈":43, "🥉":44,
  "↙️":45, "⬇️":46, "↘️":47, "❤️":48, "💔":49,
  "💰":50, "⚓":51, "⛵":52, "🏡":53, "🛡️":54,
  "🏭":55, "🚂":56, "❓":57, "🐔":58,"🐀":59
}

function setLastKnownTile(tile) {
  lastKnownTile = tile;
  try {
    if (window.top) window.top.__blonLastKnownTile = tile;
  } catch (e) {}
}

window.WebSocket = function(url, protocols) {
  const sock = protocols !== void 0 ? new NativeWS(url, protocols) : new NativeWS(url);

  sock.addEventListener("close", () => {
    if (activeSocket === sock) {
      activeSocket = null;
      try {
        if (window.top && window.top.__blonActiveSocket === sock) {
          window.top.__blonActiveSocket = null;
        }
      } catch (e) {
        console.log(e)
      }
    }
  });
  return sock;
};
window.WebSocket.prototype = NativeWS.prototype;
Object.getOwnPropertyNames(NativeWS).forEach((k) => {
  try {
    window.WebSocket[k] = NativeWS[k];
  } catch (_) {
  }
});
NativeWS.prototype.send = function(data) {
  let payload = data;
  if (typeof data === "string") {
    try {
      const msg = JSON.parse(data);
      if (msg) {
        const isGameMsg = msg.type === "intent" && msg.intent || msg.type === "quick_chat" || msg.type === "ping";
        if (isGameMsg && activeSocket !== this) {
          activeSocket = this;
          try {
            if (window.top) window.top.__blonActiveSocket = this;
          } catch (e) {
            console.log(e)
          }
        }
        const intent = msg.intent || msg;
        if (typeof intent.tile === "number") setLastKnownTile(intent.tile);
        if (typeof intent.dst === "number") setLastKnownTile(intent.dst);
        if (typeof intent.src === "number") setLastKnownTile(intent.src);
        if (intent && (intent.type === "attack" || intent.type === "boat" || intent.type === "donate_troops") && typeof intent.troops === "number") {
          try {
            const state = getGameState();
            if (state && state.myPlayer) {
              const rawMyTroops = typeof state.myPlayer.troops === "function" ? state.myPlayer.troops() : 0;
              const myTroops = typeof rawMyTroops === "bigint" ? Number(rawMyTroops) : Number(rawMyTroops || 0);
              window.__blonPendingExpansions = window.__blonPendingExpansions || [];
              const isDuplicate = window.__blonPendingExpansions.some(item => Math.abs(Date.now() - item.time) < 10 && item.amount === intent.troops);
              if (!isDuplicate) {
                window.__blonPendingExpansions.push({
                  time: Date.now(),
                  amount: intent.troops,
                  initialTroops: myTroops
                });
              }
            }
          } catch (e) {
            console.log(e)
          }
        }
        if (msg.type === "join") {
          myUsername = msg.username;
        }
      }
    } catch (e) {
      console.log(e)
    }
  }
  return NativeSend.call(this, payload);
};

function sendPacket(intentObj) {
  let sock = activeSocket;
  try {
    if (!sock && window.top && window.top.__blonActiveSocket) {
      sock = window.top.__blonActiveSocket;
    }
  } catch (e) {
    console.log(e)
  }
  if (!sock || sock.readyState !== WS_OPEN) return false;
  try {
    if (intentObj && (intentObj.type === "attack" || intentObj.type === "boat" || intentObj.type === "donate_troops") && typeof intentObj.troops === "number") {
      try {
        const state = getGameState();
        if (state && state.myPlayer) {
          const rawMyTroops = typeof state.myPlayer.troops === "function" ? state.myPlayer.troops() : 0;
          const myTroops = typeof rawMyTroops === "bigint" ? Number(rawMyTroops) : Number(rawMyTroops || 0);
          window.__blonPendingExpansions = window.__blonPendingExpansions || [];
          const isDuplicate = window.__blonPendingExpansions.some(item => Math.abs(Date.now() - item.time) < 10 && item.amount === intentObj.troops);
          if (!isDuplicate) {
            window.__blonPendingExpansions.push({
              time: Date.now(),
              amount: intentObj.troops,
              initialTroops: myTroops
            });
          }
        }
      } catch (e) {
        console.log(e)
      }
    }
    NativeSend.call(sock, JSON.stringify({ type: "intent", intent: intentObj }));
    return true;
  } catch (e) {
    console.log(e)
    return false;
  }
}

function getAccessibleDocuments() {
  const docs = [document];
  try {
    for (const frame of document.querySelectorAll("iframe")) {
      if (frame.contentDocument) docs.push(frame.contentDocument);
    }
  } catch (e) {
    console.log(e)
  }
  return docs;
}

function getGameState() {
  try {
    let overlay = null;
    for (const doc of getAccessibleDocuments()) {
      overlay = doc.querySelector("player-info-overlay");
      if (overlay) break;
    }

    let game = overlay && overlay.game ? overlay.game : null;
    if (!game) return;

    let myPlayer = game.myPlayer ? game.myPlayer() : null;
    if (!myPlayer) return;

    return { game, myPlayer };
  } catch (e) {
    console.log(e)
  }
}

function clamp(value, min, max) {
  return Math.max(min, Math.min(max, value));
}

function getPercentAmount(value, percent) {
  const n = typeof value === "bigint" ? Number(value) : Number(value || 0);
  if (!Number.isFinite(n) || n <= 0) return 0;
  const pct = clamp(Number(percent || 0), 1, 100);
  return Math.max(1, Math.floor(n * pct / 100));
}

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomPointOnCircleInt(position) {
  const angle = Math.random() * Math.PI * 2;
  const radius = getRandomInt(12, 24)
  const x = Math.round(position[0] + radius * Math.cos(angle));
  const y = Math.round(position[1] + radius * Math.sin(angle));

  return Number(`${x}${y}`)
}

function sliceIntegerInHalf(num) {
  const numStr = num.toString();
  const mid = Math.ceil(numStr.length / 2);
  const firstHalf = numStr.slice(0, mid);
  const secondHalf = numStr.slice(mid);

  return [Number(firstHalf), Number(secondHalf)];
}

function tick() {
  const cfg = getSettings();
  if (!cfg.targetName) return;

  const state = getGameState();

  if (state && state.game && state.myPlayer) {
    statusElement.classList.add("green");
    statusText.textContent = "Connected";
  } else {
    statusElement.classList.remove("green");
    statusText.textContent = "Disconnected";
    return
  }

  const game = state.game;
  const myPlayer = state.myPlayer;

  const gameConfig = game._config._gameConfig;
  let donateTroopsEnable = gameConfig.donateTroops;
  let donateGoldEnable = gameConfig.donateGold;
  let randomSpawn = gameConfig.randomSpawn;
  let disabledUnits = gameConfig.disabledUnits;
  let factoryDisabled = disabledUnits.includes("Factory");
  let allianceDisabled = game._config.disableAlliances();

  if (game.isCatchingUp() || game.config().isReplay()) return;

  const targetPlayer = game.players().find((p) => p.name() === cfg.targetName) || null;
  if (targetPlayer.id() === myPlayer.id()) return;
  if (!targetPlayer || !targetPlayer.isPlayer || !targetPlayer.isPlayer() || !targetPlayer.isAlive()) return;

  const mySmallID = myPlayer.smallID()

  if (game.inSpawnPhase()) {
    if (!randomSpawn && targetPlayer.state.spawnTile && targetPlayer.state.spawnTile != targetPlayerSpawnTile) {
      targetPlayerSpawnTile = targetPlayer.state.spawnTile
      console.log("Spawn tile: ", targetPlayerSpawnTile)

      const randomTile = randomPointOnCircleInt(sliceIntegerInHalf(targetPlayerSpawnTile));
      console.log("Random tile: ", randomTile)

      sendPacket({
        type: "spawn",
        tile: randomTile
      })
    }
    return;
  }

  // Hide canvas after spawn phase
  document.querySelectorAll("canvas").forEach((e) => {
    if (e.style.display != "none") {
      e.style.display = "none";
      document.body.style.display = "none";
    }
  })

  if (!allianceDisabled && cfg.autoAlliance) {
    if (!myPlayer.isOnSameTeam(targetPlayer)) {
      if (targetPlayer.state.outgoingAllianceRequests.includes(myPlayer.id()) && !myPlayer.isAlliedWith(targetPlayer)) {
        sendPacket({ type: "allianceRequest", recipient: targetPlayer.id() });
      }
    }
  }

  if (emojiControl) {
    const emojis = targetPlayer.state.outgoingEmojis;

    for (const emoji of emojis) {
      if (emoji && emoji.recipientID != "AllPlayers") {
        const recipientPlayer = game.players()[parseInt(emoji.recipientID) - 1]

        if (emoji.message === "💔" && recipientPlayer.id() === myPlayer.id()) {
          sendPacket({
            type: "breakAlliance",
            recipient: targetPlayer.id()
          })
        }
        if (emoji.message === "🆘") {
          if (!myPlayer.isOnSameTeam(targetPlayer) && myPlayer.isAlliedWith(targetPlayer) || myPlayer.isOnSameTeam(targetPlayer)) {
            if (donateGoldEnable && Date.now() - lastDonate > 10000 && recipientPlayer.id() === myPlayer.id()) {
              if (!myPlayer.state.outgoingEmojis.some((e) => e.message === "👍")) {
                sendPacket({type: "emoji", recipient: targetPlayer.id(), emoji: emoji_table["👍"]})
              }
              if (donateGoldEnable && sendPacket({ type: "donate_gold", recipient: targetPlayer.id(), gold: getPercentAmount(myPlayer.gold(), 100) })) {
                lastDonate = Date.now();
                lastKnownGold = 0;
              }
            } else if (donateGoldEnable && Date.now() - lastDonate > 10000 && recipientPlayer?.state?.outgoingEmojis?.some((e) => e.message === "👍")) {
              if (sendPacket({ type: "donate_gold", recipient: targetPlayer.id(), gold: getPercentAmount(myPlayer.gold(), 100) })) {
                lastDonate = Date.now();
                lastKnownGold = 0;
              }
            }
          }
        }
      }
    }
  }

  if (!myPlayer.isOnSameTeam(targetPlayer) && myPlayer.isAlliedWith(targetPlayer) || myPlayer.isOnSameTeam(targetPlayer)) {
    if (donateTroopsEnable && cfg.autoTroops && Date.now() - lastDonate > 10000 && targetPlayer.troops() / game.config().maxTroops(targetPlayer) <= 0.6) {
      if (sendPacket({ type: "donate_troops", recipient: targetPlayer.id(), troops: getPercentAmount(myPlayer.troops(), 40) })) lastDonate = Date.now();
    }

    if (donateGoldEnable && cfg.autoGold && Date.now() - lastDonate > 30000) {
      if (Number(myPlayer.gold()) - lastKnownGold < 0 || lastKnownGold < 0) lastKnownGold = 0;
      const goldDifference = Number(myPlayer.gold()) - lastKnownGold;

      if (builtFactories < cfg.maxFactories) {
        if (sendPacket({ type: "donate_gold", recipient: targetPlayer.id(), gold: getPercentAmount(goldDifference, 40) })) {
          lastDonate = Date.now();
          lastKnownGold = Number(myPlayer.gold());
        }
      } else {
        if (sendPacket({ type: "donate_gold", recipient: targetPlayer.id(), gold: getPercentAmount(myPlayer.gold(), 100) })) {
          lastDonate = Date.now();
          lastKnownGold = 0;
        }
      }
    }
  }

  for (const [key, value] of game.unitStates()) {
    if (value.ownerID == mySmallID && value.unitType == "Factory") {
      if (!factory_id) factory_id = value.id;
      builtFactories = value.level;
      break;
    }
  }

  const buildCost =
    builtFactories === 0 ? 125000 :
    builtFactories === 1 ? 250000 :
    builtFactories === 2 ? 500000 :
    builtFactories >= 3 ? 1000000 :
    null;

  if (!factoryDisabled && buildCost && cfg.autoFactory && builtFactories < cfg.maxFactories && myPlayer.gold() >= buildCost) {
    console.log("Building factory");

    if (builtFactories >= 1) {
      sendPacket({
        type:"upgrade_structure",
        unit: "Factory",
        unitId: factory_id
      })
    } else {
      sendPacket({
        type: "build_unit",
        unit: "Factory",
        tile: myPlayer.state.spawnTile,
      })
    }
  }
}
setInterval(tick, 2000);