Add completely custom NetworkAPI

- No more Rpc or Rset calls!
- Custom packet handling and de/serialization
- Lots of useful SendTo* methods!
- Appearance change in multiplayer
  waits for confirmation from server
main
copygirl 5 years ago
parent 8ee48adbd7
commit 1fa5ee9b63
  1. 7
      src/EscapeMenuAppearance.cs
  2. 2
      src/EscapeMenuMultiplayer.cs
  3. 37
      src/Extensions.cs
  4. 6
      src/LocalPlayer.cs
  5. 141
      src/Network.cs
  6. 273
      src/NetworkAPI.cs
  7. 111
      src/Player.cs

@ -45,8 +45,9 @@ public class EscapeMenuAppearance : CenterContainer
private void _on_Appearance_visibility_changed()
{
if (IsVisibleInTree()) return;
LocalPlayer.Instance.DisplayName = DisplayName.Text;
LocalPlayer.Instance.Color = ColorPreview.Modulate;
if (!IsVisibleInTree())
Player.ChangeAppearance(LocalPlayer.Instance,
DisplayName.Text, ColorPreview.Modulate,
Network.IsClient);
}
}

@ -23,7 +23,7 @@ public class EscapeMenuMultiplayer : Container
ClientDisConnect = GetNode<Button>(ClientDisConnectPath);
ClientAddress = GetNode<LineEdit>(ClientAddressPath);
Network.Instance.Connect(nameof(Network.StatusChanged), this, nameof(OnNetworkStatusChanged));
Network.StatusChanged += OnNetworkStatusChanged;
ServerPort.PlaceholderText = Network.DEFAULT_PORT.ToString();
ClientAddress.PlaceholderText = $"localhost:{Network.DEFAULT_PORT}";
}

@ -12,43 +12,6 @@ public static class Extensions
(instance as IInitializer)?.Initialize();
return instance;
}
public static void RsetProperty(this Node @this, Node propertyOwner, string property, string method, object value)
{
if (!@this.IsInsideTree()) return;
if (Network.IsServer) propertyOwner.RsetExcept(@this as Player, property, value);
else if (Network.IsMultiplayerReady) @this.RpcId(1, method, value);
}
public static void RsetPropertyUnreliable(this Node @this, Node propertyOwner, string property, string method, object value)
{
if (!@this.IsInsideTree()) return;
if (Network.IsServer) propertyOwner.RsetUnreliableExcept(@this as Player, property, value);
else if (Network.IsMultiplayerReady) @this.RpcUnreliableId(1, method, value);
}
public static void RpcExcept(this Node @this, Player except, string method, params object[] args)
{
foreach (var player in Network.Players)
if (player != except)
@this.RpcId(player.NetworkId, method, args);
}
public static void RsetExcept(this Node @this, Player except, string property, object value)
{
foreach (var player in Network.Players)
if (player != except)
@this.RsetId(player.NetworkId, property, value);
}
public static void RsetUnreliableExcept(this Node @this, Player except, string property, object value)
{
foreach (var player in Network.Players)
if (player != except)
@this.RsetUnreliableId(player.NetworkId, property, value);
}
}
public interface IInitializer

@ -48,10 +48,4 @@ public class LocalPlayer : Player
_lastOnFloor = null;
}
}
internal void ResetPositionInternal(Vector2 position)
{ Position = position; Velocity = Vector2.Zero; }
[Puppet]
internal void ResetPosition(Vector2 position)
=> ResetPositionInternal(position);
}

