From f4e8debce8b068eb3abc348c4a2c809fdcd83bf6 Mon Sep 17 00:00:00 2001 From: peshomir <80340328+peshomir@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:38:14 +0200 Subject: [PATCH] Fixes for game updates ^1.93.1; Update v0.6.3 Improvements to the build script: Regular expressions can now be generated and used automatically just by providing raw code segments of the original and the modified code; Dictionary expressions can be generated with a similar mechanism. Other changes: The settings controls are now programmatically generated; Removed the custom font setting because this was added to the base game; "Trebuchet MS" is now the default font for that setting instead of "sans-serif"; Removed the "hide all links" setting (the "Hide Links" option was removed from the base game), this could be replaced with a new "hide links" setting. --- build.js | 116 +++++++++++++++++++++++++++++++++++----------- static/fx_core.js | 102 ++++++++++++++++++++++++++++++---------- static/index.html | 42 +++++++++-------- 3 files changed, 190 insertions(+), 70 deletions(-) diff --git a/build.js b/build.js index 2a65234..4464c50 100644 --- a/build.js +++ b/build.js @@ -27,22 +27,67 @@ 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."); let 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 this function is 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); + const expressionMatchResult = replaceOne(expression, replacementString); + 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[word] !== undefined) 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[word] !== undefined) 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 }; +} + [ - /=(?\w+)\?"Players":"Bots"/g, + ///=(?\w+)\?"Players":"Bots"/g, /,(?\w+)=\(\w+=\w+\)<7\|\|9===\w+,/g, /=function\((\w+),(\w+),\w+\){\1===(?\w+)\?\w+\(175,\w+\.\w+\(18,\[(?\w+)\[\2\]\]\),1001,\2,\w+\(/g, // this one broke in 1.91.3 /{\w+===(?\w+)\?\w+\(175," Message to "/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+)\[\5\]\),\6(?\w+)\[\5\]\),\2\]\),\w+=!0\):\2=/g, // this one also broke in 1.91.3 /,\w+="Player: "\+(?\w+)\[\w+\],\w+=\(\w\+=" Balance: "\+\w+\.\w+\((?\w+)\[\w+\]\)\)\+\(" Territory: "\+\w+\.\w+\((?\w+)\[\w+\]\)\)\+\(" Coords: "/g, - /\((?\w+)=Math\.floor\(\(\w+\?\.0114:\.01296\)\*\w+\)\)/g, + ///\((?\w+)=Math\.floor\(\(\w+\?\.0114:\.01296\)\*\w+\)\)/g, /(function \w+\((\w+),(\w+),(\w+),(\w+),(\w+)\){\6\.fillText\((?\w+)\[\2\],\4,\5\)),(\2<(?\w+)&&2!==(?\w+)\[)/g, /,\w+=512,(?\w+)=\w+,(?\w+)&&\(\1=\w+\.\w+\(\)\),\w+=\1-\w+,\w+=0,/g -].forEach(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; +].forEach(matchDictionaryExpression); + +const rawCodeSegments = [ + "[0]=aV.nU[70],a0T[1]=@gIsSingleplayer?aV.nU[71]:aV.nU[72],", + "?(this.gB=Math.floor(.0536*aK.fw),g5=aK.g5-4*@uiSizes.@gap-this.gB):" +] + +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()); // Replace assets @@ -50,28 +95,34 @@ const assets = require('./assets.js'); replaceOne(/(\(4,"crown",4,")[^"]+"\),/g, "$1" + assets.crownIcon + "\"),"); replaceOne(/(\(6,"territorial\.io",6,")[^"]+"\),/g, "$1" + assets.fxClientLogo + "\"),"); -// Add FXClient menu item in "More" menu +/*// Add FXClient menu item in "More" menu // match },ug[0][5]={name:a79,id:5,mf:90,oU:0,e8:0}, replaceOne(/(},(\w+\[0\])\[\d+\]={(\w+):\w+,(\w+):\d+,(\w+):90,(\w+):0,(\w+):0},)/g, '$1$2.push({$3:"FX Client v" + fx_version + " " + fx_update, $4: 20, $5: 0, $6: 0, $7: 70}),'); // Do not display hover effect on the last 2 items (territorial.io version and FX Client version) instead of only the last item // match 0 === a9P ? ug[a9P].length - 1 : ug[a9P].length : 1, -replaceOne(/(0===(\w+)\?(\w+)\[\2\]\.length)-1:(\3\[\2\]\.length:1,)/g, "$1 - 2 : $4"); +replaceOne(/(0===(\w+)\?(\w+)\[\2\]\.length)-1:(\3\[\2\]\.length:1,)/g, "$1 - 2 : $4");*/ +// Add FX Client version info to the game version window +replaceRawCode(`ar.aAx("MenuGameVersion")||ar.aAz(new aB3("ℹ️ "+aV.nU[84],gameVersion+"
\w+)\[\d\]="Interest",\2\[\d\]="Income",\2\[\d\]="Time"),(\w+=\w+-\w+\(\w+,100\),\((?\w+)=new Array\(\2\.length\)\)\[0\]=\w+)/g, - '$1, $.push("Max Troops", "Density"), $3'); // add labels + /*const { groups: { valuesArray } } = replaceOne(/(,(?\w+)\[\d\]="Interest",\2\[\d\]="Income",\2\[\d\]="Time"),(\w+=\w+-\w+\(\w+,100\),\((?\w+)=new Array\(\2\.length\)\)\[0\]=\w+)/g, + '$1, $.push("Max Troops", "Density"), $3'); // add labels*/ + const { valuesArray } = replaceRawCode(`,labels[5]=aV.nU[76],labels[6]=aV.nU[77],labels[7]=aV.nU[78],a0Z=tn-eT(tn,100),(valuesArray=new Array(labels.length))[0]=io?`, + `,labels[5]=aV.nU[76],labels[6]=aV.nU[77],labels[7]=aV.nU[78], + labels.push("Max Troops", "Density"), // add labels + a0Z=tn-eT(tn,100),(valuesArray=new Array(labels.length))[0]=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(${dictionary.playerTerritories}, ${dictionary.playerId}) ` + `: utils.getDensity(${dictionary.playerId}) }`); // increase the size of the side panel by 25% to make the text easier to read - // match this.w = Math.floor((o ? .1646 : .126) * cZ), - replaceOne(/(this\.\w+=Math\.floor\(\(\w+\?\.1646:\.126\))\*(\w+\),)/g, "$1 * 1.25 * $2"); + replaceOne(/(this\.\w+=Math\.floor\(\(\w+\.\w+\.\w+\(\)\?\.1646:\.126\))\*(\w+\.\w+\),)/g, "$1 * 1.25 * $2"); } // Increment win counter on wins @@ -82,7 +133,7 @@ replaceOne(/(=function\((\w+)\){)([^}]+),((\w+\(0),\w+<100\?(\w+\.\w+)\(11,(\[\w { // Add settings button and win count // render gear icon and win count -// cV.textAlign=cX,cV.textBaseline=cW,a03(a9Y.gb,a9Y.gc,a9Y.m5,a9Y.tD,ug[a9P][0].mf,ug[a9P][0].oU,ug[a9P][0].e8,0===yk,ug[a9P][0].name),a9O)) +/*// cV.textAlign=cX,cV.textBaseline=cW,a03(a9Y.gb,a9Y.gc,a9Y.m5,a9Y.tD,ug[a9P][0].mf,ug[a9P][0].oU,ug[a9P][0].e8,0===yk,ug[a9P][0].name),a9O)) // l(A.f3, A.f4, A.hw, A.nI, z[0].f7, z[0].mx, z[0].cm, 0 === t, z[0].name, .6); // cH.drawImage(settingsGearIcon,A.f3-A.hw/2,A.f4,A.nI,A.nI); // cH.font = bt + Math.floor(A.nI * 0.4) + bu; @@ -93,14 +144,26 @@ const { groups } = replaceOne(/((?\w+)\.textAlign=\w+,\2\.textBaseline=\ '$.imageSmoothingEnabled = false, ' + '$.font = "bold " + Math.floor($ * 0.4) + "px " + settings.fontName, ' + '(settings.displayWinCounter && !$ && $.fillText("Win count: " + wins_counter, Math.floor($ + $ / 2), Math.floor(($ + $ / 2) * 2.1))), ' + -'$'); +'$');*/ +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.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 -replaceOne(/(this\.\w+=function\((?\w+),(?\w+)\){[^}]+?)if\((?\w+=\w+\(\)),(?\w+)\)(?{for\([^}]+"Lobby ")/g, +/*replaceOne(/(this\.\w+=function\((?\w+),(?\w+)\){[^}]+?)if\((?\w+=\w+\(\)),(?\w+)\)(?{for\([^}]+"Lobby ")/g, '$1 $; ' + `var gearIconX = ${groups.x}-${groups.w}/2; ` + // if (y > (C.f3-C.hw/2) && y < ((C.f3-C.hw/2)+C.nI) && A > C.f4 && A < (C.f4 + C.nI)) WindowManager.openWindow("settings"); `if ($ > gearIconX && $ < (gearIconX+${groups.h}) && $ > ${groups.y} && $ < (${groups.y}+${groups.h})) return WindowManager.openWindow("settings"); ` + -'if ($) $'); +'if ($) $');*/ +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 @@ -117,21 +180,21 @@ replaceOne(/(this\.\w+=function\((?\w+),(?\w+)\){[^}]+?)if\((?.key)) return; $3"); } -// Enforce custom font name -script = script.replace(/"px sans-serif"/g, '"px " + settings.fontName'); +// 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") +//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+)\)}})/g, +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 @@ -148,9 +211,10 @@ replaceOne(new RegExp(`,${dictionary.playerBalances}=new Uint32Array\\(\\w+\\),` { // Player list // Draw player list button - const { groups: { drawFunction, topBarHeight } } = replaceOne(/(=1;function (?\w+)\(\){[^}]+?(?\w+)\.fillRect\(0,(?\w+),\w+,1\),(?:\3\.fillRect\([^()]+\),)+\3\.font=\w+,\3\.textBaseline=\w+,\3\.textAlign=\w+,\3\.fillText\(\w+,Math\.floor\()(\w+)\/2\),(Math\.floor\(\w+\+\w+\/2\)\));/g, - "$1($5 + $ - 22) / 2), $6; playerList.drawButton($, 12, 12, $ - 22);"); - const buttonBoundsCheck = `utils.isPointInRectangle($, $, ${dictionary.uiOffset} + 12, ${dictionary.uiOffset} + 12, ${topBarHeight} - 22, ${topBarHeight} - 22)` + 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 mouseDown replaceOne(/(this\.\w+=function\((?\w+),(?\w+)\){return!!\w+\(\2,\3\))&&(\(\w+=\w+\.\w+,)/g, `$1 && (${buttonBoundsCheck} && playerList.display(${dictionary.playerNames}), true) && $4`); @@ -162,7 +226,7 @@ replaceOne(new RegExp(`,${dictionary.playerBalances}=new Uint32Array\\(\\w+\\),` { // Display density of other players // Applies when the "Reverse Name/Balance" setting is off - const { groups: { settingsSwitchNameAndBalance } } = replaceOne(/(,(?\w+\.\w+\.\w+)\?(?\w+)\(\w+,\w+,(?\w+),(?\w+)\+\.78\*(?\w+),(?\w+)\)):(\7\.fillText\(\w+\.\w+\.\w+\(\w+\[(\w+)\]\),\4,\5\+\.78\*\6\))\)\)/g, + const { groups: { settingsSwitchNameAndBalance } } = replaceOne(/(,(?\w+\.\w+\.\w+\[7\]\.\w+)\?(?\w+)\(\w+,\w+,(?\w+),(?\w+)\+\.78\*(?\w+),(?\w+)\)):(\7\.fillText\(\w+\.\w+\.\w+\(\w+\[(\w+)\]\),\4,\5\+\.78\*\6\))\)\)/g, `$1 : ($8, settings.showPlayerDensity && $.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+)\[\2\],\4,\5\)),(\2<(?\w+)&&2!==(?\w+)\[)/g, diff --git a/static/fx_core.js b/static/fx_core.js index 68c8b87..f7b3b33 100644 --- a/static/fx_core.js +++ b/static/fx_core.js @@ -1,5 +1,5 @@ -const fx_version = '0.6.2.1'; // FX Client Version -const fx_update = 'Mar 10'; // FX Client Last Updated +const fx_version = '0.6.3'; // FX Client Version +const fx_update = 'Mar 26'; // FX Client Last Updated if (localStorage.getItem("fx_winCount") == undefined || localStorage.getItem("fx_winCount") == null) { var wins_counter = 0; @@ -16,14 +16,25 @@ function escapeHtml(unsafe) { } function KeybindsInput(containerElement) { - this.container = containerElement; + const header = document.createElement("p"); + header.innerText = "Attack Percentage Keybinds"; + const keybindContainer = document.createElement("div"); + keybindContainer.className = "arrayinput"; + const keybindAddButton = document.createElement("button"); + keybindAddButton.innerText = "Add"; + containerElement.append(header, keybindContainer, keybindAddButton); + this.container = keybindContainer; this.keys = [ "key", "type", "value" ]; this.objectArray = []; this.addObject = function () { this.objectArray.push({ key: "", type: "absolute", value: 1 }); this.displayObjects(); }; - document.getElementById("keybindAddButton").addEventListener("click", this.addObject.bind(this)); + this.update = function () { + 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 = ""; @@ -89,11 +100,11 @@ function KeybindsInput(containerElement) { } var settings = { - "fontName": "Trebuchet MS", - "showBotDonations": false, + //"fontName": "Trebuchet MS", + //"showBotDonations": false, "displayWinCounter": true, "useFullscreenMode": false, - "hideAllLinks": false, + //"hideAllLinks": false, "realisticNames": false, "showPlayerDensity": true, "densityDisplayStyle": "percentage", @@ -101,37 +112,80 @@ var settings = { "customBackgroundUrl": "", "attackPercentageKeybinds": [], }; +const discontinuedSettings = [ "hideAllLinks", "fontName" ]; let makeMainMenuTransparent = false; var settingsManager = new (function() { - var inputFields = { // (includes select menus) - fontName: document.getElementById("settings_fontname"), - customBackgroundUrl: document.getElementById("settings_custombackgroundurl"), - densityDisplayStyle: document.getElementById("settings_densityDisplayStyle") - }; - var checkboxFields = { - //showBotDonations: document.getElementById("settings_donations_bots"), - hideAllLinks: document.getElementById("settings_hidealllinks"), - realisticNames: document.getElementById("settings_realisticnames"), - displayWinCounter: document.getElementById("settings_displaywincounter"), - useFullscreenMode: document.getElementById("settings_usefullscreenmode"), - showPlayerDensity: document.getElementById("settings_showPlayerDensity"), - //customMapFileBtn: document.getElementById("settings_custommapfileinput") - }; + const settingsStructure = [ + //{ for: "fontName", type: "textInput", label: "Font name:", placeholder: "Enter font name", tooltip: "Name of the font to be used for rendering. For example: Arial, Georgia, sans-serif, serif, Comic Sans MS, ..."}, + { type: "button", text: "Reset Wins Counter", action: removeWins }, + { for: "displayWinCounter", type: "checkbox", label: "Display win counter" }, + { for: "useFullscreenMode", type: "checkbox", label: "Use fullscreen mode", note: "Note: fullscreen mode will trigger after you click anywhere on the page due to browser policy restrictions." }, + //{ for: "hideAllLinks", type: "checkbox", label: "Hide Links option also hides app store links" }, + { for: "realisticNames", type: "checkbox", label: "Realistic Bot Names" }, + { for: "showPlayerDensity", type: "checkbox", label: "Show player density" }, + { for: "densityDisplayStyle", type: "selectMenu", label: "Density value display style:", tooltip: "Controls how the territorial density value should be rendered", options: [ + { value: "percentage", label: "Percentage" }, + { value: "absoluteQuotient", label: "Value from 0 to 150 (BetterTT style)" } + ]}, + { for: "customBackgroundUrl", type: "textInput", label: "Custom main menu background:", placeholder: "Enter an image URL here", tooltip: "A custom image to be shown as the main menu background instead of the currently selected map." }, + KeybindsInput + ]; + const settingsContainer = document.querySelector(".settings .scrollable"); + var inputFields = {}; // (includes select menus) + var checkboxFields = {}; + var customElements = []; + settingsStructure.forEach(item => { + if (typeof item === "function") { + const container = document.createElement("div"); + customElements.push(new item(container)); + return settingsContainer.append(container); + } + const label = document.createElement("label"); + if (item.tooltip) label.title = item.tooltip; + const isValueInput = item.type.endsWith("Input"); + const element = document.createElement(isValueInput || item.type === "checkbox" ? "input" : item.type === "selectMenu" ? "select" : "button"); + if (item.type === "textInput") element.type = "text"; + if (item.placeholder) element.placeholder = item.placeholder; + if (isValueInput || item.type === "selectMenu") inputFields[item.for] = element; + if (item.text) element.innerText = item.text; + if (item.action) element.addEventListener("click", item.action); + if (item.label) label.append(item.label + " "); + if (item.note) { + const note = document.createElement("small"); + note.innerText = item.note; + label.append(document.createElement("br"), note) + } + if (item.options) item.options.forEach(option => { + const optionElement = document.createElement("option"); + optionElement.setAttribute("value", option.value); + optionElement.innerText = option.label; + element.append(optionElement); + }); + label.append(element); + if (item.type === "checkbox") { + element.type = "checkbox"; + const checkmark = document.createElement("span"); + checkmark.className = "checkmark"; + label.className = "checkbox"; + label.append(checkmark); + checkboxFields[item.for] = element; + } else label.append(document.createElement("br")); + settingsContainer.append(label, document.createElement("br")); + }); this.save = function() { Object.keys(inputFields).forEach(function(key) { settings[key] = inputFields[key].value.trim(); }); Object.keys(checkboxFields).forEach(function(key) { settings[key] = checkboxFields[key].checked; }); this.applySettings(); WindowManager.closeWindow("settings"); + discontinuedSettings.forEach(settingName => delete settings[settingName]); localStorage.setItem("fx_settings", JSON.stringify(settings)); // should probably firgure out a way to do this without reloading - // You can't do it, localstorages REQUIRE you to reload window.location.reload(); }; - let keybindsInput = new KeybindsInput(document.getElementById("keybinds")); this.syncFields = function() { Object.keys(inputFields).forEach(function(key) { inputFields[key].value = settings[key]; }); Object.keys(checkboxFields).forEach(function(key) { checkboxFields[key].checked = settings[key]; }); - keybindsInput.objectArray = settings.attackPercentageKeybinds; - keybindsInput.displayObjects(); + customElements.forEach(element => element.update()); }; this.resetAll = function() { if (!confirm("Are you Really SURE you want to RESET ALL SETTINGS back to the default?")) return; diff --git a/static/index.html b/static/index.html index 0068d66..4036721 100644 --- a/static/index.html +++ b/static/index.html @@ -39,19 +39,21 @@ + + * { + box-sizing: border-box; + } + + a { + color: rgb(225, 225, 255); + } + @@ -59,13 +61,13 @@