diff --git a/fx.bundle.js b/fx.bundle.js index 43aaaad..281cc5a 100644 --- a/fx.bundle.js +++ b/fx.bundle.js @@ -1,2 +1,2 @@ -const buildTimestamp = "1752485839738"; const dictionary = {"gIsTeamGame":"he","game":"aD","playerId":"eT","playerData":"ag","playerNames":"a0N","gameState":"zo","fontSize":"fontSize","x":"eq","y":"es","canvas":"hm","gHumans":"k7","playerStates":"a39","fontGeneratorFunction":"b9.qV.sJ","rawPlayerNames":"a0S","playerBalances":"gl","playerTerritories":"gX","gLobbyMaxJoin":"wx","data":"data","playerCount":"playerCount","gBots":"kT","strs":"aAV","gIsSingleplayer":"kR","uiSizes":"bb","gap":"gap","gMaxPlayers":"eg","i":"aB","MenuManager":"aa","getState":"a10"}; -(()=>{"use strict";const e=JSON.parse('{"rE":"0.6.11","_e":"Jul 14","Ao":["Fix for game update 2.09.0","Error reporting improvements"]}'),t=e=>parseFloat(e.toFixed(12));function n(e){const n=document.createElement("p");n.innerText="Attack Percentage Keybinds";const o=document.createElement("div");o.className="arrayinput";const a=document.createElement("button");a.innerText="Add",e.append(n,o,a),e.className="keybinds-input",this.container=o,this.objectKeys=["key","type","value"],this.objectArray=[],this.addObject=function(){this.objectArray.push({key:"",type:"absolute",value:.8}),this.container.appendChild(i(this.objectArray.length-1)),a.scrollIntoView(!1)},a.addEventListener("click",this.addObject.bind(this)),this.update=function(e){this.objectArray=e.attackPercentageKeybinds,this.displayObjects()},this.displayObjects=function(){if(this.container.innerHTML="",0===this.objectArray.length)return this.container.innerText="No custom attack percentage keybinds added";for(var e=0;e{var t=document.createElement("div");this.objectKeys.forEach((n=>{t.appendChild(this.createInputField(e,n))}));var n=document.createElement("button");return n.textContent="Delete",n.addEventListener("click",this.deleteObject.bind(this,e)),t.appendChild(n),t};return this.createInputField=function(e,n){let o=document.createElement("type"===n?"select":"input");if("type"===n)o.innerHTML='',o.addEventListener("change",this.updateObject.bind(this,e,n));else if("key"===n)o.type="text",o.setAttribute("readonly",""),o.setAttribute("placeholder","No key set"),o.addEventListener("click",this.startKeyInput.bind(this,e,n));else{const t="absolute"===this.objectArray[e].type;o.type=t?"text":"number",t?o.addEventListener("click",this.convertIntoNumberInput.bind(this,e,n),{once:!0}):o.setAttribute("step","0.1"),o.addEventListener("input",this.updateObject.bind(this,e,n))}return"value"===n&&"absolute"===this.objectArray[e].type?o.value=t(100*this.objectArray[e][n])+"%":o.value=this.objectArray[e][n],o},this.recreateInputField=function(e,t){this.container.children[e].children[this.objectKeys.indexOf(t)].replaceWith(this.createInputField(e,t))},this.startKeyInput=function(e,t,n){n.target.value="Press any key";const o=this.updateObject.bind(this,e,t);n.target.addEventListener("keydown",o,{once:!0}),n.target.addEventListener("blur",(()=>{n.target.removeEventListener("keydown",o),n.target.value=this.objectArray[e][t]}),{once:!0})},this.convertIntoNumberInput=function(e,t,n){n.target.value=n.target.value.slice(0,-1),n.target.type="number",n.target.addEventListener("blur",(()=>{this.recreateInputField(e,t)}),{once:!0})},this.updateObject=function(e,n,o){if(e>=this.objectArray.length)return;const a="value"===n?"absolute"===this.objectArray[e].type?t(parseFloat(o.target.value)/100):parseFloat(o.target.value):"key"===n?o.key:o.target.value;this.objectArray[e][n]=a,"key"===n?this.recreateInputField(e,n):"type"===n&&this.recreateInputField(e,"value")},this.deleteObject=function(e){this.objectArray.splice(e,1),this.displayObjects()},this}const o={count:0,removeWins:function(){confirm("Do you really want to reset your wins?")&&(o.count=0,localStorage.removeItem("fx_winCount"),alert("Successfully reset wins"))}};null!==localStorage.getItem("fx_winCount")&&(o.count=localStorage.getItem("fx_winCount"));const a=o;var i={};const s=document.getElementById("windowContainer");function l(e){i[e.name]=e,i[e.name].isOpen=!1}function r(e){!1!==i[e].isOpen&&(i[e].isOpen=!1,i[e].element.style.display="none",void 0!==i[e].onClose&&i[e].onClose())}function c(){Object.values(i).forEach((function(e){!1!==e.closable&&r(e.name)}))}document.addEventListener("mousedown",(e=>{s.contains(e.target)||c(),T().useFullscreenMode&&k()}),{passive:!0,capture:!0}),document.getElementById("canvasA").addEventListener("touchstart",c,{passive:!0}),document.addEventListener("keydown",(e=>{"Escape"===e.key&&c()}));const d={create:function(e){const t=document.createElement("div");if(e.element=t,t.className="window"+(void 0!==e.classes?" "+e.classes:" scrollable selectable"),t.style.display="none",!0===e.closeWithButton){const n=document.createElement("button");n.addEventListener("click",(()=>r(e.name))),n.textContent="Close",setTimeout((()=>t.appendChild(n)))}return s.appendChild(t),l(e),t},add:l,openWindow:function(e,...t){!0!==i[e].isOpen&&(void 0!==i[e].beforeOpen&&i[e].beforeOpen(...t),i[e].isOpen=!0,i[e].element.style.display=null)},closeWindow:r,closeAll:c},{Ao:u,rE:p}=e,h=d.create({name:"changelog",closeWithButton:!0}),m=document.createElement("h1");m.textContent="What's new";const y=document.createElement("p");y.textContent=`in FX Client v${p}`;const b=document.createElement("ul");function f(){d.openWindow("changelog")}u.forEach((e=>{const t=document.createElement("li");t.textContent=e,b.appendChild(t)})),h.append(m,y,b),window.__fx=window.__fx||{};const g=window.__fx;var v={displayWinCounter:!0,useFullscreenMode:!1,hoveringTooltip:!0,realisticNames:!1,showPlayerDensity:!0,coloredDensity:!0,densityDisplayStyle:"absoluteQuotient",hideBotNames:!1,highlightClanSpawns:!1,detailedTeamPercentage:!1,openDonationHistoryFromLb:!0,customBackgroundUrl:"",keybindButtons:!1,attackPercentageKeybinds:[]};g.settings=v;const w=["hideAllLinks","fontName"];g.makeMainMenuTransparent=!1;const E=new function(){const t=[{for:"displayWinCounter",type:"checkbox",label:"Display win counter",note:"The win counter tracks multiplayer solo wins (not in team games)"},{type:"button",text:"Reset win counter",action:a.removeWins},{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:"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:"hideBotNames",type:"checkbox",label:"Hide bot names"},{for:"highlightClanSpawns",type:"checkbox",label:"Highlight clan spawnpoints",note:"Increases the spawnpoint glow size for members of your clan"},{for:"detailedTeamPercentage",type:"checkbox",label:"Detailed team pie chart percentage",note:"For example: this would show 25.82% instead of 26% on the pie chart in team games"},{for:"openDonationHistoryFromLb",type:"checkbox",label:"Open donation history from the leaderboard",note:"Changes whether or not clicking on a player's name in the in-game leaderboard in team games will open their donation history"},{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."},n,{for:"keybindButtons",type:"checkbox",label:"Keybind buttons",note:"Show keybind buttons above the troop selector (max 6)"},function(t){const n=document.createElement("p");n.innerText=`FX Client v${e.rE}`;const o=document.createElement("p");o.innerHTML='Discord server |\n Github repository';const a=document.createElement("button");a.innerText="Changelog",a.addEventListener("click",f),t.append(n,o,a)}],o=document.querySelector(".settings .scrollable");var i={},s={},l=[];t.forEach((e=>{if("function"==typeof e){const t=document.createElement("div");return l.push(new e(t)),o.append(t)}const t=document.createElement("label");e.tooltip&&(t.title=e.tooltip);const n=e.type.endsWith("Input"),a=document.createElement(n||"checkbox"===e.type?"input":"selectMenu"===e.type?"select":"button");if("textInput"===e.type&&(a.type="text"),e.placeholder&&(a.placeholder=e.placeholder),(n||"selectMenu"===e.type)&&(i[e.for]=a),e.text&&(a.innerText=e.text),e.action&&a.addEventListener("click",e.action),e.label&&t.append(e.label+" "),e.note){const n=document.createElement("small");n.innerText=e.note,t.append(document.createElement("br"),n)}if(e.options&&e.options.forEach((e=>{const t=document.createElement("option");t.setAttribute("value",e.value),t.innerText=e.label,a.append(t)})),t.append(a),"checkbox"===e.type){a.type="checkbox";const n=document.createElement("span");n.className="checkmark",t.className="checkbox",t.append(n),s[e.for]=a}else t.append(document.createElement("br"));o.append(t,document.createElement("br"))})),this.save=function(){Object.keys(i).forEach((function(e){v[e]=i[e].value.trim()})),Object.keys(s).forEach((function(e){v[e]=s[e].checked})),this.applySettings(),d.closeWindow("settings"),w.forEach((e=>delete v[e])),localStorage.setItem("fx_settings",JSON.stringify(v)),window.location.reload()};const r=document.createElement("input");function c(e){const t=e.target,n=t.files[0];if(!n)return;if(t.removeEventListener("change",c),t.value="",!n.name.endsWith(".json"))return alert("Invalid file format");const o=new FileReader;o.onload=function(){let e;try{e=JSON.parse(o.result),confirm('Warning: This will override all current settings, click "OK" to confirm')&&(g.settings=v=e),localStorage.setItem("fx_settings",JSON.stringify(v)),window.location.reload()}catch(e){alert("Error\n"+e)}},o.readAsText(n)}r.type="file",this.importFromFile=function(){r.click(),r.addEventListener("change",c)},this.exportToFile=function(){var e,t,n;e=JSON.stringify(v),t=document.createElement("a"),n=new Blob([e],{type:"application/json"}),t.href=URL.createObjectURL(n),t.download="FX_client_settings.json",t.click(),URL.revokeObjectURL(t.href)},this.syncFields=function(){Object.keys(i).forEach((function(e){i[e].value=v[e]})),Object.keys(s).forEach((function(e){s[e].checked=v[e]})),l.forEach((e=>e.update?.(v)))},this.resetAll=function(){confirm("Are you Really SURE you want to RESET ALL SETTINGS back to the default?")&&(localStorage.removeItem("fx_settings"),window.location.reload())},this.applySettings=function(){""!==v.customBackgroundUrl&&(document.body.style.backgroundImage="url("+v.customBackgroundUrl+")",document.body.style.backgroundSize="cover",document.body.style.backgroundPosition="center"),g.makeMainMenuTransparent=""!==v.customBackgroundUrl},v.useFullscreenMode&&k()};function k(){null===document.fullscreenElement&&document.fullscreenEnabled&&document.documentElement.requestFullscreen({navigationUI:"hide"}).then((()=>{console.log("Fullscreen mode activated")})).catch((e=>{console.warn("Could not enter fullscreen mode:",e)}))}d.add({name:"settings",element:document.querySelector(".settings"),beforeOpen:function(){E.syncFields()}}),null!==localStorage.getItem("fx_settings")&&(g.settings=v={...v,...JSON.parse(localStorage.getItem("fx_settings"))}),E.applySettings();const x=E;function T(){return v}const L=["playerTerritories","playerBalances","rawPlayerNames"],I=["playerId","gIsTeamGame","gHumans","gLobbyMaxJoin","gameState","gIsSingleplayer"],S=e=>L.includes(e)?window[dictionary.playerData]?.[dictionary[e]]:I.includes(e)?window[dictionary.game]?.[dictionary[e]]:window[dictionary[e]],C=()=>Math.floor(window[dictionary.uiSizes]?.[dictionary.gap]??10),O=new function(){this.playersToInclude=[],this.tabLabels=["ALL","CLAN"],this.filteredLeaderboard=[],this.tabBarOffset=0,this.windowWidth=0,this.verticalClickThreshold=1e3,this.hoveringOverTabs=!1,this.scrollToTop=()=>{},this.repaintLeaderboard=()=>{},this.setUpdateFlag=()=>{},this.parseClanFromPlayerName=()=>{console.warn("parse function not set")},this.selectedTab=0,this.tabHovering=-1,this.enabled=!1,this.drawTabs=function(e,t,n,o){e.textBaseline="middle",e.textAlign="center";const a=t/this.tabLabels.length,i=n+this.tabBarOffset/2;this.tabLabels.forEach(((t,s)=>{0!==s&&e.fillRect(a*s,n,1,this.tabBarOffset),this.selectedTab===s&&(e.fillStyle=o,e.fillRect(a*s,n,a,this.tabBarOffset),e.fillStyle="rgb(255,255,255)"),this.tabHovering===s&&(e.fillStyle="rgba(255,255,255,0.3)",e.fillRect(a*s,n,a,this.tabBarOffset),e.fillStyle="rgb(255,255,255)"),e.fillText(t,a*s+a/2,i)}))},this.setHovering=(e,t)=>{let n=!1;if(e){const e=Math.floor(t/(this.windowWidth/this.tabLabels.length));this.tabHovering!==e&&(this.tabHovering=e,n=!0)}return e!==this.hoveringOverTabs&&(this.hoveringOverTabs=e,!1===e&&(this.tabHovering=-1),e||(n=!0)),n&&this.repaintLeaderboard(),e},this.handleMouseDown=e=>{const t=Math.floor(e/(this.windowWidth/this.tabLabels.length));return this.selectedTab!==t&&(this.selectedTab=t,0===this.selectedTab?this.clearFilter():1===this.selectedTab&&(this.filterByOwnClan(),this.setUpdateFlag()),this.repaintLeaderboard()),!0},this.filterByOwnClan=()=>{this.playersToInclude=[];const e=S("playerId"),t=this.parseClanFromPlayerName(S("rawPlayerNames")[e]);S("rawPlayerNames").forEach(((n,o)=>{o!==e&&this.parseClanFromPlayerName(n)!==t||this.playersToInclude.push(o)})),this.enabled=!0,this.scrollToTop()},this.clearFilter=()=>{this.enabled=!1},this.reset=()=>{this.enabled=!1,this.selectedTab=0,A.refresh()}},A=new function(){this.inOwnClan=new Array(512),this.inOwnClan.fill(!1),this.refresh=()=>{const e=S("gHumans"),t=O.parseClanFromPlayerName(S("rawPlayerNames")[S("playerId")]);null===t?this.inOwnClan.fill(!1):S("rawPlayerNames").forEach(((n,o)=>{this.inOwnClan[o]=o/g,">").replace(/"/g,""").replace(/'/g,"'")}let N=null;d.add({name:"donationHistory",element:document.querySelector("#donationhistory"),beforeOpen:function(e){document.getElementById("donationhistory_note").style.display="none"},onClose:function(){F.openedWindowPlayerID=null}});const F=new function(){this.openedWindowPlayerID=null,this.contentElement=document.querySelector("#donationhistory_content"),this.donationHistory=Array(512);let e=!1;function t(e,t,n,o){const a=S("rawPlayerNames"),i=document.createElement("tr");o&&i.setAttribute("class","new");let s=`${t}. `;return n===e[1]?s+=`Received ${e[2]} resources from ${B(a[e[0]])}`:s+=`Sent ${e[2]} resources to ${B(a[e[1]])}`,s+="",i.innerHTML=s,i}this.getHistoryOf=function(t){return function(e,t){try{return e()}catch(e){throw N=t,setTimeout((()=>{null!==N&&(N=null)})),e}}((()=>this.donationHistory[t].toReversed()),{playerID:t,resetCalled:e,type:typeof this.donationHistory[t],isArray:Array.isArray(this.donationHistory[t])})},this.reset=function(){e=!0;for(var t=0;t<512;t++)this.donationHistory[t]=[]},this.logDonation=function(e,n,o){const a=[e,n,o];if(this.donationHistory[n].push(a),this.donationHistory[e].push(a),this.openedWindowPlayerID===e||this.openedWindowPlayerID===n){const o=this.donationHistory[this.openedWindowPlayerID===e?e:n].length;this.contentElement.prepend(t(a,o,this.openedWindowPlayerID,!0))}},this.displayHistory=function(e,n=S("rawPlayerNames"),o=S("gIsSingleplayer")){var a=F.getHistoryOf(e);console.log("History for "+n[e]+":"),console.log(a),document.querySelector("#donationhistory h1").innerHTML="Donation history for "+B(n[e]),this.contentElement.innerHTML="",a.length>0?a.forEach(((n,o)=>{this.contentElement.appendChild(t(n,a.length-o,e))})):this.contentElement.innerText="Nothing to display",this.openedWindowPlayerID=e,d.openWindow("donationHistory",o)}},H=F,M=new function(){const e=document.createElement("img");e.setAttribute("src","assets/players_icon.png"),document.getElementById("playerlist_content").addEventListener("click",(e=>{const t=e.target.closest("tr[data-player-id]")?.getAttribute("data-player-id");t&&S("gIsTeamGame")&&(d.closeWindow("playerList"),H.displayHistory(t))})),this.display=function(e){const t=S("gHumans"),n=S("gLobbyMaxJoin");let o=`

Players (${t})

`;for(let a=0;aBots (${n-t})`),o+=`${a+1}. ${B(e[a])}`;document.getElementById("playerlist_content").innerHTML=o,document.getElementById("playerlist_content").setAttribute("class",S("gIsTeamGame")?"clickable":""),d.openWindow("playerList")},this.hoveringOverButton=!1,this.drawButton=(t,n,o,a)=>{t.fillRect(n,o,a,a),t.fillStyle=this.hoveringOverButton?"#aaaaaaaa":"#000000aa",t.clearRect(n+1,o+1,a-2,a-2),t.fillRect(n+1,o+1,a-2,a-2),t.fillStyle="#ffffff",t.imageSmoothingEnabled=!0,t.drawImage(e,n+2,o+2,a-4,a-4),t.imageSmoothingEnabled=!1}};d.add({name:"playerList",element:document.getElementById("playerlist")});const P=M,W={getMaxTroops:function(e,t){return(150*e[t]).toString()},getDensity:function(e,t=S("playerBalances"),n=S("playerTerritories")){return"percentage"===T().densityDisplayStyle?(t[e]/(150*(0===n[e]?1:n[e]))*100).toFixed(1)+"%":(t[e]/(0===n[e]?1:n[e])).toFixed(1)},isPointInRectangle:function(e,t,n,o,a,i){return e>=n&&e<=n+a&&t>=o&&t<=o+i},fillTextMultiline:function(e,t,n,o,a){const i=parseInt(e.font.split(" ").find((e=>e.endsWith("px"))).slice(0,-2));t.split("\n").forEach(((t,s)=>e.fillText(t,n,o+s*i,a)))},textStyleBasedOnDensity:function(e){const t=S("playerBalances"),n=S("playerTerritories");return`hsl(${t[e]/(1.5*n[e])}, 100%, 50%, 1)`}},j=new function(){let e=!1;function t(t){if(!T().hoveringTooltip||!S("gameState")||e)return;let n,o;if(t.type.includes("touch")){const{touches:e,changedTouches:a}=t.originalEvent??t,i=e[0]??a[0];n=i.pageX,o=i.pageY}else t.type.includes("mouse")&&(n=t.clientX,o=t.clientY);e=!0;try{this.display(this.canvasPixelScale*n,this.canvasPixelScale*o)}catch(t){console.error(t)}setTimeout((()=>e=!1),100)}this.display=()=>{},this.canvasPixelScale=1,document.getElementById("canvasA").addEventListener("mousemove",t.bind(this)),document.getElementById("canvasA").addEventListener("touchstart",t.bind(this))},D={setAbsolute:()=>{},setRelative:()=>{},repaintAttackPercentageBar:()=>{}};function _(e){"absolute"===e.type?D.setAbsolute(e.value):D.setRelative(e.value),D.repaintAttackPercentageBar()}let R,U=0,J=0;const K={setSize:(e,t,n)=>{if(!0!==T().keybindButtons)return;U=e,J=t,R=document.createElement("canvas"),R.width=e,R.height=t;const o=R.getContext("2d"),a=n.font.split("px ",2)[1];o.font="bold "+t/2+"px "+a,o.textAlign="center",o.textBaseline="middle";const i=T().attackPercentageKeybinds.slice(0,6),s=C()/4,l=(e-5*s)/6;i.forEach(((e,n)=>{o.fillStyle="rgba(0, 0, 0, 0.8)",o.fillRect(n*(l+s),0,l,t),o.fillStyle="white";const a="absolute"===e.type?(100*e.value).toFixed()+"%":"x "+Math.round(100*e.value)/100;o.fillText(a,(n+.5)*(l+s),t/2)}))},click:e=>{if(e<0||e>U)return!1;const t=T().attackPercentageKeybinds,n=Math.floor(e/U*6);return!(n>=t.length||(_(t[n]),0))},draw:(e,t,n)=>{e.drawImage(R,t,n-(J+C()/4))}};let $=!1,G="",X=()=>{},q=()=>{},V=(e,t)=>{};const z=new TextEncoder,Q=new TextDecoder;d.add({name:"lobbyJoinMenu",element:document.getElementById("customLobbyJoinMenu")}),d.create({name:"customLobbiesUnavailable",closeWithButton:!0}).innerHTML='

The latest version of FX Client doesn\'t support custom lobbies yet. Use the stable version at https://fxclient.github.io/custom-lobbies

';const Y=d.create({name:"customLobby",classes:"scrollable selectable flex-column text-align-center",closable:!1}),Z=document.createElement("h2");Z.textContent="Custom Lobby";const ee=document.createElement("div");ee.className="customlobby-main";const te=document.createElement("div"),ne=document.createElement("p");ne.textContent="0 Players";const oe=document.createElement("div");te.append(ne,oe);const ae=document.createElement("div");ae.className="text-align-left";const ie={mode:{label:"Mode:",type:"selectMenu",options:[{value:0,label:"2 Teams"},{value:1,label:"3 Teams"},{value:2,label:"4 Teams"},{value:3,label:"5 Teams"},{value:4,label:"6 Teams"},{value:5,label:"7 Teams"},{value:6,label:"8 Teams"},{value:7,label:"Battle Royale"},{value:10,label:"No Fullsend Battle Royale"},{value:9,label:"Zombie mode"}]},map:{label:"Map:",type:"selectMenu"},difficulty:{label:"Difficulty:",type:"selectMenu",options:[{value:0,label:"Very Easy (Default)"},{value:1,label:"Easy (1v1)"},{value:2,label:"Normal"},{value:3,label:"Hard"},{value:4,label:"Very Hard"},{value:5,label:"Impossible"}]},spawnSelection:{label:"Spawn selection",type:"checkbox"},botCount:{label:"Bot & player count:",type:"numberInput",attributes:{min:"1",max:"512"}}},se={},le={};function re(e,t){"checkbox"===ie[e].type?se[e].checked=0!==t:se[e].value=t.toString(),le[e]=t}function ce(e,t){fe("options",[e,parseInt(t.target.value)])}function de(e,t){fe("options",[e,t.target.checked?1:0])}Object.entries(ie).forEach((([e,t])=>{const n=document.createElement("label");t.tooltip&&(n.title=t.tooltip);const o=t.type.endsWith("Input"),a=document.createElement(o||"checkbox"===t.type?"input":"selectMenu"===t.type?"select":"button");if(se[e]=a,"textInput"===t.type&&(a.type="text"),"numberInput"===t.type&&(a.type="number"),t.placeholder&&(a.placeholder=t.placeholder),(o||"selectMenu"===t.type)&&a.addEventListener("change",ce.bind(void 0,e)),t.text&&(a.innerText=t.text),t.action&&a.addEventListener("click",t.action),t.label&&n.append(t.label+" "),t.note){const e=document.createElement("small");e.innerText=t.note,n.append(document.createElement("br"),e)}if(t.options&&be(t.options,a),t.attributes&&Object.entries(t.attributes).forEach((([e,t])=>a.setAttribute(e,t))),n.append(a),"checkbox"===t.type){a.type="checkbox";const t=document.createElement("span");t.className="checkmark",n.className="checkbox",n.append(t),a.addEventListener("change",de.bind(void 0,e))}else n.append(document.createElement("br"));ae.append(n)})),ee.append(te,ae);const ue=document.createElement("footer");function pe(e,t){const n=document.createElement("button");return n.textContent=e,n.addEventListener("click",t),n}ue.style.marginTop="10px";const he=pe("Start game",(function(){d.closeWindow("customLobby"),fe("startGame")})),me=pe("Leave lobby",(()=>q())),ye=pe("Copy link",(()=>{navigator.clipboard.writeText(`${window.location.href}#lobby=${G}`),ye.textContent="Copied!",setTimeout((()=>ye.textContent="Copy link"),1e3)}));function be(e,t){e.forEach((e=>{const n=document.createElement("option");n.setAttribute("value",e.value),n.textContent=e.label,t.append(n)}))}function fe(e,t){const n=void 0!==t?{t:e,d:t}:{t:e},o=z.encode(JSON.stringify(n)),a=new ArrayBuffer(o.length+1);new DataView(a).setUint8(0,120),new Uint8Array(a,1).set(o),V(1,a)}ue.append(he,me,ye),Y.append(Z,ee,ue),document.getElementById("lobbyCode").addEventListener("input",(({target:e})=>{5===e.value.length&&(G=e.value.toLowerCase(),e.value="",d.closeWindow("lobbyJoinMenu"),$=!0,X())})),document.getElementById("createLobbyButton").addEventListener("click",(()=>{G="",d.closeWindow("lobbyJoinMenu"),$=!0,X()}));let ge,ve=!1,we=[];function Ee(e,t){const n=document.createElement("span");return n.textContent=e,n.className=t?"":"d-none",n}function ke(e){const t=document.createElement("div");t.className="lobby-player",t.textContent=e.name;const n=document.createElement("button");n.textContent="Kick",n.className=ve&&!e.isHost?"":"d-none",n.addEventListener("click",xe);const o=Ee("Host",e.isHost),a=Ee("In Game",e.inGame);t.append(o,a,n),oe.append(t),we.push({element:t,hostBadge:o,inGameBadge:a,kickButton:n,isHost:e.isHost,inGame:e.inGame})}function xe(e){const t=e.target;for(let e=0;eLe()));const Ie={gameInfo:le,showJoinPrompt:function(){return d.openWindow("customLobbiesUnavailable")},isCustomMessage:function(e){if(120!==e[0])return!1;if(1===e.length)return!0;const t=new Uint8Array(e.buffer,1),n=JSON.parse(Q.decode(t)),{t:o,d:a}=n;if("lobby"===o)d.openWindow("customLobby"),Z.textContent="Custom Lobby "+a.code,G=a.code,ve=a.isHost,he.disabled=!ve,ve?ae.classList.remove("disabled"):ae.classList.add("disabled"),Object.entries(a.options).forEach((([e,t])=>re(e,t))),i=a.players,s=a.id,we=[],oe.innerHTML="",i.forEach(ke),ge=we[s],Te();else if("addPlayer"===o)ke({name:a.name,inGame:!1,isHost:!1}),Te();else if("removePlayer"===o){const e=a;we[e].element.remove(),we.splice(e,1),Te()}else if("inLobby"===o){const e=a;we[e].inGame=!1,we[e].inGameBadge.className="d-none"}else if("options"===o){const[e,t]=a;re(e,t)}else if("setHost"===o){const e=a;we[e].isHost=!0,we[e].hostBadge.className=""}else"host"===o?(ve=!0,he.disabled=!1,ae.classList.remove("disabled"),we.forEach((e=>{e.isHost||(e.kickButton.className="")}))):"serverMessage"===o&&alert(a);var i,s;return!0},getSocketURL:function(){return"wss://fx.peshomir.workers.dev/"+(""===G?"create":"join?"+G)},getPlayerId:function(){let e=0;for(let t=0;tbe(e.map(((e,t)=>({value:t.toString(),label:e.name}))),se.map)),0)},rejoinLobby:function(){X()},hideWindow:function(){d.closeWindow("customLobby")},isActive:()=>$,setActive:function(e){$=e,!1===e&&d.closeWindow("customLobby")}},{rE:Se,_e:Ce}=e;"serviceWorker"in navigator&&(navigator.serviceWorker.addEventListener("message",(e=>{const t=e.data;"activate"===t.event&&buildTimestamp!==t.version&&(document.getElementById("updateNotification").style.display="block")})),navigator.serviceWorker.register("./sw.js"));const Oe=localStorage.getItem("fx_version");Oe!==Se&&(localStorage.setItem("fx_version",Se),null!==Oe&&f()),window.__fx=window.__fx||{};const Ae=window.__fx;Ae.version=Se+" "+Ce,Ae.settingsManager=x,Ae.leaderboardFilter=O,Ae.utils=W,Ae.WindowManager=d,Ae.keybindFunctions=D,Ae.keybindHandler=e=>{const t=T().attackPercentageKeybinds.find((t=>t.key===e));return void 0!==t&&(0!==S("gameState")&&_(t),!0)},Ae.mobileKeybinds=K,Ae.donationsTracker=H,Ae.reportError=function(e,t){function n(e){try{return S(e)}catch(e){return e.toString()}}t=e.filename+" "+e.lineno+" "+e.colno+" "+e.message+"\n"+t,fetch("https://fx.peshomir.workers.dev/stats/errors",{body:JSON.stringify({message:t,context:{debug:N,gameState:n("gameState"),singleplayer:n("gIsSingleplayer"),swState:navigator.serviceWorker?.controller?.state,location:window.location.toString(),userAgent:navigator.userAgent,dictionary:JSON.stringify(dictionary),buildTimestamp,scripts:Array.from(document.scripts).map((e=>e.src))}}),method:"POST"}).catch((e=>alert("Failed to report error: "+e)))},Ae.playerList=P,Ae.hoveringTooltip=j,Ae.clanFilter=A,Ae.wins=a,Ae.customLobby=Ie,console.log("Successfully loaded FX Client")})(); \ No newline at end of file +const buildTimestamp = "1752610538098"; const dictionary = {"gIsTeamGame":"he","game":"aD","playerId":"eT","playerData":"ag","playerNames":"a0N","gameState":"zo","fontSize":"fontSize","x":"eq","y":"es","canvas":"hm","gHumans":"k7","playerStates":"a39","fontGeneratorFunction":"b9.qV.sJ","rawPlayerNames":"a0S","playerBalances":"gl","playerTerritories":"gX","gLobbyMaxJoin":"wx","data":"data","playerCount":"playerCount","gBots":"kT","strs":"aAV","gIsSingleplayer":"kR","uiSizes":"bb","gap":"gap","gMaxPlayers":"eg","i":"aB","MenuManager":"aa","getState":"a10"}; +(()=>{"use strict";const e=JSON.parse('{"rE":"0.6.12","_e":"Jul 15","Ao":["Refactored the service worker code to (hopefully) fix a ton of relatively rare errors related to how the client\'s are loaded (for example, code from earlier versions would sometimes be loaded along with code from newer versions, causing all kinds of issues). FX Client should be much more stable after this change."]}'),t=e=>parseFloat(e.toFixed(12));function n(e){const n=document.createElement("p");n.innerText="Attack Percentage Keybinds";const o=document.createElement("div");o.className="arrayinput";const a=document.createElement("button");a.innerText="Add",e.append(n,o,a),e.className="keybinds-input",this.container=o,this.objectKeys=["key","type","value"],this.objectArray=[],this.addObject=function(){this.objectArray.push({key:"",type:"absolute",value:.8}),this.container.appendChild(i(this.objectArray.length-1)),a.scrollIntoView(!1)},a.addEventListener("click",this.addObject.bind(this)),this.update=function(e){this.objectArray=e.attackPercentageKeybinds,this.displayObjects()},this.displayObjects=function(){if(this.container.innerHTML="",0===this.objectArray.length)return this.container.innerText="No custom attack percentage keybinds added";for(var e=0;e{var t=document.createElement("div");this.objectKeys.forEach((n=>{t.appendChild(this.createInputField(e,n))}));var n=document.createElement("button");return n.textContent="Delete",n.addEventListener("click",this.deleteObject.bind(this,e)),t.appendChild(n),t};return this.createInputField=function(e,n){let o=document.createElement("type"===n?"select":"input");if("type"===n)o.innerHTML='',o.addEventListener("change",this.updateObject.bind(this,e,n));else if("key"===n)o.type="text",o.setAttribute("readonly",""),o.setAttribute("placeholder","No key set"),o.addEventListener("click",this.startKeyInput.bind(this,e,n));else{const t="absolute"===this.objectArray[e].type;o.type=t?"text":"number",t?o.addEventListener("click",this.convertIntoNumberInput.bind(this,e,n),{once:!0}):o.setAttribute("step","0.1"),o.addEventListener("input",this.updateObject.bind(this,e,n))}return"value"===n&&"absolute"===this.objectArray[e].type?o.value=t(100*this.objectArray[e][n])+"%":o.value=this.objectArray[e][n],o},this.recreateInputField=function(e,t){this.container.children[e].children[this.objectKeys.indexOf(t)].replaceWith(this.createInputField(e,t))},this.startKeyInput=function(e,t,n){n.target.value="Press any key";const o=this.updateObject.bind(this,e,t);n.target.addEventListener("keydown",o,{once:!0}),n.target.addEventListener("blur",(()=>{n.target.removeEventListener("keydown",o),n.target.value=this.objectArray[e][t]}),{once:!0})},this.convertIntoNumberInput=function(e,t,n){n.target.value=n.target.value.slice(0,-1),n.target.type="number",n.target.addEventListener("blur",(()=>{this.recreateInputField(e,t)}),{once:!0})},this.updateObject=function(e,n,o){if(e>=this.objectArray.length)return;const a="value"===n?"absolute"===this.objectArray[e].type?t(parseFloat(o.target.value)/100):parseFloat(o.target.value):"key"===n?o.key:o.target.value;this.objectArray[e][n]=a,"key"===n?this.recreateInputField(e,n):"type"===n&&this.recreateInputField(e,"value")},this.deleteObject=function(e){this.objectArray.splice(e,1),this.displayObjects()},this}const o={count:0,removeWins:function(){confirm("Do you really want to reset your wins?")&&(o.count=0,localStorage.removeItem("fx_winCount"),alert("Successfully reset wins"))}};null!==localStorage.getItem("fx_winCount")&&(o.count=localStorage.getItem("fx_winCount"));const a=o;var i={};const s=document.getElementById("windowContainer");function l(e){i[e.name]=e,i[e.name].isOpen=!1}function r(e){!1!==i[e].isOpen&&(i[e].isOpen=!1,i[e].element.style.display="none",void 0!==i[e].onClose&&i[e].onClose())}function c(){Object.values(i).forEach((function(e){!1!==e.closable&&r(e.name)}))}document.addEventListener("mousedown",(e=>{s.contains(e.target)||c(),T().useFullscreenMode&&x()}),{passive:!0,capture:!0}),document.getElementById("canvasA").addEventListener("touchstart",c,{passive:!0}),document.addEventListener("keydown",(e=>{"Escape"===e.key&&c()}));const d={create:function(e){const t=document.createElement("div");if(e.element=t,t.className="window"+(void 0!==e.classes?" "+e.classes:" scrollable selectable"),t.style.display="none",!0===e.closeWithButton){const n=document.createElement("button");n.addEventListener("click",(()=>r(e.name))),n.textContent="Close",setTimeout((()=>t.appendChild(n)))}return s.appendChild(t),l(e),t},add:l,openWindow:function(e,...t){!0!==i[e].isOpen&&(void 0!==i[e].beforeOpen&&i[e].beforeOpen(...t),i[e].isOpen=!0,i[e].element.style.display=null)},closeWindow:r,closeAll:c},{Ao:u,rE:p}=e,h=d.create({name:"changelog",closeWithButton:!0}),m=document.createElement("h1");m.textContent="What's new";const y=document.createElement("p");y.textContent=`in FX Client v${p}`;const b=document.createElement("ul");function f(){d.openWindow("changelog")}u.forEach((e=>{const t=document.createElement("li");t.textContent=e,b.appendChild(t)})),h.append(m,y,b),window.__fx=window.__fx||{};const g=window.__fx;var v={displayWinCounter:!0,useFullscreenMode:!1,hoveringTooltip:!0,realisticNames:!1,showPlayerDensity:!0,coloredDensity:!0,densityDisplayStyle:"absoluteQuotient",hideBotNames:!1,highlightClanSpawns:!1,detailedTeamPercentage:!1,openDonationHistoryFromLb:!0,customBackgroundUrl:"",keybindButtons:!1,attackPercentageKeybinds:[]};g.settings=v;const w=["hideAllLinks","fontName"];g.makeMainMenuTransparent=!1;const E=new function(){const t=[{for:"displayWinCounter",type:"checkbox",label:"Display win counter",note:"The win counter tracks multiplayer solo wins (not in team games)"},{type:"button",text:"Reset win counter",action:a.removeWins},{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:"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:"hideBotNames",type:"checkbox",label:"Hide bot names"},{for:"highlightClanSpawns",type:"checkbox",label:"Highlight clan spawnpoints",note:"Increases the spawnpoint glow size for members of your clan"},{for:"detailedTeamPercentage",type:"checkbox",label:"Detailed team pie chart percentage",note:"For example: this would show 25.82% instead of 26% on the pie chart in team games"},{for:"openDonationHistoryFromLb",type:"checkbox",label:"Open donation history from the leaderboard",note:"Changes whether or not clicking on a player's name in the in-game leaderboard in team games will open their donation history"},{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."},n,{for:"keybindButtons",type:"checkbox",label:"Keybind buttons",note:"Show keybind buttons above the troop selector (max 6)"},function(t){const n=document.createElement("p");n.innerText=`FX Client v${e.rE}`;const o=document.createElement("p");o.innerHTML='Discord server |\n Github repository';const a=document.createElement("button");a.innerText="Changelog",a.addEventListener("click",f),t.append(n,o,a)}],o=document.querySelector(".settings .scrollable");var i={},s={},l=[];t.forEach((e=>{if("function"==typeof e){const t=document.createElement("div");return l.push(new e(t)),o.append(t)}const t=document.createElement("label");e.tooltip&&(t.title=e.tooltip);const n=e.type.endsWith("Input"),a=document.createElement(n||"checkbox"===e.type?"input":"selectMenu"===e.type?"select":"button");if("textInput"===e.type&&(a.type="text"),e.placeholder&&(a.placeholder=e.placeholder),(n||"selectMenu"===e.type)&&(i[e.for]=a),e.text&&(a.innerText=e.text),e.action&&a.addEventListener("click",e.action),e.label&&t.append(e.label+" "),e.note){const n=document.createElement("small");n.innerText=e.note,t.append(document.createElement("br"),n)}if(e.options&&e.options.forEach((e=>{const t=document.createElement("option");t.setAttribute("value",e.value),t.innerText=e.label,a.append(t)})),t.append(a),"checkbox"===e.type){a.type="checkbox";const n=document.createElement("span");n.className="checkmark",t.className="checkbox",t.append(n),s[e.for]=a}else t.append(document.createElement("br"));o.append(t,document.createElement("br"))})),this.save=function(){Object.keys(i).forEach((function(e){v[e]=i[e].value.trim()})),Object.keys(s).forEach((function(e){v[e]=s[e].checked})),this.applySettings(),d.closeWindow("settings"),w.forEach((e=>delete v[e])),localStorage.setItem("fx_settings",JSON.stringify(v)),window.location.reload()};const r=document.createElement("input");function c(e){const t=e.target,n=t.files[0];if(!n)return;if(t.removeEventListener("change",c),t.value="",!n.name.endsWith(".json"))return alert("Invalid file format");const o=new FileReader;o.onload=function(){let e;try{e=JSON.parse(o.result),confirm('Warning: This will override all current settings, click "OK" to confirm')&&(g.settings=v=e),localStorage.setItem("fx_settings",JSON.stringify(v)),window.location.reload()}catch(e){alert("Error\n"+e)}},o.readAsText(n)}r.type="file",this.importFromFile=function(){r.click(),r.addEventListener("change",c)},this.exportToFile=function(){var e,t,n;e=JSON.stringify(v),t=document.createElement("a"),n=new Blob([e],{type:"application/json"}),t.href=URL.createObjectURL(n),t.download="FX_client_settings.json",t.click(),URL.revokeObjectURL(t.href)},this.syncFields=function(){Object.keys(i).forEach((function(e){i[e].value=v[e]})),Object.keys(s).forEach((function(e){s[e].checked=v[e]})),l.forEach((e=>e.update?.(v)))},this.resetAll=function(){confirm("Are you Really SURE you want to RESET ALL SETTINGS back to the default?")&&(localStorage.removeItem("fx_settings"),window.location.reload())},this.applySettings=function(){""!==v.customBackgroundUrl&&(document.body.style.backgroundImage="url("+v.customBackgroundUrl+")",document.body.style.backgroundSize="cover",document.body.style.backgroundPosition="center"),g.makeMainMenuTransparent=""!==v.customBackgroundUrl},v.useFullscreenMode&&x()};function x(){null===document.fullscreenElement&&document.fullscreenEnabled&&document.documentElement.requestFullscreen({navigationUI:"hide"}).then((()=>{console.log("Fullscreen mode activated")})).catch((e=>{console.warn("Could not enter fullscreen mode:",e)}))}d.add({name:"settings",element:document.querySelector(".settings"),beforeOpen:function(){E.syncFields()}}),null!==localStorage.getItem("fx_settings")&&(g.settings=v={...v,...JSON.parse(localStorage.getItem("fx_settings"))}),E.applySettings();const k=E;function T(){return v}const L=["playerTerritories","playerBalances","rawPlayerNames"],S=["playerId","gIsTeamGame","gHumans","gLobbyMaxJoin","gameState","gIsSingleplayer"],I=e=>L.includes(e)?window[dictionary.playerData]?.[dictionary[e]]:S.includes(e)?window[dictionary.game]?.[dictionary[e]]:window[dictionary[e]],C=()=>Math.floor(window[dictionary.uiSizes]?.[dictionary.gap]??10),O=new function(){this.playersToInclude=[],this.tabLabels=["ALL","CLAN"],this.filteredLeaderboard=[],this.tabBarOffset=0,this.windowWidth=0,this.verticalClickThreshold=1e3,this.hoveringOverTabs=!1,this.scrollToTop=()=>{},this.repaintLeaderboard=()=>{},this.setUpdateFlag=()=>{},this.parseClanFromPlayerName=()=>{console.warn("parse function not set")},this.selectedTab=0,this.tabHovering=-1,this.enabled=!1,this.drawTabs=function(e,t,n,o){e.textBaseline="middle",e.textAlign="center";const a=t/this.tabLabels.length,i=n+this.tabBarOffset/2;this.tabLabels.forEach(((t,s)=>{0!==s&&e.fillRect(a*s,n,1,this.tabBarOffset),this.selectedTab===s&&(e.fillStyle=o,e.fillRect(a*s,n,a,this.tabBarOffset),e.fillStyle="rgb(255,255,255)"),this.tabHovering===s&&(e.fillStyle="rgba(255,255,255,0.3)",e.fillRect(a*s,n,a,this.tabBarOffset),e.fillStyle="rgb(255,255,255)"),e.fillText(t,a*s+a/2,i)}))},this.setHovering=(e,t)=>{let n=!1;if(e){const e=Math.floor(t/(this.windowWidth/this.tabLabels.length));this.tabHovering!==e&&(this.tabHovering=e,n=!0)}return e!==this.hoveringOverTabs&&(this.hoveringOverTabs=e,!1===e&&(this.tabHovering=-1),e||(n=!0)),n&&this.repaintLeaderboard(),e},this.handleMouseDown=e=>{const t=Math.floor(e/(this.windowWidth/this.tabLabels.length));return this.selectedTab!==t&&(this.selectedTab=t,0===this.selectedTab?this.clearFilter():1===this.selectedTab&&(this.filterByOwnClan(),this.setUpdateFlag()),this.repaintLeaderboard()),!0},this.filterByOwnClan=()=>{this.playersToInclude=[];const e=I("playerId"),t=this.parseClanFromPlayerName(I("rawPlayerNames")[e]);I("rawPlayerNames").forEach(((n,o)=>{o!==e&&this.parseClanFromPlayerName(n)!==t||this.playersToInclude.push(o)})),this.enabled=!0,this.scrollToTop()},this.clearFilter=()=>{this.enabled=!1},this.reset=()=>{this.enabled=!1,this.selectedTab=0,A.refresh()}},A=new function(){this.inOwnClan=new Array(512),this.inOwnClan.fill(!1),this.refresh=()=>{const e=I("gHumans"),t=O.parseClanFromPlayerName(I("rawPlayerNames")[I("playerId")]);null===t?this.inOwnClan.fill(!1):I("rawPlayerNames").forEach(((n,o)=>{this.inOwnClan[o]=o/g,">").replace(/"/g,""").replace(/'/g,"'")}let N=null;d.add({name:"donationHistory",element:document.querySelector("#donationhistory"),beforeOpen:function(e){document.getElementById("donationhistory_note").style.display="none"},onClose:function(){F.openedWindowPlayerID=null}});const F=new function(){this.openedWindowPlayerID=null,this.contentElement=document.querySelector("#donationhistory_content"),this.donationHistory=Array(512);let e=!1;function t(e,t,n,o){const a=I("rawPlayerNames"),i=document.createElement("tr");o&&i.setAttribute("class","new");let s=`${t}. `;return n===e[1]?s+=`Received ${e[2]} resources from ${B(a[e[0]])}`:s+=`Sent ${e[2]} resources to ${B(a[e[1]])}`,s+="",i.innerHTML=s,i}this.getHistoryOf=function(t){return function(e,t){try{return e()}catch(e){throw N=t,setTimeout((()=>{null!==N&&(N=null)})),e}}((()=>this.donationHistory[t].toReversed()),{playerID:t,resetCalled:e,type:typeof this.donationHistory[t],isArray:Array.isArray(this.donationHistory[t])})},this.reset=function(){e=!0;for(var t=0;t<512;t++)this.donationHistory[t]=[]},this.logDonation=function(e,n,o){const a=[e,n,o];if(this.donationHistory[n].push(a),this.donationHistory[e].push(a),this.openedWindowPlayerID===e||this.openedWindowPlayerID===n){const o=this.donationHistory[this.openedWindowPlayerID===e?e:n].length;this.contentElement.prepend(t(a,o,this.openedWindowPlayerID,!0))}},this.displayHistory=function(e,n=I("rawPlayerNames"),o=I("gIsSingleplayer")){var a=F.getHistoryOf(e);console.log("History for "+n[e]+":"),console.log(a),document.querySelector("#donationhistory h1").innerHTML="Donation history for "+B(n[e]),this.contentElement.innerHTML="",a.length>0?a.forEach(((n,o)=>{this.contentElement.appendChild(t(n,a.length-o,e))})):this.contentElement.innerText="Nothing to display",this.openedWindowPlayerID=e,d.openWindow("donationHistory",o)}},H=F,M=new function(){const e=document.createElement("img");e.setAttribute("src","assets/players_icon.png"),document.getElementById("playerlist_content").addEventListener("click",(e=>{const t=e.target.closest("tr[data-player-id]")?.getAttribute("data-player-id");t&&I("gIsTeamGame")&&(d.closeWindow("playerList"),H.displayHistory(t))})),this.display=function(e){const t=I("gHumans"),n=I("gLobbyMaxJoin");let o=`

Players (${t})

`;for(let a=0;aBots (${n-t})`),o+=`${a+1}. ${B(e[a])}`;document.getElementById("playerlist_content").innerHTML=o,document.getElementById("playerlist_content").setAttribute("class",I("gIsTeamGame")?"clickable":""),d.openWindow("playerList")},this.hoveringOverButton=!1,this.drawButton=(t,n,o,a)=>{t.fillRect(n,o,a,a),t.fillStyle=this.hoveringOverButton?"#aaaaaaaa":"#000000aa",t.clearRect(n+1,o+1,a-2,a-2),t.fillRect(n+1,o+1,a-2,a-2),t.fillStyle="#ffffff",t.imageSmoothingEnabled=!0,t.drawImage(e,n+2,o+2,a-4,a-4),t.imageSmoothingEnabled=!1}};d.add({name:"playerList",element:document.getElementById("playerlist")});const P=M,j={getMaxTroops:function(e,t){return(150*e[t]).toString()},getDensity:function(e,t=I("playerBalances"),n=I("playerTerritories")){return"percentage"===T().densityDisplayStyle?(t[e]/(150*(0===n[e]?1:n[e]))*100).toFixed(1)+"%":(t[e]/(0===n[e]?1:n[e])).toFixed(1)},isPointInRectangle:function(e,t,n,o,a,i){return e>=n&&e<=n+a&&t>=o&&t<=o+i},fillTextMultiline:function(e,t,n,o,a){const i=parseInt(e.font.split(" ").find((e=>e.endsWith("px"))).slice(0,-2));t.split("\n").forEach(((t,s)=>e.fillText(t,n,o+s*i,a)))},textStyleBasedOnDensity:function(e){const t=I("playerBalances"),n=I("playerTerritories");return`hsl(${t[e]/(1.5*n[e])}, 100%, 50%, 1)`}},W=new function(){let e=!1;function t(t){if(!T().hoveringTooltip||!I("gameState")||e)return;let n,o;if(t.type.includes("touch")){const{touches:e,changedTouches:a}=t.originalEvent??t,i=e[0]??a[0];n=i.pageX,o=i.pageY}else t.type.includes("mouse")&&(n=t.clientX,o=t.clientY);e=!0;try{this.display(this.canvasPixelScale*n,this.canvasPixelScale*o)}catch(t){console.error(t)}setTimeout((()=>e=!1),100)}this.display=()=>{},this.canvasPixelScale=1,document.getElementById("canvasA").addEventListener("mousemove",t.bind(this)),document.getElementById("canvasA").addEventListener("touchstart",t.bind(this))},D={setAbsolute:()=>{},setRelative:()=>{},repaintAttackPercentageBar:()=>{}};function _(e){"absolute"===e.type?D.setAbsolute(e.value):D.setRelative(e.value),D.repaintAttackPercentageBar()}let R,U=0,J=0;const K={setSize:(e,t,n)=>{if(!0!==T().keybindButtons)return;U=e,J=t,R=document.createElement("canvas"),R.width=e,R.height=t;const o=R.getContext("2d"),a=n.font.split("px ",2)[1];o.font="bold "+t/2+"px "+a,o.textAlign="center",o.textBaseline="middle";const i=T().attackPercentageKeybinds.slice(0,6),s=C()/4,l=(e-5*s)/6;i.forEach(((e,n)=>{o.fillStyle="rgba(0, 0, 0, 0.8)",o.fillRect(n*(l+s),0,l,t),o.fillStyle="white";const a="absolute"===e.type?(100*e.value).toFixed()+"%":"x "+Math.round(100*e.value)/100;o.fillText(a,(n+.5)*(l+s),t/2)}))},click:e=>{if(e<0||e>U)return!1;const t=T().attackPercentageKeybinds,n=Math.floor(e/U*6);return!(n>=t.length||(_(t[n]),0))},draw:(e,t,n)=>{e.drawImage(R,t,n-(J+C()/4))}};let $=!1,G="",X=()=>{},q=()=>{},V=(e,t)=>{};const z=new TextEncoder,Q=new TextDecoder;d.add({name:"lobbyJoinMenu",element:document.getElementById("customLobbyJoinMenu")}),d.create({name:"customLobbiesUnavailable",closeWithButton:!0}).innerHTML='

The latest version of FX Client doesn\'t support custom lobbies yet. Use the stable version at https://fxclient.github.io/custom-lobbies

';const Y=d.create({name:"customLobby",classes:"scrollable selectable flex-column text-align-center",closable:!1}),Z=document.createElement("h2");Z.textContent="Custom Lobby";const ee=document.createElement("div");ee.className="customlobby-main";const te=document.createElement("div"),ne=document.createElement("p");ne.textContent="0 Players";const oe=document.createElement("div");te.append(ne,oe);const ae=document.createElement("div");ae.className="text-align-left";const ie={mode:{label:"Mode:",type:"selectMenu",options:[{value:0,label:"2 Teams"},{value:1,label:"3 Teams"},{value:2,label:"4 Teams"},{value:3,label:"5 Teams"},{value:4,label:"6 Teams"},{value:5,label:"7 Teams"},{value:6,label:"8 Teams"},{value:7,label:"Battle Royale"},{value:10,label:"No Fullsend Battle Royale"},{value:9,label:"Zombie mode"}]},map:{label:"Map:",type:"selectMenu"},difficulty:{label:"Difficulty:",type:"selectMenu",options:[{value:0,label:"Very Easy (Default)"},{value:1,label:"Easy (1v1)"},{value:2,label:"Normal"},{value:3,label:"Hard"},{value:4,label:"Very Hard"},{value:5,label:"Impossible"}]},spawnSelection:{label:"Spawn selection",type:"checkbox"},botCount:{label:"Bot & player count:",type:"numberInput",attributes:{min:"1",max:"512"}}},se={},le={};function re(e,t){"checkbox"===ie[e].type?se[e].checked=0!==t:se[e].value=t.toString(),le[e]=t}function ce(e,t){fe("options",[e,parseInt(t.target.value)])}function de(e,t){fe("options",[e,t.target.checked?1:0])}Object.entries(ie).forEach((([e,t])=>{const n=document.createElement("label");t.tooltip&&(n.title=t.tooltip);const o=t.type.endsWith("Input"),a=document.createElement(o||"checkbox"===t.type?"input":"selectMenu"===t.type?"select":"button");if(se[e]=a,"textInput"===t.type&&(a.type="text"),"numberInput"===t.type&&(a.type="number"),t.placeholder&&(a.placeholder=t.placeholder),(o||"selectMenu"===t.type)&&a.addEventListener("change",ce.bind(void 0,e)),t.text&&(a.innerText=t.text),t.action&&a.addEventListener("click",t.action),t.label&&n.append(t.label+" "),t.note){const e=document.createElement("small");e.innerText=t.note,n.append(document.createElement("br"),e)}if(t.options&&be(t.options,a),t.attributes&&Object.entries(t.attributes).forEach((([e,t])=>a.setAttribute(e,t))),n.append(a),"checkbox"===t.type){a.type="checkbox";const t=document.createElement("span");t.className="checkmark",n.className="checkbox",n.append(t),a.addEventListener("change",de.bind(void 0,e))}else n.append(document.createElement("br"));ae.append(n)})),ee.append(te,ae);const ue=document.createElement("footer");function pe(e,t){const n=document.createElement("button");return n.textContent=e,n.addEventListener("click",t),n}ue.style.marginTop="10px";const he=pe("Start game",(function(){d.closeWindow("customLobby"),fe("startGame")})),me=pe("Leave lobby",(()=>q())),ye=pe("Copy link",(()=>{navigator.clipboard.writeText(`${window.location.href}#lobby=${G}`),ye.textContent="Copied!",setTimeout((()=>ye.textContent="Copy link"),1e3)}));function be(e,t){e.forEach((e=>{const n=document.createElement("option");n.setAttribute("value",e.value),n.textContent=e.label,t.append(n)}))}function fe(e,t){const n=void 0!==t?{t:e,d:t}:{t:e},o=z.encode(JSON.stringify(n)),a=new ArrayBuffer(o.length+1);new DataView(a).setUint8(0,120),new Uint8Array(a,1).set(o),V(1,a)}ue.append(he,me,ye),Y.append(Z,ee,ue),document.getElementById("lobbyCode").addEventListener("input",(({target:e})=>{5===e.value.length&&(G=e.value.toLowerCase(),e.value="",d.closeWindow("lobbyJoinMenu"),$=!0,X())})),document.getElementById("createLobbyButton").addEventListener("click",(()=>{G="",d.closeWindow("lobbyJoinMenu"),$=!0,X()}));let ge,ve=!1,we=[];function Ee(e,t){const n=document.createElement("span");return n.textContent=e,n.className=t?"":"d-none",n}function xe(e){const t=document.createElement("div");t.className="lobby-player",t.textContent=e.name;const n=document.createElement("button");n.textContent="Kick",n.className=ve&&!e.isHost?"":"d-none",n.addEventListener("click",ke);const o=Ee("Host",e.isHost),a=Ee("In Game",e.inGame);t.append(o,a,n),oe.append(t),we.push({element:t,hostBadge:o,inGameBadge:a,kickButton:n,isHost:e.isHost,inGame:e.inGame})}function ke(e){const t=e.target;for(let e=0;eLe()));const Se={gameInfo:le,showJoinPrompt:function(){return d.openWindow("customLobbiesUnavailable")},isCustomMessage:function(e){if(120!==e[0])return!1;if(1===e.length)return!0;const t=new Uint8Array(e.buffer,1),n=JSON.parse(Q.decode(t)),{t:o,d:a}=n;if("lobby"===o)d.openWindow("customLobby"),Z.textContent="Custom Lobby "+a.code,G=a.code,ve=a.isHost,he.disabled=!ve,ve?ae.classList.remove("disabled"):ae.classList.add("disabled"),Object.entries(a.options).forEach((([e,t])=>re(e,t))),i=a.players,s=a.id,we=[],oe.innerHTML="",i.forEach(xe),ge=we[s],Te();else if("addPlayer"===o)xe({name:a.name,inGame:!1,isHost:!1}),Te();else if("removePlayer"===o){const e=a;we[e].element.remove(),we.splice(e,1),Te()}else if("inLobby"===o){const e=a;we[e].inGame=!1,we[e].inGameBadge.className="d-none"}else if("options"===o){const[e,t]=a;re(e,t)}else if("setHost"===o){const e=a;we[e].isHost=!0,we[e].hostBadge.className=""}else"host"===o?(ve=!0,he.disabled=!1,ae.classList.remove("disabled"),we.forEach((e=>{e.isHost||(e.kickButton.className="")}))):"serverMessage"===o&&alert(a);var i,s;return!0},getSocketURL:function(){return"wss://fx.peshomir.workers.dev/"+(""===G?"create":"join?"+G)},getPlayerId:function(){let e=0;for(let t=0;tbe(e.map(((e,t)=>({value:t.toString(),label:e.name}))),se.map)),0)},rejoinLobby:function(){X()},hideWindow:function(){d.closeWindow("customLobby")},isActive:()=>$,setActive:function(e){$=e,!1===e&&d.closeWindow("customLobby")}},{rE:Ie,_e:Ce}=e,Oe=localStorage.getItem("fx_version");Oe!==Ie&&(localStorage.setItem("fx_version",Ie),null!==Oe&&f()),window.__fx=window.__fx||{};const Ae=window.__fx;Ae.version=Ie+" "+Ce,Ae.settingsManager=k,Ae.leaderboardFilter=O,Ae.utils=j,Ae.WindowManager=d,Ae.keybindFunctions=D,Ae.keybindHandler=e=>{const t=T().attackPercentageKeybinds.find((t=>t.key===e));return void 0!==t&&(0!==I("gameState")&&_(t),!0)},Ae.mobileKeybinds=K,Ae.donationsTracker=H,Ae.reportError=function(e,t){function n(e){try{return I(e)}catch(e){return e.toString()}}t=e.filename+" "+e.lineno+" "+e.colno+" "+e.message+"\n"+t,fetch("https://fx.peshomir.workers.dev/stats/errors",{body:JSON.stringify({message:t,context:{debug:N,gameState:n("gameState"),singleplayer:n("gIsSingleplayer"),swState:navigator.serviceWorker?.controller?.state,location:window.location.toString(),userAgent:navigator.userAgent,dictionary:JSON.stringify(dictionary),buildTimestamp,scripts:Array.from(document.scripts).map((e=>e.src))}}),method:"POST"}).catch((e=>alert("Failed to report error: "+e)))},Ae.playerList=P,Ae.hoveringTooltip=W,Ae.clanFilter=A,Ae.wins=a,Ae.customLobby=Se,console.log("Successfully loaded FX Client")})(); \ No newline at end of file diff --git a/index.html b/index.html index 7fa280d..bd30fdc 100644 --- a/index.html +++ b/index.html @@ -1,8 +1,9 @@ + - - + - - FX Client - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + + - + + \ No newline at end of file diff --git a/sw.js b/sw.js index fd37411..98586bc 100644 --- a/sw.js +++ b/sw.js @@ -1,47 +1 @@ -const cacheName = "1752485839738"; // this gets replaced by the build script - -self.addEventListener("install", (e) => { - console.log("[Service Worker] Install"); - self.skipWaiting(); -}); - -self.addEventListener("fetch", (e) => { - const url = e.request.url; - // Cache http and https only, skip unsupported chrome-extension:// and file://... - if (!(url.startsWith('http:') || url.startsWith('https:'))) { - return; - } - e.respondWith( - (async () => { - const r = await caches.match(e.request); - console.log(`[Service Worker] Fetching resource: ${url}`); - if (r) { - return r; - } - const response = await fetch(e.request); - const cache = await caches.open(cacheName); - console.log(`[Service Worker] Caching new resource: ${url}`); - cache.put(e.request, response.clone()); - return response; - })(), - ); -}); - -self.addEventListener("activate", (e) => { - console.log("[Service Worker] Activated", cacheName); - self.clients.matchAll().then(clients => { - clients.forEach(client => client.postMessage({ event: "activate", version: cacheName })); - }); - e.waitUntil( - caches.keys().then((keyList) => { - return Promise.all( - keyList.map((key) => { - if (key === cacheName) { - return; - } - return caches.delete(key); - }), - ); - }), - ); -}); +// removed \ No newline at end of file diff --git a/sw2.js b/sw2.js new file mode 100644 index 0000000..1576c65 --- /dev/null +++ b/sw2.js @@ -0,0 +1,51 @@ +const cacheName = "1752610538098"; // this gets replaced by the build script + +self.addEventListener("install", (e) => { + console.log("[Service Worker] Install", cacheName); +}); +self.addEventListener('message', function (e) { + if (e.data.update) { + self.skipWaiting(); + } +}); + +self.addEventListener("fetch", (e) => { + const url = e.request.url; + // Cache http and https only, skip unsupported chrome-extension:// and file://... + if (!(url.startsWith('http:') || url.startsWith('https:'))) { + return; + } + e.respondWith( + (async () => { + const cache = await caches.open(cacheName); + const r = await cache.match(e.request); + console.log(`[Service Worker] Fetching resource: ${url}`); + if (r) { + return r; + } + const response = await fetch(e.request); + console.log(`[Service Worker] Caching new resource: ${url}`); + cache.put(e.request, response.clone()); + return response; + })(), + ); + self.clients.matchAll().then(clients => { + clients.forEach(client => client.postMessage({ event: "activate", version: cacheName })); + }); +}); + +self.addEventListener("activate", (e) => { + console.log("[Service Worker] Activated", cacheName); + e.waitUntil( + caches.keys().then((keyList) => { + return Promise.all( + keyList.map((key) => { + if (key === cacheName) { + return; + } + return caches.delete(key); + }), + ); + }), + ); +});