@ -16,14 +16,24 @@ public class Network : Node
public const ushort DEFAULT_PORT = 42005;
public static Network Instance { get; private set; }
public static NetworkAPI API { get; private set; }
public static NetworkStatus Status { get; private set; } = NetworkStatus.NoConnection;
public static bool IsMultiplayerReady => (Status == NetworkStatus.ServerRunning) || (Status == NetworkStatus.ConnectedToServer);
public static bool IsAuthoratative => Status <= NetworkStatus.ServerRunning;
public static bool IsServer => Status == NetworkStatus.ServerRunning;
public static int LocalNetworkId => Instance.GetTree().GetNetworkUniqueId();
public static bool IsClient => Status > NetworkStatus.Connecting;
public static int LocalNetworkID => Instance.GetTree().GetNetworkUniqueId();
public static IEnumerable<Player> Players => Instance._playersById.Values;
public static event Action<NetworkStatus> StatusChanged;
public static Player GetPlayer(int id)
=> Instance._playersById.TryGetValue(id, out var value) ? value : null;
public static Player GetPlayerOrThrow(int id)
=> GetPlayer(id) ?? throw new ArgumentException(
$"No player instance found for ID {id}", nameof(id));
private readonly Dictionary<int, Player> _playersById = new Dictionary<int, Player>();
@ -32,10 +42,11 @@ public class Network : Node
public Node PlayerContainer { get; private set; }
[Signal] public delegate void StatusChanged(NetworkStatus status);
public Network() => Instance = this;
public Network()
{
Instance = this;
}
public override void _Ready()
{
@ -47,27 +58,35 @@ public class Network : Node
GetTree().Connect("network_peer_connected", this, nameof(OnPeerConnected));
GetTree().Connect("network_peer_disconnected", this, nameof(OnPeerDisconnected));
var multiplayerApi = GetTree().Multiplayer;
API = new NetworkAPI(multiplayerApi);
multiplayerApi.Connect("network_peer_packet", this, nameof(OnPacketReceived));
API.RegisterC2SPacket<ClientAuthPacket>(OnClientAuthPacket);
API.RegisterS2CPacket<SpawnPlayerPacket>(OnSpawnPlayerPacket);
Player.RegisterPackets();
}
// Let NetworkAPI handle receiving of custom packages.
private void OnPacketReceived(int id, byte[] bytes)
=> API.OnPacketReceived(id, bytes);
public Player GetPlayer(int id)
=> _playersById.TryGetValue(id, out var value) ? value : null;
public void ClearPlayers()
{
LocalPlayer.Instance.NetworkId = -1;
LocalPlayer.Instance.NetworkID = -1;
foreach (var player in _playersById.Values)
if (!player.IsLocal)
player.RemoveFromParent();
_playersById.Clear();
}
private void ChangeStatus(NetworkStatus status)
{
if (Status == status) return;
Status = status;
EmitSignal(nameof(StatusChanged), status);
StatusChanged?.Invoke(status);
PlayerContainer.PauseMode = IsMultiplayerReady
? PauseModeEnum.Process : PauseModeEnum.Stop;
@ -83,7 +102,7 @@ public class Network : Node
if (error != Error.Ok) return error;
GetTree().NetworkPeer = peer;
LocalPlayer.Instance.NetworkId = 1;
LocalPlayer.Instance.NetworkID = 1;
_playersById.Add(1, LocalPlayer.Instance);
ChangeStatus(NetworkStatus.ServerRunning);
@ -101,6 +120,7 @@ public class Network : Node
ChangeStatus(NetworkStatus.NoConnection);
}
public Error ConnectToServer(string address, ushort port)
{
if (GetTree().NetworkPeer != null) throw new InvalidOperationException();
@ -114,6 +134,20 @@ public class Network : Node
return Error.Ok;
}
private void OnClientConnected()
{
ChangeStatus(NetworkStatus.Authenticating);
var id = GetTree().GetNetworkUniqueId();
LocalPlayer.Instance.NetworkID = id;
_playersById.Add(id, LocalPlayer.Instance);
API.SendToServer(new ClientAuthPacket {
DisplayName = LocalPlayer.Instance.DisplayName,
Color = LocalPlayer.Instance.Color
});
}
public void DisconnectFromServer()
{
if ((GetTree().NetworkPeer == null) || GetTree().IsNetworkServer()) throw new InvalidOperationException();
@ -126,64 +160,61 @@ public class Network : Node
}
private void OnClientConnected()
private Player SpawnOtherPlayer(int networkID, Vector2 position, string displayName, Color color)
{
ChangeStatus(NetworkStatus.Authenticating);
var player = OtherPlayerScene.Init<Player>();
player.NetworkID = networkID;
// TODO: We need to find a way to sync these property automatically.
player.Position = position;
player.DisplayName = displayName;
player.Color = color;
_playersById.Add(networkID, player);
PlayerContainer.AddChild(player);
return player;
}
var id = GetTree().GetNetworkUniqueId();
LocalPlayer.Instance.NetworkId = id;
_playersById.Add(id, LocalPlayer.Instance);
Rpc(nameof(OnClientAuthenticate), LocalPlayer.Instance.DisplayName, LocalPlayer.Instance.Color);
private class ClientAuthPacket
{
public string DisplayName { get; set; }
public Color Color { get; set; }
}
[Master]
private void OnClientAuthenticate(string displayName, Color color)
private void OnClientAuthPacket(int networkID, ClientAuthPacket packet)
{
var id = GetTree().GetRpcSenderId();
// Authentication message is only sent once, so once the Player object exists, ignore this message.
if (GetPlayer(id) != null) return;
var newPlayer = SpawnOtherPlayerInternal(id, Vector2.Zero, displayName, color);
RpcId(id, nameof(SpawnLocalPlayer), newPlayer.Position);
if (GetPlayer(networkID) != null) return;
foreach (var player in _playersById.Values) {
if (player == newPlayer) continue;
// Spawn existing players for the new player.
RpcId(id, nameof(SpawnOtherPlayer), player.NetworkId, player.Position, player.DisplayName, player.Color);
// Spawn new player for existing players.
if (!player.IsLocal) // Don't spawn the player for the host, it already called SpawnOtherPlayer itself.
RpcId(player.NetworkId, nameof(SpawnOtherPlayer), newPlayer.NetworkId, newPlayer.Position, newPlayer.DisplayName, newPlayer.Color);
}
foreach (var player in _playersById.Values)
API.SendTo(networkID, new SpawnPlayerPacket(player));
var newPlayer = SpawnOtherPlayer(networkID, Vector2.Zero, packet.DisplayName, packet.Color);
API.SendToEveryone(new SpawnPlayerPacket(newPlayer));
}
[Puppet]
private void SpawnLocalPlayer(Vector2 position)
{
LocalPlayer.Instance.Position = position;
LocalPlayer.Instance.Velocity = Vector2.Zero;
ChangeStatus(NetworkStatus.ConnectedToServer);
private class SpawnPlayerPacket
{
public int NetworkID { get; set; }
public Vector2 Position { get; set; }
public string DisplayName { get; set; }
public Color Color { get; set; }
public SpawnPlayerPacket(Player player)
{
NetworkID = player.NetworkID;
Position = player.Position;
DisplayName = player.DisplayName;
Color = player.Color;
}
}
private Player SpawnOtherPlayerInternal(int id, Vector2 position, string displayName, Color color)
private void OnSpawnPlayerPacket(SpawnPlayerPacket packet)
{
var player = OtherPlayerScene.Init<Player>();
player.NetworkId = id;
// TODO: We need to find a way to sync these property automatically.
player.Position = position;
player.DisplayName = displayName;
player.Color = color;
_playersById.Add(id, player);
PlayerContainer.AddChild(player);
return player;
if (packet.NetworkID == LocalNetworkID) {
var player = LocalPlayer.Instance;
player.Position = packet.Position;
player.Velocity = Vector2.Zero;
ChangeStatus(NetworkStatus.ConnectedToServer);
} else SpawnOtherPlayer(packet.NetworkID, packet.Position, packet.DisplayName, packet.Color);
}
[Puppet]
private void SpawnOtherPlayer(int id, Vector2 position, string displayName, Color color)
=> SpawnOtherPlayerInternal(id, position, displayName, color);
private void OnPeerConnected(int id)

@ -0,0 +1,273 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Godot;
[Flags]
public enum PacketDirection
{
ServerToClient = 0b01,
ClientToServer = 0b10,
Both = ServerToClient | ClientToServer,
}
public enum TransferMode
{
Unreliable = NetworkedMultiplayerPeer.TransferModeEnum.Unreliable,
UnreliableOrdered = NetworkedMultiplayerPeer.TransferModeEnum.UnreliableOrdered,
Reliable = NetworkedMultiplayerPeer.TransferModeEnum.Reliable,
}
// TODO: Improve performance and type safety of de/serialization.
// TODO: Support easily spawning and syncronizing objects and their properties.
public class NetworkAPI
{
private readonly MultiplayerAPI _multiplayerAPI;
private readonly List<PacketInfo> _packetsById = new List<PacketInfo>();
private readonly Dictionary<Type, PacketInfo> _packetsByType = new Dictionary<Type, PacketInfo>();
private readonly Dictionary<Type, INetworkDeSerializer> _deSerializers = new Dictionary<Type, INetworkDeSerializer>();
// private readonly List<INetworkDeSerializerMulti> _multiDeSerializers = new List<INetworkDeSerializerMulti>();
private class PacketInfo
{
public int ID { get; }
public Type Type { get; }
public PacketDirection Direction { get; }
public TransferMode DefaultTransformMode { get; }
public INetworkDeSerializer DeSerializer { get; }
public Action<int, object> OnPacketReceived { get; }
public PacketInfo(int id, Type type, PacketDirection direction,
TransferMode defaultTransferMode, INetworkDeSerializer deSerializer,
Action<int, object> onPacketReceived)
{
ID = id;
Type = type;
Direction = direction;
DefaultTransformMode = defaultTransferMode;
DeSerializer = deSerializer;
OnPacketReceived = onPacketReceived;
}
}
public NetworkAPI(MultiplayerAPI multiplayerAPI)
{
_multiplayerAPI = multiplayerAPI;
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadBoolean());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadByte());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadSByte());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadInt16());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadUInt16());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadInt32());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadUInt32());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadInt64());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadUInt64());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadSingle());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadDouble());
RegisterDeSerializer((writer, value) => writer.Write(value), reader => reader.ReadString());
// byte[]
RegisterDeSerializer((writer, value) => { writer.Write(value.Length); writer.Write(value); },
reader => reader.ReadBytes(reader.ReadInt32()));
// Vector2
RegisterDeSerializer((writer, value) => { writer.Write(value.x); writer.Write(value.y); },
reader => new Vector2(reader.ReadSingle(), reader.ReadSingle()));
// Color
RegisterDeSerializer((writer, value) => writer.Write(value.ToRgba32()),
reader => new Color(reader.ReadInt32()));
// TODO: Add handling for Array, List and Dictionary.
}
public void RegisterDeSerializer<T>(Action<BinaryWriter, T> serialize, Func<BinaryReader, T> deserialize)
=> _deSerializers.Add(typeof(T), new SimpleNetworkDeSerializer<T>(serialize, deserialize));
public void RegisterDeSerializer<T>(INetworkDeSerializer deSerializer)
=> _deSerializers.Add(typeof(T), deSerializer);
// public void RegisterDeSerializer(INetworkDeSerializerMulti deSerializer)
// => _multiDeSerializers.Add(deSerializer);
public void RegisterS2CPacket<T>(Action<T> action, TransferMode defaultTransferMode = TransferMode.Reliable)
=> RegisterPacket((int _id, T packet) => action(packet), defaultTransferMode, PacketDirection.ServerToClient);
public void RegisterC2SPacket<T>(Action<int, T> action, TransferMode defaultTransferMode = TransferMode.Reliable)
=> RegisterPacket(action, defaultTransferMode, PacketDirection.ClientToServer);
public void RegisterC2SPacket<T>(Action<Player, T> action, TransferMode defaultTransferMode = TransferMode.Reliable)
=> RegisterPacket(action, defaultTransferMode, PacketDirection.ClientToServer);
public void RegisterPacket<T>(Action<Player, T> action,
TransferMode defaultTransferMode = TransferMode.Reliable,
PacketDirection direction = PacketDirection.Both)
=> RegisterPacket((int id, T packet) => action(Network.GetPlayerOrThrow(id), packet), defaultTransferMode, direction);
public void RegisterPacket<T>(Action<int, T> action,
TransferMode defaultTransferMode = TransferMode.Reliable,
PacketDirection direction = PacketDirection.Both)
{
var deSerializer = GetDeSerializer(typeof(T), true);
var info = new PacketInfo(_packetsById.Count, typeof(T),
direction, defaultTransferMode, deSerializer,
(id, packet) => action(id, (T)packet));
_packetsByType.Add(typeof(T), info);
_packetsById.Add(info);
}
public void SendToServer<T>(T packet, TransferMode? transferMode = null)
=> SendTo(1, packet, transferMode);
public void SendTo<T>(Player player, T packet, TransferMode? transferMode = null)
=> SendTo(player.NetworkID, packet, transferMode);
public void SendTo<T>(int id, T packet, TransferMode? transferMode = null)
=> SendTo(new []{ id }, packet, transferMode);
public void SendToEveryone<T>(T packet, TransferMode? transferMode = null)
=> SendTo(_multiplayerAPI.GetNetworkConnectedPeers(), packet, transferMode);
public void SendToEveryoneExcept<T>(Player except, T packet, TransferMode? transferMode = null)
=> SendToEveryoneExcept(except?.NetworkID ?? 0, packet, transferMode);
public void SendToEveryoneExcept<T>(int except, T packet, TransferMode? transferMode = null)
=> SendTo(_multiplayerAPI.GetNetworkConnectedPeers().Where(id => id != except), packet, transferMode);
public void SendTo<T>(IEnumerable<Player> players, T packet, TransferMode? transferMode = null)
=> SendTo(players.Select(p => p.NetworkID), packet, transferMode);
public void SendTo<T>(IEnumerable<int> ids, T packet, TransferMode? transferMode = null)
{
var info = GetPacketInfoAndVerifyDirection<T>();
var mode = ToPeerTransferMode(info, transferMode);
byte[] bytes = null;
foreach (var id in ids) {
// Only serialize the packet if sending to at least 1 player.
bytes = bytes ?? PacketToBytes(info, packet);
_multiplayerAPI.SendBytes(bytes, id, mode);
}
}
private PacketInfo GetPacketInfoAndVerifyDirection<T>()
{
if (!_packetsByType.TryGetValue(typeof(T), out var info))
throw new InvalidOperationException($"No packet of type {typeof(T)} has been registered");
var direction = Network.IsServer ? PacketDirection.ServerToClient : PacketDirection.ClientToServer;
if ((direction & info.Direction) == 0) throw new InvalidOperationException(
$"Attempting to send packet {typeof(T)} in invalid direction {direction}");
return info;
}
private byte[] PacketToBytes(PacketInfo info, object packet)
{
using (var stream = new MemoryStream()) {
using (var writer = new BinaryWriter(stream)) {
writer.Write((ushort)info.ID);
info.DeSerializer.Serialize(writer, packet);
}
return stream.ToArray();
}
}
private NetworkedMultiplayerPeer.TransferModeEnum ToPeerTransferMode(PacketInfo info, TransferMode? transferMode)
=> (NetworkedMultiplayerPeer.TransferModeEnum)(transferMode ?? info.DefaultTransformMode);
internal void OnPacketReceived(int id, byte[] bytes)
{
if (!Network.IsServer && (id != 1))
throw new Exception($"Received packet from other player (ID {id})");
using (var stream = new MemoryStream(bytes)) {
using (var reader = new BinaryReader(stream)) {
var packetId = reader.ReadUInt16();
if (packetId >= _packetsById.Count) throw new Exception(
$"Received packet with invalid ID {packetId}");
var info = _packetsById[packetId];
var direction = Network.IsServer ? PacketDirection.ClientToServer : PacketDirection.ServerToClient;
if ((direction & info.Direction) == 0) throw new Exception(
$"Received packet {info.Type} on invalid side {(_multiplayerAPI.IsNetworkServer() ? "server" : "client")}");
var packet = info.DeSerializer.Deserialize(reader);
var playerID = Network.IsServer ? id : Network.LocalNetworkID;
info.OnPacketReceived(id, packet);
}
}
}
private INetworkDeSerializer GetDeSerializer(Type type, bool createIfMissing)
{
if (!_deSerializers.TryGetValue(type, out var value)) {
if (!createIfMissing) throw new InvalidOperationException(
$"No DeSerializer for type {type} found");
value = new ComplexNetworkDeSerializer(type);
_deSerializers.Add(type, value);
}
return value;
}
private class SimpleNetworkDeSerializer<T>
: INetworkDeSerializer
{
private readonly Action<BinaryWriter, T> _serialize;
private readonly Func<BinaryReader, T> _deserialize;
public SimpleNetworkDeSerializer(Action<BinaryWriter, T> serialize, Func<BinaryReader, T> deserialize)
{ _serialize = serialize; _deserialize = deserialize; }
public void Serialize(BinaryWriter writer, object value) => _serialize(writer, (T)value);
public object Deserialize(BinaryReader reader) => _deserialize(reader);
}
// private class ArrayNetworkDeSerializer
// : INetworkDeSerializerMulti
// {
// public bool Handles(Type type) => type.IsArray;
// public void Serialize(BinaryWriter writer, object value)
// {
// var array = (Array)value;
// writer.Write(array.Length);
// var deSerializer = Network.API.GetOrCreateDeserializer(array.GetType().GetElementType());
// foreach (var element in array) deSerializer.Serialize(writer, element);
// }
// public object Deserialize(BinaryReader reader)
// {
// TODO: This doesn't work. We need the type to initialize the array.
// We may want to generate a new INetworkDeSerializer for each array type..?
// }
// }
private class ComplexNetworkDeSerializer
: INetworkDeSerializer
{
private readonly Type _type;
private event Action<BinaryWriter, object> OnSerialize;
private event Action<BinaryReader, object> OnDeserialize;
public ComplexNetworkDeSerializer(Type type)
{
_type = type;
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) {
var deSerializer = Network.API.GetDeSerializer(field.FieldType, false);
OnSerialize += (writer, value) => deSerializer.Serialize(writer, field.GetValue(value));
OnDeserialize += (reader, instance) => field.SetValue(instance, deSerializer.Deserialize(reader));
}
if (OnSerialize == null) throw new InvalidOperationException(
$"Unable to create serializer for type {type}");
}
public void Serialize(BinaryWriter writer, object value)
=> OnSerialize(writer, value);
public object Deserialize(BinaryReader reader)
{
var instance = FormatterServices.GetUninitializedObject(_type);
OnDeserialize(reader, instance);
return instance;
}
}
}
public interface INetworkDeSerializer
{
void Serialize(BinaryWriter writer, object value);
object Deserialize(BinaryReader reader);
}

