Openfront Multitab cheat

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

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

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         Openfront Multitab cheat
// @namespace    Openfront Multitab cheat
// @version      1.2.1
// @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==

let style = `
body {
    background-color: black;
    padding: 10px;
}
#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: 0.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: 0.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: none;
    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: 0.4s;
    border-radius: 34px;
}

.slider::before {
    position: absolute;
    content: "";
    height: 16px;
    width: 16px;
    left: 2px;
    bottom: 2px;
    background-color: var(--text-dim);
    transition: 0.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: transparent;
    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: transparent;
    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: none;
}
.save-note {
    font-family: var(--mono);
    font-size: 12px;
    color: var(--text-dim);
    transition: opacity 0.3s ease;
}
.reset-btn {
    background: transparent;
    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: 0.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: 0.06em;
}
@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.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 class="text-field" id="targetName">
            </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 type="checkbox" id="autoAlliance">
                    <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 type="checkbox" id="autoTroops">
                    <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 type="checkbox" id="autoGold">
                    <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 type="checkbox" id="autoFactory">
                    <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 inputmode="numeric" id="maxFactories">
                    <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 styleElement = document.createElement("style");
styleElement.textContent = style;
document.head.appendChild(styleElement);
document.body.insertAdjacentHTML("beforeend", panel);

let toggled = true;
let isDragging = false;   // ← New flag

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 setting = null;
let uiState = null;

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() {
    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.type === "number" ?
                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);
            element.addEventListener("change", saveSetting);
        } else {
            element.value = value;
            element.addEventListener("input", saveSetting);
        }
    }
}

// Draggable Panel - Improved
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();        // Save position after drag
    }
}

// Stepper
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();
    });
}

// Header Click (Toggle) - Now respects dragging
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();   // Save collapsed state
});

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

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


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)];
}

const statusElement = document.querySelector(".dot")
const statusText = document.getElementById("status-text")
function tick() {
    const cfg = getSettings();           // ← Get latest settings
    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";
  }

  if (!state || !state.game || !state.myPlayer) return;

  const game = state.game;
  const myPlayer = state.myPlayer;
  if (game.isCatchingUp()) return;

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

  const mySmallID = game.players().indexOf(myPlayer) + 1
  let donateTroopsEnable = game._config._gameConfig.donateTroops;
  let donateGoldEnable = game._config._gameConfig.donateGold;

  if (game.inSpawnPhase() && 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;
  }

  if (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;
        }
      }
    }
  }

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

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

    if (builtFactories >= 1) {
      if (!factory_id) {
        for (const [key, value] of state.game.unitStates()) {
            if (value.ownerID == mySmallID && value.unitType == "Factory") {
              factory_id = value.id;
              break;
            };
        }
      }
      if (sendPacket({
        type:"upgrade_structure",
        unit: "Factory",
        unitId: factory_id
      })) {
        builtFactories += 1;
      }
    } else {
      if (sendPacket({
        type: "build_unit",
        unit: "Factory",
        tile: myPlayer.state.spawnTile,
      })) {
        builtFactories += 1;
      }
    }
  }
}
setInterval(tick, 2000);