const beautify = require('js-beautify').js; const fs = require('fs'); if (!fs.existsSync("./build")) fs.mkdirSync("./build"); fs.cpSync("./static/", "./build/", { recursive: true }); fs.cpSync("./assets/", "./build/assets/", { recursive: true }); fs.cpSync("./src/fx_core.js", "./build/fx_core.js"); fs.writeFileSync("./build/index.html", fs.readFileSync("./build/index.html").toString().replace(/buildTimestamp/g, Date.now())); let script = fs.readFileSync('./game/latest.js', { encoding: 'utf8' }).replace("\n", "").trim(); const replaceOne = (expression, replaceValue) => { const result = matchOne(expression); // this (below) works correctly because expression.lastIndex gets reset above in matchOne when there is no match script = script.replace(expression, replaceValue); return result; } const matchOne = (expression) => { const result = expression.exec(script); if (result === null) throw new Error("no match for: ") + expression; if (expression.exec(script) !== null) throw new Error("more than one match for: " + expression); return result; } // https://stackoverflow.com/a/63838890 const escapeRegExp = (string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); //const dictionary = { __dictionaryVersion: '1.90.0 4 Feb 2024', playerId: 'bB', playerNames: 'hA', playerBalances: 'bC', playerTerritories: 'bj', gIsSingleplayer: 'fc', gIsTeamGame: 'cH' }; //if (!script.includes(`"${dictionary.__dictionaryVersion}"`)) throw new Error("Dictionary is outdated."); const dictionary = {}; const matchDictionaryExpression = expression => { result = expression.exec(script); if (result === null) throw new Error("no match for ") + expression; if (expression.exec(script) !== null) throw new Error("more than one match for: ") + expression; for (let [key, value] of Object.entries(result.groups)) dictionary[key] = value; } // Return value example: // When replaceRawCode or matchRawCode are called with "var1=var2+1;" as the code // and this matches "a=b+1;", the returned value will be the object: { var1: "a", var2: "b" } const replaceRawCode = (/** @type {string} */ raw, /** @type {string} */ result, nameMappings) => { const { expression, groups } = generateRegularExpression(raw, false, nameMappings); let replacementString = result.replaceAll("$", "$$").replace(/\w+/g, match => { return groups.hasOwnProperty(match) ? "$" + groups[match] : match; }); //console.log(replacementString); let expressionMatchResult; try { expressionMatchResult = replaceOne(expression, replacementString); } catch (e) { throw new Error("replaceRawCode match error:\n\n" + e + "\n\nRaw code: " + raw + "\n"); } return Object.fromEntries(Object.entries(groups).map(([identifier, groupNumber]) => [identifier, expressionMatchResult[groupNumber]])); } const matchRawCode = (/** @type {string} */ raw, nameMappings) => { const { expression, groups } = generateRegularExpression(raw, false, nameMappings); const expressionMatchResult = matchOne(expression); return Object.fromEntries(Object.entries(groups).map(([identifier, groupNumber]) => [identifier, expressionMatchResult[groupNumber]])); } const generateRegularExpression = (/** @type {string} */ code, /** @type {boolean} */ isForDictionary, nameMappings) => { const groups = {}; let groupNumberCounter = 1; let raw = escapeRegExp(code).replace(isForDictionary ? /(?:@@)*(@?)(\w+)/g : /()(\w+)/g, (_match, modifier, word) => { // if a substitution string for the "word" is specified in the nameMappings, use it if (nameMappings && nameMappings.hasOwnProperty(word)) return nameMappings[word]; // if the "word" is a number or is one of these specific words, ingore it if (/^\d/.test(word) || ["return", "this", "var", "function", "Math"].includes(word)) return word; else if (groups.hasOwnProperty(word)) return "\\" + groups[word]; // regex numeric reference to the group else { groups[word] = groupNumberCounter++; return modifier === "@" ? `(?<${word}>\\w+)` : "(\\w+)"; } }); let expression = new RegExp(isForDictionary ? raw.replaceAll("@@", "@") : raw, "g"); return { expression, groups }; } [ /,this\.(?\w+)=this\.\w+<7\|\|9===this\.\w+,/g, /=function\((\w+),(\w+),\w+\){\1===(?\w+)\.(?\w+)\?\w+\(175,\w+\.\w+\(18,\[(?\w+)\.(?\w+)\[\2\]\]\),1001,\2,\w+\(/g, /\w+\.\w+\((\w+)\)\?\w+\.\w+\(\1\)\?(\w+)=(\w+\.\w+)\(13,\[\2\]\):\(\w+=\w+\.\w+\(\1\),\2=\3\(14,\[\w+\.\w+\.\w+\((?\w+)\.(?\w+)\[(\w+)\],\w+\.\w+\.\w+\(0,10\),150\),(\w+\.\w+\.\w+\()\4\.(?\w+)\[\6\]\),\7\4\.(?\w+)\[\6\]\),\2\]\),\w+=!0\):\2=/g, /,this\.(?\w+)=this\.(?\w+),this\.(?\w+)&&\(this\.\1=\w+\.\w+\(\)\),this\.\w+=this\.\1-this\.\w+,this\.\w+=0,/g, /function \w+\(\)\{if\(2===(?\w+)\.(?\w+)\)return 1;\w+\.\w+\(\),\1\.\2=2,\1\.\w+=\1.\w+\}/g, /(function \w+\((\w+),(?\w+),(?\w+),(?\w+),(?\w+)\){)(\6\.fillText\((?\w+)\.(?\w+)\[\2\],\4,\5\)),(\2<(?\w+)\.(?\w+)&&2!==\8\.(?\w+)\[[^}]+)}/g ].forEach(matchDictionaryExpression); const rawCodeSegments = [ "[0]=@Translations.@txt[70],@strs[1]=@game.@gIsSingleplayer?@Translations.txt[71]:@Translations.txt[72],", "?(this.gB=Math.floor(.0536*aK.fw),g5=aK.g5-4*@uiSizes.@gap-this.gB):", `for(a0L=new Array(@game.@gMaxPlayers),a0A.font=a07,@i=game.gMaxPlayers-1;0<=i;i--)a0L[i]=i+1+".",@playerData.@playerNames[i]=aY.qW.tm(playerData.@rawPlayerNames[i],a07,a0W),a0K[i]=Math.floor(a0A.measureText(playerData.playerNames[i]).width);`, ] rawCodeSegments.forEach(code => { const { expression } = generateRegularExpression(code, true); //console.log(expression); matchDictionaryExpression(expression); }); fs.writeFileSync("./build/fx_core.js", `const dictionary = ${JSON.stringify(dictionary)};\n` + fs.readFileSync("./build/fx_core.js").toString()); // Constants for easy usage of otherwise long variable access expressions const dict = dictionary; const playerId = `${dict.game}.${dict.playerId}`; const rawPlayerNames = `${dict.playerData}.${dict.rawPlayerNames}`; const gIsSingleplayer = `${dict.game}.${dict.gIsSingleplayer}`; // Replace assets const assets = require('./assets.js'); replaceOne(/(\(4,"crown",4,")[^"]+"\),/g, "$1" + assets.crownIcon + "\"),"); replaceOne(/(\(6,"territorial\.io",6,")[^"]+"\),/g, "$1" + assets.fxClientLogo + "\"),"); // Add FX Client version info to the game version window replaceRawCode(`ar.oa(4,1,new s8("ℹ️ "+Translations.txt[84],gameVersion+"
"+ah.aC5+"",`, `ar.oa(4,1,new s8("ℹ️ "+Translations.txt[84],gameVersion+"
"+ah.aC5+"" + "

" + "FX Client v" + fx_version + " " + fx_update + "
FX Client Discord server" + "
Github repository
",`); // Max size for custom maps: from 4096x4096 to 8192x8192 // TODO: test this; it might cause issues with new boat mechanics? { // Add Troop Density and Maximum Troops in side panel const { valuesArray } = replaceRawCode(`,labels[5]=aV.nU[76],labels[6]=aV.nU[77],labels[7]=aV.nU[78],a0Z=game.tn-eT(game.tn,100),(valuesArray=new Array(labels.length))[0]=game.io?`, `,labels[5]=aV.nU[76],labels[6]=aV.nU[77],labels[7]=aV.nU[78], labels.push("Max Troops", "Density"), // add labels a0Z=game.tn-eT(game.tn,100),(valuesArray=new Array(labels.length))[0]=game.io?`); replaceOne(new RegExp(/(:(?\w+)<7\?\w+\.\w+\.\w+\(valuesArray\[\2\]\)):(\w+\.\w+\(valuesArray\[7\]\))}/ .source.replace(/valuesArray/g, valuesArray), "g"), '$1 : $ === 7 ? $3 ' + `: $ === 8 ? utils.getMaxTroops(${dict.playerData}.${dict.playerTerritories}, ${playerId}) ` + `: utils.getDensity(${playerId}) }`); // increase the size of the side panel by 25% to make the text easier to read replaceOne(/(this\.\w+=Math\.floor\(\(\w+\.\w+\.\w+\(\)\?\.1646:\.126\))\*(\w+\.\w+\),)/g, "$1 * 1.25 * $2"); } // Increment win counter on wins replaceRawCode(`=function(sE){o.ha(sE,2),b.h9<100?xD(0,aZ.tG(4,[a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0):xD(0,aZ.tG(12,[a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0),`, `=function(sE){ if (${playerId} === sE && !${gIsSingleplayer}) wins_counter++, window.localStorage.setItem("fx_winCount", wins_counter), xD(0,"Your Win Count is now " + wins_counter,3,sE,ad.gN,ad.kl,-1,!0); o.ha(sE,2),b.h9<100?xD(0,aZ.tG(4,[a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0):xD(0,aZ.tG(12,[a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0),`); { // Add settings button and win count // render gear icon and win count replaceRawCode(`,fy=aV.nU[80],fontSize=.65*height,canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6)",canvas.fillRect(x,y,width,height),`, `,fy=aV.nU[80],fontSize=.65*height, canvas.imageSmoothingEnabled = true, canvas.drawImage(settingsGearIcon, x - width / 2, y, height, height), canvas.imageSmoothingEnabled = false, (settings.displayWinCounter && ( canvas.font = aY.g0.g1(1, Math.floor(height * 0.4)), canvas.fillStyle = "#ffffff", canvas.fillText("Win count: " + wins_counter, Math.floor(x + width / 2), Math.floor((y + height / 2) * 2)) )), canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6)",canvas.fillRect(x,y,width,height),`); // handle settings button click replaceRawCode(`(q6=Math.floor((b7.cv.fv()?.145:.09)*aK.fw),gap=Math.floor(.065*(b7.cv.fv()?.53:.36)*aK.fw),gap=aK.g5-q6-gap,jd=b0.gap,q6=Math.floor(.35*q6),gap<=mouseX&&mouseY= gap - q6 / 0.7 && mouseY < jd + q6 && WindowManager.openWindow("settings")) )`); } { // Keybinds // match required variables const { 0: match, groups: { attackBarObject, setRelative } } = matchOne(/:"."===(\w+\.key)\?(?\w+)\.(?\w+)\(31\/32\):"."===\1\?\2\.\3\(32\/31\):/g,); // create a setAbsolutePercentage function on the attack percentage bar object, // and also register the keybind handler functions replaceOne(/}(function \w+\((\w+)\){return!\(1<\2&&1===(?\w+)\|\|\(1<\2&&\2\*\3-\3<1\/1024\?\2=\(\3\+1\/1024\)\/\3:\2<1)/g, "} this.setAbsolutePercentage = function(newPercentage) { $ = newPercentage; }; " + "keybindFunctions.setAbsolute = this.setAbsolutePercentage; " + `keybindFunctions.setRelative = (arg1) => ${attackBarObject}.${setRelative}(arg1); $1`); // insert keybind handling code into the keyDown handler function replaceOne(new RegExp(/(function \w+\((?\w+)\){)([^}]+matched)/g.source.replace(/matched/g, escapeRegExp(match)), "g"), "$1 if (keybindHandler($.key)) return; $3"); } // Set the default font to Trebuchet MS script = script.replace(/sans-serif"/g, 'Trebuchet MS"'); // Realistic bot names setting // matches c4[i] = c4[i].replace(a6U[dx], a6V[dx]) replaceOne(/(((\w+)\[\w+\])=\2\.replace\(\w+(\[\w+\]),\w+\4\))/g, "$1; if (settings.realisticNames) $3 = realisticNames;") // Hide all links in main menu depending on settings //replaceOne(/(this\.\w+=function\(\){)((\w+\.\w+)\[2\]=\3\[3\]=\3\[4\]=(?!this\.\w+\.\w+),)/g, //"$1 if (settings.hideAllLinks) $3[0] = $3[1] = $; else $3[0] = $3[1] = true; $2") // Make the main canvas context have an alpha channel if a custom background is being used replaceOne(/(document\.getElementById\("canvasA"\),\(\w+=\w+\.getContext\("2d",){alpha:!1}/g, "$1 {alpha: makeMainMenuTransparent}") // Clear canvas background if a custom background is being used replaceOne(/(this\.\w+=function\(\){var (\w+),(\w+);)(\w+\.\w+\?\([^()]+setTransform\(\3=\2<\3\?\3:\2,0,0,\3,(?:Math\.floor\(\([^)]+\)\/2\)[,)]){2},(?:[^)]+\),){2}[^)]+\):(?\w+)\.fillStyle=\w+\.\w+,\5\.fillRect\((?0,0,\w+\.\w+,\w+\.\w+)\)}})/g, '$1 if (makeMainMenuTransparent) $.clearRect($); else $4') // Track donations replaceOne(/(this\.\w+=function\((\w+),(\w+)\)\{)(\2===\w+\.\w+&&\(\w+\.\w+\((\w+\.\w+)\[0\],\5\[1\],\3\),this\.(\w+)\[12\]\+=\5\[1\],this\.\6\[16\]\+=\5\[0\]\),\3===\w+\.\w+&&\()/g, "$1 donationsTracker.logDonation($2, $3, $5[0]); $4") // Display donations for a player when clicking on them in the leaderboard // and skip handling clicks when clicking on an empty space (see the isEmptySpace // variable in the modified leaderboard click handler from the leaderboard filter) // match , 0 !== dG[x]) && fq.hB(x, 800, false, 0), replaceOne(/,(0!==\w+\.\w+\[(\w+)\])(\)&&\w+\.\w+\(\2,800,!1,0\),)/g, `, ${dict.game}.${dict.gIsTeamGame} && donationsTracker.displayHistory($2, ${rawPlayerNames}, ${gIsSingleplayer}), $1 && !isEmptySpace $3`); // Reset donation history and leaderboard filter when a new game is started replaceOne(new RegExp(`,this\\.${dictionary.playerBalances}.fill\\(0\\),`, "g"), "$& donationsTracker.reset(), leaderboardFilter.reset(), "); { // Player list and leaderboard filter tabs // Draw player list button const uiOffset = dictionary.uiSizes + "." + dictionary.gap; const { groups: { drawFunction, topBarHeight } } = replaceOne(/(=1;function (?\w+)\(\){[^}]+?(?\w+)\.fillRect\(0,(?\w+),\w+,1\),(?:\3\.fillRect\([^()]+\),)+\3\.font=\w+,(\w+\.\w+)\.textBaseline\(\3,1\),\5\.textAlign\(\3,1\),\3\.fillText\(\w+\.\w+\[65\],Math\.floor\()(\w+)\/2\),(Math\.floor\(\w+\+\w+\/2\)\));/g, "$1($6 + $ - 22) / 2), $7; playerList.drawButton($, 12, 12, $ - 22);"); const buttonBoundsCheck = `utils.isPointInRectangle($, $, ${uiOffset} + 12, ${uiOffset} + 12, ${topBarHeight} - 22, ${topBarHeight} - 22)` // Handle player list button and leaderboard tabs mouseDown // and create a function for scrolling the leaderboard to the top replaceOne(/(this\.\w+=function\((?\w+),(?\w+)\){return!!\w+\(\2,\3\))&&(\(\w+=\w+\.\w+,[^}]+),!0\)/g, `leaderboardFilter.scrollToTop = function(){position = 0;}, $1 && ((${buttonBoundsCheck} && playerList.display(${rawPlayerNames}), true) && !($ - ${uiOffset} > leaderboardFilter.verticalClickThreshold && leaderboardFilter.handleMouseDown($ - ${uiOffset})) && $4),!0)`); // Handle player list button and leaderboard tabs hover // and create a function for repainting the leaderboard replaceOne(/(this\.\w+=function\((?\w+),(?\w+)\){)(var \w+,\w+=\w+\(\3\);return \w+\?\(\w+=(\w+),\(\5=\w+\(0,\5\+=(?:[^}]+,(?\w+\.\w+=!0)){2})/g, `leaderboardFilter.repaintLeaderboard = function() { ${drawFunction}(), $; }, $1 if (${buttonBoundsCheck}) { playerList.hoveringOverButton === false && (playerList.hoveringOverButton = true, ${drawFunction}(), $); } else { playerList.hoveringOverButton === true && (playerList.hoveringOverButton = false, ${drawFunction}(), $); } if (leaderboardFilter.setHovering( utils.isPointInRectangle($, $, ${uiOffset}, ${uiOffset} + leaderboardFilter.verticalClickThreshold, leaderboardFilter.windowWidth, leaderboardFilter.tabBarOffset), $ - ${uiOffset} )) return; $4`); } { // Display density of other players // Applies when the "Reverse Name/Balance" setting is off const { groups: { settingsSwitchNameAndBalance } } = replaceOne(/(,(?\w+\.\w+\.\w+\[7\]\.\w+)\?(?\w+)\(\w+,\w+,(?\w+),(?\w+)\+\.78\*(?\w+),(?\w+)\)):(\7\.fillText\(\w+\.\w+\.\w+\(\w+\.\w+\[(\w+)\]\),\4,\5\+\.78\*\6\))\)\)/g, `$1 : ($8, settings.showPlayerDensity && (settings.coloredDensity && ($.fillStyle = utils.textStyleBasedOnDensity($9)), $.fillText(utils.getDensity($9), $, $ + $ * 1.5)) ) ) )`); // Applies when the "Reverse Name/Balance" setting is on (default) replaceOne(/(function \w+\((\w+),(?\w+),(?\w+),(?\w+),(?\w+)\){)(\6\.fillText\((?\w+)\.(?\w+)\[\2\],\4,\5\)),(\2<(?\w+)\.(?\w+)&&2!==\8\.(?\w+)\[[^}]+)}/g, `$1 var ___id = $2; $7, $10; ${settingsSwitchNameAndBalance} && settings.showPlayerDensity && (settings.coloredDensity && ($.fillStyle = utils.textStyleBasedOnDensity(___id)), $.fillText(utils.getDensity(___id), $, $ + $)); }`); } { // Leaderboard filter // for the leaderboard draw function: replaceRawCode("function drawFunction(){a0A.clearRect(0,0,a04,y9),a0A.fillStyle=aZ.lE,a0A.fillRect(0,0,a04,a0F),a0A.fillStyle=aZ.kZ,a0A.fillRect(0,a0F,a04,y9-a0F),leaderboardPositionsById[game.playerId]>=position&&a0Z(leaderboardPositionsById[game.playerId]-position,aZ.kw),0!==leaderboardPositionsById[game.playerId]&&0===position&&a0Z(0,aZ.lJ),-1!==a0P&&a0Z(a0P,aZ.kd),a0A.fillStyle=aZ.gF,a0A.fillRect(0,a0F,a04,1),a0A.fillRect(0,0,a04,b0.ur),a0A.fillRect(0,0,b0.ur,y9),a0A.fillRect(a04-b0.ur,0,b0.ur,y9),a0A.fillRect(0,y9-b0.ur,a04,b0.ur),", `var leaderboardHasChanged = true; this.playerPos = game.playerId; function updateFilteredLb() { if (!leaderboardHasChanged) return; leaderboardFilter.filteredLeaderboard = leaderboardFilter.playersToInclude .map(id => leaderboardPositionsById[id]).sort((a, b) => a - b); leaderboardHasChanged = false; this.playerPos = leaderboardFilter.filteredLeaderboard.indexOf(leaderboardPositionsById[game.playerId]); } function drawFunction() { a0A.clearRect(0, 0, a04, y9), a0A.fillStyle = aZ.lE, a0A.fillRect(0, 0, a04, a0F), a0A.fillStyle = aZ.kZ, a0A.fillRect(0, a0F, a04, y9 - a0F); if (leaderboardFilter.enabled) updateFilteredLb(); var playerPos = (leaderboardFilter.enabled ? this.playerPos : leaderboardPositionsById[game.playerId] ); if (leaderboardFilter.hoveringOverTabs) a0P = -1; if (leaderboardFilter.enabled && a0P >= leaderboardFilter.filteredLeaderboard.length) a0P = -1; playerPos >= position && a0Z(playerPos - position, aZ.kw), 0 !== leaderboardPositionsById[game.playerId] && 0 === position && a0Z(0, aZ.lJ), -1 !== a0P && a0Z(a0P, aZ.kd), a0A.fillStyle = aZ.kZ, //console.log("drawing", a0P), a0A.clearRect(0, y9 - leaderboardFilter.tabBarOffset, a04, leaderboardFilter.tabBarOffset); a0A.fillRect(0, y9 - leaderboardFilter.tabBarOffset, a04, leaderboardFilter.tabBarOffset); a0A.fillStyle = aZ.gF, a0A.fillRect(0, a0F, a04, 1), a0A.fillRect(0, y9 - leaderboardFilter.tabBarOffset, a04, 1), leaderboardFilter.drawTabs(a0A, a04, y9 - leaderboardFilter.tabBarOffset, aZ.kw), a0A.fillRect(0, 0, a04, b0.ur), a0A.fillRect(0, 0, b0.ur, y9), a0A.fillRect(a04 - b0.ur, 0, b0.ur, y9), a0A.fillRect(0, y9 - b0.ur, a04, b0.ur),`) replaceRawCode("var hZ,eh=leaderboardPositionsById[game.playerId]= result.length - windowHeight) position = (result.length > windowHeight ? result.length : windowHeight) - windowHeight; //if (position >= result.length) position = result.length - 1; for (a0A.font = a07, aY.g0.textAlign(a0A, 0), hZ = windowHeight - eh; 0 <= hZ; hZ--) { const pos = result[hZ + position]; if (pos !== undefined) a0a(leaderboardArray[pos]), a0b(hZ, pos, leaderboardArray[pos]); } for (aY.g0.textAlign(a0A, 2), hZ = windowHeight - eh; 0 <= hZ; hZ--) { const pos = result[hZ + position]; if (pos !== undefined) a0a(leaderboardArray[pos]), a0c(hZ, leaderboardArray[pos]); } } else { for (a0A.font = a07, aY.g0.textAlign(a0A, 0), hZ = windowHeight - eh; 0 <= hZ; hZ--) a0a(leaderboardArray[hZ + position]), a0b(hZ, hZ + position, leaderboardArray[hZ + position]); for (aY.g0.textAlign(a0A, 2), hZ = windowHeight - eh; 0 <= hZ; hZ--) a0a(leaderboardArray[hZ + position]), a0c(hZ, leaderboardArray[hZ + position]); }`) // in the leaderboard resize handler: make space for the tab buttons at the bottom of the leaderboard replaceRawCode(",a09.height=y9,a09_ctx=a09.getContext(\"2d\",{alpha:!0}),a0D=.025*a04,a06=.16*a04,a0E=0*a04,a0F=Math.floor(.45*a0D+a06),a0G=(y9-a06-2*a0D-a0E)/a08,a05=aY.g0.g1(1,Math.floor(.55*a06)),", `,a09.height=y9,a09_ctx=a09.getContext("2d",{alpha:!0}),a0D=.025*a04,a06=.16*a04,a0E=0*a04,a0F=Math.floor(.45*a0D+a06),a0G=(y9-a06-2*a0D-a0E)/a08, a09.height = y9 += a0G, leaderboardFilter.tabBarOffset = Math.floor(a0G * 1.3), leaderboardFilter.verticalClickThreshold = y9 - leaderboardFilter.tabBarOffset, leaderboardFilter.windowWidth = a04, a05=aY.g0.g1(1,Math.floor(.55*a06)),`) // Set the leaderboardHasChanged flag on leaderboard updates replaceRawCode("for(var eM=a0q-1;0<=eM;eM--)a14[eM]=jR[eM],a15[eM]=a8.f8[jR[eM]];a14[a0q]=a0l[b.ed],a15[a0q]=a8.f8[b.ed]", `for(var eM=a0q-1;0<=eM;eM--)a14[eM]=jR[eM],a15[eM]=a8.f8[jR[eM]];a14[a0q]=a0l[b.ed],a15[a0q]=a8.f8[b.ed]; leaderboardHasChanged = true;`); // handle clicking on a player in the leaderboard replaceRawCode("var a0p=a0q(fJ);return ag.tQ()&&-1!==a0P&&(a0P=-1,a0Y(),b3.d1=!0),b3.dY-a0Q<350&&a0T===a0p&&-1!==(a0p=(a0p=yr(-1,a0p,windowHeight))!==windowHeight&&vU(x,y)?a0p:-1)&&(x=leaderboardArray[a0p+position],a0p===windowHeight-1&&leaderboardPositionsById[game.playerId]>=position+windowHeight-1&&(x=game.playerId),", `var a0p = a0q(fJ); var isEmptySpace = false; return ag.tQ() && -1 !== a0P && (a0P = -1, a0Y(), b3.d1 = !0), b3.dY - a0Q < 350 && a0T === a0p && -1 !== (a0p = (a0p = yr(-1, a0p, windowHeight)) !== windowHeight && vU(x, y) ? a0p : -1) && (x = (leaderboardFilter.enabled ? (updateFilteredLb(), leaderboardArray[leaderboardFilter.filteredLeaderboard[a0p + position] ?? (isEmptySpace = true, leaderboardPositionsById[game.playerId])]) : leaderboardArray[a0p + position]), a0p === windowHeight - 1 && (leaderboardFilter.enabled ? this.playerPos : leaderboardPositionsById[game.playerId]) >= position + windowHeight - 1 && (x = game.playerId), !isEmptySpace && `); // Get clan parsing function replaceRawCode(`this.uI=function(username){var uK,uJ=username.indexOf("[");return!(uJ<0)&&1<(uK=username.indexOf("]"))-uJ&&uK-uJ<=8?username.substring(uJ+1,uK).toUpperCase().trim():null}`, `this.uI=function(username){var uK,uJ=username.indexOf("[");return!(uJ<0)&&1<(uK=username.indexOf("]"))-uJ&&uK-uJ<=8?username.substring(uJ+1,uK).toUpperCase().trim():null}, leaderboardFilter.parseClanFromPlayerName = this.uI;`); } { // Hovering tooltip replaceRawCode("this.click=function(g8,g9,tE){var fT=aj.fU(g8),fV=aj.fW(g9),fX=aj.fY(fT,fV),fZ=aj.fa(fX);return!(!aj.fb(fT,fV)||(fT=(b7.cv.fv()?.025:.0144)*aK.fw,fV=performance.now(),Math.abs(g8-uu)>fT)||Math.abs(g9-uv)>fT||dY+500 fT) || Math.abs(g9 - uv) > fT || dY + 500 < fV) && (dY = fV, tE && function(g8, g9, fZ) { a2.eb(fZ) || -1 === (g8 = ak.ff.vR(g8, g9)) ? k.vQ(fZ) : k.vS(g8) }(g8, g9, fZ),`) replaceRawCode("aK.nH=(window.devicePixelRatio||1)*aEr,", `aK.nH = (window.devicePixelRatio || 1) * aEr, hoveringTooltip.canvasPixelScale = aK.nH,`) } // Disable built-in Territorial.io error reporting replaceOne(/window\.addEventListener\("error",function (\w+)\((\w+)\){/g, '$& window.removeEventListener("error", $1); return alert("Error:\\n" + $2.filename + " " + $2.lineno + " " + $2.colno + " " + $2.message);'); console.log('Removing ads...'); // Remove ads script = script.replace('//api.adinplay.com/libs/aiptag/pub/TRT/territorial.io/tag.min.js',''); console.log("Formatting code..."); exposeVarsToGlobalScope = true; if (exposeVarsToGlobalScope && script.startsWith("\"use strict\"; (function () {") && script.endsWith("})();")) script = script.slice("\"use strict\"; (function () {".length, -"})();".length); if (exposeVarsToGlobalScope && script.startsWith("(function () {") && script.endsWith("})();")) script = script.slice("(function () {".length, -"})();".length); script = beautify(script, { "indent_size": "1", "indent_char": "\t", "max_preserve_newlines": "5", "preserve_newlines": true, "keep_array_indentation": false, "break_chained_methods": false, "indent_scripts": "normal", "brace_style": "collapse", //"brace_style": "expand", "space_before_conditional": true, "unescape_strings": false, "jslint_happy": false, "end_with_newline": false, "wrap_line_length": "250", "indent_inner_html": false, "comma_first": false, "e4x": false, "indent_empty_lines": false }); fs.writeFileSync("./build/game.js", script); console.log("Wrote ./build/game.js"); console.log("Build done");