@ -7,33 +7,23 @@ public class Player : KinematicBody2D, IInitializer
public Label DisplayNameLabel { get; private set; }
public Sprite Sprite { get; private set; }
public Network Network { get; private set; }
public bool IsLocal => this is LocalPlayer;
private int _networkId = -1;
public int NetworkId {
public int NetworkID {
get => _networkId;
set {
_networkId = value;
Name = (_networkId > 0) ? value.ToString() : "LocalPlayer";
}
set { Name = ((_networkId = value) > 0) ? value.ToString() : "LocalPlayer"; }
}
public Color Color {
get => Sprite.Modulate;
set {
Sprite.Modulate = value;
this.RsetProperty(Sprite, "modulate", nameof(OnColorChanged), value);
}
set { Sprite.Modulate = value; }
}
public string DisplayName {
get => DisplayNameLabel.Text;
set {
DisplayNameLabel.Text = value;
this.RsetProperty(DisplayNameLabel, "text", nameof(OnDisplayNameChanged), value);
}
set { DisplayNameLabel.Text = value; }
}
@ -46,33 +36,92 @@ public class Player : KinematicBody2D, IInitializer
public override void _Ready()
{
Initialize();
Network = GetNode<Network>("/root/Game/Network");
RsetConfig("position", MultiplayerAPI.RPCMode.Puppetsync);
Sprite.RsetConfig("modulate", MultiplayerAPI.RPCMode.Puppetsync);
DisplayNameLabel.RsetConfig("text", MultiplayerAPI.RPCMode.Puppetsync);
}
public override void _Process(float delta)
{
this.RsetPropertyUnreliable(this, "position", nameof(OnPositionChanged), Position);
if (Network.IsMultiplayerReady) {
if (Network.IsServer) Network.API.SendToEveryoneExcept(this, new PositionChangedPacket(this));
else if (IsLocal) Network.API.SendToServer(new MovePacket(Position));
}
if (Network.IsAuthoratative && (Position.y > 9000)) {
if (this is LocalPlayer localPlayer) localPlayer.ResetPositionInternal(Vector2.Zero);
else RpcId(NetworkId, nameof(LocalPlayer.ResetPosition), Vector2.Zero);
if (this is LocalPlayer localPlayer) {
localPlayer.Position = Vector2.Zero;
localPlayer.Velocity = Vector2.Zero;
} else Network.API.SendTo(this, new PositionChangedPacket(this));
}
}
[Master]
private void OnPositionChanged(Vector2 value)
{ if (GetTree().GetRpcSenderId() == NetworkId) Position = value.Floor(); }
public static void RegisterPackets()
{
Network.API.RegisterS2CPacket<PositionChangedPacket>(packet => {
var player = Network.GetPlayerOrThrow(packet.ID);
player.Position = packet.Position;
if (player is LocalPlayer localPlayer)
localPlayer.Velocity = Vector2.Zero;
}, TransferMode.UnreliableOrdered);
Network.API.RegisterS2CPacket<ColorChangedPacket>(packet =>
Network.GetPlayerOrThrow(packet.ID).Color = packet.Color);
Network.API.RegisterS2CPacket<DisplayNameChangedPacket>(packet =>
Network.GetPlayerOrThrow(packet.ID).DisplayName = packet.DisplayName);
Network.API.RegisterC2SPacket<MovePacket>((player, packet) => {
// TODO: Somewhat verify the movement of players.
player.Position = packet.Position;
}, TransferMode.UnreliableOrdered);
Network.API.RegisterC2SPacket<ChangeAppearancePacket>((player, packet) =>
ChangeAppearance(player, packet.DisplayName, packet.Color, false));
}
public static void ChangeAppearance(Player player,
string displayName, Color color, bool sendPacket)
{
if (!sendPacket) {
player.DisplayName = displayName;
player.Color = color;
if (Network.IsServer) {
Network.API.SendToEveryone(new DisplayNameChangedPacket(player));
Network.API.SendToEveryone(new ColorChangedPacket(player));
}
} else Network.API.SendToServer(new ChangeAppearancePacket(displayName, color));
}
[Master]
private void OnColorChanged(Color value)
{ if (GetTree().GetRpcSenderId() == NetworkId) Color = value; }
private class PositionChangedPacket
{
public int ID { get; set; }
public Vector2 Position { get; set; }
public PositionChangedPacket(Player player)
{ ID = player.NetworkID; Position = player.Position; }
}
private class DisplayNameChangedPacket
{
public int ID { get; set; }
public string DisplayName { get; set; }
public DisplayNameChangedPacket(Player player)
{ ID = player.NetworkID; DisplayName = player.DisplayName; }
}
private class ColorChangedPacket
{
public int ID { get; set; }
public Color Color { get; set; }
public ColorChangedPacket(Player player)
{ ID = player.NetworkID; Color = player.Color; }
}
[Master]
private void OnDisplayNameChanged(string value)
{ if (GetTree().GetRpcSenderId() == NetworkId) DisplayName = value; }
private class MovePacket
{
public Vector2 Position { get; set; }
public MovePacket(Vector2 position) => Position = position;
}
private class ChangeAppearancePacket
{
public string DisplayName { get; set; }
public Color Color { get; set; }
public ChangeAppearancePacket(string displayName, Color color)
{ DisplayName = displayName; Color = color; }
}
}

Loading…
Cancel
Save