From 9b85d5d9b4a369eea09a12bfd92f55880a3552be Mon Sep 17 00:00:00 2001 From: peshomir <80340328+peshomir@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:27:09 +0200 Subject: [PATCH] Custom lobby difficulty option, hide bot names setting Custom lobby options are now automatically generated from a specified structure, similar to the settings menu Update v0.6.6.7 --- patches.js | 27 +++++++--- src/customLobby.js | 122 ++++++++++++++++++++++++++++++++------------- src/main.js | 4 +- src/settings.js | 2 + 4 files changed, 110 insertions(+), 45 deletions(-) diff --git a/patches.js b/patches.js index 0af857b..573408f 100644 --- a/patches.js +++ b/patches.js @@ -140,7 +140,7 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 `, ${dict.game}.${dict.gIsTeamGame} && __fx.donationsTracker.displayHistory($2, ${rawPlayerNames}, ${gIsSingleplayer}), $1 && !isEmptySpace $3`); // Reset donation history and leaderboard filter when a new game is started - replaceOne(new RegExp(`,this\\.${dictionary.playerBalances}.fill\\(0\\),`, "g"), "$& __fx.donationsTracker.reset(), __fx.leaderboardFilter.reset(), __fx.customLobby.isActive() && __fx.customLobby.setActive(false), "); + replaceOne(new RegExp(`,this\\.${dictionary.playerBalances}.fill\\(0\\),`, "g"), "$& __fx.donationsTracker.reset(), __fx.leaderboardFilter.reset(), __fx.customLobby.isActive() && __fx.customLobby.hideWindow(), "); { // Player list and leaderboard filter tabs // Draw player list button @@ -164,19 +164,24 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 )) return; $4`); } - { // Display density of other players + { // Name rendering patches - Display density of other players & Hide bot names features const r = matchRawCode(`bD.dO.data[7].value?a9W(i,jm,jk,jl,ctx):a9V(ctx,i,jm,jk,jl,a9S)))`); const settingsSwitchNameAndBalance = `${r.bD}.${r.dO}.${r.data}[7].${r.value}`; - //console.log(settingsSwitchNameAndBalance); - // Applies when the "Reverse Name/Balance" setting is off + // Balance rendering; Renders density when the "Reverse Name/Balance" setting is off replaceRawCode("function a9V(ctx,i,fontSize,x,y,a9S){i=ac.jv.formatNumber(playerData.playerBalances[i]);a9S>>1&1?(ctx.lineWidth=.05*fontSize,ctx.strokeStyle=a9U(fontSize,a9S%2),ctx.strokeText(i,x,y)):(1>1&1?(ctx.lineWidth=.05*fontSize,ctx.strokeStyle=a9U(fontSize,a9S%2),ctx.strokeText(i,x,y)):(1\w+),(?\w+),(?\w+),(?\w+)\){)(\6\.fillText\((?\w+)\.(?\w+)\[\2\],\4,\5\)),(\2<(?\w+)\.(?\w+)&&2!==\8\.(?\w+)\[[^}]+)}/g, - `$1 var ___id = $2; $7, $10; ${settingsSwitchNameAndBalance} && __fx.settings.showPlayerDensity && (__fx.settings.coloredDensity && ($.fillStyle = __fx.utils.textStyleBasedOnDensity(___id)), $.fillText(__fx.utils.getDensity(___id), $, $ + $)); }`); + // Name rendering; Renders density when the "Reverse Name/Balance" setting is on (default) + replaceOne(/(function \w+\((?\w+),(?\w+),(?\w+),(?\w+),(?\w+)\){)(\6\.fillText\((?\w+)\.(?\w+)\[\2\],\4,\5\)),(\2<(?\w+)\.(?\w+)&&2!==\8\.(?\w+)\[[^}]+)}/g, + `$1 var ___id = $2; + var showName = $ < $.$ || !__fx.settings.hideBotNames; + if (showName) $7, $10; + ${settingsSwitchNameAndBalance} && __fx.settings.showPlayerDensity && ( + __fx.settings.coloredDensity && ($.fillStyle = __fx.utils.textStyleBasedOnDensity(___id)), + $.fillText(__fx.utils.getDensity(___id), $, showName ? $ + $ : $) + ); }`); } { // Leaderboard filter @@ -315,6 +320,9 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 __fx.customLobby.setLeaveFunction(() => this.xY())`) replaceRawCode("this.wQ=function(wR,d){if(8===i.pz&&0===wR)if(4211===d)wS(d);", "this.wQ=function(wR,d){ wR===1&&__fx.customLobby.isActive()&&__fx.customLobby.setActive(false); if(8===i.pz&&0===wR)if(4211===d)wS(d);") + // when leaving a game + replaceRawCode("this.wl=function(zs){ap.ky.zt(),this.vH=0,bU.zu(),m.n.setState(0),zs||bJ.df.show(),aN.setState(0),i.j(5,5)}", + "this.wl=function(zs){__fx.customLobby.setActive(false); ap.ky.zt(),this.vH=0,bU.zu(),m.n.setState(0),zs||bJ.df.show(),aN.setState(0),i.j(5,5)}") // if the server is unreachable replaceRawCode("0===a7Q?g.wc(3249):", "0===a7Q?g.wc(3249):1===a7Q&&__fx.customLobby.isActive()?(g.wc(3249),__fx.customLobby.setActive(false)):") // error descriptions @@ -324,6 +332,11 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 // map info (for the map selection menu) replaceRawCode("this.info=new Array(Maps.totalMapCount+1),this.info[0]={name:__L(),", "this.info=new Array(Maps.totalMapCount+1),__fx.customLobby.setMapInfo(this.info),this.info[0]={name:__L(),") + // custom difficulty + replaceRawCode("if(ax.jm){if(ax.jn.jo)for(i=game.jp-1;0<=i;i--)this.strength[i+jl]=ax.jn.jo[i+1]}else if(9===game.jq)this.jr();", + `if(ax.jm){if(ax.jn.jo)for(i=game.jp-1;0<=i;i--)this.strength[i+jl]=ax.jn.jo[i+1]}else if(9===game.jq)this.jr(); + else if (__fx.customLobby.isActive()) for(i=game.jp-1;0<=i;i--) this.strength[i+jl] = __fx.customLobby.gameInfo.difficulty;` + ) } // Invalid hostname detection avoidance diff --git a/src/customLobby.js b/src/customLobby.js index cd244a5..5cac7c0 100644 --- a/src/customLobby.js +++ b/src/customLobby.js @@ -10,9 +10,6 @@ let leaveLobby = () => { }; let sendRaw = (socketId, data) => { }; const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); -/*const gameInfo = { - botCount: 512 -}*/ WindowManager.add({ name: "lobbyJoinMenu", @@ -36,38 +33,87 @@ playerCount.textContent = "0 Players"; const playerList = document.createElement("div"); playerListContainer.append(playerCount, playerList); -// todo: convert the options into something similar to the automatic settings generator const optionsContainer = document.createElement("div"); -const gameModeSelectMenu = document.createElement("select"); -setSelectMenuOptions([ - { 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" } -], gameModeSelectMenu); -gameModeSelectMenu.value = "7"; -const mapSelectMenu = document.createElement("select"); -function setMapInfo(maps) { - setTimeout(() => setSelectMenuOptions(maps.map((info, index) => ({ value: index.toString(), label: info.name })), mapSelectMenu), 0); + +const optionsStructure = { + 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" } + ]} +} +const optionsElements = {}; +const optionsValues = {}; + +function updateOption(option, value) { + value = value.toString(); + optionsElements[option].value = value.toString(); + optionsValues[option] = value; +} +function inputUpdateHandler(key, e) { + sendMessage("options", [key, parseInt(e.target.value)]) +} +Object.entries(optionsStructure).forEach(([key, item]) => { + 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" + ); + optionsElements[key] = element; + if (item.type === "textInput") element.type = "text"; + if (item.placeholder) element.placeholder = item.placeholder; + if (isValueInput || item.type === "selectMenu") + element.addEventListener("change", inputUpdateHandler.bind(undefined, key)) + 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) setSelectMenuOptions(item.options, element); + 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")); + optionsContainer.append(label, document.createElement("br")); +}); + +function setMapInfo(maps) { + setTimeout(() => setSelectMenuOptions(maps.map((info, index) => ({ value: index.toString(), label: info.name })), optionsElements["map"]), 0); } -gameModeSelectMenu.addEventListener("change", e => sendMessage("options", ["mode", parseInt(e.target.value)])); -mapSelectMenu.addEventListener("change", e => sendMessage("options", ["map", parseInt(e.target.value)])); /*const botCountInput = document.createElement("input"); botCountInput.setAttribute("type", "number"); botCountInput.setAttribute("min", "0"); botCountInput.setAttribute("max", "512"); botCountInput.value = "512";*/ -optionsContainer.append( - "Mode: ", gameModeSelectMenu, document.createElement("br"), - "Map: ", mapSelectMenu, document.createElement("br"), - //"Bot count: " -); main.append(playerListContainer, optionsContainer); @@ -138,8 +184,7 @@ function isCustomMessage(raw) { playerIsHost = data.isHost; startButton.disabled = !playerIsHost; optionsContainer.className = playerIsHost ? "" : "disabled"; - gameModeSelectMenu.value = data.options.mode.toString(); - mapSelectMenu.value = data.options.map.toString(); + Object.entries(data.options).forEach(([option, value]) => updateOption(option, value)); displayPlayers(data.players); } else if (type === "addPlayer") { addPlayer(data); @@ -152,8 +197,7 @@ function isCustomMessage(raw) { } else if (type === "options") { console.log(data); const [option, value] = data; - if (option === "mode") gameModeSelectMenu.value = value.toString(); - else if (option === "map") mapSelectMenu.value = value.toString(); + updateOption(option, value); } else if (type === "setHost") { const index = data; playerElements[index].isHost = true; @@ -163,7 +207,7 @@ function isCustomMessage(raw) { startButton.disabled = false; optionsContainer.className = ""; playerElements.forEach(p => { if (!p.isHost) p.kickButton.className = "" }); - } + } else if (type === "serverMessage") alert(data); return true; } /** @type {{ element: HTMLDivElement, hostBadge: HTMLSpanElement, kickButton: HTMLButtonElement, isHost: boolean }[]} */ @@ -187,7 +231,10 @@ function addPlayer(player) { function kickButtonHandler(event) { const button = event.target; for (let index = 0; index < playerElements.length; index++) { - if (playerElements[index].kickButton === button) sendMessage("kick", index); + if (playerElements[index].kickButton === button) { + sendMessage("kick", index); + break; + } } } /** @param {{ name: string, isHost: boolean }[]} players */ @@ -206,7 +253,7 @@ function getSocketURL() { } function startGame() { WindowManager.closeWindow("customLobby"); - sendMessage("startGame" /*{ mode: gameModeSelectMenu.value, map: mapSelectMenu.value }*/); + sendMessage("startGame"); } @@ -217,7 +264,10 @@ function setActive(active) { isActive = active; if (active === false) WindowManager.closeWindow("customLobby"); } -const gameInterface = { showJoinPrompt, isCustomMessage, getSocketURL, setJoinFunction, setLeaveFunction, setSendFunction, setMapInfo, isActive: () => isActive, setActive } +function hideWindow() { + WindowManager.closeWindow("customLobby"); +} +const gameInterface = { gameInfo: optionsValues, showJoinPrompt, isCustomMessage, getSocketURL, setJoinFunction, setLeaveFunction, setSendFunction, setMapInfo, hideWindow, isActive: () => isActive, setActive } const customLobby = gameInterface export default customLobby \ No newline at end of file diff --git a/src/main.js b/src/main.js index 8cbca9c..e0d9e60 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,5 @@ -const fx_version = '0.6.6.6'; // FX Client Version -const fx_update = 'Nov 14'; // FX Client Last Updated +const fx_version = '0.6.6.7'; // FX Client Version +const fx_update = 'Nov 25'; // FX Client Last Updated import settingsManager from './settings.js'; import { clanFilter, leaderboardFilter } from "./clanFilters.js"; diff --git a/src/settings.js b/src/settings.js index 966f915..38ef4c3 100644 --- a/src/settings.js +++ b/src/settings.js @@ -16,6 +16,7 @@ var settings = { showPlayerDensity: true, coloredDensity: true, densityDisplayStyle: "percentage", + hideBotNames: false, highlightClanSpawns: false, //"customMapFileBtn": true customBackgroundUrl: "", @@ -79,6 +80,7 @@ const settingsManager = new (function () { }, ], }, + { for: "hideBotNames", type: "checkbox", label: "Hide bot names" }, { for: "highlightClanSpawns", type: "checkbox",