Cheat GUI, teaming up bot, control with emoji, auto spawn, auto factory, auto give troops and gold, auto alliance.
// ==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);