2D multiplayer platformer using Godot Engine
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

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));
}
}