Compare commits
5 Commits
05f59c31bd
...
23a272385c
Author | SHA1 | Date |
---|---|---|
|
23a272385c | |
|
1b5a60d624 | |
|
ade2d45708 | |
|
998ecc3bd4 | |
|
14c94507f3 |
210
build.js
210
build.js
|
@ -15,127 +15,135 @@ fs.writeFileSync("./build/index.html", fs.readFileSync("./build/index.html").toS
|
|||
fs.writeFileSync("./build/sw.js", fs.readFileSync("./build/sw.js").toString().replace("buildTimestamp", buildTimestamp));
|
||||
|
||||
const buildClientCode = () => /** @type {Promise<void>} */(new Promise((resolve, reject) => {
|
||||
console.log("Building client code...");
|
||||
webpack({
|
||||
mode: 'production',
|
||||
entry: { fxClient: "./src/main.js" },
|
||||
output: {
|
||||
path: path.resolve(import.meta.dirname, 'build'),
|
||||
filename: 'fx.bundle.js',
|
||||
path: path.resolve(import.meta.dirname, 'build'),
|
||||
filename: 'fx.bundle.js',
|
||||
},
|
||||
}, (err, stats) => {
|
||||
if (err) {
|
||||
if (err.details) console.error(err.details);
|
||||
return reject(err);
|
||||
}
|
||||
const info = stats.toJson();
|
||||
if (stats.hasWarnings()) console.warn(info.warnings);
|
||||
if (stats.hasErrors()) {
|
||||
console.error(info.errors);
|
||||
reject("Webpack compilation error");
|
||||
const info = stats?.toJson();
|
||||
if (stats?.hasWarnings()) console.warn(info?.warnings);
|
||||
if (stats?.hasErrors()) {
|
||||
console.error(info?.errors);
|
||||
return reject("Webpack compilation error");
|
||||
}
|
||||
else resolve();
|
||||
fs.writeFileSync(
|
||||
"./build/fx.bundle.js",
|
||||
Buffer.concat([fs.readFileSync("./game/build_artefacts.js"), fs.readFileSync("./build/fx.bundle.js")])
|
||||
);
|
||||
resolve();
|
||||
});
|
||||
}));
|
||||
|
||||
let script = fs.readFileSync('./game/latest.js', { encoding: 'utf8' }).trim();
|
||||
async function patchGameCode() {
|
||||
|
||||
const exposeVarsToGlobalScope = true;
|
||||
// need to first remove the iife wrapper so the top-level functions aren't inlined
|
||||
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);
|
||||
let script = fs.readFileSync('./game/latest.js', { encoding: 'utf8' }).trim();
|
||||
|
||||
// uncompress strings
|
||||
// this will break if the sequence `"];` appears in one of the strings
|
||||
const stringArrayRaw = script.match(/var S=(\[.+?"\]);/)?.[1];
|
||||
if (stringArrayRaw === undefined) throw new Error("cannot find the string array");
|
||||
const stringArray = JSON.parse(stringArrayRaw);
|
||||
script = script.replace(/\bS\[(\d+)\]/g, (_match, index) => `"${stringArray[index]}"`);
|
||||
const exposeVarsToGlobalScope = true;
|
||||
// need to first remove the iife wrapper so the top-level functions aren't inlined
|
||||
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);
|
||||
|
||||
const modUtils = new ModUtils(minifyCode(script));
|
||||
// uncompress strings
|
||||
// this will break if the sequence `"];` appears in one of the strings
|
||||
const stringArrayRaw = script.match(/var S=(\[.+?"\]);/)?.[1];
|
||||
if (stringArrayRaw === undefined) throw new Error("cannot find the string array");
|
||||
const stringArray = JSON.parse(stringArrayRaw);
|
||||
script = script.replace(/\bS\[(\d+)\]/g, (_match, index) => `"${stringArray[index]}"`);
|
||||
|
||||
import applyPatches from './patches/main.js';
|
||||
console.log("Applying patches...");
|
||||
applyPatches(modUtils);
|
||||
const modUtils = new ModUtils(minifyCode(script));
|
||||
|
||||
// for versions ^1.99.5.2
|
||||
const minificationResult = UglifyJS.minify(modUtils.script, {
|
||||
"compress": { "arrows": false },
|
||||
"mangle": false
|
||||
});
|
||||
if (minificationResult.error) {
|
||||
console.log("error while passing through UglifyJS, replaceCode replacements might have caused errors");
|
||||
throw minificationResult.error;
|
||||
const { default: applyPatches } = await import('./patches/main.js');
|
||||
console.log("Applying patches...");
|
||||
applyPatches(modUtils);
|
||||
|
||||
// for versions ^1.99.5.2
|
||||
const minificationResult = UglifyJS.minify(modUtils.script, {
|
||||
"compress": { "arrows": false },
|
||||
"mangle": false
|
||||
});
|
||||
if (minificationResult.error) {
|
||||
console.log("error while passing through UglifyJS, replaceCode replacements might have caused errors");
|
||||
throw minificationResult.error;
|
||||
}
|
||||
if (minificationResult.warnings) console.log(minificationResult.warnings);
|
||||
modUtils.script = minificationResult.code;
|
||||
|
||||
const {
|
||||
matchDictionaryExpression,
|
||||
generateRegularExpression
|
||||
} = modUtils;
|
||||
const dictionary = modUtils.dictionary;
|
||||
|
||||
[
|
||||
/,this\.(?<gIsTeamGame>\w+)=this\.\w+<7\|\|9===this\.\w+,/g,
|
||||
/=function\((\w+),(\w+),\w+\){\1===(?<game>\w+)\.(?<playerId>\w+)\?\w+\(175," "\+\w+\(\d+,\[(?<playerData>\w+)\.(?<playerNames>\w+)\[\2\]\]\)\+": ",1001,\2,\w+\(/g,
|
||||
/function \w+\(\)\{if\(2===(?<game>\w+)\.(?<gameState>\w+)\)return 1;\w+\.\w+\(\),\1\.\2=2,\1\.\w+=\1.\w+\}/g,
|
||||
/(function \w+\((\w+),(?<fontSize>\w+),(?<x>\w+),(?<y>\w+),(?<canvas>\w+)\){)(\6\.fillText\((?<playerData>\w+)\.(?<playerNames>\w+)\[\2\],\4,\5\)),(\2<(?<game>\w+)\.(?<gHumans>\w+)&&2!==\8\.(?<playerStates>\w+)\[[^}]+)}/g,
|
||||
/\w+\.font=(?<fontGeneratorFunction>\w+\.\w+\.\w+)\(1,\.39\*this\.\w+\),/g
|
||||
].forEach(matchDictionaryExpression);
|
||||
|
||||
const rawCodeSegments = [
|
||||
`aQ.eI(e0)?aQ.eE(e0)?a38=__L([a38]):(player=aQ.eF(e0),oq=__L([b0.uS.zG(@playerData.@rawPlayerNames[player],b0.p9.qQ(0,10),150)])+" ",oq=(oq+=__L([b0.wx.a07(playerData.@playerBalances[player])])+" ")+__L([b0.wx.a07(playerData.@playerTerritories[player])])+" ",`,
|
||||
"1===a.b?this.@gLobbyMaxJoin=this.@gHumans:this.gLobbyMaxJoin=this.@data.@playerCount,this.tZ=this.gLobbyMaxJoin,this.@gBots=this.gLobbyMaxJoin-this.gHumans,this.sg=0,",
|
||||
"[0]=__L(),@strs[1]=@game.@gIsSingleplayer?__L():__L(),",
|
||||
"?(this.gB=Math.floor(.066*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);`,
|
||||
`var dt=@MenuManager.@getState();if(6===dt){if(4211===d)`
|
||||
]
|
||||
|
||||
rawCodeSegments.forEach(code => {
|
||||
const { expression } = generateRegularExpression(code, true);
|
||||
//console.log(expression);
|
||||
matchDictionaryExpression(expression);
|
||||
});
|
||||
|
||||
modUtils.executePostMinifyHandlers();
|
||||
script = modUtils.script;
|
||||
|
||||
// the dictionary should maybe get embedded into one of the files in the bundle
|
||||
fs.writeFileSync(
|
||||
"./game/build_artefacts.js",
|
||||
`const buildTimestamp = "${buildTimestamp}"; const dictionary = ${JSON.stringify(dictionary)};\n`
|
||||
);
|
||||
|
||||
console.log("Formatting code...");
|
||||
|
||||
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");
|
||||
}
|
||||
if (minificationResult.warnings) console.log(minificationResult.warnings);
|
||||
modUtils.script = minificationResult.code;
|
||||
|
||||
const {
|
||||
matchDictionaryExpression,
|
||||
generateRegularExpression
|
||||
} = modUtils;
|
||||
const dictionary = modUtils.dictionary;
|
||||
if (!process.argv.includes("--skip-patching")) await patchGameCode();
|
||||
if (!process.argv.includes("--patch-only")) await buildClientCode();
|
||||
|
||||
[
|
||||
/,this\.(?<gIsTeamGame>\w+)=this\.\w+<7\|\|9===this\.\w+,/g,
|
||||
/=function\((\w+),(\w+),\w+\){\1===(?<game>\w+)\.(?<playerId>\w+)\?\w+\(175," "\+\w+\(\d+,\[(?<playerData>\w+)\.(?<playerNames>\w+)\[\2\]\]\)\+": ",1001,\2,\w+\(/g,
|
||||
/function \w+\(\)\{if\(2===(?<game>\w+)\.(?<gameState>\w+)\)return 1;\w+\.\w+\(\),\1\.\2=2,\1\.\w+=\1.\w+\}/g,
|
||||
/(function \w+\((\w+),(?<fontSize>\w+),(?<x>\w+),(?<y>\w+),(?<canvas>\w+)\){)(\6\.fillText\((?<playerData>\w+)\.(?<playerNames>\w+)\[\2\],\4,\5\)),(\2<(?<game>\w+)\.(?<gHumans>\w+)&&2!==\8\.(?<playerStates>\w+)\[[^}]+)}/g,
|
||||
/\w+\.font=(?<fontGeneratorFunction>\w+\.\w+\.\w+)\(1,\.39\*this\.\w+\),/g
|
||||
].forEach(matchDictionaryExpression);
|
||||
|
||||
const rawCodeSegments = [
|
||||
`aQ.eI(e0)?aQ.eE(e0)?a38=__L([a38]):(player=aQ.eF(e0),oq=__L([b0.uS.zG(@playerData.@rawPlayerNames[player],b0.p9.qQ(0,10),150)])+" ",oq=(oq+=__L([b0.wx.a07(playerData.@playerBalances[player])])+" ")+__L([b0.wx.a07(playerData.@playerTerritories[player])])+" ",`,
|
||||
"1===a.b?this.@gLobbyMaxJoin=this.@gHumans:this.gLobbyMaxJoin=this.@data.@playerCount,this.tZ=this.gLobbyMaxJoin,this.@gBots=this.gLobbyMaxJoin-this.gHumans,this.sg=0,",
|
||||
"[0]=__L(),@strs[1]=@game.@gIsSingleplayer?__L():__L(),",
|
||||
"?(this.gB=Math.floor(.066*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);`,
|
||||
`var dt=@MenuManager.@getState();if(6===dt){if(4211===d)`
|
||||
]
|
||||
|
||||
rawCodeSegments.forEach(code => {
|
||||
const { expression } = generateRegularExpression(code, true);
|
||||
//console.log(expression);
|
||||
matchDictionaryExpression(expression);
|
||||
});
|
||||
|
||||
modUtils.executePostMinifyHandlers();
|
||||
script = modUtils.script;
|
||||
|
||||
console.log("Building client code...")
|
||||
|
||||
await buildClientCode();
|
||||
// the dictionary should maybe get embedded into one of the files in the bundle
|
||||
fs.writeFileSync(
|
||||
"./build/fx.bundle.js",
|
||||
`const buildTimestamp = "${buildTimestamp}"; const dictionary = ${JSON.stringify(dictionary)};\n`
|
||||
+ fs.readFileSync("./build/fx.bundle.js").toString()
|
||||
);
|
||||
|
||||
console.log("Formatting code...");
|
||||
|
||||
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");
|
|
@ -6,7 +6,9 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node index.js",
|
||||
"build-only": "node build.js"
|
||||
"build-only": "node build.js",
|
||||
"build-client": "node build.js --skip-patching",
|
||||
"patch": "node build.js --patch-only"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -2,6 +2,15 @@ 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.utils.reportError(e, msg);
|
||||
return alert("Error:\\n" + e.filename + " " + e.lineno + " " + e.colno + " " + e.message);`
|
||||
)
|
||||
|
||||
modUtils.waitForMinification(() => applyPatches(modUtils))
|
||||
}
|
||||
//export const requiredVariables = ["game", "playerId", "playerData", "rawPlayerNames", "gIsSingleplayer", "playerTerritories"];
|
||||
|
@ -301,10 +310,6 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6
|
|||
replaceRawCode(`,this.hostnameIsValid=0<=window.location.hostname.toLowerCase().indexOf("territorial.io"),`,
|
||||
`,this.hostnameIsValid=true,`)
|
||||
|
||||
// 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
|
||||
replace('//api.adinplay.com/libs/aiptag/pub/TRT/territorial.io/tag.min.js', '');
|
||||
|
|
|
@ -1,23 +1,43 @@
|
|||
import { getSettings } from "./settings.js";
|
||||
import { getVar } from "./gameInterface.js";
|
||||
|
||||
const utils = new (function() {
|
||||
this.getMaxTroops = function(playerTerritories, playerID) { return (playerTerritories[playerID]*150).toString(); };
|
||||
this.getDensity = function(playerID, playerBalances = getVar("playerBalances"), playerTerritories = getVar("playerTerritories")) {
|
||||
if (getSettings().densityDisplayStyle === "percentage") return (((playerBalances[playerID] / ((playerTerritories[playerID] === 0 ? 1 : playerTerritories[playerID]) * 150)) * 100).toFixed(1) + "%");
|
||||
else return (playerBalances[playerID] / (playerTerritories[playerID] === 0 ? 1 : playerTerritories[playerID])).toFixed(1);
|
||||
};
|
||||
this.isPointInRectangle = function(x, y, rectangleStartX, rectangleStartY, width, height) {
|
||||
return x >= rectangleStartX && x <= rectangleStartX + width && y >= rectangleStartY && y <= rectangleStartY + height;
|
||||
};
|
||||
/** @param {CanvasRenderingContext2D} canvas @param {string} text */
|
||||
this.fillTextMultiline = function(canvas, text, x, y, maxWidth) {
|
||||
const lineHeight = parseInt(canvas.font.split(" ").find(part => part.endsWith("px")).slice(0, -2));
|
||||
text.split("\n").forEach((line, index) => canvas.fillText(line, x, y + index * lineHeight, maxWidth));
|
||||
}
|
||||
this.textStyleBasedOnDensity = function(playerID) {
|
||||
const playerBalances = getVar("playerBalances"), playerTerritories = getVar("playerTerritories");
|
||||
return `hsl(${playerBalances[playerID] / (playerTerritories[playerID] * 1.5)}, 100%, 50%, 1)`;
|
||||
}
|
||||
});
|
||||
export default utils
|
||||
// Example usage from game script: __fx.utils.getMaxTroops(...)
|
||||
|
||||
function getMaxTroops(playerTerritories, playerID) {
|
||||
return (playerTerritories[playerID] * 150).toString();
|
||||
};
|
||||
function getDensity(playerID, playerBalances = getVar("playerBalances"), playerTerritories = getVar("playerTerritories")) {
|
||||
if (getSettings().densityDisplayStyle === "percentage") return (((playerBalances[playerID] / ((playerTerritories[playerID] === 0 ? 1 : playerTerritories[playerID]) * 150)) * 100).toFixed(1) + "%");
|
||||
else return (playerBalances[playerID] / (playerTerritories[playerID] === 0 ? 1 : playerTerritories[playerID])).toFixed(1);
|
||||
};
|
||||
function isPointInRectangle(x, y, rectangleStartX, rectangleStartY, width, height) {
|
||||
return x >= rectangleStartX && x <= rectangleStartX + width && y >= rectangleStartY && y <= rectangleStartY + height;
|
||||
};
|
||||
/** @param {CanvasRenderingContext2D} canvas @param {string} text */
|
||||
function fillTextMultiline(canvas, text, x, y, maxWidth) {
|
||||
const lineHeight = parseInt(canvas.font.split(" ").find(part => part.endsWith("px")).slice(0, -2));
|
||||
text.split("\n").forEach((line, index) => canvas.fillText(line, x, y + index * lineHeight, maxWidth));
|
||||
}
|
||||
function textStyleBasedOnDensity(playerID) {
|
||||
const playerBalances = getVar("playerBalances"), playerTerritories = getVar("playerTerritories");
|
||||
return `hsl(${playerBalances[playerID] / (playerTerritories[playerID] * 1.5)}, 100%, 50%, 1)`;
|
||||
}
|
||||
function reportError(e, message) {
|
||||
message = e.filename + " " + e.lineno + " " + e.colno + " " + e.message + "\n" + message;
|
||||
fetch("https://fx.peshomir.workers.dev/stats/errors", {
|
||||
body: JSON.stringify({
|
||||
message,
|
||||
context: {
|
||||
swState: navigator.serviceWorker?.controller?.state,
|
||||
location: window.location.toString(),
|
||||
userAgent: navigator.userAgent,
|
||||
dictionary,
|
||||
buildTimestamp,
|
||||
scripts: Array.from(document.scripts).map(s => s.src)
|
||||
}
|
||||
}),
|
||||
method: "POST"
|
||||
}).catch(e => alert("Failed to report error: " + e));
|
||||
}
|
||||
|
||||
export default { getMaxTroops, getDensity, isPointInRectangle, fillTextMultiline, textStyleBasedOnDensity, reportError }
|
|
@ -1,4 +1,7 @@
|
|||
const truncate = n => parseFloat(n.toFixed(12));
|
||||
|
||||
export function KeybindsInput(/** @type {HTMLElement} */ containerElement) {
|
||||
|
||||
const header = document.createElement("p");
|
||||
header.innerText = "Attack Percentage Keybinds";
|
||||
const keybindContainer = document.createElement("div");
|
||||
|
@ -6,61 +9,70 @@ export function KeybindsInput(/** @type {HTMLElement} */ containerElement) {
|
|||
const keybindAddButton = document.createElement("button");
|
||||
keybindAddButton.innerText = "Add";
|
||||
containerElement.append(header, keybindContainer, keybindAddButton);
|
||||
|
||||
containerElement.className = "keybinds-input";
|
||||
this.container = keybindContainer;
|
||||
this.keys = [ "key", "type", "value" ];
|
||||
this.objectKeys = ["key", "type", "value"];
|
||||
this.objectArray = [];
|
||||
this.addObject = function () {
|
||||
this.objectArray.push({ key: "", type: "absolute", value: 0.8 });
|
||||
this.displayObjects();
|
||||
this.container.appendChild(createInputRow(this.objectArray.length - 1));
|
||||
keybindAddButton.scrollIntoView(false);
|
||||
};
|
||||
keybindAddButton.addEventListener("click", this.addObject.bind(this));
|
||||
this.update = function (settings) {
|
||||
this.objectArray = settings.attackPercentageKeybinds;
|
||||
this.displayObjects();
|
||||
}
|
||||
keybindAddButton.addEventListener("click", this.addObject.bind(this));
|
||||
this.displayObjects = function () {
|
||||
// Clear the content of the container
|
||||
this.container.innerHTML = "";
|
||||
if (this.objectArray.length === 0) return this.container.innerText = "No custom attack percentage keybinds added";
|
||||
// Loop through the array and display input fields for each object
|
||||
for (var i = 0; i < this.objectArray.length; i++) {
|
||||
var objectDiv = document.createElement("div");
|
||||
// Create input fields for each key
|
||||
this.keys.forEach(function (key) {
|
||||
let inputField = document.createElement(key === "type" ? "select" : "input");
|
||||
if (key === "type") {
|
||||
inputField.innerHTML = '<option value="absolute">Absolute</option><option value="relative">Relative</option>';
|
||||
inputField.addEventListener("change", this.updateObject.bind(this, i, key));
|
||||
} else if (key === "key") {
|
||||
inputField.type = "text";
|
||||
inputField.setAttribute("readonly", "");
|
||||
inputField.setAttribute("placeholder", "No key set");
|
||||
inputField.addEventListener("click", this.startKeyInput.bind(this, i, key));
|
||||
} else { // key === "value"
|
||||
const isAbsolute = this.objectArray[i].type === "absolute";
|
||||
inputField.type = isAbsolute ? "text" : "number";
|
||||
if (isAbsolute) inputField.addEventListener("click", this.convertIntoNumberInput.bind(this, i, key), { once: true });
|
||||
else inputField.setAttribute("step", "0.1");
|
||||
inputField.addEventListener("input", this.updateObject.bind(this, i, key));
|
||||
}
|
||||
if (key === "value" && this.objectArray[i].type === "absolute")
|
||||
inputField.value = this.objectArray[i][key] * 100 + "%";
|
||||
else inputField.value = this.objectArray[i][key];
|
||||
// Append input field to the object div
|
||||
objectDiv.appendChild(inputField);
|
||||
}, this);
|
||||
// Button to delete the object
|
||||
var deleteButton = document.createElement("button");
|
||||
deleteButton.textContent = "Delete";
|
||||
deleteButton.addEventListener("click", this.deleteObject.bind(this, i));
|
||||
// Append delete button to the object div
|
||||
objectDiv.appendChild(deleteButton);
|
||||
// Append the object div to the container
|
||||
this.container.appendChild(objectDiv);
|
||||
this.container.appendChild(createInputRow(i));
|
||||
}
|
||||
};
|
||||
const createInputRow = (i) => {
|
||||
var objectDiv = document.createElement("div");
|
||||
// Create input fields for each modifiable parameter
|
||||
this.objectKeys.forEach(key => {
|
||||
objectDiv.appendChild(this.createInputField(i, key))
|
||||
});
|
||||
// Button to delete the object
|
||||
var deleteButton = document.createElement("button");
|
||||
deleteButton.textContent = "Delete";
|
||||
deleteButton.addEventListener("click", this.deleteObject.bind(this, i));
|
||||
objectDiv.appendChild(deleteButton);
|
||||
|
||||
return objectDiv;
|
||||
}
|
||||
this.createInputField = function (i, property) {
|
||||
let inputField = document.createElement(property === "type" ? "select" : "input");
|
||||
if (property === "type") {
|
||||
inputField.innerHTML = '<option value="absolute">Absolute</option><option value="relative">Relative</option>';
|
||||
inputField.addEventListener("change", this.updateObject.bind(this, i, property));
|
||||
} else if (property === "key") {
|
||||
inputField.type = "text";
|
||||
inputField.setAttribute("readonly", "");
|
||||
inputField.setAttribute("placeholder", "No key set");
|
||||
inputField.addEventListener("click", this.startKeyInput.bind(this, i, property));
|
||||
} else { // property === "value"
|
||||
const isAbsolute = this.objectArray[i].type === "absolute";
|
||||
inputField.type = isAbsolute ? "text" : "number";
|
||||
if (isAbsolute) inputField.addEventListener("click", this.convertIntoNumberInput.bind(this, i, property), { once: true });
|
||||
else inputField.setAttribute("step", "0.1");
|
||||
inputField.addEventListener("input", this.updateObject.bind(this, i, property));
|
||||
}
|
||||
if (property === "value" && this.objectArray[i].type === "absolute")
|
||||
inputField.value = truncate(this.objectArray[i][property] * 100) + "%";
|
||||
else inputField.value = this.objectArray[i][property];
|
||||
|
||||
return inputField;
|
||||
};
|
||||
this.recreateInputField = function (index, property) {
|
||||
this.container.children[index].children[this.objectKeys.indexOf(property)].replaceWith(this.createInputField(index, property))
|
||||
};
|
||||
this.startKeyInput = function (index, property, event) {
|
||||
event.target.value = "Press any key";
|
||||
const handler = this.updateObject.bind(this, index, property);
|
||||
|
@ -68,25 +80,28 @@ export function KeybindsInput(/** @type {HTMLElement} */ containerElement) {
|
|||
event.target.addEventListener("blur", () => {
|
||||
event.target.removeEventListener('keydown', handler);
|
||||
event.target.value = this.objectArray[index][property];
|
||||
//this.displayObjects();
|
||||
}, { once: true });
|
||||
};
|
||||
this.convertIntoNumberInput = function (index, property, event) {
|
||||
event.target.value = event.target.value.slice(0, -1);
|
||||
event.target.type = "number";
|
||||
event.target.addEventListener("blur", () => {
|
||||
//event.target.value = this.objectArray[index][property];
|
||||
this.displayObjects();
|
||||
this.recreateInputField(index, property);
|
||||
}, { once: true });
|
||||
};
|
||||
this.updateObject = function (index, property, event) {
|
||||
if (index >= this.objectArray.length) return;
|
||||
// Update the corresponding property of the object in the array
|
||||
const value = property === "value" ? (
|
||||
this.objectArray[index].type === "absolute" ? parseFloat(event.target.value) / 100 : parseFloat(event.target.value)
|
||||
this.objectArray[index].type === "absolute"
|
||||
? truncate(parseFloat(event.target.value) / 100)
|
||||
: parseFloat(event.target.value)
|
||||
) : property === "key" ? event.key : event.target.value;
|
||||
this.objectArray[index][property] = value;
|
||||
if (property === "key") this.displayObjects();
|
||||
|
||||
if (property === "key") this.recreateInputField(index, property);
|
||||
// when the keybind's type (absolute or relative) is changed
|
||||
else if (property === "type") this.recreateInputField(index, "value");
|
||||
};
|
||||
this.deleteObject = function (index) {
|
||||
// Remove the object from the array
|
||||
|
|
|
@ -17,7 +17,7 @@ var settings = {
|
|||
realisticNames: false,
|
||||
showPlayerDensity: true,
|
||||
coloredDensity: true,
|
||||
densityDisplayStyle: "percentage",
|
||||
densityDisplayStyle: "absoluteQuotient",
|
||||
hideBotNames: false,
|
||||
highlightClanSpawns: false,
|
||||
detailedTeamPercentage: false,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
{
|
||||
"version": "0.6.9.1",
|
||||
"lastUpdated": "Jul 3",
|
||||
"version": "0.6.10",
|
||||
"lastUpdated": "Jul 8",
|
||||
"changes": [
|
||||
"Updated FX Client to the latest game version"
|
||||
"Fixed floating point number imprecision when configuring keybinds",
|
||||
"Minor improvements to the keybinds settings menu",
|
||||
"Added automatic error reporting to simplify bug identification and removal",
|
||||
"The BetterTT density style (a value from 0 to 150) is now the default way that density is displayed. The percentage style will remain as is it for players who have already used FX Client, but you can change it in the settings at any time."
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue