mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2025-10-30 08:11:25 +00:00
properly network the ready state
This commit is contained in:
parent
604f858573
commit
d8aef2d092
10 changed files with 217 additions and 144 deletions
BIN
data/ui/xbox/NotReady.png
Normal file
BIN
data/ui/xbox/NotReady.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 152 B |
BIN
data/ui/xbox/platform_android.png
Normal file
BIN
data/ui/xbox/platform_android.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 516 B |
BIN
data/ui/xbox/platform_desktop.png
Normal file
BIN
data/ui/xbox/platform_desktop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 373 B |
BIN
data/ui/xbox/platform_web.png
Normal file
BIN
data/ui/xbox/platform_web.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 560 B |
|
|
@ -1,13 +1,7 @@
|
|||
import hx.ws.WebSocket;
|
||||
import haxe.net.WebSocket;
|
||||
import haxe.CallStack;
|
||||
import haxe.net.WebSocketServer;
|
||||
import haxe.Json;
|
||||
import hx.ws.SocketImpl;
|
||||
import hx.ws.WebSocketHandler;
|
||||
import hx.ws.WebSocketServer;
|
||||
import hx.ws.State;
|
||||
import hx.ws.Log;
|
||||
import hx.ws.HttpHeader;
|
||||
import hx.ws.HttpResponse;
|
||||
import hx.ws.HttpRequest;
|
||||
|
||||
using Lambda;
|
||||
|
||||
|
|
@ -37,7 +31,7 @@ class ServerInfo {
|
|||
}
|
||||
}
|
||||
|
||||
class SignallingHandler extends WebSocketHandler {
|
||||
class SignallingHandler {
|
||||
static var clients:Array<SignallingHandler> = [];
|
||||
|
||||
static var servers:Array<ServerInfo> = [];
|
||||
|
|
@ -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<SignallingHandler>("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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
--library hxWebSockets
|
||||
--library colyseus-websocket
|
||||
--main MasterServer
|
||||
-cp .
|
||||
--hl bin/master.hl
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 '<img src="${player.state ? "ready" : "notready"}"></img>${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 '<img src="${player.state ? "ready" : "notready"}"></img>${player.name}';
|
||||
}));
|
||||
}
|
||||
|
||||
public function updatePlayerCount(pub:Int, priv:Int, publicTotal:Int, privateTotal:Int) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue