mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2025-10-30 08:11:25 +00:00
908 lines
27 KiB
Haxe
908 lines
27 KiB
Haxe
package net;
|
|
|
|
import net.NetPacket.ExplodableUpdatePacket;
|
|
import gui.MPMessageGui;
|
|
import gui.MessageBoxOkDlg;
|
|
import gui.JoinServerGui;
|
|
import gui.MPPreGameDlg;
|
|
import net.NetPacket.ScoreboardPacket;
|
|
import gui.MPPlayMissionGui;
|
|
import gui.Canvas;
|
|
import net.MasterServerClient.RemoteServerInfo;
|
|
import src.ResourceLoader;
|
|
import src.AudioManager;
|
|
import net.NetPacket.GemPickupPacket;
|
|
import net.NetPacket.GemSpawnPacket;
|
|
import net.BitStream.InputBitStream;
|
|
import net.BitStream.OutputBitStream;
|
|
import net.NetPacket.PowerupPickupPacket;
|
|
import net.ClientConnection;
|
|
import net.NetPacket.MarbleUpdatePacket;
|
|
import net.NetPacket.MarbleMovePacket;
|
|
import haxe.Json;
|
|
import datachannel.RTCPeerConnection;
|
|
import datachannel.RTCDataChannel;
|
|
import src.Console;
|
|
import net.NetCommands;
|
|
import src.MarbleGame;
|
|
import src.Settings;
|
|
|
|
enum abstract NetPacketType(Int) from Int to Int {
|
|
var NullPacket;
|
|
var ClientIdAssign;
|
|
var NetCommand;
|
|
var Ping;
|
|
var PingBack;
|
|
var MarbleUpdate;
|
|
var MarbleMove;
|
|
var PowerupPickup;
|
|
var GemSpawn;
|
|
var GemPickup;
|
|
var ExplodableUpdate;
|
|
var PlayerInfo;
|
|
var ScoreBoardInfo;
|
|
}
|
|
|
|
@:publicFields
|
|
class ServerInfo {
|
|
var id:String;
|
|
var name:String;
|
|
var hostname:String;
|
|
var description:String;
|
|
var players:Int;
|
|
var maxPlayers:Int;
|
|
var password:String;
|
|
var state:String;
|
|
var platform:NetPlatform;
|
|
|
|
public function new(name:String, hostname:String, description:String, players:Int, maxPlayers:Int, password:String, state:String, platform:NetPlatform) {
|
|
this.id = Uuid.v4();
|
|
this.name = name;
|
|
this.hostname = hostname;
|
|
this.description = description;
|
|
this.players = players;
|
|
this.maxPlayers = maxPlayers;
|
|
this.password = password;
|
|
this.state = state;
|
|
this.platform = platform;
|
|
}
|
|
}
|
|
|
|
@:publicFields
|
|
@:structInit
|
|
class ConnectedServerInfo {
|
|
var name:String;
|
|
var description:String;
|
|
var quickRespawn:Bool;
|
|
var forceSpectator:Bool;
|
|
var competitiveMode:Bool;
|
|
var oldSpawns:Bool;
|
|
}
|
|
|
|
class Net {
|
|
static var client:RTCPeerConnection;
|
|
static var clientDatachannel:RTCDataChannel;
|
|
static var clientDatachannelUnreliable:RTCDataChannel;
|
|
|
|
public static var isMP:Bool;
|
|
public static var isHost:Bool;
|
|
public static var isClient:Bool;
|
|
|
|
public static var lobbyHostReady:Bool;
|
|
public static var lobbyClientReady:Bool;
|
|
public static var hostReady:Bool;
|
|
public static var hostSpectate:Bool;
|
|
public static var clientSpectate:Bool;
|
|
|
|
static var clientIdAllocs:Int = 1;
|
|
public static var clientId:Int;
|
|
public static var networkRNG:Float;
|
|
public static var clients:Map<RTCPeerConnection, GameConnection> = [];
|
|
public static var clientIdMap:Map<Int, GameConnection> = [];
|
|
public static var clientConnection:ClientConnection;
|
|
public static var serverInfo:ServerInfo;
|
|
public static var remoteServerInfo:RemoteServerInfo;
|
|
public static var connectedServerInfo:ConnectedServerInfo;
|
|
|
|
static var stunServers = ["stun:stun.l.google.com:19302"];
|
|
|
|
public static var turnServer:String = "";
|
|
|
|
public static function hostServer(name:String, description:String, maxPlayers:Int, password:String, onHosted:() -> Void) {
|
|
serverInfo = new ServerInfo(name, Settings.highscoreName, description, 1, maxPlayers, password, "LOBBY", getPlatform());
|
|
MasterServerClient.connectToMasterServer(() -> {
|
|
isHost = true;
|
|
isClient = false;
|
|
clientId = 0;
|
|
isMP = true;
|
|
MasterServerClient.instance.sendServerInfo(serverInfo);
|
|
Net.connectedServerInfo = {
|
|
name: name,
|
|
description: description,
|
|
competitiveMode: Settings.serverSettings.competitiveMode,
|
|
quickRespawn: Settings.serverSettings.quickRespawn,
|
|
forceSpectator: Settings.serverSettings.forceSpectators,
|
|
oldSpawns: Settings.serverSettings.oldSpawns
|
|
};
|
|
onHosted();
|
|
});
|
|
}
|
|
|
|
public static function addClientFromSdp(sdpString:String, onFinishSdp:String->Void) {
|
|
var peer = new RTCPeerConnection(stunServers, "0.0.0.0");
|
|
var sdpObj = Json.parse(sdpString);
|
|
peer.setRemoteDescription(sdpObj.sdp, sdpObj.type);
|
|
addClient(peer, onFinishSdp);
|
|
}
|
|
|
|
static function addClient(peer:RTCPeerConnection, onFinishSdp:String->Void) {
|
|
var candidates = [];
|
|
peer.onLocalCandidate = (c) -> {
|
|
Console.log('Local candidate: ' + c);
|
|
if (c != "")
|
|
candidates.push('a=${c}');
|
|
}
|
|
peer.onStateChange = (s) -> {
|
|
switch (s) {
|
|
case RTC_CLOSED:
|
|
Console.log("RTC State change: Connection closed!");
|
|
case RTC_CONNECTED:
|
|
Console.log("RTC State change: Connected!");
|
|
case RTC_CONNECTING:
|
|
Console.log("RTC State change: Connecting...");
|
|
case RTC_DISCONNECTED:
|
|
Console.log("RTC State change: Disconnected!");
|
|
case RTC_FAILED:
|
|
Console.log("RTC State change: Failed!");
|
|
case RTC_NEW:
|
|
Console.log("RTC State change: New...");
|
|
}
|
|
}
|
|
|
|
var sdpFinished = false;
|
|
|
|
var finishSdp = () -> {
|
|
if (sdpFinished)
|
|
return;
|
|
if (peer == null)
|
|
return;
|
|
sdpFinished = true;
|
|
var sdpObj = StringTools.trim(peer.localDescription);
|
|
sdpObj = sdpObj + '\r\n' + candidates.join('\r\n') + '\r\n';
|
|
onFinishSdp(Json.stringify({
|
|
sdp: sdpObj,
|
|
type: "answer"
|
|
}));
|
|
}
|
|
|
|
peer.onGatheringStateChange = (s) -> {
|
|
switch (s) {
|
|
case RTC_GATHERING_COMPLETE:
|
|
Console.log("Gathering complete!");
|
|
case RTC_GATHERING_INPROGRESS:
|
|
Console.log("Gathering in progress...");
|
|
case RTC_GATHERING_NEW:
|
|
Console.log("Gathering new...");
|
|
}
|
|
if (s == RTC_GATHERING_COMPLETE) {
|
|
finishSdp();
|
|
}
|
|
}
|
|
var reliable:datachannel.RTCDataChannel = null;
|
|
var unreliable:datachannel.RTCDataChannel = null;
|
|
peer.onDataChannel = (dc:datachannel.RTCDataChannel) -> {
|
|
if (dc.name == "mp")
|
|
reliable = dc;
|
|
if (dc.name == "unreliable") {
|
|
unreliable = dc;
|
|
switch (dc.reliability) {
|
|
case Reliable:
|
|
Console.log("Error opening unreliable datachannel!");
|
|
case Unreliable(maxRetransmits, maxLifetime):
|
|
Console.log("Opened unreliable datachannel: " + maxRetransmits + " " + maxLifetime);
|
|
}
|
|
}
|
|
if (reliable != null && unreliable != null)
|
|
onClientConnect(peer, reliable, unreliable);
|
|
}
|
|
}
|
|
|
|
static function addGhost(id:Int) {
|
|
var ghost = new DummyConnection(id);
|
|
clientIdMap[id] = ghost;
|
|
}
|
|
|
|
public static function joinServer(serverName:String, password:String, connectedCb:() -> Void) {
|
|
MasterServerClient.connectToMasterServer(() -> {
|
|
client = new RTCPeerConnection(stunServers, "0.0.0.0");
|
|
var candidates = [];
|
|
|
|
var closing = false;
|
|
|
|
isMP = true;
|
|
isHost = false;
|
|
isClient = true;
|
|
|
|
var closeFunc = (msg:String, forceShow:Bool) -> {
|
|
if (closing)
|
|
return;
|
|
closing = true;
|
|
var weLeftOurselves = !Net.isClient; // If we left ourselves, this would be set to false due to order of ops, disconnect being called first, and then the datachannel closing
|
|
disconnect();
|
|
if (MarbleGame.instance.world != null) {
|
|
MarbleGame.instance.quitMission();
|
|
}
|
|
if (!weLeftOurselves || forceShow) {
|
|
if (MarbleGame.canvas.content is MPMessageGui) {
|
|
var loadGui:MPMessageGui = cast MarbleGame.canvas.content;
|
|
if (loadGui != null) {
|
|
loadGui.setTexts("Error", msg);
|
|
}
|
|
} else {
|
|
MarbleGame.canvas.setContent(new MPMessageGui("Error", msg));
|
|
}
|
|
}
|
|
}
|
|
|
|
client.onLocalCandidate = (c) -> {
|
|
Console.log('Local candidate: ' + c);
|
|
if (c != "")
|
|
candidates.push('a=${c}');
|
|
}
|
|
client.onStateChange = (s) -> {
|
|
switch (s) {
|
|
case RTC_CLOSED:
|
|
Console.log("RTC State change: Connection closed!");
|
|
closeFunc("Connection closed", false);
|
|
case RTC_CONNECTED:
|
|
Console.log("RTC State change: Connected!");
|
|
case RTC_CONNECTING:
|
|
Console.log("RTC State change: Connecting...");
|
|
case RTC_DISCONNECTED:
|
|
Console.log("RTC State change: Disconnected!");
|
|
case RTC_FAILED:
|
|
Console.log("RTC State change: Failed!");
|
|
case RTC_NEW:
|
|
Console.log("RTC State change: New...");
|
|
}
|
|
}
|
|
|
|
var sdpFinished = false;
|
|
var finishSdp = () -> {
|
|
if (sdpFinished)
|
|
return;
|
|
sdpFinished = true;
|
|
if (client == null)
|
|
return;
|
|
Console.log("Local Description Set!");
|
|
var sdpObj = StringTools.trim(client.localDescription);
|
|
sdpObj = sdpObj + '\r\n' + candidates.join('\r\n') + '\r\n';
|
|
MasterServerClient.instance.sendConnectToServer(serverName, Json.stringify({
|
|
sdp: sdpObj,
|
|
type: "offer"
|
|
}), password);
|
|
}
|
|
|
|
client.onGatheringStateChange = (s) -> {
|
|
switch (s) {
|
|
case RTC_GATHERING_COMPLETE:
|
|
Console.log("Gathering complete!");
|
|
case RTC_GATHERING_INPROGRESS:
|
|
Console.log("Gathering in progress...");
|
|
case RTC_GATHERING_NEW:
|
|
Console.log("Gathering new...");
|
|
}
|
|
if (s == RTC_GATHERING_COMPLETE) {
|
|
finishSdp();
|
|
}
|
|
}
|
|
|
|
// haxe.Timer.delay(() -> {
|
|
// finishSdp();
|
|
// }, 5000);
|
|
|
|
clientDatachannel = client.createDatachannel("mp");
|
|
clientDatachannelUnreliable = client.createDatachannelWithOptions("unreliable", true, null, 600);
|
|
|
|
var openFlags = 0;
|
|
|
|
var onDatachannelOpen = (idx:Int) -> {
|
|
if (!Net.isMP) {
|
|
// Close
|
|
client.close();
|
|
return;
|
|
}
|
|
openFlags |= idx;
|
|
if (openFlags == 3) {
|
|
if (MarbleGame.canvas.content is MPMessageGui) {
|
|
var loadGui:MPMessageGui = cast MarbleGame.canvas.content;
|
|
if (loadGui != null) {
|
|
loadGui.setTexts("Please Wait", "Handshaking");
|
|
}
|
|
}
|
|
Console.log("Successfully connected!");
|
|
clients.set(client, new ClientConnection(0, client, clientDatachannel, clientDatachannelUnreliable)); // host is always 0
|
|
clientIdMap[0] = clients[client];
|
|
clientConnection = cast clients[client];
|
|
onConnectedToServer();
|
|
haxe.Timer.delay(() -> connectedCb(), 1500); // 1.5 second delay to do the RTT calculation
|
|
}
|
|
}
|
|
var onDatachannelMessage = (dc:RTCDataChannel, b:haxe.io.Bytes) -> {
|
|
onPacketReceived(clientConnection, client, clientDatachannel, new InputBitStream(b));
|
|
}
|
|
|
|
var onDatachannelClose = (dc:RTCDataChannel) -> {
|
|
closeFunc("Disconnected", false);
|
|
}
|
|
|
|
var onDatachannelError = (msg:String) -> {
|
|
Console.log('Errored out due to ${msg}');
|
|
closeFunc("Connection error", false);
|
|
}
|
|
|
|
clientDatachannel.onOpen = (n) -> {
|
|
onDatachannelOpen(1);
|
|
}
|
|
clientDatachannel.onMessage = (b) -> {
|
|
onDatachannelMessage(clientDatachannel, b);
|
|
}
|
|
clientDatachannel.onClosed = () -> {
|
|
onDatachannelClose(clientDatachannel);
|
|
}
|
|
clientDatachannel.onError = (msg) -> {
|
|
onDatachannelError(msg);
|
|
}
|
|
clientDatachannelUnreliable.onOpen = (n) -> {
|
|
onDatachannelOpen(2);
|
|
}
|
|
clientDatachannelUnreliable.onMessage = (b) -> {
|
|
onDatachannelMessage(clientDatachannelUnreliable, b);
|
|
}
|
|
clientDatachannelUnreliable.onClosed = () -> {
|
|
onDatachannelClose(clientDatachannelUnreliable);
|
|
}
|
|
clientDatachannelUnreliable.onError = (msg) -> {
|
|
onDatachannelError(msg);
|
|
}
|
|
});
|
|
}
|
|
|
|
public static function disconnect() {
|
|
if (Net.isClient) {
|
|
NetCommands.clientLeave(Net.clientId);
|
|
Net.isMP = false;
|
|
Net.isClient = false;
|
|
Net.isHost = false;
|
|
if (Net.client != null)
|
|
Net.client.close();
|
|
Net.client = null;
|
|
Net.clientDatachannel = null;
|
|
Net.clientId = 0;
|
|
Net.clientIdAllocs = 1;
|
|
Net.clients.clear();
|
|
Net.clientIdMap.clear();
|
|
Net.clientConnection = null;
|
|
Net.serverInfo = null;
|
|
Net.remoteServerInfo = null;
|
|
Net.connectedServerInfo = null;
|
|
Net.lobbyHostReady = false;
|
|
Net.lobbyClientReady = false;
|
|
Net.hostReady = false;
|
|
Net.hostSpectate = false;
|
|
Net.clientSpectate = false;
|
|
MPPlayMissionGui.allChats = [];
|
|
// MultiplayerLevelSelectGui.custSelected = false;
|
|
}
|
|
if (Net.isHost) {
|
|
NetCommands.serverClosed();
|
|
for (client => gc in clients) {
|
|
client.close();
|
|
}
|
|
Net.isMP = false;
|
|
Net.isClient = false;
|
|
Net.isHost = false;
|
|
Net.clientId = 0;
|
|
Net.clientIdAllocs = 1;
|
|
Net.clients.clear();
|
|
Net.clientIdMap.clear();
|
|
MasterServerClient.disconnectFromMasterServer();
|
|
Net.serverInfo = null;
|
|
Net.remoteServerInfo = null;
|
|
Net.connectedServerInfo = null;
|
|
Net.lobbyHostReady = false;
|
|
Net.lobbyClientReady = false;
|
|
Net.hostReady = false;
|
|
Net.hostSpectate = false;
|
|
Net.clientSpectate = false;
|
|
MPPlayMissionGui.allChats = [];
|
|
// MultiplayerLevelSelectGui.custSelected = false;
|
|
}
|
|
}
|
|
|
|
public static function checkPacketTimeout(dt:Float) {
|
|
if (!Net.isMP)
|
|
return;
|
|
static var accum = 0.0;
|
|
static var wsAccum = 0.0;
|
|
accum += dt;
|
|
wsAccum += dt;
|
|
if (accum > 1.0) {
|
|
accum = 0;
|
|
var t = Console.time();
|
|
for (dc => cc in clients) {
|
|
if (cc is ClientConnection) {
|
|
var conn = cast(cc, ClientConnection);
|
|
if (Net.isHost && conn.state == LOADING)
|
|
continue;
|
|
if (Net.isClient && MarbleGame.instance.world != null && !MarbleGame.instance.world._ready)
|
|
continue; // We still loading, don't disconnect
|
|
if (conn.needsTimeoutWarn(t)) {
|
|
conn.didWarnTimeout = true;
|
|
if (Net.isClient) {
|
|
NetCommands.requestPing();
|
|
}
|
|
if (Net.isHost) {
|
|
NetCommands.pingClient(cc, t);
|
|
}
|
|
}
|
|
if (conn.needsTimeoutKick(t)) {
|
|
if (Net.isHost) {
|
|
dc.close();
|
|
}
|
|
if (Net.isClient) {
|
|
disconnect();
|
|
if (MarbleGame.instance.world != null) {
|
|
MarbleGame.instance.quitMission();
|
|
}
|
|
if (!(MarbleGame.canvas.content is MPMessageGui)) {
|
|
var loadGui = new MPMessageGui("Error", "Timed out");
|
|
MarbleGame.canvas.setContent(loadGui);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (wsAccum >= 15.0) {
|
|
wsAccum = 0;
|
|
if (Net.isHost) {
|
|
if (MasterServerClient.instance != null)
|
|
MasterServerClient.instance.sendServerInfo(serverInfo); // Heartbeat
|
|
else
|
|
MasterServerClient.connectToMasterServer(() -> {
|
|
MasterServerClient.instance.sendServerInfo(serverInfo); // Heartbeat
|
|
});
|
|
}
|
|
if (Net.isClient) {
|
|
if (MasterServerClient.instance != null)
|
|
MasterServerClient.instance.heartBeat();
|
|
else
|
|
MasterServerClient.connectToMasterServer(() -> {
|
|
MasterServerClient.instance.heartBeat();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
static function onClientConnect(c:RTCPeerConnection, dc:RTCDataChannel, dcu:RTCDataChannel) {
|
|
if (!Net.isMP) {
|
|
c.close();
|
|
return;
|
|
}
|
|
var clientId = allocateClientId();
|
|
if (clientId == -1) {
|
|
c.close();
|
|
return; // Failed to allocate ID
|
|
}
|
|
var cc = new ClientConnection(clientId, c, dc, dcu);
|
|
clients.set(c, cc);
|
|
clientIdMap[clientId] = clients[c];
|
|
cc.lastRecvTime = Console.time(); // So it doesnt get timed out
|
|
|
|
var closing = false;
|
|
|
|
var onMessage = (dc:RTCDataChannel, msgBytes:haxe.io.Bytes) -> {
|
|
onPacketReceived(cc, c, dc, new InputBitStream(msgBytes));
|
|
}
|
|
var onClosed = () -> {
|
|
if (closing)
|
|
return;
|
|
closing = true;
|
|
clients.remove(c);
|
|
onClientLeave(cc);
|
|
}
|
|
|
|
var onError = (msg:String) -> {
|
|
if (closing)
|
|
return;
|
|
closing = true;
|
|
clients.remove(c);
|
|
Console.log('Client ${cc.id} errored out due to: ${msg}');
|
|
onClientLeave(cc);
|
|
}
|
|
|
|
dc.onMessage = (msgBytes) -> {
|
|
onMessage(dc, msgBytes);
|
|
}
|
|
dc.onClosed = () -> {
|
|
onClosed();
|
|
}
|
|
dc.onError = (msg) -> {
|
|
onError(msg);
|
|
}
|
|
|
|
dcu.onMessage = (msgBytes) -> {
|
|
onMessage(dcu, msgBytes);
|
|
}
|
|
dcu.onClosed = () -> {
|
|
onClosed();
|
|
}
|
|
dcu.onError = (msg) -> {
|
|
onError(msg);
|
|
}
|
|
|
|
var b = haxe.io.Bytes.alloc(2);
|
|
b.set(0, ClientIdAssign);
|
|
b.set(1, clientId);
|
|
dc.sendBytes(b);
|
|
Console.log("Client has connected!");
|
|
// Send the ping packet to calculcate the RTT
|
|
var b = haxe.io.Bytes.alloc(2);
|
|
b.set(0, Ping);
|
|
b.set(1, 3); // Count
|
|
cast(clients[c], ClientConnection).pingSendTime = Console.time();
|
|
dc.sendBytes(b);
|
|
Console.log("Sending ping packet!");
|
|
|
|
// AudioManager.playSound(ResourceLoader.getAudio("data/sound/spawn_alternate.wav").resource);
|
|
|
|
serverInfo.players = 1;
|
|
for (k => v in clients) { // Recount
|
|
serverInfo.players++;
|
|
}
|
|
|
|
serverInfo.players++;
|
|
MasterServerClient.instance.sendServerInfo(serverInfo); // notify the server of the new player
|
|
|
|
if (MarbleGame.canvas.content is MPPlayMissionGui) {
|
|
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
|
|
}
|
|
|
|
if (MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1] is MPPreGameDlg) {
|
|
cast(MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1], MPPreGameDlg).updatePlayerList();
|
|
}
|
|
}
|
|
|
|
static function onConnectedToServer() {
|
|
Console.log("Connected to the server!");
|
|
// Send the ping packet to calculate the RTT
|
|
var b = haxe.io.Bytes.alloc(2);
|
|
b.set(0, Ping);
|
|
b.set(1, 3); // Count
|
|
cast(clients[client], ClientConnection).pingSendTime = Console.time();
|
|
clientDatachannel.sendBytes(b);
|
|
Console.log("Sending ping packet!");
|
|
}
|
|
|
|
static function onClientLeave(cc:ClientConnection) {
|
|
if (!Net.isMP || cc == null)
|
|
return;
|
|
NetCommands.clientDisconnected(cc.id);
|
|
|
|
if (cc.id != 0) {
|
|
freeClientId(cc.id);
|
|
}
|
|
|
|
serverInfo.players = 1;
|
|
for (k => v in clients) { // Recount
|
|
serverInfo.players++;
|
|
}
|
|
|
|
MasterServerClient.instance.sendServerInfo(serverInfo); // notify the server of the player leave
|
|
|
|
// AudioManager.playSound(ResourceLoader.getAudio("data/sound/infotutorial.wav").resource);
|
|
|
|
if (MarbleGame.canvas.content is MPPlayMissionGui) {
|
|
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
|
|
}
|
|
|
|
if (MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1] is MPPreGameDlg) {
|
|
cast(MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1], MPPreGameDlg).updatePlayerList();
|
|
}
|
|
}
|
|
|
|
static function onClientHandshakeComplete(conn:ClientConnection) {
|
|
// Send our current mission to connecting client
|
|
// if (MultiplayerLevelSelectGui.custSelected) {
|
|
// NetCommands.setLobbyCustLevelNameClient(conn, MultiplayerLevelSelectGui.custPath);
|
|
// } else {
|
|
NetCommands.sendServerSettingsClient(conn, Settings.serverSettings.name, Settings.serverSettings.description, Settings.serverSettings.quickRespawn,
|
|
Settings.serverSettings.forceSpectators, Settings.serverSettings.competitiveMode, Settings.serverSettings.oldSpawns);
|
|
NetCommands.setLobbyLevelIndexClient(conn, MPPlayMissionGui.currentCategoryStatic, MPPlayMissionGui.currentSelectionStatic);
|
|
// }
|
|
|
|
if (serverInfo.state == "PLAYING") { // We initiated the game, directly add in the marble
|
|
// if (MultiplayerLevelSelectGui.custSelected) {
|
|
// NetCommands.playCustomLevelMidJoinClient(conn, MultiplayerLevelSelectGui.custPath);
|
|
// } else
|
|
NetCommands.playLevelMidJoinClient(conn, MPPlayMissionGui.currentCategoryStatic, MPPlayMissionGui.currentSelectionStatic);
|
|
MarbleGame.instance.world.addJoiningClient(conn, () -> {});
|
|
var playerInfoBytes = sendPlayerInfosBytes();
|
|
for (dc => cc in clients) {
|
|
if (cc != conn) {
|
|
cc.sendBytes(playerInfoBytes);
|
|
NetCommands.addMidGameJoinMarbleClient(cc, conn.id);
|
|
}
|
|
}
|
|
}
|
|
if (serverInfo.state == "LOBBY") {
|
|
// Connect client to lobby
|
|
NetCommands.enterLobbyClient(conn);
|
|
}
|
|
}
|
|
|
|
public static function sendPlayerInfosBytes() {
|
|
var b = new haxe.io.BytesOutput();
|
|
b.writeByte(PlayerInfo);
|
|
var cnt = 0;
|
|
for (c in clientIdMap)
|
|
cnt++;
|
|
b.writeByte(cnt + 1); // all + host
|
|
for (c => v in clientIdMap) {
|
|
b.writeByte(c);
|
|
b.writeByte(v.lobbyReady ? 1 : 0);
|
|
b.writeByte(v.platform);
|
|
b.writeByte(v.marbleId);
|
|
b.writeByte(v.marbleCatId);
|
|
b.writeByte(v.spectator ? 1 : 0);
|
|
var name = v.getName();
|
|
b.writeByte(name.length);
|
|
for (i in 0...name.length) {
|
|
b.writeByte(StringTools.fastCodeAt(name, i));
|
|
}
|
|
}
|
|
// Write host data
|
|
b.writeByte(0);
|
|
b.writeByte(Net.lobbyHostReady ? 1 : 0);
|
|
b.writeByte(getPlatform());
|
|
b.writeByte(Settings.optionsSettings.marbleIndex);
|
|
b.writeByte(Settings.optionsSettings.marbleCategoryIndex);
|
|
b.writeByte(Net.hostSpectate ? 1 : 0);
|
|
var name = Settings.highscoreName;
|
|
b.writeByte(name.length);
|
|
for (i in 0...name.length) {
|
|
b.writeByte(StringTools.fastCodeAt(name, i));
|
|
}
|
|
return b.getBytes();
|
|
}
|
|
|
|
static function onPacketReceived(conn:ClientConnection, c:RTCPeerConnection, dc:RTCDataChannel, input:InputBitStream) {
|
|
if (!Net.isMP)
|
|
return; // only for MP
|
|
conn.lastRecvTime = Console.time();
|
|
conn.didWarnTimeout = false;
|
|
|
|
var packetType = input.readByte();
|
|
switch (packetType) {
|
|
case NetCommand:
|
|
NetCommands.readPacket(input);
|
|
|
|
case ClientIdAssign:
|
|
clientId = input.readByte(); // 8 bit client id, hopefully we don't exceed this
|
|
Console.log('Client ID set to ${clientId}');
|
|
NetCommands.setPlayerData(clientId, Settings.highscoreName, Settings.optionsSettings.marbleIndex,
|
|
Settings.optionsSettings.marbleCategoryIndex, false); // Send our player name to the server
|
|
NetCommands.transmitPlatform(clientId, getPlatform()); // send our platform too
|
|
|
|
case Ping:
|
|
var pingLeft = input.readByte();
|
|
Console.log("Got ping packet!");
|
|
var b = haxe.io.Bytes.alloc(2);
|
|
b.set(0, PingBack);
|
|
b.set(1, pingLeft);
|
|
dc.sendBytes(b);
|
|
|
|
case PingBack:
|
|
var pingLeft = input.readByte();
|
|
Console.log("Got pingback packet!");
|
|
var now = Console.time();
|
|
conn._rttRecords.push((now - conn.pingSendTime));
|
|
if (pingLeft > 0) {
|
|
conn.pingSendTime = now;
|
|
var b = haxe.io.Bytes.alloc(2);
|
|
b.set(0, Ping);
|
|
b.set(1, pingLeft - 1);
|
|
dc.sendBytes(b);
|
|
} else {
|
|
for (r in conn._rttRecords)
|
|
conn.rtt += r;
|
|
conn.rtt /= conn._rttRecords.length;
|
|
Console.log('Got RTT ${conn.rtt} for client ${conn.id}');
|
|
if (Net.isHost) {
|
|
var b = sendPlayerInfosBytes();
|
|
for (cc in clients) {
|
|
cc.sendBytes(b);
|
|
}
|
|
onClientHandshakeComplete(conn);
|
|
}
|
|
}
|
|
|
|
case MarbleUpdate:
|
|
var marbleUpdatePacket = new MarbleUpdatePacket();
|
|
marbleUpdatePacket.deserialize(input);
|
|
var cc = marbleUpdatePacket.clientId;
|
|
var client = cc != Net.clientId ? clientIdMap[cc] : Net.clientConnection;
|
|
client.pingTicks = marbleUpdatePacket.pingTicks;
|
|
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
|
var m = MarbleGame.instance.world.lastMoves;
|
|
m.enqueue(marbleUpdatePacket);
|
|
}
|
|
|
|
case MarbleMove:
|
|
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
|
var movePacket = new MarbleMovePacket();
|
|
movePacket.deserialize(input);
|
|
var cc = clientIdMap[movePacket.clientId];
|
|
if (cc.state == GAME) {
|
|
var startRecvId = cc.moveManager.getQueueSize();
|
|
for (move in movePacket.moves)
|
|
cc.queueMove(move);
|
|
var endRecvId = cc.moveManager.getQueueSize();
|
|
cc.pingTicks = movePacket.moves.length - (endRecvId - startRecvId);
|
|
}
|
|
}
|
|
|
|
case PowerupPickup:
|
|
var powerupPickupPacket = new PowerupPickupPacket();
|
|
powerupPickupPacket.deserialize(input);
|
|
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
|
var m = @:privateAccess MarbleGame.instance.world.powerupPredictions;
|
|
m.acknowledgePowerupPickup(powerupPickupPacket, MarbleGame.instance.world.timeState, clientConnection.moveManager.getQueueSize());
|
|
}
|
|
|
|
case GemSpawn:
|
|
var gemSpawnPacket = new GemSpawnPacket();
|
|
gemSpawnPacket.deserialize(input);
|
|
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
|
MarbleGame.instance.world.spawnHuntGemsClientSide(gemSpawnPacket.gemIds, gemSpawnPacket.expireds);
|
|
@:privateAccess MarbleGame.instance.world.gemPredictions.acknowledgeGemSpawn(gemSpawnPacket);
|
|
}
|
|
|
|
case GemPickup:
|
|
var gemPickupPacket = new GemPickupPacket();
|
|
gemPickupPacket.deserialize(input);
|
|
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
|
@:privateAccess MarbleGame.instance.world.playGui.incrementPlayerScore(gemPickupPacket.clientId, gemPickupPacket.scoreIncr);
|
|
@:privateAccess MarbleGame.instance.world.gemPredictions.acknowledgeGemPickup(gemPickupPacket);
|
|
}
|
|
|
|
case PlayerInfo:
|
|
var count = input.readByte();
|
|
var newP = false;
|
|
for (i in 0...count) {
|
|
var id = input.readByte();
|
|
var cready = input.readByte() == 1;
|
|
var platform = input.readByte();
|
|
var marble = input.readByte();
|
|
var marbleCat = input.readByte();
|
|
var cspectator = input.readByte() == 1;
|
|
if (id != 0 && id != Net.clientId && !clientIdMap.exists(id)) {
|
|
Console.log('Adding ghost connection ${id}');
|
|
addGhost(id);
|
|
newP = true;
|
|
}
|
|
var nameLength = input.readByte();
|
|
var name = "";
|
|
for (j in 0...nameLength) {
|
|
name += String.fromCharCode(input.readByte());
|
|
}
|
|
if (clientIdMap.exists(id)) {
|
|
clientIdMap[id].setName(name);
|
|
clientIdMap[id].setMarbleId(marble, marbleCat);
|
|
clientIdMap[id].lobbyReady = cready;
|
|
clientIdMap[id].platform = platform;
|
|
clientIdMap[id].spectator = cspectator;
|
|
}
|
|
if (Net.clientId == id) {
|
|
Net.lobbyClientReady = cready;
|
|
Net.clientSpectate = cspectator;
|
|
}
|
|
}
|
|
if (MarbleGame.canvas.content is MPPlayMissionGui) {
|
|
cast(MarbleGame.canvas.content, MPPlayMissionGui).updateLobbyNames();
|
|
}
|
|
if (MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1] is MPPreGameDlg) {
|
|
cast(MarbleGame.canvas.children[MarbleGame.canvas.children.length - 1], MPPreGameDlg).updatePlayerList();
|
|
}
|
|
|
|
case ScoreBoardInfo:
|
|
var scoreboardPacket = new ScoreboardPacket();
|
|
scoreboardPacket.deserialize(input);
|
|
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
|
@:privateAccess MarbleGame.instance.world.playGui.updatePlayerScores(scoreboardPacket);
|
|
}
|
|
|
|
case ExplodableUpdate:
|
|
var explodableUpdatePacket = new ExplodableUpdatePacket();
|
|
explodableUpdatePacket.deserialize(input);
|
|
if (MarbleGame.instance.world != null && !MarbleGame.instance.world._disposed) {
|
|
@:privateAccess MarbleGame.instance.world.explodablePredictions.acknowledgeExplodableUpdate(explodableUpdatePacket);
|
|
}
|
|
|
|
case _:
|
|
Console.log("unknown command: " + packetType);
|
|
}
|
|
}
|
|
|
|
static function allocateClientId() {
|
|
for (id in 0...32) {
|
|
if (Net.clientIdAllocs & (1 << id) == 0) {
|
|
Net.clientIdAllocs |= (1 << id);
|
|
return id;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static function freeClientId(id:Int) {
|
|
Net.clientIdAllocs &= ~(1 << id);
|
|
}
|
|
|
|
public static function sendPacketToAll(packetData:OutputBitStream) {
|
|
var bytes = packetData.getBytes();
|
|
for (c => v in clients) {
|
|
v.sendBytes(bytes);
|
|
}
|
|
}
|
|
|
|
public static function sendPacketToIngame(packetData:OutputBitStream) {
|
|
var bytes = packetData.getBytes();
|
|
for (c => v in clients) {
|
|
if (v.state == GAME)
|
|
v.sendBytes(bytes);
|
|
}
|
|
}
|
|
|
|
public static function sendPacketToHost(packetData:OutputBitStream) {
|
|
if (clientDatachannel.state == Open) {
|
|
var bytes = packetData.getBytes();
|
|
clientDatachannel.sendBytes(bytes);
|
|
}
|
|
}
|
|
|
|
public static function sendPacketToHostUnreliable(packetData:OutputBitStream) {
|
|
if (clientDatachannelUnreliable.state == Open) {
|
|
var bytes = packetData.getBytes();
|
|
clientDatachannelUnreliable.sendBytes(bytes);
|
|
}
|
|
}
|
|
|
|
public static function sendPacketToClient(client:GameConnection, packetData:OutputBitStream) {
|
|
var bytes = packetData.getBytes();
|
|
client.sendBytes(bytes);
|
|
}
|
|
|
|
public static function addDummyConnection() {
|
|
if (Net.isHost) {
|
|
addGhost(Net.clientId++);
|
|
}
|
|
}
|
|
|
|
public static inline function getPlatform() {
|
|
#if js
|
|
return NetPlatform.Web;
|
|
#end
|
|
#if hl
|
|
#if MACOS_BUNDLE
|
|
return NetPlatform.MacOS;
|
|
#else
|
|
#if android
|
|
return NetPlatform.Android;
|
|
#else
|
|
return NetPlatform.PC;
|
|
#end
|
|
#end
|
|
#end
|
|
}
|
|
}
|