You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
4.1 KiB
117 lines
4.1 KiB
using System; |
|
using Godot; |
|
|
|
// TODO: Allow for initially private integrated server to open itself up to the public. |
|
public class Server : Game |
|
{ |
|
internal Player LocalPlayer { get; private set; } |
|
private bool _isLocalPlayerConnected = false; |
|
|
|
public NetworkedMultiplayerENet Peer => (NetworkedMultiplayerENet)Multiplayer.NetworkPeer; |
|
public bool IsRunning => Peer != null; |
|
public bool IsSingleplayer { get; private set; } |
|
|
|
public override void _Ready() |
|
{ |
|
base._Ready(); |
|
Multiplayer.Connect("network_peer_connected", this, nameof(OnPeerConnected)); |
|
Multiplayer.Connect("network_peer_disconnected", this, nameof(OnPeerDisconnected)); |
|
} |
|
|
|
public ushort StartSingleplayer() |
|
{ |
|
for (var retries = 0; ; retries++) { |
|
try { |
|
IsSingleplayer = true; |
|
// TODO: When `get_local_port` is available, just use port 0 for an auto-assigned port. |
|
// Also see this PR: https://github.com/godotengine/godot/pull/48235 |
|
var port = (ushort)GD.RandRange(42000, 43000); |
|
Start(port, "127.0.0.1", 1); |
|
return port; |
|
} catch (Exception ex) { |
|
// Do throw the "Server is already running" exception. |
|
// 3 retries should be good enough to find a random unused port. |
|
if ((ex is InvalidOperationException) || (retries == 2)) throw; |
|
} |
|
} |
|
} |
|
public void Start(ushort port) |
|
=> Start(port, "*", 32); |
|
private void Start(ushort port, string bindIP, int maxClients) |
|
{ |
|
if (IsRunning) throw new InvalidOperationException("Server is already running"); |
|
|
|
var peer = new NetworkedMultiplayerENet(); |
|
peer.SetBindIp(bindIP); |
|
peer.ServerRelay = false; |
|
|
|
var error = peer.CreateServer(port, maxClients); |
|
if (error != Error.Ok) throw new Exception($"Error when starting the server: {error}"); |
|
|
|
Multiplayer.NetworkPeer = peer; |
|
} |
|
|
|
public void Stop() |
|
{ |
|
if (!IsRunning) throw new InvalidOperationException("Server is not running"); |
|
|
|
Peer.CloseConnection(); |
|
Multiplayer.NetworkPeer = null; |
|
|
|
IsSingleplayer = false; |
|
_isLocalPlayerConnected = false; |
|
|
|
foreach (var player in this.GetWorld().Players) |
|
if (player != LocalPlayer) |
|
player.RemoveFromParent(); |
|
} |
|
|
|
|
|
private void OnPeerConnected(int networkID) |
|
{ |
|
if (IsSingleplayer) { |
|
if (Peer.GetPeerAddress(networkID) != "127.0.0.1") |
|
{ Peer.DisconnectPeer(networkID, true); return; } |
|
Multiplayer.RefuseNewNetworkConnections = true; |
|
} |
|
|
|
if ((LocalPlayer != null) && !_isLocalPlayerConnected && |
|
(Peer.GetPeerAddress(networkID) == "127.0.0.1")) { |
|
LocalPlayer.NetworkID = networkID; |
|
_isLocalPlayerConnected = true; |
|
} else { |
|
var world = this.GetWorld(); |
|
|
|
foreach (var player in world.Players) { |
|
RPC.Reliable(networkID, world.SpawnPlayer, player.NetworkID, player.Position); |
|
|
|
// Send player's appearance. |
|
player.Rset(nameof(Player.DisplayName), player.DisplayName); |
|
player.Rset(nameof(Player.Color), player.Color); |
|
|
|
// Send player's currently equipped item. |
|
if (player.Items.Current != null) RPC.Reliable(networkID, |
|
((Items)player.Items).DoSetCurrent, player.Items.Current.Name); |
|
} |
|
|
|
foreach (var block in world.Blocks) |
|
RPC.Reliable(networkID, world.SpawnBlock, |
|
block.Position.X, block.Position.Y, |
|
block.Color, block.Unbreakable); |
|
|
|
RPC.Reliable(world.SpawnPlayer, networkID, Vector2.Zero); |
|
if (IsSingleplayer) LocalPlayer = world.GetPlayer(networkID); |
|
} |
|
} |
|
|
|
private void OnPeerDisconnected(int networkID) |
|
{ |
|
var world = this.GetWorld(); |
|
var player = world.GetPlayer(networkID); |
|
|
|
// Local player stays around for reconnecting. |
|
if (LocalPlayer == player) return; |
|
|
|
RPC.Reliable(world.Despawn, world.GetPathTo(player)); |
|
} |
|
}
|
|
|