diff --git a/build.js b/build.js index ba157de..f93527f 100644 --- a/build.js +++ b/build.js @@ -145,6 +145,7 @@ const rawCodeSegments = [ "[0]=__L(),@strs[1]=@game.@gIsSingleplayer?__L():__L(),", "?(this.gB=Math.floor(.066*aK.fw),g5=aK.g5-4*@uiSizes.@gap-this.gB):", `for(a0L=new Array(@game.@gMaxPlayers),a0A.font=a07,@i=game.gMaxPlayers-1;0<=i;i--)a0L[i]=i+1+".",@playerData.@playerNames[i]=aY.qW.tm(playerData.@rawPlayerNames[i],a07,a0W),a0K[i]=Math.floor(a0A.measureText(playerData.playerNames[i]).width);`, + `var dt=@MenuManager.@getState();if(6===dt){if(4211===d)` ] rawCodeSegments.forEach(code => { diff --git a/patches.js b/patches.js index 573408f..4d3799c 100644 --- a/patches.js +++ b/patches.js @@ -318,11 +318,16 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 replaceRawCode("this.xY=function(){this.wg(),Sockets.kt.wf(3240),aN.setState(0),i___.j(5,5)}", `this.xY=function(){this.wg(),Sockets.kt.wf(3240),__fx.customLobby.setActive(false),aN.setState(0),i___.j(5,5)}, __fx.customLobby.setLeaveFunction(() => this.xY())`) + // when a socket error occurs on the custom lobby socket 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);") + `this.wQ=function(wR,d){ + wR===1 && __fx.customLobby.isActive() && ${dict.MenuManager}.${dict.getState}() !== 6 && __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)}") + `this.wl=function(zs){ __fx.customLobby.isActive() === false && ap.ky.zt(), + this.vH=0,bU.zu(),m.n.setState(0),zs||bJ.df.show(),aN.setState(0); + if (__fx.customLobby.isActive()) __fx.customLobby.rejoinLobby(); else 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 @@ -332,11 +337,23 @@ 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(),") + // to not set custom lobby games as singleplayer + replaceRawCode(",a.b(c,d),1===players.length&&SingleplayerMenu.a9H(type),", + ",a.b(c,d),1===players.length&&!__fx.customLobby.isActive()&&SingleplayerMenu.a9H(type),"); + replaceRawCode(",this.vK=this.jS=players.length,this.gameIsSingleplayer=1===this.vK,", + ",this.vK=this.jS=players.length,this.gameIsSingleplayer=1===this.vK&&!__fx.customLobby.isActive(),") // 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;` ) + // spawn selection + replaceRawCode(",ax.jm&&!ax.jn.zi?this.zZ=this.gh=!1:this.zZ=this.gh=this.iS||this.jS<100,", + ",ax.jm&&!ax.jn.zi?this.zZ=this.gh=!1 : __fx.customLobby.isActive() ? this.zZ=this.gh=__fx.customLobby.gameInfo.spawnSelection : this.zZ=this.gh=this.iS||this.jS<100,") + // bot count + replaceRawCode(",this.js?this.t1=aO.zj():this.t1=this.maxPlayers,this.jp=this.t1-this.jS,", + `,__fx.customLobby.isActive() ? this.t1 = Math.max(Math.min(__fx.customLobby.gameInfo.botCount, this.maxPlayers), this.jS) + : this.js?this.t1=aO.zj():this.t1=this.maxPlayers,this.jp=this.t1-this.jS,`) } // Invalid hostname detection avoidance diff --git a/src/customLobby.js b/src/customLobby.js index 5cac7c0..424251a 100644 --- a/src/customLobby.js +++ b/src/customLobby.js @@ -34,6 +34,7 @@ const playerList = document.createElement("div"); playerListContainer.append(playerCount, playerList); const optionsContainer = document.createElement("div"); +optionsContainer.className = "text-align-left"; const optionsStructure = { mode: { @@ -51,26 +52,32 @@ const optionsStructure = { ] }, map: { label: "Map:", type: "selectMenu" }, - difficulty: { label: "Difficulty", type: "selectMenu", options: [ + 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" } } } const optionsElements = {}; const optionsValues = {}; function updateOption(option, value) { - value = value.toString(); - optionsElements[option].value = value.toString(); + if (optionsStructure[option].type === "checkbox") + optionsElements[option].checked = (value === 0 ? false : true); + else optionsElements[option].value = value.toString(); optionsValues[option] = value; } function inputUpdateHandler(key, e) { sendMessage("options", [key, parseInt(e.target.value)]) } +function checkboxUpdateHandler(key, e) { + sendMessage("options", [key, e.target.checked ? 1 : 0]) +} Object.entries(optionsStructure).forEach(([key, item]) => { const label = document.createElement("label"); if (item.tooltip) label.title = item.tooltip; @@ -82,6 +89,7 @@ Object.entries(optionsStructure).forEach(([key, item]) => { ); optionsElements[key] = element; if (item.type === "textInput") element.type = "text"; + if (item.type === "numberInput") element.type = "number"; if (item.placeholder) element.placeholder = item.placeholder; if (isValueInput || item.type === "selectMenu") element.addEventListener("change", inputUpdateHandler.bind(undefined, key)) @@ -94,6 +102,9 @@ Object.entries(optionsStructure).forEach(([key, item]) => { label.append(document.createElement("br"), note); } if (item.options) setSelectMenuOptions(item.options, element); + if (item.attributes) Object.entries(item.attributes).forEach( + ([name, value]) => element.setAttribute(name, value) + ); label.append(element); if (item.type === "checkbox") { element.type = "checkbox"; @@ -102,18 +113,14 @@ Object.entries(optionsStructure).forEach(([key, item]) => { label.className = "checkbox"; label.append(checkmark); //checkboxFields[item.for] = element; - }// else label.append(document.createElement("br")); - optionsContainer.append(label, document.createElement("br")); + element.addEventListener("change", checkboxUpdateHandler.bind(undefined, key)) + } 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); } -/*const botCountInput = document.createElement("input"); -botCountInput.setAttribute("type", "number"); -botCountInput.setAttribute("min", "0"); -botCountInput.setAttribute("max", "512"); -botCountInput.value = "512";*/ main.append(playerListContainer, optionsContainer); @@ -183,7 +190,8 @@ function isCustomMessage(raw) { currentCode = data.code; playerIsHost = data.isHost; startButton.disabled = !playerIsHost; - optionsContainer.className = playerIsHost ? "" : "disabled"; + if (playerIsHost) optionsContainer.classList.remove("disabled"); + else optionsContainer.classList.add("disabled"); Object.entries(data.options).forEach(([option, value]) => updateOption(option, value)); displayPlayers(data.players); } else if (type === "addPlayer") { @@ -194,8 +202,10 @@ function isCustomMessage(raw) { playerElements[index].element.remove(); playerElements.splice(index, 1); updatePlayerCount(); + } else if (type === "inLobby") { + const index = data; + playerElements[index].inGameBadge.className = "d-none"; } else if (type === "options") { - console.log(data); const [option, value] = data; updateOption(option, value); } else if (type === "setHost") { @@ -205,14 +215,21 @@ function isCustomMessage(raw) { } else if (type === "host") { playerIsHost = true; startButton.disabled = false; - optionsContainer.className = ""; + optionsContainer.classList.remove("disabled"); 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 }[]} */ +/** @type {{ element: HTMLDivElement, hostBadge: HTMLSpanElement, inGameBadge: HTMLSpanElement, kickButton: HTMLButtonElement, isHost: boolean }[]} */ let playerElements = []; -/** @param {{ name: string, isHost: boolean }} player */ +function createBadge(text, visible) { + const badge = document.createElement("span"); + badge.textContent = text; + badge.className = visible ? "" : "d-none"; + return badge; +} +/** @typedef {{ name: string, isHost?: boolean, inGame?: boolean }} PlayerInfo */ +/** @param {PlayerInfo} player */ function addPlayer(player) { const div = document.createElement("div"); div.className = "lobby-player"; @@ -221,12 +238,11 @@ function addPlayer(player) { kickButton.textContent = "Kick"; kickButton.className = playerIsHost && !player.isHost ? "" : "d-none"; kickButton.addEventListener("click", kickButtonHandler); - const badge = document.createElement("span"); - badge.textContent = "Host"; - badge.className = player.isHost ? "" : "d-none"; - div.append(badge, kickButton); + const hostBadge = createBadge("Host", player.isHost); + const inGameBadge = createBadge("In Game", player.inGame); + div.append(hostBadge, inGameBadge, kickButton); playerList.append(div); - playerElements.push({ element: div, hostBadge: badge, kickButton, isHost: player.isHost }); + playerElements.push({ element: div, hostBadge, inGameBadge, kickButton, isHost: player.isHost }); } function kickButtonHandler(event) { const button = event.target; @@ -237,7 +253,7 @@ function kickButtonHandler(event) { } } } -/** @param {{ name: string, isHost: boolean }[]} players */ +/** @param {PlayerInfo[]} players */ function displayPlayers(players) { playerElements = []; playerList.innerHTML = ""; @@ -255,7 +271,9 @@ function startGame() { WindowManager.closeWindow("customLobby"); sendMessage("startGame"); } - +function rejoinLobby() { + joinLobby(); +} function setJoinFunction(f) { joinLobby = f; } function setLeaveFunction(f) { leaveLobby = f; } @@ -267,7 +285,7 @@ function setActive(active) { function hideWindow() { WindowManager.closeWindow("customLobby"); } -const gameInterface = { gameInfo: optionsValues, showJoinPrompt, isCustomMessage, getSocketURL, setJoinFunction, setLeaveFunction, setSendFunction, setMapInfo, hideWindow, isActive: () => isActive, setActive } +const gameInterface = { gameInfo: optionsValues, showJoinPrompt, isCustomMessage, getSocketURL, setJoinFunction, setLeaveFunction, setSendFunction, setMapInfo, rejoinLobby, 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 e0d9e60..4e5a7e6 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,5 @@ -const fx_version = '0.6.6.7'; // FX Client Version -const fx_update = 'Nov 25'; // FX Client Last Updated +const fx_version = '0.6.6.8'; // FX Client Version +const fx_update = 'Dec 8'; // FX Client Last Updated import settingsManager from './settings.js'; import { clanFilter, leaderboardFilter } from "./clanFilters.js"; diff --git a/static/main.css b/static/main.css index ad5e63e..264a1c5 100644 --- a/static/main.css +++ b/static/main.css @@ -110,6 +110,9 @@ .text-align-center { text-align: center; } +.text-align-left { + text-align: left; +} hr { width: 100%;