From d8aef2d092be18d2484a380238fe559c0fa7149a Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:40:33 +0530 Subject: [PATCH] properly network the ready state --- data/ui/xbox/NotReady.png | Bin 0 -> 152 bytes data/ui/xbox/platform_android.png | Bin 0 -> 516 bytes data/ui/xbox/platform_desktop.png | Bin 0 -> 373 bytes data/ui/xbox/platform_web.png | Bin 0 -> 560 bytes server/MasterServer.hx | 248 +++++++++++++-------------- server/build_master.hxml | 2 +- src/MarbleWorld.hx | 6 + src/gui/MultiplayerLevelSelectGui.hx | 57 +++++- src/net/Net.hx | 12 +- src/net/NetCommands.hx | 36 +++- 10 files changed, 217 insertions(+), 144 deletions(-) create mode 100644 data/ui/xbox/NotReady.png create mode 100644 data/ui/xbox/platform_android.png create mode 100644 data/ui/xbox/platform_desktop.png create mode 100644 data/ui/xbox/platform_web.png diff --git a/data/ui/xbox/NotReady.png b/data/ui/xbox/NotReady.png new file mode 100644 index 0000000000000000000000000000000000000000..ab78aa9cc570010763b608d5e8a1ecb8d0e9b9c7 GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^l0YoX!3HE#iPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0hvieK~y+TtyIxX z12GJ>b2(5YKDY@e8^8wLfDu@s8!!Ux24w;mp-j+@P}Bwbfm9vjVxE(L+Fq_bi9X3^ zJITG+P8?pZ*K|(g2=5@zn1V7S#u0n9i(rcwG6GLzLnPCNd9FYGB12fb5;a3ZwDf5t zqJT8%3oteYF>oKg3AqRtM$E>upC-Qp#UrRSo7?C|WC+cI$m_xg%@&a)^#}Dm%Zi2f z2$9(#LZXu^=PKw5EYqJyx#urJz#s9%5dNg16$!CC&A$gD2r=-Swg^3c6#7=)dK!%XxouU+YO|+#ZFekpK=Y{7R6_71; zOa+lHwE+&%BGe^2QBZAb?TC*gD;-m4^tlGeri`7hA#cWCz>O#FjVI#`SrJ0p>Ch-l zoO-^TbtCKSlag|{Li=fHnz8LXA}?QvzGrbJA{XI5K=cE5!mm=NS%G}c0*}aI1_r*vAk26?e?EYw=PQ&mMo4qqn8^NbgeBKd^c# zLt{_-4FT^9Eh&>(WrF?0PfV{V=9PcYlle*M+?(l)2LiL_NISnXDdU;HKu5+zs!w>VPDA1k$sEHFRjrtYD}He30Mm}9%l2d{7|gC$P_Kio5$$J_gpVa^oSFAFjy R8h}B?;OXk;vd$@?2>{9ulo0>` literal 0 HcmV?d00001 diff --git a/data/ui/xbox/platform_web.png b/data/ui/xbox/platform_web.png new file mode 100644 index 0000000000000000000000000000000000000000..9925a98926e5b3453cc7cb9c920de26ca794de7e GIT binary patch literal 560 zcmV-00?+-4P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0mVr~K~y+TtyJ4p z0x=X!vdcdO_#qXD6`&nR2hxF5pmsn9+JRaDS^;YX?m77H{W5$feZve4x~sD*k*x_rnY6PDC1?em>< zJh+!4czZ0;Ib5NROr5jVgJ}=9qZn`pqW~(#EV>Cu51qC)?ef;g1eJ=o`F;{WD0!=9 zu+Y4eux6KqY = []; static var servers:Array = []; @@ -46,62 +40,26 @@ class SignallingHandler extends WebSocketHandler { static var clientId = 0; - public override function handshake(httpRequest:HttpRequest) { - var httpResponse = new HttpResponse(); + static var _nextId = 0; - httpResponse.headers.set(HttpHeader.SEC_WEBSOSCKET_VERSION, "13"); - httpResponse.headers.set("Access-Control-Allow-Origin", "*"); // Enable CORS pls, why do i have to override this entire function for a single line - if (httpRequest.method != "GET" || httpRequest.httpVersion != "HTTP/1.1") { - httpResponse.code = 400; - httpResponse.text = "Bad"; - httpResponse.headers.set(HttpHeader.CONNECTION, "close"); - httpResponse.headers.set(HttpHeader.X_WEBSOCKET_REJECT_REASON, 'Bad request'); - } else if (httpRequest.headers.get(HttpHeader.SEC_WEBSOSCKET_VERSION) != "13") { - httpResponse.code = 426; - httpResponse.text = "Upgrade"; - httpResponse.headers.set(HttpHeader.CONNECTION, "close"); - httpResponse.headers.set(HttpHeader.X_WEBSOCKET_REJECT_REASON, - 'Unsupported websocket client version: ${httpRequest.headers.get(HttpHeader.SEC_WEBSOSCKET_VERSION)}, Only version 13 is supported.'); - } else if (httpRequest.headers.get(HttpHeader.UPGRADE) != "websocket") { - httpResponse.code = 426; - httpResponse.text = "Upgrade"; - httpResponse.headers.set(HttpHeader.CONNECTION, "close"); - httpResponse.headers.set(HttpHeader.X_WEBSOCKET_REJECT_REASON, 'Unsupported upgrade header: ${httpRequest.headers.get(HttpHeader.UPGRADE)}.'); - } else if (httpRequest.headers.get(HttpHeader.CONNECTION).indexOf("Upgrade") == -1) { - httpResponse.code = 426; - httpResponse.text = "Upgrade"; - httpResponse.headers.set(HttpHeader.CONNECTION, "close"); - httpResponse.headers.set(HttpHeader.X_WEBSOCKET_REJECT_REASON, 'Unsupported connection header: ${httpRequest.headers.get(HttpHeader.CONNECTION)}.'); - } else { - Log.debug('Handshaking', id); - var key = httpRequest.headers.get(HttpHeader.SEC_WEBSOCKET_KEY); - var result = makeWSKeyResponse(key); - Log.debug('Handshaking key - ${result}', id); + var _id = _nextId++; + var _websocket:WebSocket; - httpResponse.code = 101; - httpResponse.text = "Switching Protocols"; - httpResponse.headers.set(HttpHeader.UPGRADE, "websocket"); - httpResponse.headers.set(HttpHeader.CONNECTION, "Upgrade"); - httpResponse.headers.set(HttpHeader.SEC_WEBSOSCKET_ACCEPT, result); - } - - sendHttpResponse(httpResponse); - - if (httpResponse.code == 101) { - _onopenCalled = false; - state = State.Head; - Log.debug('Connected', id); - } else { - close(); - } + public function update():Bool { + _websocket.process(); + return _websocket.readyState != Closed; } - public function new(s:SocketImpl) { - super(s); - onopen = () -> { + inline function sendString(str:String) { + _websocket.sendString(str); + } + + public function new(s:WebSocket) { + _websocket.onopen = () -> { clients.push(this); } - onclose = () -> { + _websocket.onclose = function(?e:Dynamic) { + trace('Removing server'); servers = servers.filter(x -> x.socket != this); // remove server for (key => val in joiningClients) { if (val == this) { @@ -111,76 +69,83 @@ class SignallingHandler extends WebSocketHandler { } clients.remove(this); } - onmessage = (m) -> { - switch (m) { - case StrMessage(content): - var conts = Json.parse(content); - trace('Received ${conts.type}'); - if (conts.type == "serverInfo") { - var serverInfo = new ServerInfo(this, conts.name, conts.players, conts.maxPlayers, conts.privateSlots, conts.privateServer, - conts.inviteCode, conts.state, conts.platform); - if (servers.find(x -> x.name == serverInfo.name) == null) { - servers.push(serverInfo); - } else { - servers = servers.filter(x -> x.socket != this); - servers.push(serverInfo); // update server - } - } - if (conts.type == "connect") { - var serverInfo = servers.find(x -> x.name == conts.serverName); - if (serverInfo != null) { - if (serverInfo.players >= serverInfo.maxPlayers) { - this.send(Json.stringify({ - type: "connectFailed", - reason: "The server is full" - })); - } else { - var cid = clientId++; - joiningClients.set(cid, this); - serverInfo.socket.send(Json.stringify({ - type: "connect", - sdp: conts.sdp, - clientId: cid - })); - } - } else { - this.send(Json.stringify({ - type: "connectFailed", - reason: "Server not found" - })); - } - } - if (conts.type == "connectResponse") { - var client = joiningClients.get(conts.clientId); - if (client != null) { - var success = conts.success; - if (!success) { - client.send(Json.stringify({ - type: "connectFailed", - reason: conts.reason - })); - } else { - client.send(Json.stringify({ - type: "connectResponse", - sdp: conts.sdp - })); - } - } - } - if (conts.type == "serverList") { - this.send(Json.stringify({ - type: "serverList", - servers: servers.filter(x -> !x.privateServer && x.state == "Lobby").map(x -> { - return { - name: x.name, - players: x.players, - maxPlayers: x.maxPlayers, - platform: x.platform - } - }) + _websocket.onerror = (msg) -> { + trace('Removing server'); + servers = servers.filter(x -> x.socket != this); // remove server + for (key => val in joiningClients) { + if (val == this) { + joiningClients.remove(key); + break; + } + } + clients.remove(this); + } + _websocket.onmessageString = (content) -> { + var conts = Json.parse(content); + trace('Received ${conts.type}'); + if (conts.type == "serverInfo") { + var serverInfo = new ServerInfo(this, conts.name, conts.players, conts.maxPlayers, conts.privateSlots, conts.privateServer, conts.inviteCode, + conts.state, conts.platform); + if (servers.find(x -> x.name == serverInfo.name) == null) { + servers.push(serverInfo); + } else { + servers = servers.filter(x -> x.socket != this); + servers.push(serverInfo); // update server + } + } + if (conts.type == "connect") { + var serverInfo = servers.find(x -> x.name == conts.serverName); + if (serverInfo != null) { + if (serverInfo.players >= serverInfo.maxPlayers) { + this.sendString(Json.stringify({ + type: "connectFailed", + reason: "The server is full" + })); + } else { + var cid = clientId++; + joiningClients.set(cid, this); + serverInfo.socket.sendString(Json.stringify({ + type: "connect", + sdp: conts.sdp, + clientId: cid })); } - case _: {} + } else { + this.sendString(Json.stringify({ + type: "connectFailed", + reason: "Server not found" + })); + } + } + if (conts.type == "connectResponse") { + var client = joiningClients.get(conts.clientId); + if (client != null) { + var success = conts.success; + if (!success) { + client.sendString(Json.stringify({ + type: "connectFailed", + reason: conts.reason + })); + } else { + client.sendString(Json.stringify({ + type: "connectResponse", + sdp: conts.sdp + })); + } + } + } + if (conts.type == "serverList") { + this.sendString(Json.stringify({ + type: "serverList", + servers: servers.filter(x -> !x.privateServer && x.state == "Lobby").map(x -> { + return { + name: x.name, + players: x.players, + maxPlayers: x.maxPlayers, + platform: x.platform + } + }) + })); } } } @@ -188,7 +153,30 @@ class SignallingHandler extends WebSocketHandler { class MasterServer { static function main() { - var ws = new WebSocketServer("0.0.0.0", 8080, 2); - ws.start(); + var ws = WebSocketServer.create("0.0.0.0", 8080, 100, true, false); + var handlers = []; + while (true) { + try { + var websocket = ws.accept(); + if (websocket != null) { + handlers.push(new SignallingHandler(websocket)); + } + + var toRemove = []; + for (handler in handlers) { + if (!handler.update()) { + toRemove.push(handler); + } + } + + while (toRemove.length > 0) + handlers.remove(toRemove.pop()); + + Sys.sleep(0.1); + } catch (e:Dynamic) { + trace('Error', e); + trace(CallStack.exceptionStack()); + } + } } } diff --git a/server/build_master.hxml b/server/build_master.hxml index 511943f5..6a59349e 100644 --- a/server/build_master.hxml +++ b/server/build_master.hxml @@ -1,4 +1,4 @@ ---library hxWebSockets +--library colyseus-websocket --main MasterServer -cp . --hl bin/master.hl \ No newline at end of file diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 82ad4224..0de1c94d 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -510,6 +510,12 @@ class MarbleWorld extends Scheduler { shape.onLevelStart(); if (this.isMultiplayer && Net.isClient) NetCommands.clientIsReady(Net.clientId); + var cc = 0; + for (client in Net.clients) + cc++; + if (Net.isHost && cc == 0) { + allClientsReady(); + } } public function restartMultiplayerState() { diff --git a/src/gui/MultiplayerLevelSelectGui.hx b/src/gui/MultiplayerLevelSelectGui.hx index 0cd8588b..97ae8f9b 100644 --- a/src/gui/MultiplayerLevelSelectGui.hx +++ b/src/gui/MultiplayerLevelSelectGui.hx @@ -148,14 +148,39 @@ class MultiplayerLevelSelectGui extends GuiImage { playerWnd.extent = new Vector(640, 480); innerCtrl.addChild(playerWnd); - var playerListArr = [Settings.highscoreName]; + var playerListArr = []; + if (Net.isHost) { + playerListArr.push({ + name: Settings.highscoreName, + state: Net.lobbyHostReady + }); + } if (Net.isClient) { + playerListArr.push({ + name: Settings.highscoreName, + state: Net.clientConnection.lobbyReady + }); for (c => v in Net.clientIdMap) { - playerListArr.push(v.getName()); + playerListArr.push({ + name: v.name, + state: v.lobbyReady + }); } } - playerList = new GuiMLTextListCtrl(arial14, playerListArr, null); + function imgLoader(path:String) { + switch (path) { + case "ready": + return ResourceLoader.getResource("data/ui/xbox/Ready.png", ResourceLoader.getImage, this.imageResources).toTile(); + case "notready": + return ResourceLoader.getResource("data/ui/xbox/NotReady.png", ResourceLoader.getImage, this.imageResources).toTile(); + } + return null; + } + + playerList = new GuiMLTextListCtrl(arial14, playerListArr.map(player -> { + return '${player.name}'; + }), imgLoader); playerList.selectedColor = 0xF29515; playerList.selectedFillColor = 0xEBEBEB; playerList.position = new Vector(25, 22); @@ -303,11 +328,29 @@ class MultiplayerLevelSelectGui extends GuiImage { } public function updateLobbyNames() { - var names = [Settings.highscoreName]; - for (id => c in Net.clientIdMap) { - names.push(c.getName()); + var playerListArr = []; + if (Net.isHost) { + playerListArr.push({ + name: Settings.highscoreName, + state: Net.lobbyHostReady + }); } - playerList.setTexts(names); + if (Net.isClient) { + playerListArr.push({ + name: Settings.highscoreName, + state: Net.lobbyClientReady + }); + } + for (c => v in Net.clientIdMap) { + playerListArr.push({ + name: v.name, + state: v.lobbyReady + }); + } + + playerList.setTexts(playerListArr.map(player -> { + return '${player.name}'; + })); } public function updatePlayerCount(pub:Int, priv:Int, publicTotal:Int, privateTotal:Int) { diff --git a/src/net/Net.hx b/src/net/Net.hx index 3efe4a22..83915f79 100644 --- a/src/net/Net.hx +++ b/src/net/Net.hx @@ -68,6 +68,7 @@ class Net { public static var isClient:Bool; public static var lobbyHostReady:Bool; + public static var lobbyClientReady:Bool; public static var clientId:Int; public static var networkRNG:Float; @@ -202,6 +203,7 @@ class Net { Net.serverInfo = null; Net.remoteServerInfo = null; Net.lobbyHostReady = false; + Net.lobbyClientReady = false; } if (Net.isHost) { NetCommands.serverClosed(); @@ -217,6 +219,7 @@ class Net { Net.serverInfo = null; Net.remoteServerInfo = null; Net.lobbyHostReady = false; + Net.lobbyClientReady = false; } } @@ -285,7 +288,7 @@ class Net { } } - static function sendPlayerInfosBytes() { + public static function sendPlayerInfosBytes() { var b = new haxe.io.BytesOutput(); b.writeByte(PlayerInfo); var cnt = 0; @@ -294,6 +297,7 @@ class Net { b.writeByte(cnt + 1); // all + host for (c => v in clientIdMap) { b.writeByte(c); + b.writeByte(v.lobbyReady ? 1 : 0); var name = v.getName(); b.writeByte(name.length); for (i in 0...name.length) { @@ -302,6 +306,7 @@ class Net { } // Write host data b.writeByte(0); + b.writeByte(Net.lobbyHostReady ? 1 : 0); var name = Settings.highscoreName; b.writeByte(name.length); for (i in 0...name.length) { @@ -401,6 +406,7 @@ class Net { var newP = false; for (i in 0...count) { var id = input.readByte(); + var cready = input.readByte() == 1; if (id != 0 && id != Net.clientId && !clientIdMap.exists(id)) { Console.log('Adding ghost connection ${id}'); addGhost(id); @@ -413,6 +419,10 @@ class Net { } if (clientIdMap.exists(id)) { clientIdMap[id].setName(name); + clientIdMap[id].lobbyReady = cready; + } + if (Net.clientId == id) { + Net.lobbyClientReady = cready; } } if (newP) { diff --git a/src/net/NetCommands.hx b/src/net/NetCommands.hx index 5ea8862e..455393cd 100644 --- a/src/net/NetCommands.hx +++ b/src/net/NetCommands.hx @@ -1,5 +1,6 @@ package net; +import gui.EndGameGui; import modes.HuntMode; import net.ClientConnection.GameplayState; import net.Net.NetPacketType; @@ -42,6 +43,14 @@ class NetCommands { break; } } + if (MarbleGame.canvas.content is MultiplayerLevelSelectGui) { + cast(MarbleGame.canvas.content, MultiplayerLevelSelectGui).updateLobbyNames(); + } + var b = Net.sendPlayerInfosBytes(); + for (cc in Net.clients) { + cc.sendBytes(b); + } + if (allReady && Net.lobbyHostReady) { NetCommands.playLevel(MultiplayerLevelSelectGui.currentSelectionStatic); } @@ -66,12 +75,10 @@ class NetCommands { } } - @:rpc(server) public static function setStartTime(t:Float) { + @:rpc(server) public static function setStartTicks(ticks:Int) { if (MarbleGame.instance.world != null) { - if (Net.isClient) { - t -= cast(Net.clientIdMap[0], ClientConnection).rtt / 2; // Subtract receving time - } - MarbleGame.instance.world.startRealTime = MarbleGame.instance.world.timeState.timeSinceLoad + t; + MarbleGame.instance.world.serverStartTicks = ticks; + MarbleGame.instance.world.startTime = MarbleGame.instance.world.timeState.timeSinceLoad + 3.5; } } @@ -135,4 +142,23 @@ class NetCommands { Net.lobbyHostReady = false; } } + + @:rpc(server) public static function restartGame() { + var world = MarbleGame.instance.world; + if (Net.isHost) { + world.schedule(1, () -> { + world.allClientsReady(); + return null; + }); + } + if (Net.isClient) { + var gui = MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1]; + if (gui is EndGameGui) { + var egg = cast(gui, EndGameGui); + egg.retryFunc(null); + world.restartMultiplayerState(); + } + } + world.multiplayerStarted = false; + } }