Move archives to a separate branch
|  | @ -1,32 +0,0 @@ | |||
| name: Build and Publish to GitHub Pages | ||||
| 
 | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
| 
 | ||||
| jobs: | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
| 
 | ||||
|     steps: | ||||
|     - name: Checkout Repository | ||||
|       uses: actions/checkout@v2 | ||||
| 
 | ||||
|     - name: Set up Node.js | ||||
|       uses: actions/setup-node@v3 | ||||
|       with: | ||||
|         node-version: '20' | ||||
| 
 | ||||
|     - name: Install Dependencies | ||||
|       run: npm ci | ||||
| 
 | ||||
|     - name: Build Project | ||||
|       run: npm run build | ||||
| 
 | ||||
|     - name: Deploy to GitHub Pages | ||||
|       uses: peaceiris/actions-gh-pages@v3 | ||||
|       with: | ||||
|         github_token: ${{ secrets.GITHUB_TOKEN }} | ||||
|         publish_dir: ./build | ||||
|  | @ -1,4 +0,0 @@ | |||
| .vscode | ||||
| node_modules | ||||
| game | ||||
| build | ||||
| Before Width: | Height: | Size: 15 KiB | 
								
									
									
										
											
												BIN
											
										
									
									assets/logo.png
									
									
									
									
								
								
							
							| Before Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 91 KiB | 
| Before Width: | Height: | Size: 103 KiB | 
| Before Width: | Height: | Size: 99 KiB | 
| Before Width: | Height: | Size: 17 KiB | 
								
									
									
										
											394
										
									
									build.js
									
									
									
									
								
								
							
							|  | @ -1,394 +0,0 @@ | |||
| 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.");
 | ||||
| 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 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);
 | ||||
| 	const expressionMatchResult = replaceOne(expression, replacementString); | ||||
| 	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 }; | ||||
| } | ||||
| 
 | ||||
| [ | ||||
| 	///=(?<gIsSingleplayer>\w+)\?"Players":"Bots"/g,
 | ||||
| 	/,(?<gIsTeamGame>\w+)=\(\w+=\w+\)<7\|\|9===\w+,/g, | ||||
| 	/=function\((\w+),(\w+),\w+\){\1===(?<playerId>\w+)\?\w+\(175,\w+\.\w+\(18,\[(?<playerNames>\w+)\[\2\]\]\),1001,\2,\w+\(/g, | ||||
| 	// this one broke in 1.91.3 /{\w+===(?<playerId>\w+)\?\w+\(175," Message to "/g,
 | ||||
| 	/\w+\.\w+\((\w+)\)\?\w+\.\w+\(\1\)\?(\w+)=(\w+\.\w+)\(13,\[\2\]\):\(\w+=\w+\.\w+\(\1\),\2=\3\(14,\[(?<playerNames>\w+)\[(\w+)\],(\w+\.\w+\.\w+\()(?<playerBalances>\w+)\[\5\]\),\6(?<playerTerritories>\w+)\[\5\]\),\2\]\),\w+=!0\):\2=/g, | ||||
| 	// this one also broke in 1.91.3 /,\w+="Player: "\+(?<playerNames>\w+)\[\w+\],\w+=\(\w\+="   Balance: "\+\w+\.\w+\((?<playerBalances>\w+)\[\w+\]\)\)\+\("   Territory: "\+\w+\.\w+\((?<playerTerritories>\w+)\[\w+\]\)\)\+\("   Coords: "/g,
 | ||||
| 	///\((?<uiOffset>\w+)=Math\.floor\(\(\w+\?\.0114:\.01296\)\*\w+\)\)/g,
 | ||||
| 	/(function \w+\((\w+),(\w+),(\w+),(\w+),(\w+)\){\6\.fillText\((?<playerNames>\w+)\[\2\],\4,\5\)),(\2<(?<gHumans>\w+)&&2!==(?<playerStates>\w+)\[)/g, | ||||
| 	/,\w+=512,(?<gLobbyMaxJoin>\w+)=\w+,(?<gIsSingleplayer>\w+)&&\(\1=\w+\.\w+\(\)\),\w+=\1-\w+,\w+=0,/g, | ||||
| 	/function \w+\(\)\{if\(2===(?<gameState>\w+)\)return 1;\w+\.\w+\(\),\1=2,\w+=\w+\}/g | ||||
| ].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
 | ||||
| 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 | ||||
| // 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");*/ | ||||
| // Add FX Client version info to the game version window
 | ||||
| //replaceRawCode(`ar.aAx("MenuGameVersion")||ar.aAz(new aB3("ℹ️ "+aV.nU[84],gameVersion+"<br><a href='"`,
 | ||||
| replaceRawCode(`ar.oa(4,1,new s8("ℹ️ "+Translations.txt[84],gameVersion+"<br><a href='"+ah.aC5+"' target='_blank'>"+ah.aC5+"</a>",`, | ||||
| `ar.oa(4,1,new s8("ℹ️ "+Translations.txt[84],gameVersion+"<br><a href='"+ah.aC5+"' target='_blank'>"+ah.aC5+"</a>"
 | ||||
| + "<br><br><b>" + "FX Client v" + fx_version + " " + fx_update + "<br><a href='https://discord.gg/dyxcwdNKwK' target='_blank'>FX Client Discord server</a>" | ||||
| + "<br><a href='https://github.com/fxclient/FXclient' target='_blank'>Github repository</a></b>",`);
 | ||||
| 
 | ||||
| // 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 { groups: { valuesArray } } = replaceOne(/(,(?<labelsArray>\w+)\[\d\]="Interest",\2\[\d\]="Income",\2\[\d\]="Time"),(\w+=\w+-\w+\(\w+,100\),\((?<valuesArray>\w+)=new Array\(\2\.length\)\)\[0\]=\w+)/g, | ||||
| 		'$1, $<labelsArray>.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(/(:(?<valueIndex>\w+)<7\?\w+\.\w+\.\w+\(valuesArray\[\2\]\)):(\w+\.\w+\(valuesArray\[7\]\))}/ | ||||
| 		.source.replace(/valuesArray/g, valuesArray), "g"), | ||||
| 		'$1 : $<valueIndex> === 7 ? $3 ' | ||||
| 		+ `: $<valueIndex> === 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
 | ||||
| 	replaceOne(/(this\.\w+=Math\.floor\(\(\w+\.\w+\.\w+\(\)\?\.1646:\.126\))\*(\w+\.\w+\),)/g, "$1 * 1.25 * $2"); | ||||
| } | ||||
| 
 | ||||
| // Increment win counter on wins
 | ||||
| /*replaceOne(/(=function\((\w+)\){)([^}]+),((\w+\(0),\w+<100\?(\w+\.\w+)\(11,(\[\w+\[\w+\]\])\):\6\(12,\7\),(3,\2,[^()]+?\))),(?<end>[^}]+},)/g, | ||||
| `$1 if (${dictionary.playerId} === $2) wins_counter++, window.localStorage.setItem("fx_winCount", wins_counter); ` + | ||||
| `$3, $4, $5, "Your Current Win Count is Now " + wins_counter, $8, $<end>`);*/ | ||||
| replaceRawCode(`=function(rC){n.hQ(rC,2),vm(0,h2<100?aV.s9(4,[jm[rC]]):aV.s9(12,[jm[rC]]),3,rC,aZ.gG,aZ.ka,-1,!0),`, | ||||
| 	`=function(rC){
 | ||||
| 		if (${dictionary.playerId} === rC && !${dictionary.gIsSingleplayer}) | ||||
| 			wins_counter++, window.localStorage.setItem("fx_winCount", wins_counter), | ||||
| 			vm(0,"Your Win Count is now " + wins_counter,3,rC,aZ.gG,aZ.ka,-1,!0); | ||||
| 	n.hQ(rC,2),vm(0,h2<100?aV.s9(4,[jm[rC]]):aV.s9(12,[jm[rC]]),3,rC,aZ.gG,aZ.ka,-1,!0),`);
 | ||||
| 
 | ||||
| 
 | ||||
| { // 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)) | ||||
| // 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;
 | ||||
| // cH.fillText("Win count: " + wins_counter, Math.floor(A.f3 + A.hw / 2), Math.floor((A.f4 + A.nI / 2) * 2.1));
 | ||||
| const { groups } = replaceOne(/((?<canvas>\w+)\.textAlign=\w+,\2\.textBaseline=\w+,\w+\((?<x>(?<coords>\w+).\w+),(?<y>\4.\w+),(?<w>\4.\w+),(?<h>\4.\w+),[^)]+\)),(?<end>(?<isMenuOpened>\w+)\)\))/g, '$1, ' + | ||||
| '$<canvas>.imageSmoothingEnabled = true, ' + | ||||
| '$<canvas>.drawImage(settingsGearIcon, $<x>-$<w>/2, $<y>, $<h>, $<h>), ' + | ||||
| '$<canvas>.imageSmoothingEnabled = false, ' + | ||||
| '$<canvas>.font = "bold " + Math.floor($<h> * 0.4) + "px " + settings.fontName, ' + | ||||
| '(settings.displayWinCounter && !$<isMenuOpened> && $<canvas>.fillText("Win count: " + wins_counter, Math.floor($<x> + $<w> / 2), Math.floor(($<y> + $<h> / 2) * 2.1))), ' + | ||||
| '$<end>');*/ | ||||
| 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
 | ||||
| /*replaceOne(/(this\.\w+=function\((?<mouseX>\w+),(?<mouseY>\w+)\){[^}]+?)if\((?<coordsGet>\w+=\w+\(\)),(?<isMenuOpened>\w+)\)(?<end>{for\([^}]+"Lobby ")/g, | ||||
| '$1 $<coordsGet>; ' + | ||||
| `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 ($<mouseX> > gearIconX && $<mouseX> < (gearIconX+${groups.h}) && $<mouseY> > ${groups.y} && $<mouseY> < (${groups.y}+${groups.h})) return WindowManager.openWindow("settings"); ` + | ||||
| 'if ($<isMenuOpened>) $<end>');*/ | ||||
| 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<jd+q6&&ar.v2(1))`, | ||||
| `(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 < jd + q6 && (ar.v2(1), true)) || (mouseX >= gap - q6 / 0.7 && mouseY < jd + q6 && WindowManager.openWindow("settings")) | ||||
| )`);
 | ||||
| } | ||||
| 
 | ||||
| { // Keybinds
 | ||||
| 	// match required variables
 | ||||
| 	const { 0: match, groups: { attackBarObject, setRelative } } = matchOne(/:"."===(\w+\.key)\?(?<attackBarObject>\w+)\.(?<setRelative>\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===(?<attackPercentage>\w+)\|\|\(1<\2&&\2\*\3-\3<1\/1024\?\2=\(\3\+1\/1024\)\/\3:\2<1)/g, | ||||
| 	"} this.setAbsolutePercentage = function(newPercentage) { $<attackPercentage> = 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+\((?<event>\w+)\){)([^}]+matched)/g.source.replace(/matched/g, escapeRegExp(match)), "g"), | ||||
| 	"$1 if (keybindHandler($<event>.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\]=(?<linksHidden>!this\.\w+\.\w+),)/g,
 | ||||
| //"$1 if (settings.hideAllLinks) $3[0] = $3[1] = $<linksHidden>; 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}[^)]+\):(?<canvas>\w+)\.fillStyle=\w+\.\w+,\5\.fillRect\((?<wholeCanvas>0,0,\w+\.\w+,\w+\.\w+)\)}})/g, | ||||
| 	'$1 if (makeMainMenuTransparent) $<canvas>.clearRect($<wholeCanvas>); else $4') | ||||
| 
 | ||||
| // Track donations
 | ||||
| replaceOne(/(this\.\w+=function\((\w+),(\w+)\)\{)(\2===\w+&&\(\w+\.\w+\((\w+\.\w+)\[0\],\5\[1\],\3\),this\.(\w+)\[12\]\+=\5\[1\],this\.\6\[16\]\+=\5\[0\]\),\3===\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+\(\2,800,!1,0\),)/g, | ||||
| 	`, ${dictionary.gIsTeamGame} && donationsTracker.displayHistory($2, ${dictionary.playerNames}, ${dictionary.gIsSingleplayer}), $1 && !isEmptySpace $3`); | ||||
| 
 | ||||
| // Reset donation history and leaderboard filter when a new game is started
 | ||||
| replaceOne(new RegExp(`,${dictionary.playerBalances}=new Uint32Array\\(\\w+\\),`, "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 (?<drawFunction>\w+)\(\){[^}]+?(?<canvas>\w+)\.fillRect\(0,(?<topBarHeight>\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 + $<topBarHeight> - 22) / 2), $7; playerList.drawButton($<canvas>, 12, 12, $<topBarHeight> - 22);"); | ||||
| 	const buttonBoundsCheck = `utils.isPointInRectangle($<x>, $<y>, ${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\((?<x>\w+),(?<y>\w+)\){return!!\w+\(\2,\3\))&&(\(\w+=\w+\.\w+,[^}]+),!0\)/g, | ||||
| 	`leaderboardFilter.scrollToTop = function(){position = 0;}, $1 && ((${buttonBoundsCheck} && playerList.display(${dictionary.playerNames}), true)
 | ||||
| 		&& !($<y> - ${uiOffset} > leaderboardFilter.verticalClickThreshold && leaderboardFilter.handleMouseDown($<x> - ${uiOffset})) && $4),!0)`);
 | ||||
| 	// Handle player list button and leaderboard tabs hover
 | ||||
| 	// and create a function for repainting the leaderboard
 | ||||
| 	replaceOne(/(this\.\w+=function\((?<x>\w+),(?<y>\w+)\){)(var \w+,\w+=\w+\(\3\);return \w+\?\(\w+=(\w+),\(\5=\w+\(0,\5\+=(?:[^}]+,(?<setRepaintNeeded>\w+\.\w+=!0)){2})/g, | ||||
| 	`leaderboardFilter.repaintLeaderboard = function() { ${drawFunction}(), $<setRepaintNeeded>; },
 | ||||
| 	$1 if (${buttonBoundsCheck}) { playerList.hoveringOverButton === false && (playerList.hoveringOverButton = true, ${drawFunction}(), $<setRepaintNeeded>); } | ||||
| 	else { playerList.hoveringOverButton === true && (playerList.hoveringOverButton = false, ${drawFunction}(), $<setRepaintNeeded>); } | ||||
| 	if (leaderboardFilter.setHovering( | ||||
| 		utils.isPointInRectangle($<x>, $<y>, ${uiOffset}, ${uiOffset} + leaderboardFilter.verticalClickThreshold, leaderboardFilter.windowWidth, leaderboardFilter.tabBarOffset), $<x> - ${uiOffset} | ||||
| 	)) return; $4`);
 | ||||
| } | ||||
| 
 | ||||
| { // Display density of other players
 | ||||
| 	// Applies when the "Reverse Name/Balance" setting is off
 | ||||
| 	const { groups: { settingsSwitchNameAndBalance } } = replaceOne(/(,(?<settingsSwitchNameAndBalance>\w+\.\w+\.\w+\[7\]\.\w+)\?(?<nameDrawingFunction>\w+)\(\w+,\w+,(?<x>\w+),(?<y>\w+)\+\.78\*(?<fontSize>\w+),(?<canvas>\w+)\)):(\7\.fillText\(\w+\.\w+\.\w+\(\w+\[(\w+)\]\),\4,\5\+\.78\*\6\))\)\)/g, | ||||
| 	`$1 : ($8, settings.showPlayerDensity && (settings.coloredDensity && ($<canvas>.fillStyle = utils.textStyleBasedOnDensity($9)), $<canvas>.fillText(utils.getDensity($9), $<x>, $<y> + $<fontSize> * 1.5)) ) ) )`); | ||||
| 	// Applies when the "Reverse Name/Balance" setting is on (default)
 | ||||
| 	replaceOne(/(function \w+\((\w+),(?<fontSize>\w+),(?<x>\w+),(?<y>\w+),(?<canvas>\w+)\){)(\6\.fillText\((?<playerNames>\w+)\[\2\],\4,\5\)),(\2<(?<gHumans>\w+)&&2!==(?<playerStates>\w+)\[[^}]+)}/g, | ||||
| 	`$1 var ___id = $2; $7, $9; ${settingsSwitchNameAndBalance} && settings.showPlayerDensity && (settings.coloredDensity && ($<canvas>.fillStyle = utils.textStyleBasedOnDensity(___id)), $<canvas>.fillText(utils.getDensity(___id), $<x>, $<y> + $<fontSize>)); }`); | ||||
| } | ||||
| 
 | ||||
| { // Leaderboard filter
 | ||||
| 	// for the leaderboard draw function:
 | ||||
| 	replaceRawCode("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[playerId]>=position&&a0Z(leaderboardPositionsById[playerId]-position,aZ.kw),0!==leaderboardPositionsById[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),", | ||||
| 		`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) { | ||||
| 			leaderboardFilter.filteredLeaderboard = leaderboardFilter.playersToInclude | ||||
| 				.map(id => leaderboardPositionsById[id]).sort((a, b) => a - b); | ||||
| 		} | ||||
| 		var playerPos = (leaderboardFilter.enabled | ||||
| 			? leaderboardFilter.filteredLeaderboard.indexOf(leaderboardPositionsById[playerId]) | ||||
| 			: leaderboardPositionsById[playerId] | ||||
| 		); | ||||
| 		this.playerPos = playerPos; | ||||
| 		if (leaderboardFilter.hoveringOverTabs) a0P = -1; | ||||
| 		if (leaderboardFilter.enabled && a0P >= leaderboardFilter.filteredLeaderboard.length) a0P = -1; | ||||
| 		playerPos >= position && a0Z(playerPos - position, aZ.kw), | ||||
| 		0 !== leaderboardPositionsById[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[playerId]<position+windowHeight-1?1:2;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]);", | ||||
| 		`var hZ, eh = playerPos < position + windowHeight - 1 ? 1 : 2;
 | ||||
| 		 | ||||
| 		if (leaderboardFilter.enabled) { | ||||
| 			let result = leaderboardFilter.filteredLeaderboard; | ||||
| 			if (position !== 0 && position >= 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(",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)),", | ||||
| 		`,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)),`)
 | ||||
| 	// 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[playerId]>=position+windowHeight-1&&(x=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 ? leaderboardArray[leaderboardFilter.filteredLeaderboard[a0p + position] ?? (isEmptySpace = true, leaderboardPositionsById[playerId])] : leaderboardArray[a0p + position]), a0p === windowHeight - 1 && (leaderboardFilter.enabled ? this.playerPos : leaderboardPositionsById[playerId]) >= | ||||
| 			position + windowHeight - 1 && (x = playerId), !isEmptySpace && `);
 | ||||
