Compare commits

..

3 Commits

Author SHA1 Message Date
peshomir ad6f0e2f05 Update readme 2024-05-20 21:24:29 +03:00
peshomir 9e45327969 Update v0.6.4.1 - Small hotfix to resolve issues with the hovering tooltip 2024-05-20 18:38:54 +03:00
peshomir 0e4ecbb36f Update v0.6.4 - Clan leaderboard and hovering tooltip
- Added a leaderboard filter with tabs "All" and "Clan"
- Added the "hovering tooltip" (constantly displaying the territory information normally visible when right clicking on the map) and an option to toggle it in the settings
- The absolute keybinds' value is now displayed as a percentage
2024-05-20 17:09:29 +03:00
3 changed files with 250 additions and 28 deletions

125
build.js
View File

@ -78,7 +78,8 @@ const generateRegularExpression = (/** @type {string} */ code, /** @type {boolea
// 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, // 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, ///\((?<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, /(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 /,\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); ].forEach(matchDictionaryExpression);
const rawCodeSegments = [ const rawCodeSegments = [
@ -219,26 +220,35 @@ replaceOne(/(this\.\w+=function\((\w+),(\w+)\)\{)(\2===\w+&&\(\w+\.\w+\((\w+\.\w
"$1 donationsTracker.logDonation($2, $3, $5[0]); $4") "$1 donationsTracker.logDonation($2, $3, $5[0]); $4")
// Display donations for a player when clicking on them in the leaderboard // 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), // match , 0 !== dG[x]) && fq.hB(x, 800, false, 0),
replaceOne(/,(0!==\w+\[(\w+)\]\)&&\w+\.\w+\(\2,800,!1,0\),)/g, replaceOne(/,(0!==\w+\[(\w+)\])(\)&&\w+\.\w+\(\2,800,!1,0\),)/g,
`, ${dictionary.gIsTeamGame} && donationsTracker.displayHistory($2, ${dictionary.playerNames}, ${dictionary.gIsSingleplayer}), $1`); `, ${dictionary.gIsTeamGame} && donationsTracker.displayHistory($2, ${dictionary.playerNames}, ${dictionary.gIsSingleplayer}), $1 && !isEmptySpace $3`);
// Reset donation history when a new game is started // Reset donation history and leaderboard filter when a new game is started
replaceOne(new RegExp(`,${dictionary.playerBalances}=new Uint32Array\\(\\w+\\),`, "g"), "$& donationsTracker.reset(), "); replaceOne(new RegExp(`,${dictionary.playerBalances}=new Uint32Array\\(\\w+\\),`, "g"), "$& donationsTracker.reset(), leaderboardFilter.reset(), ");
{ // Player list { // Player list and leaderboard filter tabs
// Draw player list button // Draw player list button
const uiOffset = dictionary.uiSizes + "." + dictionary.gap; 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, 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);"); "$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)` const buttonBoundsCheck = `utils.isPointInRectangle($<x>, $<y>, ${uiOffset} + 12, ${uiOffset} + 12, ${topBarHeight} - 22, ${topBarHeight} - 22)`
// Handle player list button mouseDown // Handle player list button and leaderboard tabs mouseDown
replaceOne(/(this\.\w+=function\((?<x>\w+),(?<y>\w+)\){return!!\w+\(\2,\3\))&&(\(\w+=\w+\.\w+,)/g, // and create a function for scrolling the leaderboard to the top
`$1 && (${buttonBoundsCheck} && playerList.display(${dictionary.playerNames}), true) && $4`); replaceOne(/(this\.\w+=function\((?<x>\w+),(?<y>\w+)\){return!!\w+\(\2,\3\))&&(\(\w+=\w+\.\w+,[^}]+),!0\)/g,
// Handle player list button hover `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, 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,
`$1 if (${buttonBoundsCheck}) { playerList.hoveringOverButton === false && (playerList.hoveringOverButton = true, ${drawFunction}(), $<setRepaintNeeded>); } ` `leaderboardFilter.repaintLeaderboard = function() { ${drawFunction}(), $<setRepaintNeeded>; },
+ ` else { playerList.hoveringOverButton === true && (playerList.hoveringOverButton = false, ${drawFunction}(), $<setRepaintNeeded>); } $4`); $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 { // Display density of other players
@ -250,6 +260,97 @@ replaceOne(new RegExp(`,${dictionary.playerBalances}=new Uint32Array\\(\\w+\\),`
`$1 var ___id = $2; $7, $9; ${settingsSwitchNameAndBalance} && settings.showPlayerDensity && (settings.coloredDensity && ($<canvas>.fillStyle = utils.textStyleBasedOnDensity(___id)), $<canvas>.fillText(utils.getDensity(___id), $<x>, $<y> + $<fontSize>)); }`); `$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 // Disable built-in Territorial.io error reporting
replaceOne(/window\.addEventListener\("error",function (\w+)\((\w+)\){/g, replaceOne(/window\.addEventListener\("error",function (\w+)\((\w+)\){/g,
'$& window.removeEventListener("error", $1); return alert("Error:\\n" + $2.filename + " " + $2.lineno + " " + $2.colno + " " + $2.message);'); '$& window.removeEventListener("error", $1); return alert("Error:\\n" + $2.filename + " " + $2.lineno + " " + $2.colno + " " + $2.message);');

View File

@ -20,21 +20,22 @@ FX Client is the first Territorial.io client, targeting better User Interface an
## Features: ## Features:
1. It's 100% free and open source on Github 1. It's 100% free and open source on Github
2. It's Ad free and removes game's default ads. 2. It's ad-free and removes game's default ads.
3. It makes game look cooler, by replacing default assets with new ones. 3. It makes game look cooler, by replacing default assets with new ones.
4. Displays your troop density and maximum troops 4. Displays your troop density and maximum troops
5. Displays the density of players and bots 5. Displays the density of players and bots
6. Adds a win counter 6. Adds a "Clan" tab on the leaderboard, allowing you to easily see your clanmates
7. Adds a player list 7. Hovering tooltip: makes the territory map information (normally visible on right click) be visible constantly (on hover)
8. Adds the ability to view the history of who donated to a player during the game by clicking on their name in the leaderboard or the player list 8. Adds a player list
9. Can be installed as a PWA (progressive web app) ensuring maximum enjoyment on consoles, phones and even desktop devices 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: #### The client has a settings menu, from which you can:
10. Change the game font 12. Make fullscreen mode trigger automatically
11. Make fullscreen mode trigger automatically 13. Set a custom main menu background
12. Set a custom main menu background 14. Create custom attack percentage keybinds
13. Create custom attack percentage keybinds
## Building Locally ## Building Locally

View File

@ -1,5 +1,5 @@
const fx_version = '0.6.3.3'; // FX Client Version const fx_version = '0.6.4.1'; // FX Client Version
const fx_update = 'Apr 11'; // FX Client Last Updated const fx_update = 'May 20'; // FX Client Last Updated
if (localStorage.getItem("fx_winCount") == undefined || localStorage.getItem("fx_winCount") == null) { if (localStorage.getItem("fx_winCount") == undefined || localStorage.getItem("fx_winCount") == null) {
var wins_counter = 0; var wins_counter = 0;
@ -27,8 +27,9 @@ function KeybindsInput(containerElement) {
this.keys = [ "key", "type", "value" ]; this.keys = [ "key", "type", "value" ];
this.objectArray = []; this.objectArray = [];
this.addObject = function () { this.addObject = function () {
this.objectArray.push({ key: "", type: "absolute", value: 1 }); this.objectArray.push({ key: "", type: "absolute", value: 0.8 });
this.displayObjects(); this.displayObjects();
keybindAddButton.scrollIntoView(false);
}; };
this.update = function () { this.update = function () {
this.objectArray = settings.attackPercentageKeybinds; this.objectArray = settings.attackPercentageKeybinds;
@ -54,11 +55,15 @@ function KeybindsInput(containerElement) {
inputField.setAttribute("placeholder", "No key set"); inputField.setAttribute("placeholder", "No key set");
inputField.addEventListener("click", this.startKeyInput.bind(this, i, key)); inputField.addEventListener("click", this.startKeyInput.bind(this, i, key));
} else { // key === "value" } else { // key === "value"
inputField.type = "number"; const isAbsolute = this.objectArray[i].type === "absolute";
inputField.setAttribute("step", "0.1"); 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)); inputField.addEventListener("input", this.updateObject.bind(this, i, key));
} }
inputField.value = this.objectArray[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 // Append input field to the object div
objectDiv.appendChild(inputField); objectDiv.appendChild(inputField);
}, this); }, this);
@ -83,10 +88,21 @@ function KeybindsInput(containerElement) {
//this.displayObjects(); //this.displayObjects();
}, { once: true }); }, { 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) { this.updateObject = function (index, property, event) {
if (index >= this.objectArray.length) return; if (index >= this.objectArray.length) return;
// Update the corresponding property of the object in the array // Update the corresponding property of the object in the array
const value = property === "value" ? parseFloat(event.target.value) : property === "key" ? event.key : event.target.value; 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; this.objectArray[index][property] = value;
if (property === "key") this.displayObjects(); if (property === "key") this.displayObjects();
}; };
@ -104,6 +120,7 @@ var settings = {
//"showBotDonations": false, //"showBotDonations": false,
"displayWinCounter": true, "displayWinCounter": true,
"useFullscreenMode": false, "useFullscreenMode": false,
"hoveringTooltip": true,
//"hideAllLinks": false, //"hideAllLinks": false,
"realisticNames": false, "realisticNames": false,
"showPlayerDensity": true, "showPlayerDensity": true,
@ -123,6 +140,8 @@ var settingsManager = new (function() {
note: "The win counter tracks multiplayer solo wins (not in team games)" }, note: "The win counter tracks multiplayer solo wins (not in team games)" },
{ for: "useFullscreenMode", type: "checkbox", label: "Use fullscreen mode", { 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." }, 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: "hideAllLinks", type: "checkbox", label: "Hide Links option also hides app store links" },
{ for: "realisticNames", type: "checkbox", label: "Realistic Bot Names" }, { for: "realisticNames", type: "checkbox", label: "Realistic Bot Names" },
{ for: "showPlayerDensity", type: "checkbox", label: "Show player density" }, { for: "showPlayerDensity", type: "checkbox", label: "Show player density" },
@ -322,6 +341,107 @@ const playerList = new (function () {
canvas.imageSmoothingEnabled = false; 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(){ var donationsTracker = new (function(){
this.openedWindowPlayerID = null; this.openedWindowPlayerID = null;
this.contentElement = document.querySelector("#donationhistory_content"); this.contentElement = document.querySelector("#donationhistory_content");