import assets from '../assets.js';
import ModUtils from '../modUtils.js';
export default (/** @type {ModUtils} */ modUtils) => {
// Disable built-in Territorial.io error reporting
modUtils.insertCode(
`window.removeEventListener("error", err);
msg = e.lineno + " " + e.colno + "|" + getStack(e); /* here */`,
`__fx.reportError(e, msg);
return alert("Error:\\n" + e.filename + " " + e.lineno + " " + e.colno + " " + e.message);`
)
// Render win count
modUtils.insertCode(`var s = Math.floor((Device.a1.largeUIEnabled() ? 0.018 : 0.0137) * h___.hz);
ctx.font = Util.qd.sS(0, Math.max(5, s));
Util.qd.textBaseline(ctx, 0);
Util.qd.textAlign(ctx, 2);
ctx.fillStyle = Colors.nl;
ctx.fillText(clientInfo.gameVersion, h___.w, 0);
/* here */`,
`const text = "Win count: " + __fx.wins.count;
const textLength = ctx.measureText(text).width;
const size = Math.max(5, s);
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillText(text, ctx.canvas.width - textLength - size / 2, size * 2);`)
// Make the main canvas context have an alpha channel if a custom background is being used
modUtils.insertCode(`mainCanvasElement = document.getElementById("canvasA");
if (Device.id === 2) { mainCanvasElement.style.webkitUserSelect = "none"; }
mainCanvas = mainCanvasElement.getContext("2d", { alpha: /* here */ false });`,
`__fx.makeMainMenuTransparent ? true :`)
modUtils.waitForMinification(() => applyPatches(modUtils))
}
//export const requiredVariables = ["game", "playerId", "playerData", "rawPlayerNames", "gIsSingleplayer", "playerTerritories"];
function applyPatches(/** @type {ModUtils} */ { replace, replaceOne, replaceRawCode, safeDictionary, matchOne, matchRawCode, escapeRegExp }) {
// Constants for easy usage of otherwise long variable access expressions
const dict = safeDictionary;
const playerId = `${dict.game}.${dict.playerId}`;
const rawPlayerNames = `${dict.playerData}.${dict.rawPlayerNames}`;
const gIsSingleplayer = `${dict.game}.${dict.gIsSingleplayer}`;
// Replace assets
replaceOne(/(\(4,"crown",4,")[^"]+"\),/g, "$1" + assets.crownIcon + "\"),");
replaceOne(/(\(6,"territorial\.io",6,")[^"]+"\),/g, "$1" + assets.fxClientLogo + "\"),");
replaceOne(/(\(22,"logo",8,")[^"]+"\)/g, "$1" + assets.smallLogo + "\")");
// Add FX Client version info to the game version window
replaceRawCode(`ar.oa(4,1,new s8(__L(),c.gameVersion+"
"+ah.aC5+"",`,
`ar.oa(4,1,new s8(__L(),c.gameVersion+"
"+ah.aC5+""
+ "
" + "FX Client v" + __fx.version + "
FX Client Discord server"
+ "
Github repository",`);
// Add update information
replaceRawCode(`new k("🚀 New Game Update","The game was updated! Please reload the game.",!0,[`,
`new k("🚀 New Game Update","The game was updated! Please reload the game."
+ "
FX Client is not yet compatible with the latest version of the game.
Updates should normally be available within a few hours.
You can still use FX to play in singleplayer mode.
",!0,[`
);
// 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]=__L(0,"Interest"),labels[6]=__L(),labels[7]=__L(),(truncatedLabels=new Array(labels.length)).fill(""),(valuesArray=new Array(labels.length))[0]=game.io?`,
`,labels[5]=__L(0,"Interest"),labels[6]=__L(),labels[7]=__L(),
labels.push("Max Troops", "Density"), // add labels
(truncatedLabels=new Array(labels.length)).fill(""),(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 ? __fx.utils.getMaxTroops(${dict.playerData}.${dict.playerTerritories}, ${playerId}) `
+ `: __fx.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){a8.gD[sE]&&(o.ha(sE,2),b.h9<100?xD(0,__L([a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0):xD(0,__L([a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0))`,
`=function(sE){
if (${playerId} === sE && !${gIsSingleplayer} && !${dict.game}.${dict.gIsReplay})
__fx.wins.count++, window.localStorage.setItem("fx_winCount", __fx.wins.count),
xD(0,"Your Win Count is now " + __fx.wins.count,3,sE,ad.gN,ad.kl,-1,!0);
a8.gD[sE]&&(o.ha(sE,2),b.h9<100?xD(0,__L([a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0):xD(0,__L([a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0))`);
{ // Add settings button and custom lobby button
// add buttons
replaceRawCode(`,new nQ("☰
"+__L(),function(){aD6(3)},aa.ks),new nQ("",function(){at.d5(12)},aa.kg,!1)]`,
`,new nQ("☰
"+__L(),function(){aD6(3)},aa.ks),new nQ("",function(){at.d5(12)},aa.kg,!1),
new nQ("FX Client settings", function() { __fx.WindowManager.openWindow("settings"); }, "rgba(0, 0, 20, 0.5)"),
new nQ("Join/Create custom lobby", function() { __fx.customLobby.showJoinPrompt(); }, "rgba(20, 9, 77, 0.5)")]`)
// set position
replaceRawCode(`aZ.g5.vO(aD3[3].button,x+a0S+gap,a3X+h+gap,a0S,h);`,
`aZ.g5.vO(aD3[3].button,x+a0S+gap,a3X+h+gap,a0S,h);
aZ.g5.vO(aD3[5].button, x, a3X + h * 2 + gap * 2, a0S * 2 + gap, h / 3);
aZ.g5.vO(aD3[6].button, x, a3X + h * 2.33 + gap * 3, a0S * 2 + gap, h / 3);`);
}
{ // Keybinds
// match required variables
const { 0: match, groups: { attackBarObject, setRelative } } = matchOne(/:\w+\.\w+\(\w+,8\)\?(?\w+)\.(?\w+)\(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; }; "
+ "__fx.keybindFunctions.setAbsolute = this.setAbsolutePercentage; "
+ `__fx.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 (__fx.keybindHandler($.key)) return; $3");
}
// Set the default font to Trebuchet MS
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 (__fx.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")
// Clear canvas background if a custom background is being used
replaceRawCode(`,this.qk=function(){var a4n,a4m;aq.pd?(a4m=aL.gA/aq.eE,a4n=aL.gF/aq.eF,canvas.setTransform(a4m=a4n\w+)\(\){[^}]+?(?