| } | ||||
| 
 | ||||
| { // 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<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),!1)", | ||||
| 		`hoveringTooltip.display = function(mouseX, mouseY) {
 | ||||
| 			var coordX = aj.fU(mouseX), coordY = aj.fW(mouseY), | ||||
| 				coord = aj.fY(coordX, coordY), point = aj.fa(coord); | ||||
| 			if (coordX < 0 || coordY < 0) return; | ||||
| 			k.vQ(point); | ||||
| 		} | ||||
| 		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 < 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), false)`)
 | ||||
| 	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"); | ||||
								
									
									
										
											35
										
									
									download.js
									
									
									
									
								
								
							
							|  | @ -1,35 +0,0 @@ | |||
| const downloadGame = () => new Promise(resolve => { | ||||
| // Download game
 | ||||
| // https://stackoverflow.com/a/11944984
 | ||||
| const https = require('https'); // or 'https' for https:// URLs
 | ||||
| const fs = require('fs'); | ||||
| 
 | ||||
| if (!fs.existsSync("./game")) fs.mkdirSync("./game"); | ||||
| const file = fs.createWriteStream("./game/latest.html"); | ||||
| // Download the game's code from the website
 | ||||
| const request = https.get("https://territorial.io", function (response) { | ||||
| 	// and save it to ./game/latest.html
 | ||||
| 	response.pipe(file); | ||||
| 
 | ||||
| 	// after download completed close filestream
 | ||||
| 	file.on("finish", () => { | ||||
| 		file.close(); | ||||
| 		console.log("Download Completed [downloaded to latest.html]"); | ||||
| 		fs.readFile('./game/latest.html', 'utf8', function (err, data) { | ||||
| 			if (err) throw err; | ||||
| 			// Extract the game script from the html
 | ||||
| 			// https://stackoverflow.com/a/14867897
 | ||||
| 			const scriptContent = data.substring( | ||||
| 				data.indexOf("<script>") + "<script>".length, | ||||
| 				data.lastIndexOf("</script>") | ||||
| 			); | ||||
| 			// Write the script to ./game/latest.js without any line breaks
 | ||||
| 			fs.writeFileSync("./game/latest.js", scriptContent.replace(/\r?\n|\r/g, "")); | ||||
| 			console.log("Wrote script to latest.js"); | ||||
| 			resolve(); | ||||
| 		}); | ||||
| 
 | ||||
| 	}); | ||||
| }); | ||||
| }); | ||||
| module.exports = downloadGame; | ||||
								
									
									
										
											2
										
									
									index.js
									
									
									
									
								
								
							
							|  | @ -1,2 +0,0 @@ | |||
| console.log("Building FXClient"); | ||||
| require("./download.js")().then(() => require("./build.js")); | ||||
|  | @ -1,953 +0,0 @@ | |||
| { | ||||
|   "name": "fxclient", | ||||
|   "version": "0.5.3", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "fxclient", | ||||
|       "version": "0.5.3", | ||||
|       "license": "ISC", | ||||
|       "dependencies": { | ||||
|         "js-beautify": "^1.14.11" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@isaacs/cliui": { | ||||
|       "version": "8.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", | ||||
|       "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", | ||||
|       "dependencies": { | ||||
|         "string-width": "^5.1.2", | ||||
|         "string-width-cjs": "npm:string-width@^4.2.0", | ||||
|         "strip-ansi": "^7.0.1", | ||||
|         "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", | ||||
|         "wrap-ansi": "^8.1.0", | ||||
|         "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=12" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@one-ini/wasm": { | ||||
|       "version": "0.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", | ||||
|       "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" | ||||
|     }, | ||||
|     "node_modules/@pkgjs/parseargs": { | ||||
|       "version": "0.11.0", | ||||
|       "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", | ||||
|       "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", | ||||
|       "optional": true, | ||||
|       "engines": { | ||||
|         "node": ">=14" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/abbrev": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", | ||||
|       "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", | ||||
|       "engines": { | ||||
|         "node": "^14.17.0 || ^16.13.0 || >=18.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ansi-regex": { | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", | ||||
|       "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", | ||||
|       "engines": { | ||||
|         "node": ">=12" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/chalk/ansi-regex?sponsor=1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ansi-styles": { | ||||
|       "version": "6.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", | ||||
|       "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", | ||||
|       "engines": { | ||||
|         "node": ">=12" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/chalk/ansi-styles?sponsor=1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/balanced-match": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", | ||||
|       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" | ||||
|     }, | ||||
|     "node_modules/brace-expansion": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", | ||||
|       "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", | ||||
|       "dependencies": { | ||||
|         "balanced-match": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/color-convert": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", | ||||
|       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", | ||||
|       "dependencies": { | ||||
|         "color-name": "~1.1.4" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=7.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/color-name": { | ||||
|       "version": "1.1.4", | ||||
|       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", | ||||
|       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" | ||||
|     }, | ||||
|     "node_modules/commander": { | ||||
|       "version": "10.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", | ||||
|       "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", | ||||
|       "engines": { | ||||
|         "node": ">=14" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/config-chain": { | ||||
|       "version": "1.1.13", | ||||
|       "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", | ||||
|       "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", | ||||
|       "dependencies": { | ||||
|         "ini": "^1.3.4", | ||||
|         "proto-list": "~1.2.1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cross-spawn": { | ||||
|       "version": "7.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", | ||||
|       "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", | ||||
|       "dependencies": { | ||||
|         "path-key": "^3.1.0", | ||||
|         "shebang-command": "^2.0.0", | ||||
|         "which": "^2.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/eastasianwidth": { | ||||
|       "version": "0.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", | ||||
|       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" | ||||
|     }, | ||||
|     "node_modules/editorconfig": { | ||||
|       "version": "1.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", | ||||
|       "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", | ||||
|       "dependencies": { | ||||
|         "@one-ini/wasm": "0.1.1", | ||||
|         "commander": "^10.0.0", | ||||
|         "minimatch": "9.0.1", | ||||
|         "semver": "^7.5.3" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "editorconfig": "bin/editorconfig" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=14" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/emoji-regex": { | ||||
|       "version": "9.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", | ||||
|       "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" | ||||
|     }, | ||||
|     "node_modules/foreground-child": { | ||||
|       "version": "3.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", | ||||
|       "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", | ||||
|       "dependencies": { | ||||
|         "cross-spawn": "^7.0.0", | ||||
|         "signal-exit": "^4.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=14" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/isaacs" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/glob": { | ||||
|       "version": "10.3.10", | ||||
|       "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", | ||||
|       "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", | ||||
|       "dependencies": { | ||||
|         "foreground-child": "^3.1.0", | ||||
|         "jackspeak": "^2.3.5", | ||||
|         "minimatch": "^9.0.1", | ||||
|         "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", | ||||
|         "path-scurry": "^1.10.1" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "glob": "dist/esm/bin.mjs" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=16 || 14 >=14.17" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/isaacs" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ini": { | ||||
|       "version": "1.3.8", | ||||
|       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", | ||||
|       "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" | ||||
|     }, | ||||
|     "node_modules/is-fullwidth-code-point": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", | ||||
|       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/isexe": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", | ||||
|       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" | ||||
|     }, | ||||
|     "node_modules/jackspeak": { | ||||
|       "version": "2.3.6", | ||||
|       "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", | ||||
|       "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", | ||||
|       "dependencies": { | ||||
|         "@isaacs/cliui": "^8.0.2" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=14" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/isaacs" | ||||
|       }, | ||||
|       "optionalDependencies": { | ||||
|         "@pkgjs/parseargs": "^0.11.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/js-beautify": { | ||||
|       "version": "1.14.11", | ||||
|       "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz", | ||||
|       "integrity": "sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==", | ||||
|       "dependencies": { | ||||
|         "config-chain": "^1.1.13", | ||||
|         "editorconfig": "^1.0.3", | ||||
|         "glob": "^10.3.3", | ||||
|         "nopt": "^7.2.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "css-beautify": "js/bin/css-beautify.js", | ||||
|         "html-beautify": "js/bin/html-beautify.js", | ||||
|         "js-beautify": "js/bin/js-beautify.js" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=14" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/lru-cache": { | ||||
|       "version": "10.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", | ||||
|       "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", | ||||
|       "engines": { | ||||
|         "node": "14 || >=16.14" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/minimatch": { | ||||
|       "version": "9.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", | ||||
|       "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", | ||||
|       "dependencies": { | ||||
|         "brace-expansion": "^2.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=16 || 14 >=14.17" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/isaacs" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/minipass": { | ||||
|       "version": "7.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", | ||||
|       "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", | ||||
|       "engines": { | ||||
|         "node": ">=16 || 14 >=14.17" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/nopt": { | ||||
|       "version": "7.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", | ||||
|       "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", | ||||
|       "dependencies": { | ||||
|         "abbrev": "^2.0.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "nopt": "bin/nopt.js" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": "^14.17.0 || ^16.13.0 || >=18.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/path-key": { | ||||
|       "version": "3.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", | ||||
|       "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/path-scurry": { | ||||
|       "version": "1.10.1", | ||||
|       "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", | ||||
|       "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", | ||||
|       "dependencies": { | ||||
|         "lru-cache": "^9.1.1 || ^10.0.0", | ||||
|         "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=16 || 14 >=14.17" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/isaacs" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/proto-list": { | ||||
|       "version": "1.2.4", | ||||
|       "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", | ||||
|       "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" | ||||
|     }, | ||||
|     "node_modules/semver": { | ||||
|       "version": "7.5.4", | ||||
|       "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", | ||||
|       "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", | ||||
|       "dependencies": { | ||||
|         "lru-cache": "^6.0.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "semver": "bin/semver.js" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=10" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/semver/node_modules/lru-cache": { | ||||
|       "version": "6.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", | ||||
|       "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", | ||||
|       "dependencies": { | ||||
|         "yallist": "^4.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=10" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/shebang-command": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", | ||||
|       "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", | ||||
|       "dependencies": { | ||||
|         "shebang-regex": "^3.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/shebang-regex": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", | ||||
|       "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/signal-exit": { | ||||
|       "version": "4.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", | ||||
|       "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", | ||||
|       "engines": { | ||||
|         "node": ">=14" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/isaacs" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/string-width": { | ||||
|       "version": "5.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", | ||||
|       "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", | ||||
|       "dependencies": { | ||||
|         "eastasianwidth": "^0.2.0", | ||||
|         "emoji-regex": "^9.2.2", | ||||
|         "strip-ansi": "^7.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=12" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/sponsors/sindresorhus" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/string-width-cjs": { | ||||
|       "name": "string-width", | ||||
|       "version": "4.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | ||||
|       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", | ||||
|       "dependencies": { | ||||
|         "emoji-regex": "^8.0.0", | ||||
|         "is-fullwidth-code-point": "^3.0.0", | ||||
|         "strip-ansi": "^6.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/string-width-cjs/node_modules/ansi-regex": { | ||||
|       "version": "5.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||||
|       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/string-width-cjs/node_modules/emoji-regex": { | ||||
|       "version": "8.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", | ||||
|       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" | ||||
|     }, | ||||
|     "node_modules/string-width-cjs/node_modules/strip-ansi": { | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||||
|       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||||
|       "dependencies": { | ||||
|         "ansi-regex": "^5.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/strip-ansi": { | ||||
|       "version": "7.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", | ||||
|       "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", | ||||
|       "dependencies": { | ||||
|         "ansi-regex": "^6.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=12" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/chalk/strip-ansi?sponsor=1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/strip-ansi-cjs": { | ||||
|       "name": "strip-ansi", | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||||
|       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||||
|       "dependencies": { | ||||
|         "ansi-regex": "^5.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { | ||||
|       "version": "5.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||||
|       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/which": { | ||||
|       "version": "2.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", | ||||
|       "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", | ||||
|       "dependencies": { | ||||
|         "isexe": "^2.0.0" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "node-which": "bin/node-which" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/wrap-ansi": { | ||||
|       "version": "8.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", | ||||
|       "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", | ||||
|       "dependencies": { | ||||
|         "ansi-styles": "^6.1.0", | ||||
|         "string-width": "^5.0.1", | ||||
|         "strip-ansi": "^7.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=12" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/chalk/wrap-ansi?sponsor=1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/wrap-ansi-cjs": { | ||||
|       "name": "wrap-ansi", | ||||
|       "version": "7.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", | ||||
|       "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", | ||||
|       "dependencies": { | ||||
|         "ansi-styles": "^4.0.0", | ||||
|         "string-width": "^4.1.0", | ||||
|         "strip-ansi": "^6.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=10" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/chalk/wrap-ansi?sponsor=1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { | ||||
|       "version": "5.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||||
|       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { | ||||
|       "version": "4.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", | ||||
|       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", | ||||
|       "dependencies": { | ||||
|         "color-convert": "^2.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       }, | ||||
|       "funding": { | ||||
|         "url": "https://github.com/chalk/ansi-styles?sponsor=1" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { | ||||
|       "version": "8.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", | ||||
|       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" | ||||
|     }, | ||||
|     "node_modules/wrap-ansi-cjs/node_modules/string-width": { | ||||
|       "version": "4.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | ||||
|       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", | ||||
|       "dependencies": { | ||||
|         "emoji-regex": "^8.0.0", | ||||
|         "is-fullwidth-code-point": "^3.0.0", | ||||
|         "strip-ansi": "^6.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||||
|       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||||
|       "dependencies": { | ||||
|         "ansi-regex": "^5.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/yallist": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", | ||||
|       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" | ||||
|     } | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@isaacs/cliui": { | ||||
|       "version": "8.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", | ||||
|       "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", | ||||
|       "requires": { | ||||
|         "string-width": "^5.1.2", | ||||
|         "string-width-cjs": "npm:string-width@^4.2.0", | ||||
|         "strip-ansi": "^7.0.1", | ||||
|         "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", | ||||
|         "wrap-ansi": "^8.1.0", | ||||
|         "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "@one-ini/wasm": { | ||||
|       "version": "0.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", | ||||
|       "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" | ||||
|     }, | ||||
|     "@pkgjs/parseargs": { | ||||
|       "version": "0.11.0", | ||||
|       "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", | ||||
|       "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", | ||||
|       "optional": true | ||||
|     }, | ||||
|     "abbrev": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", | ||||
|       "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==" | ||||
|     }, | ||||
|     "ansi-regex": { | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", | ||||
|       "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" | ||||
|     }, | ||||
|     "ansi-styles": { | ||||
|       "version": "6.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", | ||||
|       "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" | ||||
|     }, | ||||
|     "balanced-match": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", | ||||
|       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" | ||||
|     }, | ||||
|     "brace-expansion": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", | ||||
|       "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", | ||||
|       "requires": { | ||||
|         "balanced-match": "^1.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "color-convert": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", | ||||
|       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", | ||||
|       "requires": { | ||||
|         "color-name": "~1.1.4" | ||||
|       } | ||||
|     }, | ||||
|     "color-name": { | ||||
|       "version": "1.1.4", | ||||
|       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", | ||||
|       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" | ||||
|     }, | ||||
|     "commander": { | ||||
|       "version": "10.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", | ||||
|       "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==" | ||||
|     }, | ||||
|     "config-chain": { | ||||
|       "version": "1.1.13", | ||||
|       "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", | ||||
|       "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", | ||||
|       "requires": { | ||||
|         "ini": "^1.3.4", | ||||
|         "proto-list": "~1.2.1" | ||||
|       } | ||||
|     }, | ||||
|     "cross-spawn": { | ||||
|       "version": "7.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", | ||||
|       "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", | ||||
|       "requires": { | ||||
|         "path-key": "^3.1.0", | ||||
|         "shebang-command": "^2.0.0", | ||||
|         "which": "^2.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "eastasianwidth": { | ||||
|       "version": "0.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", | ||||
|       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" | ||||
|     }, | ||||
|     "editorconfig": { | ||||
|       "version": "1.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", | ||||
|       "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", | ||||
|       "requires": { | ||||
|         "@one-ini/wasm": "0.1.1", | ||||
|         "commander": "^10.0.0", | ||||
|         "minimatch": "9.0.1", | ||||
|         "semver": "^7.5.3" | ||||
|       } | ||||
|     }, | ||||
|     "emoji-regex": { | ||||
|       "version": "9.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", | ||||
|       "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" | ||||
|     }, | ||||
|     "foreground-child": { | ||||
|       "version": "3.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", | ||||
|       "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", | ||||
|       "requires": { | ||||
|         "cross-spawn": "^7.0.0", | ||||
|         "signal-exit": "^4.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "glob": { | ||||
|       "version": "10.3.10", | ||||
|       "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", | ||||
|       "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", | ||||
|       "requires": { | ||||
|         "foreground-child": "^3.1.0", | ||||
|         "jackspeak": "^2.3.5", | ||||
|         "minimatch": "^9.0.1", | ||||
|         "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", | ||||
|         "path-scurry": "^1.10.1" | ||||
|       } | ||||
|     }, | ||||
|     "ini": { | ||||
|       "version": "1.3.8", | ||||
|       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", | ||||
|       "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" | ||||
|     }, | ||||
|     "is-fullwidth-code-point": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", | ||||
|       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" | ||||
|     }, | ||||
|     "isexe": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", | ||||
|       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" | ||||
|     }, | ||||
|     "jackspeak": { | ||||
|       "version": "2.3.6", | ||||
|       "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", | ||||
|       "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", | ||||
|       "requires": { | ||||
|         "@isaacs/cliui": "^8.0.2", | ||||
|         "@pkgjs/parseargs": "^0.11.0" | ||||
|       } | ||||
|     }, | ||||
|     "js-beautify": { | ||||
|       "version": "1.14.11", | ||||
|       "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz", | ||||
|       "integrity": "sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==", | ||||
|       "requires": { | ||||
|         "config-chain": "^1.1.13", | ||||
|         "editorconfig": "^1.0.3", | ||||
|         "glob": "^10.3.3", | ||||
|         "nopt": "^7.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "lru-cache": { | ||||
|       "version": "10.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", | ||||
|       "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==" | ||||
|     }, | ||||
|     "minimatch": { | ||||
|       "version": "9.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", | ||||
|       "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", | ||||
|       "requires": { | ||||
|         "brace-expansion": "^2.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "minipass": { | ||||
|       "version": "7.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", | ||||
|       "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==" | ||||
|     }, | ||||
|     "nopt": { | ||||
|       "version": "7.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", | ||||
|       "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", | ||||
|       "requires": { | ||||
|         "abbrev": "^2.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "path-key": { | ||||
|       "version": "3.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", | ||||
|       "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" | ||||
|     }, | ||||
|     "path-scurry": { | ||||
|       "version": "1.10.1", | ||||
|       "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", | ||||
|       "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", | ||||
|       "requires": { | ||||
|         "lru-cache": "^9.1.1 || ^10.0.0", | ||||
|         "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "proto-list": { | ||||
|       "version": "1.2.4", | ||||
|       "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", | ||||
|       "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" | ||||
|     }, | ||||
|     "semver": { | ||||
|       "version": "7.5.4", | ||||
|       "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", | ||||
|       "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", | ||||
|       "requires": { | ||||
|         "lru-cache": "^6.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "lru-cache": { | ||||
|           "version": "6.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", | ||||
|           "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", | ||||
|           "requires": { | ||||
|             "yallist": "^4.0.0" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "shebang-command": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", | ||||
|       "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", | ||||
|       "requires": { | ||||
|         "shebang-regex": "^3.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "shebang-regex": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", | ||||
|       "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" | ||||
|     }, | ||||
|     "signal-exit": { | ||||
|       "version": "4.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", | ||||
|       "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" | ||||
|     }, | ||||
|     "string-width": { | ||||
|       "version": "5.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", | ||||
|       "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", | ||||
|       "requires": { | ||||
|         "eastasianwidth": "^0.2.0", | ||||
|         "emoji-regex": "^9.2.2", | ||||
|         "strip-ansi": "^7.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "string-width-cjs": { | ||||
|       "version": "npm:string-width@4.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | ||||
|       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", | ||||
|       "requires": { | ||||
|         "emoji-regex": "^8.0.0", | ||||
|         "is-fullwidth-code-point": "^3.0.0", | ||||
|         "strip-ansi": "^6.0.1" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "ansi-regex": { | ||||
|           "version": "5.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||||
|           "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" | ||||
|         }, | ||||
|         "emoji-regex": { | ||||
|           "version": "8.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", | ||||
|           "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" | ||||
|         }, | ||||
|         "strip-ansi": { | ||||
|           "version": "6.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||||
|           "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||||
|           "requires": { | ||||
|             "ansi-regex": "^5.0.1" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "strip-ansi": { | ||||
|       "version": "7.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", | ||||
|       "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", | ||||
|       "requires": { | ||||
|         "ansi-regex": "^6.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "strip-ansi-cjs": { | ||||
|       "version": "npm:strip-ansi@6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||||
|       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||||
|       "requires": { | ||||
|         "ansi-regex": "^5.0.1" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "ansi-regex": { | ||||
|           "version": "5.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||||
|           "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "which": { | ||||
|       "version": "2.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", | ||||
|       "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", | ||||
|       "requires": { | ||||
|         "isexe": "^2.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "wrap-ansi": { | ||||
|       "version": "8.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", | ||||
|       "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", | ||||
|       "requires": { | ||||
|         "ansi-styles": "^6.1.0", | ||||
|         "string-width": "^5.0.1", | ||||
|         "strip-ansi": "^7.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "wrap-ansi-cjs": { | ||||
|       "version": "npm:wrap-ansi@7.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", | ||||
|       "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", | ||||
|       "requires": { | ||||
|         "ansi-styles": "^4.0.0", | ||||
|         "string-width": "^4.1.0", | ||||
|         "strip-ansi": "^6.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "ansi-regex": { | ||||
|           "version": "5.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||||
|           "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" | ||||
|         }, | ||||
|         "ansi-styles": { | ||||
|           "version": "4.3.0", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", | ||||
|           "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", | ||||
|           "requires": { | ||||
|             "color-convert": "^2.0.1" | ||||
|           } | ||||
|         }, | ||||
|         "emoji-regex": { | ||||
|           "version": "8.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", | ||||
|           "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" | ||||
|         }, | ||||
|         "string-width": { | ||||
|           "version": "4.2.3", | ||||
|           "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | ||||
|           "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", | ||||
|           "requires": { | ||||
|             "emoji-regex": "^8.0.0", | ||||
|             "is-fullwidth-code-point": "^3.0.0", | ||||
|             "strip-ansi": "^6.0.1" | ||||
|           } | ||||
|         }, | ||||
|         "strip-ansi": { | ||||
|           "version": "6.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||||
|           "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||||
|           "requires": { | ||||
|             "ansi-regex": "^5.0.1" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "yallist": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", | ||||
|       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" | ||||
|     } | ||||
|   } | ||||
| } | ||||
								
									
									
										
											23
										
									
									package.json
									
									
									
									
								
								
							
							|  | @ -1,23 +0,0 @@ | |||
| { | ||||
|   "name": "fxclient", | ||||
|   "version": "0.5.3", | ||||
|   "description": "A modded territorial.io client", | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
|     "build": "node index.js", | ||||
|     "build-only": "node build.js" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git+https://github.com/fxclient/FXclient.git" | ||||
|   }, | ||||
|   "author": "", | ||||
|   "license": "ISC", | ||||
|   "bugs": { | ||||
|     "url": "https://github.com/fxclient/FXclient/issues" | ||||
|   }, | ||||
|   "homepage": "https://github.com/fxclient/FXclient#readme", | ||||
|   "dependencies": { | ||||
|     "js-beautify": "^1.14.11" | ||||
|   } | ||||
| } | ||||
								
									
									
										
											55
										
									
									readme.md
									
									
									
									
								
								
							
							|  | @ -1,55 +0,0 @@ | |||
| <p align="center"> | ||||
|   <a href="https://fxclient.github.io/FXclient/"> | ||||
|     <picture> | ||||
|       <source media="(prefers-color-scheme: dark)" srcset="/assets/logo_text_dark.png"> | ||||
|       <source media="(prefers-color-scheme: light)" srcset="/assets/logo_text_light.png"> | ||||
|       <img src="/assets/logo_text_light.png" width="20%"> | ||||
|     </picture> | ||||
|   </a> | ||||
| </p> | ||||
| <p align="center"> | ||||
|   <a href="https://discord.gg/JEwYWGraj7"><img alt="FX Client Discord" src="https://img.shields.io/discord/1055801912286515220?logo=discord&logoColor=white&label=FX%20Client&color=5865F2"></a> | ||||
|   <a href="https://github.com/fxclient/FXclient/actions/workflows/deploy_github_pages.yml"><img src="https://github.com/fxclient/FXclient/actions/workflows/deploy_github_pages.yml/badge.svg" alt="Build and Publish to GitHub Pages"></a> | ||||
|   <a href="https://fxclient.github.io/FXclient/"><img src="https://github.com/fxclient/FXclient/actions/workflows/pages/pages-build-deployment/badge.svg" alt="pages-build-deployment"></a> | ||||
| </p> | ||||
| 
 | ||||
| ## What is FX Client? | ||||
| FX Client is the first Territorial.io client, targeting better User Interface and better User Experience, It's basically Territorial.io but better. | ||||
| 
 | ||||
| **You can use the latest version of the client here: https://fxclient.github.io/FXclient/** | ||||
| 
 | ||||
| ## Features: | ||||
| 1. It's 100% free and open source on Github | ||||
| 2. It's ad-free and removes game's default ads. | ||||
| 3. It makes game look cooler, by replacing default assets with new ones. | ||||
| 4. Displays your troop density and maximum troops | ||||
| 5. Displays the density of players and bots | ||||
| 6. Adds a "Clan" tab on the leaderboard, allowing you to easily see your clanmates | ||||
| 7. Hovering tooltip: makes the territory map information (normally visible on right click) be visible constantly (on hover) | ||||
| 8. Adds a player list | ||||
| 9. Adds the ability to view the history of who donated to a player during a team game by clicking on their name in the leaderboard or the player list | ||||
| 10. Adds a win counter | ||||
| 11. Can be installed as a PWA (progressive web app) ensuring maximum enjoyment on consoles, phones and even desktop devices | ||||
| 
 | ||||
| #### The client has a settings menu, from which you can: | ||||
| 
 | ||||
| 12. Make fullscreen mode trigger automatically | ||||
| 13. Set a custom main menu background | ||||
| 14. Create custom attack percentage keybinds | ||||
| 
 | ||||
| ## Building Locally | ||||
| 
 | ||||
| To build the client locally, install Node.js if you haven't already, clone the repo, then run: | ||||
| 
 | ||||
| ``` | ||||
| npm install | ||||
| npm run build | ||||
| ``` | ||||
| 
 | ||||
| This will install the dependencies, download the game and build the client. | ||||
| 
 | ||||
| To build from an already downloaded copy of the game, use `npm run build-only`. | ||||
| 
 | ||||
| ## Contact Us | ||||
| 
 | ||||
| Join the FX Client Discord server: https://discord.gg/JEwYWGraj7 | ||||
								
									
									
										
											525
										
									
									src/fx_core.js
									
									
									
									
								
								
							
							|  | @ -1,525 +0,0 @@ | |||
| const fx_version = '0.6.4.1'; // FX Client Version
 | ||||
| const fx_update = 'May 20'; // FX Client Last Updated
 | ||||
| 
 | ||||
| if (localStorage.getItem("fx_winCount") == undefined || localStorage.getItem("fx_winCount") == null) { | ||||
|     var wins_counter = 0; | ||||
|     console.log('Couldn\'t find a saved win data. creating one...'); | ||||
| } else if (localStorage.getItem("fx_winCount") != undefined || localStorage.getItem("fx_winCount") != null) { | ||||
|     var wins_counter = localStorage.getItem("fx_winCount"); | ||||
| } | ||||
| 
 | ||||
| const getVar = varName => window[dictionary[varName]]; | ||||
| 
 | ||||
| // https://stackoverflow.com/a/6234804
 | ||||
| function escapeHtml(unsafe) { | ||||
|     return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); | ||||
| } | ||||
| 
 | ||||
| function KeybindsInput(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: 0.8 }); | ||||
|         this.displayObjects(); | ||||
|         keybindAddButton.scrollIntoView(false); | ||||
|     }; | ||||
|     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 = ""; | ||||
|         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); | ||||
|         } | ||||
|     }; | ||||
|     /** @param {PointerEvent} event */ | ||||
|     this.startKeyInput = function (index, property, event) { | ||||
|         event.target.value = "Press any key"; | ||||
|         const handler = this.updateObject.bind(this, index, property); | ||||
|         event.target.addEventListener('keydown', handler, { once: true }); | ||||
|         event.target.addEventListener("blur", () => { | ||||
|             event.target.removeEventListener('keydown', handler, { once: true }); | ||||
|             event.target.value = this.objectArray[index][property]; | ||||
|             //this.displayObjects();
 | ||||
|         }, { once: true }); | ||||
|     }; | ||||
|     /** @param {PointerEvent} event */ | ||||
|     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(); | ||||
|         }, { 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) | ||||
|         ) : property === "key" ? event.key : event.target.value; | ||||
|         this.objectArray[index][property] = value; | ||||
|         if (property === "key") this.displayObjects(); | ||||
|     }; | ||||
|     this.deleteObject = function (index) { | ||||
|         // Remove the object from the array
 | ||||
|         this.objectArray.splice(index, 1); | ||||
|         // Display the updated input fields for objects
 | ||||
|         this.displayObjects(); | ||||
|     }; | ||||
|     return this; | ||||
| } | ||||
| 
 | ||||
| var settings = { | ||||
|     //"fontName": "Trebuchet MS",
 | ||||
|     //"showBotDonations": false,
 | ||||
|     "displayWinCounter": true, | ||||
|     "useFullscreenMode": false, | ||||
|     "hoveringTooltip": true, | ||||
|     //"hideAllLinks": false,
 | ||||
|     "realisticNames": false, | ||||
|     "showPlayerDensity": true, | ||||
|     "coloredDensity": true, | ||||
|     "densityDisplayStyle": "percentage", | ||||
|     //"customMapFileBtn": true
 | ||||
|     "customBackgroundUrl": "", | ||||
|     "attackPercentageKeybinds": [], | ||||
| }; | ||||
| const discontinuedSettings = [ "hideAllLinks", "fontName" ]; | ||||
| let makeMainMenuTransparent = false; | ||||
| var settingsManager = new (function() { | ||||
|     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", | ||||
|         note: "The win counter tracks multiplayer solo wins (not in team games)" }, | ||||
|         { 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: "hoveringTooltip", type: "checkbox", label: "Hovering tooltip", | ||||
|         note: "Display map territory info constantly (on mouse hover) instead of only when right clicking on the map" }, | ||||
|         //{ 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: "coloredDensity", type: "checkbox", label: "Colored density", note: "Display the density with a color between red and green depending on the density value" }, | ||||
|         { 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(); | ||||
|     }; | ||||
|     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]; }); | ||||
|         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; | ||||
|         localStorage.removeItem("fx_settings"); | ||||
|         window.location.reload(); | ||||
|     }; | ||||
|     this.applySettings = function() { | ||||
|         //setVarByName("bu", "px " + settings.fontName);
 | ||||
|         if (settings.useFullscreenMode && document.fullscreenEnabled) { | ||||
|             function tryEnterFullscreen() { | ||||
|                 if (document.fullscreenElement !== null) return; | ||||
|                 document.documentElement.requestFullscreen({ navigationUI: "hide" }) | ||||
|                     .then(() => { console.log('Fullscreen mode activated'); }) | ||||
|                     .catch((error) => { console.warn('Could not enter fullscreen mode:', error); }); | ||||
|             } | ||||
|             document.addEventListener('mousedown', tryEnterFullscreen, { once: true }); | ||||
|             document.addEventListener('click', tryEnterFullscreen, { once: true }); | ||||
|         } | ||||
|         if (settings.customBackgroundUrl !== "") { | ||||
|             document.body.style.backgroundImage = "url(" + settings.customBackgroundUrl + ")"; | ||||
|             document.body.style.backgroundSize = "cover"; | ||||
|             document.body.style.backgroundPosition = "center"; | ||||
|         } | ||||
|         makeMainMenuTransparent = settings.customBackgroundUrl !== ""; | ||||
|     }; | ||||
| }); | ||||
| function removeWins() { | ||||
|     var confirm1 = confirm('Do you really want to reset your Wins?'); | ||||
|     if (confirm1) { | ||||
|         wins_counter = 0; | ||||
|         localStorage.removeItem('fx_winCount'); | ||||
|         alert("Successfully reset wins"); | ||||
|     } | ||||
| } | ||||
| const openCustomBackgroundFilePicker = () => { | ||||
|     const fileInput = document.getElementById("customBackgroundFileInput"); | ||||
|     fileInput.click(); | ||||
|     fileInput.addEventListener('change', handleFileSelect); | ||||
| } | ||||
| function handleFileSelect(event) { | ||||
|     const fileInput = event.target; | ||||
|     const selectedFile = fileInput.files[0]; | ||||
|     console.log(fileInput.files); | ||||
|     console.log(fileInput.files[0]); | ||||
|     if (selectedFile) { | ||||
|         const fileUrl = URL.createObjectURL(selectedFile); | ||||
|         console.log("File URL:", fileUrl); | ||||
|         fileInput.value = ""; | ||||
|         fileInput.removeEventListener("change", handleFileSelect); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| var WindowManager = new (function() { | ||||
|     var windows = {}; | ||||
|     this.add = function(newWindow) { | ||||
|         windows[newWindow.name] = newWindow; | ||||
|         windows[newWindow.name].isOpen = false; | ||||
|     }; | ||||
|     this.openWindow = function(windowName, ...args) { | ||||
|         if (windows[windowName].isOpen === true) return; | ||||
|         if (windows[windowName].beforeOpen !== undefined) windows[windowName].beforeOpen(...args); | ||||
|         windows[windowName].isOpen = true; | ||||
|         windows[windowName].element.style.display = null; | ||||
|     }; | ||||
|     this.closeWindow = function(windowName) { | ||||
|         if (windows[windowName].isOpen === false) return; | ||||
|         windows[windowName].isOpen = false; | ||||
|         windows[windowName].element.style.display = "none"; | ||||
|         if (windows[windowName].onClose !== undefined) windows[windowName].onClose(); | ||||
|     }; | ||||
|     this.closeAll = function() { | ||||
|         Object.values(windows).forEach(function(windowObj) { | ||||
|             WindowManager.closeWindow(windowObj.name); | ||||
|         }); | ||||
|     }; | ||||
| }); | ||||
| WindowManager.add({ | ||||
|     name: "settings", | ||||
|     element: document.querySelector(".settings"), | ||||
|     beforeOpen: function() { settingsManager.syncFields(); } | ||||
| }); | ||||
| WindowManager.add({ | ||||
|     name: "donationHistory", | ||||
|     element: document.querySelector("#donationhistory"), | ||||
|     beforeOpen: function(isSingleplayer) { | ||||
|         document.getElementById("donationhistory_note").style.display = ((true || settings.showBotDonations || /*getVarByName("dt")*/ isSingleplayer) ? "none" : "block"); | ||||
|     }, | ||||
|     onClose: function() { donationsTracker.openedWindowPlayerID = null; } | ||||
| }); | ||||
| WindowManager.add({ | ||||
|     name: "playerList", | ||||
|     element: document.getElementById("playerlist"), | ||||
|     beforeOpen: function() {} | ||||
| }); | ||||
| document.getElementById("canvasA").addEventListener("mousedown", WindowManager.closeAll); | ||||
| document.getElementById("canvasA").addEventListener("touchstart", WindowManager.closeAll, { passive: true }); | ||||
| document.addEventListener("keydown", event => { if (event.key === "Escape") WindowManager.closeAll(); }); | ||||
| var settingsGearIcon = document.createElement('img'); | ||||
| settingsGearIcon.setAttribute('src', 'assets/geari_white.png'); | ||||
| 
 | ||||
| const playerList = new (function () { | ||||
|     const playersIcon = document.createElement('img'); | ||||
|     playersIcon.setAttribute('src', 'assets/players_icon.png'); | ||||
|     document.getElementById("playerlist_content").addEventListener("click", event => { | ||||
|         const playerId = event.target.closest("tr[data-player-id]")?.getAttribute("data-player-id"); | ||||
|         if (!playerId) return; | ||||
|         if (getVar("gIsTeamGame")) WindowManager.closeWindow("playerList"), donationsTracker.displayHistory(playerId); | ||||
|     }); | ||||
|     this.display = function displayPlayerList(playerNames) { | ||||
|         const gHumans = getVar("gHumans"); | ||||
|         const gLobbyMaxJoin = getVar("gLobbyMaxJoin"); | ||||
|         let listContent = `<h3>Players (${gHumans})</h3>`; | ||||
|         for (let i = 0; i < gLobbyMaxJoin; i++) { | ||||
|             if (i === gHumans) listContent += `<h3>Bots (${gLobbyMaxJoin - gHumans})</h3>`; | ||||
|             listContent += `<tr data-player-id="${i}"><td><span class="color-light-gray">${i + 1}.</span> ${escapeHtml(playerNames[i])}</td></tr>` | ||||
|         } | ||||
|         document.getElementById("playerlist_content").innerHTML = listContent; | ||||
|         document.getElementById("playerlist_content").setAttribute("class", getVar("gIsTeamGame") ? "clickable" : ""); | ||||
|         WindowManager.openWindow("playerList"); | ||||
|     } | ||||
|     this.hoveringOverButton = false; | ||||
|     this.drawButton = (canvas, x, y, size) => { | ||||
|         canvas.fillRect(x, y, size, size); | ||||
|         canvas.fillStyle = this.hoveringOverButton ? "#aaaaaaaa" : "#000000aa"; | ||||
|         canvas.clearRect(x + 1, y + 1, size - 2, size - 2); | ||||
|         canvas.fillRect(x + 1, y + 1, size - 2, size - 2); | ||||
|         canvas.fillStyle = "#ffffff"; | ||||
|         canvas.imageSmoothingEnabled = true; | ||||
|         canvas.drawImage(playersIcon, x + 2, y + 2, size - 4, size - 4); | ||||
|         canvas.imageSmoothingEnabled = false; | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| /** @param {string} name */ | ||||
| function parseClanFromPlayerName(name) { | ||||
|     const startIndex = name.indexOf("["); | ||||
|     // this is probably how the algorithm works, since a player with
 | ||||
|     // the name "][a]" will count as not being in a clan in the base game
 | ||||
|     return startIndex === -1 ? "" : name.slice(startIndex + 1, name.indexOf("]")).toUpperCase(); | ||||
| } | ||||
| 
 | ||||
| const leaderboardFilter = new (function() { | ||||
|     this.playersToInclude = [0,1,8,20,24,30,32,42,50,69,200,400,500,510,511]; // for testing
 | ||||
|     //this.playersToInclude = [];
 | ||||
|     this.tabLabels = ["ALL", "CLAN"]; | ||||
|     // these get populated by the modified game code
 | ||||
|     this.filteredLeaderboard = []; | ||||
|     this.tabBarOffset = 0; | ||||
|     this.windowWidth = 0; | ||||
|     this.verticalClickThreshold = 1000; | ||||
|     this.hoveringOverTabs = false; | ||||
|     this.scrollToTop = () => {}; | ||||
|     this.repaintLeaderboard = () => {}; | ||||
|      | ||||
|     this.selectedTab = 0; | ||||
|     this.tabHovering = -1; | ||||
|     //this.enabled = false;
 | ||||
|     this.enabled = true; | ||||
|     this.drawTabs = function(canvas, totalWidth, verticalOffset, colorForSelectedTab) { | ||||
|         canvas.textBaseline = "middle"; | ||||
|         canvas.textAlign = "center"; | ||||
|         const tabWidth = totalWidth / this.tabLabels.length; | ||||
|         const textOffsetY = verticalOffset + this.tabBarOffset / 2; | ||||
|         //console.log(verticalOffset, this.tabBarOffset, textOffsetY);
 | ||||
|         this.tabLabels.forEach((label, index) => { | ||||
|             if (index !== 0) canvas.fillRect(tabWidth * index, verticalOffset, 1, this.tabBarOffset); | ||||
|             if (this.selectedTab === index) { | ||||
|                 canvas.fillStyle = colorForSelectedTab; | ||||
|                 canvas.fillRect(tabWidth * index, verticalOffset, tabWidth, this.tabBarOffset); | ||||
|                 canvas.fillStyle = "rgb(255,255,255)"; | ||||
|             } | ||||
|             if (this.tabHovering === index) { | ||||
|                 canvas.fillStyle = "rgba(255,255,255,0.3)"; | ||||
|                 canvas.fillRect(tabWidth * index, verticalOffset, tabWidth, this.tabBarOffset); | ||||
|                 canvas.fillStyle = "rgb(255,255,255)"; | ||||
|             } | ||||
|             canvas.fillText(label, tabWidth * index + tabWidth / 2, textOffsetY); | ||||
|         }); | ||||
|     } | ||||
|     this.setHovering = (isHovering, xRelative) => { | ||||
|         let repaintNeeded = false; | ||||
|         if (isHovering) { | ||||
|             const tab = Math.floor(xRelative / (this.windowWidth / this.tabLabels.length)); | ||||
|             if (this.tabHovering !== tab) { | ||||
|                 this.tabHovering = tab; | ||||
|                 repaintNeeded = true; | ||||
|             } | ||||
|         } | ||||
|         if (isHovering !== this.hoveringOverTabs) { | ||||
|             this.hoveringOverTabs = isHovering; | ||||
|             if (isHovering === false) this.tabHovering = -1; | ||||
|             if (!isHovering) repaintNeeded = true; | ||||
|         } | ||||
|         if (repaintNeeded) this.repaintLeaderboard(); | ||||
|         return isHovering; | ||||
|     } | ||||
|     this.handleMouseDown = (xRelative) => { | ||||
|         //console.log("click; x: ", xRelative);
 | ||||
|         if (this.tabHovering !== this.selectedTab) { | ||||
|             this.selectedTab = this.tabHovering; | ||||
|             if (this.selectedTab === 0) this.clearFilter(); | ||||
|             else if (this.selectedTab === 1) this.filterByOwnClan(); | ||||
|             this.repaintLeaderboard(); | ||||
|         } | ||||
|         return true; | ||||
|     }; | ||||
|     this.filterByOwnClan = () => { | ||||
|         this.playersToInclude = []; | ||||
|         const ownClan = parseClanFromPlayerName(getVar("playerNames")[getVar("playerId")]); | ||||
|         getVar("playerNames").forEach((name, id) => { | ||||
|             if (parseClanFromPlayerName(name) === ownClan) this.playersToInclude.push(id); | ||||
|         }); | ||||
|         this.enabled = true; | ||||
|         this.scrollToTop(); | ||||
|     }; | ||||
|     this.clearFilter = () => { this.enabled = false; } | ||||
|     this.reset = () => { | ||||
|         this.enabled = false; | ||||
|         this.selectedTab = 0; | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| const hoveringTooltip = new (function() { | ||||
|     this.display = () => {}; // this gets populated by the modified game script
 | ||||
|     this.canvasPixelScale = 1; | ||||
|     document.getElementById("canvasA").addEventListener("mousemove", e => { | ||||
|         if (!settings.hoveringTooltip || !getVar("gameState")) return; | ||||
|         try { | ||||
|             this.display(this.canvasPixelScale * e.clientX, this.canvasPixelScale * e.clientY); | ||||
|         } catch (e) { console.error(e) } | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| var donationsTracker = new (function(){ | ||||
|     this.openedWindowPlayerID = null; | ||||
|     this.contentElement = document.querySelector("#donationhistory_content"); | ||||
|     this.donationHistory = Array(512); | ||||
|     // fill the array with empty arrays with length of 3
 | ||||
|     //for (var i = 0; i < 512; i++) this.donationHistory.push([]); // not needed as .reset is called on game start
 | ||||
|     this.getHistoryOf = function(playerID) { | ||||
|         return this.donationHistory[playerID].toReversed(); | ||||
|     } | ||||
|     this.reset = function() { for (var i = 0; i < 512; i++) this.donationHistory[i] = []; }; | ||||
|     this.logDonation = function(senderID, receiverID, amount) { | ||||
|         const donationInfo = [senderID, receiverID, amount]; | ||||
|         this.donationHistory[receiverID].push(donationInfo); | ||||
|         this.donationHistory[senderID].push(donationInfo); | ||||
|         if (this.openedWindowPlayerID === senderID || this.openedWindowPlayerID === receiverID) { | ||||
|             const indexOfNewItem = this.donationHistory[this.openedWindowPlayerID === senderID ? senderID : receiverID].length; | ||||
|             this.contentElement.prepend(generateTableRowItem(donationInfo, indexOfNewItem, this.openedWindowPlayerID, true)); | ||||
|         } | ||||
|     }; | ||||
|     function generateTableRowItem(historyItem, index, playerID, isNew) { | ||||
|         const playerNames = getVar("playerNames"); | ||||
|         const row = document.createElement("tr"); | ||||
|         if (isNew) row.setAttribute("class", "new"); | ||||
|         let content = `<td><span class="color-light-gray">${index}.</span> `; | ||||
|         if (playerID === historyItem[1]) | ||||
|             content += `Received <span class="color-green">${historyItem[2]}</span> resources from ${escapeHtml(playerNames[historyItem[0]])}`; | ||||
|         else content += `Sent <span class="color-red">${historyItem[2]}</span> resources to ${escapeHtml(playerNames[historyItem[1]])}`; | ||||
|         content += "</td>"; | ||||
|         row.innerHTML = content; | ||||
|         return row; | ||||
|     } | ||||
|     this.displayHistory = function displayDonationsHistory(playerID, playerNames = getVar("playerNames"), isSingleplayer = getVar("gIsSingleplayer")) { | ||||
|         var history = donationsTracker.getHistoryOf(playerID); | ||||
|         console.log("History for " + playerNames[playerID] + ":"); | ||||
|         console.log(history); | ||||
|         document.querySelector("#donationhistory h1").innerHTML = "Donation history for " + escapeHtml(playerNames[playerID]); | ||||
|         this.contentElement.innerHTML = ""; | ||||
|         if (history.length > 0) history.forEach((historyItem, index) => { | ||||
|             this.contentElement.appendChild(generateTableRowItem(historyItem, history.length - index, playerID)); | ||||
|         }); | ||||
|         else this.contentElement.innerText = "Nothing to display"; | ||||
|         this.openedWindowPlayerID = playerID; | ||||
|         WindowManager.openWindow("donationHistory", isSingleplayer); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| var utils = new (function() { | ||||
|     this.getMaxTroops = function(playerTerritories, playerID) { return (playerTerritories[playerID]*150).toString(); }; | ||||
|     this.getDensity = function(playerID, playerBalances = getVar("playerBalances"), playerTerritories = getVar("playerTerritories")) { | ||||
|         if (settings.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)`; | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| const keybindFunctions = { setAbsolute: () => {}, setRelative: () => {} }; | ||||
| const keybindHandler = key => { | ||||
|     const keybindData = settings.attackPercentageKeybinds.find(keybind => keybind.key === key); | ||||
|     if (keybindData === undefined) return false; | ||||
|     if (keybindData.type === "absolute") keybindFunctions.setAbsolute(keybindData.value); | ||||
|     else keybindFunctions.setRelative(keybindData.value); | ||||
|     return true; | ||||
| }; | ||||
| 
 | ||||
| if (localStorage.getItem("fx_settings") !== null) { | ||||
|     settings = {...settings, ...JSON.parse(localStorage.getItem("fx_settings"))}; | ||||
| } | ||||
| settingsManager.applySettings(); | ||||
| 
 | ||||
| console.log('Successfully loaded FX Client'); | ||||
|  | @ -1,32 +0,0 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <title>404 Not Found</title> | ||||
| 
 | ||||
|     <style> | ||||
|         body { | ||||
|             background-color: #000; | ||||
|             color: #fff; | ||||
|             text-decoration: none; | ||||
|             text-align: center; | ||||
|             font-family: 'Courier New', Courier, monospace; | ||||
|             font-size: 30px; | ||||
|         } | ||||
|         a { | ||||
|             color: red; | ||||
|             text-decoration: none; | ||||
|             transition: 0.4s; | ||||
|         } | ||||
|         a:hover { | ||||
|             color: darkgreen; | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
| <body> | ||||
|     <p>404, Not Found</p> | ||||
|     <a href="https://fxclient.cf"><p>Visit FXclient.cf</p></a> | ||||
| </body> | ||||
| </html> | ||||
|  | @ -1,108 +0,0 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <meta name="og:image" content="https://fxclient.github.io/FXclient/assets/logo.png" /> | ||||
|     <meta property="og:image" content="https://fxclient.github.io/FXclient/assets/logo.png"> | ||||
|     <link rel="shortcut icon" href="https://fxclient.github.io/FXclient/favicon.ico" type="image/x-icon"> | ||||
|     <meta property="og:title" content="FX Client"> | ||||
|     <meta name="description" content="FX Client Cookie Policy"> | ||||
|     <title>FX Client - Cookie Policy</title> | ||||
|     <style> | ||||
|         body { | ||||
|             text-align: center; | ||||
|             background-color: #fff; | ||||
|             color: #000; | ||||
|             font-size: 25px; | ||||
|             font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; | ||||
|             display: block; | ||||
|         } | ||||
|         #dd { | ||||
|             text-align: center; | ||||
|             background-color: #fff; | ||||
|             color: #000; | ||||
|             font-size: 40px; | ||||
|             font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; | ||||
|             display: block; | ||||
|         } | ||||
|         a { | ||||
|             text-decoration: none; | ||||
|             transition: 0.2s; | ||||
|         } | ||||
|          | ||||
|         a:hover { | ||||
|             text-decoration: underline; | ||||
|             color: red; | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
| <body> | ||||
|     <span id="dd">Cookie Policy</span> | ||||
| 
 | ||||
| <p>Last Update: 29 March 2024</p> | ||||
| 
 | ||||
| 
 | ||||
| <p>1. General Information | ||||
| <br /> | ||||
| FX Client uses cookies to improve the user experience. You can find out more about cookies in the sections below. | ||||
| 
 | ||||
| You don't have to accept cookies in order to use FX Client. We recommend cookies as they will improve your gaming experience. | ||||
| 
 | ||||
| We also use Local Storage to store your win counts and settngs. | ||||
| 
 | ||||
| To understand how we use your information, you may also check out our privacy policy: <a href="https://fxclient.github.io/FXclient/privacy_policy">fxclient.github.io/FXclient/privacy_policy</a> | ||||
| </p> | ||||
| <hr /> | ||||
| <p>2. What is a cookie? | ||||
| <br /> | ||||
| Cookies are small text files that are stored on your computer. For example, a cookie can be used to remember your username. | ||||
| </p> | ||||
| <hr /> | ||||
| <p>3. What is Local Storage? | ||||
|     <br /> | ||||
|     Local Storage is a place where we can store some information like your settings. It is not accessible by FX Client servers and is private. | ||||
|     </p> | ||||
|     <hr /> | ||||
| <p> | ||||
| 4. What types of cookies do we use? | ||||
| <br /> | ||||
| We use functionality cookies to store your preferences: | ||||
| <br> | ||||
| your username;  | ||||
| a boolean, indicating if your interface is magnified or not;  | ||||
| the password of your one-vs-one account;  | ||||
| the color of your country;  | ||||
| selected emojis;  | ||||
| <br> | ||||
| Furthermore, we use a cookie that assigns a random number to your device. | ||||
| <br /> | ||||
| </p> | ||||
| <hr /> | ||||
| <p> | ||||
| 5. How do our servers handle cookies transmitted by users? | ||||
| <br /> | ||||
| We do not store and process cookies on our servers. Cookies on the client side will be deleted after one year of inactivity. Cookies will not be shared with third parties. | ||||
| </p> | ||||
| <hr /> | ||||
| <p> | ||||
| 6. More information about cookies | ||||
| <br /> | ||||
| If you want to know how to delete or manage cookies, the following links may be helpful: | ||||
| <br /> | ||||
| Cookie settings in Chrome and Android: <a href="https://support.google.com/chrome/answer/95647">support.google.com/chrome/answer/95647</a> | ||||
| <br /> | ||||
| Cookie settings in iOS: <a href="http://support.apple.com/kb/PH5042">support.apple.com/kb/PH5042</a> | ||||
| <br /> | ||||
| If you need more information about cookies, you may check out this link: <a href="http://www.allaboutcookies.org/">allaboutcookies.org</a> | ||||
| 
 | ||||
| </p> | ||||
| <hr/> | ||||
| <p> | ||||
| 7. Contact Information | ||||
| <br /> | ||||
| If you have more questions, please contact us at this discord server: <a href="https://discord.gg/JEwYWGraj7">discord.gg/JEwYWGraj7</a> | ||||
| </p> | ||||
| </body> | ||||
| </html> | ||||
| Before Width: | Height: | Size: 17 KiB | 
|  | @ -1,128 +0,0 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <!-- Google tag (gtag.js) --> | ||||
|     <!--<script async src="https://www.googletagmanager.com/gtag/js?id=G-Q96FGB3L05"></script> | ||||
|     <script> | ||||
|         window.dataLayer = window.dataLayer || []; | ||||
|         function gtag() { dataLayer.push(arguments); } | ||||
|         gtag('js', new Date()); | ||||
|         gtag('config', 'G-Q96FGB3L05'); | ||||
|     </script>--> | ||||
|     <meta charset="utf-8" /> | ||||
|     <title>FX Client</title> | ||||
|     <meta name="description" content="Modified Version of Territorial.io - FX Client"> | ||||
|     <meta name="keywords" | ||||
|         content="territorial.io,territory games,territorial io,map games,conquest games,conquest game,david tschacher,territorial,territory game,io game,io games,territory.io,territory io,territory games io"> | ||||
|     <meta name="author" content="MohsenEMX, peshomir,orlemley1, David Tschacher"> | ||||
|     <meta name="viewport" content="width=device-width, maximum-scale=1"> | ||||
|     <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> | ||||
|     <link rel="manifest" href="manifest.json" /> | ||||
|      | ||||
|     <meta name="og:image" content="https://fxclient.github.io/FXclient/assets/logo.png" /> | ||||
|     <meta property="og:url" content="https://fxclient.github.io/FXclient/"> | ||||
|     <meta property="og:type" content="website"> | ||||
|     <meta property="og:title" content="FX Client"> | ||||
|     <meta property="og:description" content="Modified Version of Territorial.io - FX Client"> | ||||
|     <meta property="og:image" content="https://fxclient.github.io/FXclient/assets/logo.png"> | ||||
|     <meta name="twitter:card" content="summary"> | ||||
|     <meta name="twitter:title" content="FX Client"> | ||||
|     <meta name="twitter:description" content="Modified Version of Territorial.io - FX Client"> | ||||
|     <meta name="twitter:image" content="https://fxclient.github.io/FXclient/assets/logo.png"> | ||||
|     <meta itemprop="name" content="FX Client"> | ||||
|     <meta itemprop="description" content="Modified Version of Territorial.io - FX Client"> | ||||
|     <meta itemprop="image" content="https://fxclient.github.io/FXclient/assets/logo.png"> | ||||
|      | ||||
|     <!-- FX Client CSS --> | ||||
|     <link rel="stylesheet" href="main.css?buildTimestamp"> | ||||
|     <!-- Game CSS --> | ||||
|     <style> | ||||
|         html, | ||||
|         body { | ||||
|           overflow: hidden; | ||||
|           padding: 0; | ||||
|           margin: 0; | ||||
|           background: rgb(0, 0, 0); | ||||
|           color: rgb(255, 255, 255); | ||||
|         } | ||||
|      | ||||
|         * { | ||||
|           box-sizing: border-box; | ||||
|         } | ||||
|      | ||||
|         a { | ||||
|           color: rgb(225, 225, 255); | ||||
|         } | ||||
|       </style> | ||||
| </head> | ||||
| 
 | ||||
| <body onload="aiCommand746(0);"> | ||||
|     <canvas id="canvasA" width="128" height="128"></canvas> | ||||
|     <div class="window flex settings" style="display:none"> | ||||
|         <h1>Settings</h1> | ||||
|         <div class="scrollable"> | ||||
|             <!--<label title="Name of the font to be used for rendering. For example: Arial, Georgia, sans-serif, serif, Comic Sans MS, ..."> | ||||
|                 Font name: <input id="settings_fontname" placeholder="Enter font name" value="Arial"></label><br> | ||||
|             <br><button onclick="removeWins()">Reset Wins Counter</button><br><br> | ||||
|             <!- -<label for="settings_donations_bots" class="checkbox"> | ||||
|                 Display donations from bots in donation history viewer (applies to multiplayer only) | ||||
|                 <input type="checkbox" id="settings_donations_bots"><span class="checkmark"></span> | ||||
|             </label><br>- -> | ||||
|             <label for="settings_displaywincounter" class="checkbox"> | ||||
|                 Display win counter | ||||
|                 <input type="checkbox" id="settings_displaywincounter"><span class="checkmark"></span> | ||||
|             </label><br> | ||||
|             <label for="settings_usefullscreenmode" class="checkbox"> | ||||
|                 Use fullscreen mode<br> | ||||
|                 <small>Note: fullscreen mode will trigger after you click anywhere on the page due to browser policy restrictions.</small> | ||||
|                 <input type="checkbox" id="settings_usefullscreenmode"><span class="checkmark"></span> | ||||
|             </label><br> | ||||
|             <label for="settings_hidealllinks" class="checkbox"> | ||||
|                 Hide Links option also hides app store links | ||||
|                 <input type="checkbox" id="settings_hidealllinks"><span class="checkmark"></span> | ||||
|             </label><br> | ||||
|             <label for="settings_realisticnames" class="checkbox"> | ||||
|                 Realistic Bot Names | ||||
|                 <input type="checkbox" id="settings_realisticnames"><span class="checkmark"></span> | ||||
|             </label><br> | ||||
|             <label for="settings_showPlayerDensity" class="checkbox"> | ||||
|                 Show player density | ||||
|                 <input type="checkbox" id="settings_showPlayerDensity"><span class="checkmark"></span> | ||||
|             </label><br> | ||||
|             <label title="Controls how the territorial density value should be rendered"> | ||||
|                 Density value display style: <select id="settings_densityDisplayStyle"> | ||||
|                     <option value="percentage">Percentage</option> | ||||
|                     <option value="absoluteQuotient">Value from 0 to 150 (BetterTT style)</option> | ||||
|                 </select></label><br><br> | ||||
|             <label title="A custom image to be shown in the main menu background instead of the currently selected map."> | ||||
|                 Custom main menu background: <input id="settings_custombackgroundurl" placeholder="Enter an image URL here"></label> | ||||
|             <!- -<input type="file" id="customBackgroundFileInput" style="display:none;"> | ||||
|             or <button onclick="openCustomBackgroundFilePicker()">Open a local file</button>- -><br><br> | ||||
|             <!- -<label for="settings_custommapfileinput" class="checkbox"> | ||||
|                 Bring back the custom map file button after the creator removed it in 1.83.0 | ||||
|                 <input type="checkbox" id="settings_custommapfileinput"><span class="checkmark"></span> | ||||
|             </label>- -> | ||||
|             <p>Attack Percentage Keybinds</p> | ||||
|             <div id="keybinds" class="arrayinput"></div> | ||||
|             <button id="keybindAddButton">Add</button>--> | ||||
|         </div> | ||||
|         <hr> | ||||
|         <footer> | ||||
|             <button onclick="settingsManager.resetAll()">Reset Settings</button> | ||||
|             <button onclick="settingsManager.save()">Save Settings</button> | ||||
|         </footer> | ||||
|     </div> | ||||
|     <div class="window scrollable selectable" id="playerlist" style="display: none;"> | ||||
|         <h1>Player List</h1> | ||||
|         <table><tbody id="playerlist_content"></tbody></table> | ||||
|     </div> | ||||
|     <div class="window scrollable selectable" id="donationhistory" style="display:none"> | ||||
|         <h1>Donation history for </h1> | ||||
|         <p id="donationhistory_note">Note: donations from bots are not shown here</p> | ||||
|         <table><tbody id="donationhistory_content"></tbody></table> | ||||
|     </div> | ||||
|     <script src="variables.js?buildTimestamp"></script> | ||||
|     <script src="fx_core.js?buildTimestamp"></script> | ||||
|     <script src="game.js?buildTimestamp"></script> | ||||
| </body> | ||||
| </html> | ||||
								
									
									
										
											208
										
									
									static/main.css
									
									
									
									
								
								
							
							|  | @ -1,208 +0,0 @@ | |||
| .scrollable { | ||||
| 	overflow-y: auto; | ||||
| } | ||||
| 
 | ||||
| ::-webkit-scrollbar { | ||||
| 	width: 10px; | ||||
| } | ||||
| 
 | ||||
| ::-webkit-scrollbar-thumb { | ||||
| 	background-color: #aaaaaa; | ||||
| } | ||||
| 
 | ||||
| .window { | ||||
| 	position        : fixed; | ||||
| 	background-color: rgba(0, 0, 0, 0.7); | ||||
| 	width           : 90%; | ||||
| 	top             : 0; | ||||
| 	color           : white; | ||||
| 	font-family     : 'Franklin Gothic Medium', 'Trebuchet MS', Arial, sans-serif; | ||||
| 	margin          : auto; | ||||
| 	margin-top      : 20px; | ||||
| 	right           : 0; | ||||
| 	left            : 0; | ||||
| 	padding         : 15px; | ||||
| 	box-sizing      : border-box; | ||||
| 	border-color    : white; | ||||
| 	border-style    : solid; | ||||
| 	border-width    : 2px; | ||||
| 	border-width    : calc(0.15 * (1vw + 1vh)); | ||||
| 	font-size       : 20px; | ||||
| 	font-size       : calc(14px + ((0.4 * (0.8vw + 1vh)) + 0.15rem)); | ||||
| 	max-height      : 90%; | ||||
| 	transition      : 0.2s; | ||||
| 	z-index         : 10; | ||||
| } | ||||
| 
 | ||||
| .window.flex { | ||||
| 	display       : flex; | ||||
| 	flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| hr { | ||||
| 	width: 100%; | ||||
| } | ||||
| 
 | ||||
| .window button, | ||||
| .window input, | ||||
| .window select { | ||||
| 	background-color: rgba(0, 0, 0, 0.7); | ||||
| 	color           : white; | ||||
| 	font-size       : 20px; | ||||
| 	font-size       : 0.9em; | ||||
| 	padding         : 0.4rem; | ||||
| 	transition      : 0.2s; | ||||
| 	border          : 1px solid #fff; | ||||
| 	border-radius   : 5px; | ||||
| } | ||||
| 
 | ||||
| h1 { | ||||
| 	font-weight       : normal; | ||||
| 	margin-block-start: 0.5em; | ||||
| 	margin-block-end  : 0.5em; | ||||
| 	transition        : 0.2s; | ||||
| } | ||||
| #playerlist h1 { | ||||
| 	margin-block-start: 0.3em; | ||||
| 	margin-block-end  : 0.3em; | ||||
| } | ||||
| h3 { | ||||
| 	font-weight       : normal; | ||||
| 	margin-block-start: 0.6em; | ||||
| 	margin-block-end  : 0.6em; | ||||
| } | ||||
| 
 | ||||
| canvas, | ||||
| input, | ||||
| textarea { | ||||
| 	transition: 0.2s; | ||||
| } | ||||
| 
 | ||||
| button:hover { | ||||
| 	background-color: rgba(222, 222, 222, 0.52); | ||||
| 	border-radius   : 8px; | ||||
| } | ||||
| 
 | ||||
| .window input:focus { | ||||
| 	background-color: rgba(222, 222, 222, 0.36); | ||||
| 	border-radius   : 8px; | ||||
| } | ||||
| 
 | ||||
| td { | ||||
| 	padding: 0px; | ||||
| } | ||||
| 
 | ||||
| #playerlist_content.clickable td { cursor: pointer; } | ||||
| #playerlist_content.clickable td:hover { background-color: #00ff0040; } | ||||
| 
 | ||||
| tr.new { | ||||
|     animation: flashAnimation 0.4s ease-out; | ||||
| } | ||||
| 
 | ||||
| @keyframes flashAnimation { | ||||
|     0% { | ||||
|         background-color: #ffffffaa; | ||||
|     } | ||||
|     100% { | ||||
|         background-color: transparent; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| table { | ||||
| 	border-spacing: 0px; | ||||
| } | ||||
| 
 | ||||
| input#userna, | ||||
| input#inputfilebtn { | ||||
| 	transition: 0.2s | ||||
| } | ||||
| 
 | ||||
| .selectable { | ||||
| 	user-select: text; | ||||
| } | ||||
| 
 | ||||
| .color-red { | ||||
| 	color: #dc143c; | ||||
| } | ||||
| 
 | ||||
| .color-green { | ||||
| 	color: #32cd32; | ||||
| } | ||||
| 
 | ||||
| .color-light-gray { | ||||
| 	color: #aaaaaa; | ||||
| } | ||||
| 
 | ||||
| /* Checkbox */ | ||||
| label.checkbox { | ||||
| 	display        : block; | ||||
| 	position       : relative; | ||||
| 	padding-left   : 35px; | ||||
| 	/*margin-bottom: 12px;*/ | ||||
| 	cursor         : pointer; | ||||
| } | ||||
| 
 | ||||
| label.checkbox input { | ||||
| 	position: absolute; | ||||
| 	opacity : 0; | ||||
| 	cursor  : pointer; | ||||
| 	height  : 0; | ||||
| 	width   : 0; | ||||
| } | ||||
| 
 | ||||
| .checkmark { | ||||
| 	position          : absolute; | ||||
| 	top               : 0; | ||||
| 	left              : 0; | ||||
| 	height            : 25px; | ||||
| 	width             : 25px; | ||||
| 	/*background-color: #eee;*/ | ||||
| 	background-color  : rgb(255 255 255 / 70%); | ||||
| } | ||||
| 
 | ||||
| label.checkbox:hover .checkmark { | ||||
| 	background-color: #ccc; | ||||
| } | ||||
| 
 | ||||
| label.checkbox input:checked~.checkmark { | ||||
| 	/*background-color: #2196F3;*/ | ||||
| 	background-color: rgba(0, 255, 0, 0.5); | ||||
| } | ||||
| 
 | ||||
| .checkmark::after { | ||||
| 	content : ""; | ||||
| 	position: absolute; | ||||
| 	display : none; | ||||
| } | ||||
| 
 | ||||
| label.checkbox input:checked~.checkmark:after { | ||||
| 	display: block; | ||||
| } | ||||
| 
 | ||||
| label.checkbox .checkmark:after { | ||||
| 	left             : 9px; | ||||
| 	top              : 5px; | ||||
| 	width            : 5px; | ||||
| 	height           : 10px; | ||||
| 	border           : solid white; | ||||
| 	border-width     : 0 3px 3px 0; | ||||
| 	-webkit-transform: rotate(45deg); | ||||
| 	-ms-transform    : rotate(45deg); | ||||
| 	transform        : rotate(45deg); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Custom Map Button */ | ||||
| ::-webkit-file-upload-button { | ||||
| 	color           : white; | ||||
| 	background-color: #120076; | ||||
| 	border          : 2px solid #fff; | ||||
| 	top             : 0; | ||||
| 	bottom          : 0; | ||||
| 	padding-top     : 20px; | ||||
| 	padding-bottom  : 20px; | ||||
| 	padding-left    : 20px; | ||||
| 	padding-right   : 20px; | ||||
| 	font-family     : 'Trebuchet MS', 'Arial Narrow', Arial, sans-serif; | ||||
| 	text-align      : center; | ||||
| } | ||||
|  | @ -1,19 +0,0 @@ | |||
| { | ||||
|     "name": "FX Client", | ||||
|     "short_name": "FXclient", | ||||
|     "start_url": ".", | ||||
|     "display": "standalone", | ||||
|     "background_color": "#000000", | ||||
|     "description": "Client for Territorial.io", | ||||
|     "icons": [ | ||||
|         { | ||||
|             "src": "favicon.ico", | ||||
|             "sizes": "64x64" | ||||
|         }, | ||||
|         { | ||||
|             "src": "assets/logo.png", | ||||
|             "sizes": "any", | ||||
|             "type": "image/png" | ||||
|         } | ||||
|     ] | ||||
| } | ||||
|  | @ -1,96 +0,0 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| 
 | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <title>FX Client - Privacy Policy</title> | ||||
|     <meta name="og:image" content="https://fxclient.github.io/FXclient/assets/logo.png" /> | ||||
|     <meta property="og:image" content="https://fxclient.github.io/FXclient/assets/logo.png"> | ||||
|     <link rel="shortcut icon" href="https://fxclient.github.io/FXclient/favicon.ico" type="image/x-icon"> | ||||
|     <meta property="og:title" content="FX Client"> | ||||
|     <meta name="description" content="FX Client Privacy Policy"> | ||||
|     <style> | ||||
|         body { | ||||
|             text-align: center; | ||||
|             background-color: #fff; | ||||
|             color: #000; | ||||
|             font-size: 25px; | ||||
|             font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; | ||||
|             display: block; | ||||
|         } | ||||
| 
 | ||||
|         #dd { | ||||
|             text-align: center; | ||||
|             background-color: #fff; | ||||
|             color: #000; | ||||
|             font-size: 40px; | ||||
|             font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; | ||||
|             display: block; | ||||
|         } | ||||
| 
 | ||||
|         a { | ||||
|             text-decoration: none; | ||||
|             transition: 0.2s; | ||||
|         } | ||||
|          | ||||
|         a:hover { | ||||
|             text-decoration: underline; | ||||
|             color: red; | ||||
|         } | ||||
|     </style> | ||||
| </head> | ||||
| 
 | ||||
| <body> | ||||
|     <span id="dd">Privacy Policy</span> | ||||
| 
 | ||||
|     <p>Last Update: 29 March 2024</p> | ||||
| 
 | ||||
|     <p>1. General Notes | ||||
|         <br /> | ||||
|         Unless otherwise indicated, this privacy policy applies for the website (<a href="https://fxclient.github.io/FXclient/">fxclient.github.io/FXclient/</a>). | ||||
| 
 | ||||
|         In this policy, "fxclient", "we" or "us" refers to a group of developers scattered around the world. The | ||||
|         contact information of the owner of this enterprise can be found below. | ||||
|     </p> | ||||
|     <hr /> | ||||
|     <p>2. Third Parties | ||||
|         <br /> | ||||
|         We don't share your information with any 3rd parties. | ||||
|     </p> | ||||
|     <hr /> | ||||
|     <p> | ||||
|         3. What information do we collect? | ||||
|         <br /> | ||||
|         We do not collect any user information. Information is collected by territorial.io. The privacy policy for | ||||
|         territorial.io can be found at <a href="https://territorial.io/privacy_policy"> | ||||
|             territorial.io/privacy_policy </a> | ||||
|         <br /> | ||||
|         On the website we collect cookies: If you need more information about our cookies, you may check out our cookie | ||||
|         policy: <a href="https://fxclient.github.io/FXclient/cookie_policy">fxclient.github.io/FXclient/cookie_policy</a> | ||||
|     </p> | ||||
|     <hr /> | ||||
|     <p> | ||||
|         4. How do we collect and use your information? | ||||
|         <br /> | ||||
|         Information is sent to and used by Territorial.io and not encrypted in transit. Your information is used to | ||||
|         improve the game and to prevent fraud. Collection and usage of the data by Territorial.io is explained in more | ||||
|         detail at <a href="https://territorial.io/privacy_policy"> territorial.io/privacy_policy </a> | ||||
|         Usernames and rankings are publicly available. Statistical and anonymous game data are shown on Territorial.io | ||||
|         (<a href="https://territorial.io/players">territorial.io/players</a> and <a href="https://territorial.io/clans">territorial.io/clans</a>). | ||||
|     </p> | ||||
|     <hr /> | ||||
|     <p> | ||||
|         5. How can you delete your information? | ||||
|         <br /> | ||||
|         Your data will be automatically deleted after 6 months of inactivity. | ||||
|     </p> | ||||
|     <hr /> | ||||
|     <p> | ||||
|         6. Contact Information | ||||
|         <br /> | ||||
|         If you have more questions, please contact us at this discord server: <a href="https://discord.gg/JEwYWGraj7">discord.gg/JEwYWGraj7</a> | ||||
|     </p> | ||||
| </body> | ||||
| </html> | ||||
| Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB | 
| Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB | 
| Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |