Przeglądaj źródła

添加玩家Keep Alive处理

SunnyCase 8 lat temu
rodzic
commit
4c439afcd1
31 zmienionych plików z 625 dodań i 17 usunięć
  1. 4 0
      src/MineCase.Protocol/MineCase.Protocol.csproj
  2. 43 0
      src/MineCase.Protocol/Protocol/Play/ClientSettings.cs
  3. 31 0
      src/MineCase.Protocol/Protocol/Play/ConfirmTransaction.cs
  4. 44 0
      src/MineCase.Protocol/Protocol/Play/JoinGame.cs
  5. 35 0
      src/MineCase.Protocol/Protocol/Play/KeepAlive.cs
  6. 27 0
      src/MineCase.Protocol/Protocol/Play/PluginMessage.cs
  7. 50 0
      src/MineCase.Protocol/Protocol/Play/UseEntity.cs
  8. 22 0
      src/MineCase.Protocol/Serialization/BinaryReaderExtensions.cs
  9. 11 0
      src/MineCase.Protocol/Serialization/BinaryWriterExtensions.cs
  10. 25 7
      src/MineCase.Server.Grains/Game/GameSession.cs
  11. 1 0
      src/MineCase.Server.Grains/Network/Login/LoginFlowGrain.cs
  12. 1 1
      src/MineCase.Server.Grains/Network/PacketRouterGrain.Handshaking.cs
  13. 6 1
      src/MineCase.Server.Grains/Network/PacketRouterGrain.Login.cs
  14. 68 0
      src/MineCase.Server.Grains/Network/PacketRouterGrain.Play.cs
  15. 9 0
      src/MineCase.Server.Grains/Network/PacketRouterGrain.cs
  16. 24 5
      src/MineCase.Server.Grains/Network/Play/ClientPlayPacketGenerator.cs
  17. 78 1
      src/MineCase.Server.Grains/Player/PlayerGrain.cs
  18. 25 0
      src/MineCase.Server.Grains/World/WorldGrain.cs
  19. 14 0
      src/MineCase.Server.Interfaces/Game/Difficulty.cs
  20. 13 0
      src/MineCase.Server.Interfaces/Game/Dimension.cs
  21. 10 0
      src/MineCase.Server.Interfaces/Game/EntityNotFoundException.cs
  22. 19 0
      src/MineCase.Server.Interfaces/Game/EntityUsage.cs
  23. 21 0
      src/MineCase.Server.Interfaces/Game/GameMode.cs
  24. 13 0
      src/MineCase.Server.Interfaces/Game/IEntity.cs
  25. 1 0
      src/MineCase.Server.Interfaces/Game/IGameSession.cs
  26. 15 0
      src/MineCase.Server.Interfaces/Game/LevelTYpes.cs
  27. 1 0
      src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj
  28. 2 0
      src/MineCase.Server.Interfaces/Network/IPacketRouter.cs
  29. 6 1
      src/MineCase.Server.Interfaces/Player/IPlayer.cs
  30. 5 1
      src/MineCase.Server.Interfaces/World/IWorld.cs
  31. 1 0
      src/MineCase.Server/MineCase.Server.csproj

+ 4 - 0
src/MineCase.Protocol/MineCase.Protocol.csproj

@@ -5,4 +5,8 @@
     <RootNamespace>MineCase</RootNamespace>
   </PropertyGroup>
 
+  <ItemGroup>
+    <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.3.0" />
+  </ItemGroup>
+
 </Project>

+ 43 - 0
src/MineCase.Protocol/Protocol/Play/ClientSettings.cs

@@ -0,0 +1,43 @@
+using MineCase.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MineCase.Protocol.Play
+{
+    [Packet(0x05)]
+    public sealed class ClientSettings
+    {
+        [SerializeAs(DataType.String)]
+        public string Locale;
+
+        [SerializeAs(DataType.Byte)]
+        public byte ViewDistance;
+
+        [SerializeAs(DataType.VarInt)]
+        public uint ChatMode;
+
+        [SerializeAs(DataType.Boolean)]
+        public bool ChatColors;
+
+        [SerializeAs(DataType.Byte)]
+        public byte DisplayedSkinParts;
+
+        [SerializeAs(DataType.VarInt)]
+        public uint MainHand;
+
+        public static ClientSettings Deserialize(BinaryReader br)
+        {
+            return new ClientSettings
+            {
+                Locale = br.ReadAsString(),
+                ViewDistance = br.ReadAsByte(),
+                ChatMode = br.ReadAsVarInt(out _),
+                ChatColors = br.ReadAsBoolean(),
+                DisplayedSkinParts = br.ReadAsByte(),
+                MainHand = br.ReadAsVarInt(out _)
+            };
+        }
+    }
+}

+ 31 - 0
src/MineCase.Protocol/Protocol/Play/ConfirmTransaction.cs

@@ -0,0 +1,31 @@
+using MineCase.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MineCase.Protocol.Play
+{
+    [Packet(0x06)]
+    public sealed class ServerboundConfirmTransaction
+    {
+        [SerializeAs(DataType.Byte)]
+        public byte WindowId;
+
+        [SerializeAs(DataType.Short)]
+        public short ActionNumber;
+
+        [SerializeAs(DataType.Boolean)]
+        public bool Accepted;
+
+        public static ServerboundConfirmTransaction Deserialize(BinaryReader br)
+        {
+            return new ServerboundConfirmTransaction
+            {
+                WindowId = br.ReadAsByte(),
+                ActionNumber = br.ReadAsShort(),
+                Accepted = br.ReadAsBoolean()
+            };
+        }
+    }
+}

+ 44 - 0
src/MineCase.Protocol/Protocol/Play/JoinGame.cs

@@ -0,0 +1,44 @@
+using MineCase.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MineCase.Protocol.Play
+{
+    [Packet(0x23)]
+    public class JoinGame : ISerializablePacket
+    {
+        [SerializeAs(DataType.Int)]
+        public int EID;
+
+        [SerializeAs(DataType.Byte)]
+        public byte GameMode;
+
+        [SerializeAs(DataType.Int)]
+        public int Dimension;
+
+        [SerializeAs(DataType.Byte)]
+        public byte Difficulty;
+
+        [SerializeAs(DataType.Byte)]
+        public byte MaxPlayers;
+
+        [SerializeAs(DataType.String)]
+        public string LevelType;
+
+        [SerializeAs(DataType.Boolean)]
+        public bool ReducedDebugInfo;
+
+        public void Serialize(BinaryWriter bw)
+        {
+            bw.WriteAsInt(EID);
+            bw.WriteAsByte(GameMode);
+            bw.WriteAsInt(Dimension);
+            bw.WriteAsByte(Difficulty);
+            bw.WriteAsByte(MaxPlayers);
+            bw.WriteAsString(LevelType);
+            bw.WriteAsBoolean(ReducedDebugInfo);
+        }
+    }
+}

+ 35 - 0
src/MineCase.Protocol/Protocol/Play/KeepAlive.cs

@@ -0,0 +1,35 @@
+using MineCase.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MineCase.Protocol.Play
+{
+    [Packet(0x0C)]
+    public sealed class ServerboundKeepAlive
+    {
+        [SerializeAs(DataType.VarInt)]
+        public uint KeepAliveId;
+
+        public static ServerboundKeepAlive Deserialize(BinaryReader br)
+        {
+            return new ServerboundKeepAlive
+            {
+                KeepAliveId = br.ReadAsVarInt(out _)
+            };
+        }
+    }
+
+    [Packet(0x1F)]
+    public sealed class ClientboundKeepAlive : ISerializablePacket
+    {
+        [SerializeAs(DataType.VarInt)]
+        public uint KeepAliveId;
+
+        public void Serialize(BinaryWriter bw)
+        {
+            bw.WriteAsVarInt(KeepAliveId, out _);
+        }
+    }
+}

+ 27 - 0
src/MineCase.Protocol/Protocol/Play/PluginMessage.cs

@@ -0,0 +1,27 @@
+using MineCase.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MineCase.Protocol.Play
+{
+    [Packet(0x0A)]
+    public sealed class ServerboundPluginMessage
+    {
+        [SerializeAs(DataType.String)]
+        public string Channel;
+
+        [SerializeAs(DataType.ByteArray)]
+        public byte[] Data;
+
+        public static ServerboundPluginMessage Deserialize(BinaryReader br)
+        {
+            return new ServerboundPluginMessage
+            {
+                Channel = br.ReadAsString(),
+                Data = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position))
+            };
+        }
+    }
+}

+ 50 - 0
src/MineCase.Protocol/Protocol/Play/UseEntity.cs

@@ -0,0 +1,50 @@
+using MineCase.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MineCase.Protocol.Play
+{
+    [Packet(0x0A)]
+    public sealed class UseEntity
+    {
+        [SerializeAs(DataType.VarInt)]
+        public int Target;
+
+        [SerializeAs(DataType.VarInt)]
+        public int Type;
+
+        [SerializeAs(DataType.Float)]
+        public float? TargetX;
+
+        [SerializeAs(DataType.Float)]
+        public float? TargetY;
+
+        [SerializeAs(DataType.Float)]
+        public float? TargetZ;
+
+        [SerializeAs(DataType.VarInt)]
+        public int? Hand;
+
+        public static UseEntity Deserialize(BinaryReader br)
+        {
+            var packet = new UseEntity
+            {
+                Target = (int)br.ReadAsVarInt(out _),
+                Type = (int)br.ReadAsVarInt(out _)
+            };
+            // Only if Type is interact at
+            if (packet.Type == 2)
+            {
+                packet.TargetX = br.ReadAsFloat();
+                packet.TargetY = br.ReadAsFloat();
+                packet.TargetZ = br.ReadAsFloat();
+            }
+            // Only if Type is interact or interact at
+            if (packet.Type == 0 || packet.Type == 2)
+                packet.Hand = (int)br.ReadAsVarInt(out _);
+            return packet;
+        }
+    }
+}

+ 22 - 0
src/MineCase.Protocol/Serialization/BinaryReaderExtensions.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Runtime.CompilerServices;
 using System.Text;
 using System.Threading.Tasks;
 
@@ -8,6 +9,9 @@ namespace MineCase.Serialization
 {
     internal static class BinaryReaderExtensions
     {
+        public static byte ReadAsByte(this BinaryReader br) =>
+            br.ReadByte();
+
         public static bool ReadAsBoolean(this BinaryReader br) =>
             br.ReadBoolean();
 
@@ -39,17 +43,35 @@ namespace MineCase.Serialization
             return Encoding.UTF8.GetString(bytes);
         }
 
+        public static short ReadAsShort(this BinaryReader br) =>
+            (short)br.ReadAsUnsignedShort();
+
         public static ushort ReadAsUnsignedShort(this BinaryReader br)
         {
             var value = br.ReadUInt16();
             return value.ToBigEndian();
         }
 
+        public static uint ReadAsUnsignedInt(this BinaryReader br)
+        {
+            var value = br.ReadUInt32();
+            return value.ToBigEndian();
+        }
+
+        public static int ReadAsInt(this BinaryReader br) =>
+            (int)br.ReadAsUnsignedInt();
+
         public static long ReadAsLong(this BinaryReader br)
         {
             var value = br.ReadUInt64();
             return (long)value.ToBigEndian();
         }
+
+        public static float ReadAsFloat(this BinaryReader br)
+        {
+            var value = br.ReadAsUnsignedInt();
+            return Unsafe.As<uint, float>(ref value);
+        }
     }
 
     internal static class StreamExtensions

+ 11 - 0
src/MineCase.Protocol/Serialization/BinaryWriterExtensions.cs

@@ -39,6 +39,11 @@ namespace MineCase.Serialization
             bw.Write(bytes);
         }
 
+        public static void WriteAsInt(this BinaryWriter bw, int value)
+        {
+            bw.Write(((uint)value).ToBigEndian());
+        }
+
         public static void WriteAsLong(this BinaryWriter bw, long value)
         {
             bw.Write(((ulong)value).ToBigEndian());
@@ -63,6 +68,12 @@ namespace MineCase.Serialization
             return (ushort)((value >> 8) | (((byte)value) << 8));
         }
 
+        public static uint ToBigEndian(this uint value)
+        {
+            return (value >> 24) | ((value & 0x00FF_0000) >> 8) |
+                ((value & 0x0000_FF00) << 8) | ((value & 0x0000_00FF) << 24);
+        }
+
         public static ulong ToBigEndian(this ulong value)
         {
             return (value >> 56) | ((value & 0x00FF_0000_0000_0000) >> 40) | ((value & 0x0000_FF00_0000_0000) >> 24) |

+ 25 - 7
src/MineCase.Server.Grains/Game/GameSession.cs

@@ -12,18 +12,36 @@ namespace MineCase.Server.Game
     class GameSession : Grain, IGameSession
     {
         private IWorld _world;
-        private readonly Dictionary<IPlayer, ClientPlayPacketGenerator> _players = new Dictionary<IPlayer, ClientPlayPacketGenerator>();
-        
+        private readonly Dictionary<IPlayer, PlayerContext> _players = new Dictionary<IPlayer, PlayerContext>();
+
+        public override async Task OnActivateAsync()
+        {
+            _world = await GrainFactory.GetGrain<IWorldAccessor>(0).GetWorld(this.GetPrimaryKeyString());
+        }
+
         public async Task JoinGame(IPlayer player)
         {
-            var generator = new ClientPlayPacketGenerator(await player.GetClientPacketSink());
-            _players.Add(player, generator);
-            await generator.JoinGame()
+            var sink = await player.GetClientPacketSink();
+            var generator = new ClientPlayPacketGenerator(sink);
+
+            _players[player] = new PlayerContext
+            {
+                Generator = generator
+            };
+
+            await generator.JoinGame(await _world.AttachEntity(player), new GameMode { ModeClass = GameMode.Class.Survival },
+                 Dimension.Overworld, Difficulty.Easy, 10, LevelTypes.Default, false);
         }
 
-        public override async Task OnActivateAsync()
+        public Task LeaveGame(IPlayer player)
         {
-            _world = await GrainFactory.GetGrain<IWorldAccessor>(0).GetWorld(this.GetPrimaryKeyString());
+            _players.Remove(player);
+            return Task.CompletedTask;
+        }
+
+        class PlayerContext
+        {
+            public ClientPlayPacketGenerator Generator { get; set; }
         }
     }
 }

+ 1 - 0
src/MineCase.Server.Grains/Network/Login/LoginFlowGrain.cs

@@ -24,6 +24,7 @@ namespace MineCase.Server.Network.Login
 
                 var player = GrainFactory.GetGrain<IPlayer>(uuid);
                 await player.SetClientPacketSink(GrainFactory.GetGrain<IClientboundPacketSink>(this.GetPrimaryKey()));
+                await GrainFactory.GetGrain<IPacketRouter>(this.GetPrimaryKey()).BindToPlayer(player);
 
                 var world = await player.GetWorld();
                 var game = GrainFactory.GetGrain<IGameSession>(world.GetPrimaryKeyString());

+ 1 - 1
src/MineCase.Server.Grains/Network/PacketRouterGrain.Handshaking.cs

@@ -22,7 +22,7 @@ namespace MineCase.Server.Network
                     case 0x00:
                         return Handshake.Deserialize(br);
                     default:
-                        throw new InvalidDataException($"Unrecognizable packet id: 0x{packet.PacketId: X}.");
+                        throw new InvalidDataException($"Unrecognizable packet id: 0x{packet.PacketId:X}.");
                 }
             }
         }

+ 6 - 1
src/MineCase.Server.Grains/Network/PacketRouterGrain.Login.cs

@@ -16,14 +16,19 @@ namespace MineCase.Server.Network
         {
             using (var br = new BinaryReader(new MemoryStream(packet.Data)))
             {
+                object innerPacket;
                 switch (packet.PacketId)
                 {
                     // Login Start
                     case 0x00:
-                        return LoginStart.Deserialize(br);
+                        innerPacket = LoginStart.Deserialize(br);
+                        break;
                     default:
                         throw new InvalidDataException($"Unrecognizable packet id: 0x{packet.PacketId:X}.");
                 }
+                if (br.BaseStream.Position != br.BaseStream.Length)
+                    throw new InvalidDataException($"Packet data is not fully consumed.");
+                return innerPacket;
             }
         }
 

+ 68 - 0
src/MineCase.Server.Grains/Network/PacketRouterGrain.Play.cs

@@ -0,0 +1,68 @@
+using MineCase.Protocol;
+using MineCase.Protocol.Play;
+using MineCase.Server.Game;
+using Orleans;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MineCase.Server.Network
+{
+    partial class PacketRouterGrain
+    {
+        private object DeserializePlayPacket(ref UncompressedPacket packet)
+        {
+            using (var br = new BinaryReader(new MemoryStream(packet.Data)))
+            {
+                object innerPacket;
+                switch (packet.PacketId)
+                {
+                    // Client Settings
+                    case 0x05:
+                        innerPacket = ClientSettings.Deserialize(br);
+                        break;
+                    // Plugin Message
+                    case 0x0A:
+                        innerPacket = ServerboundPluginMessage.Deserialize(br);
+                        break;
+                    // Keep Alive
+                    case 0x0C:
+                        innerPacket = ServerboundKeepAlive.Deserialize(br);
+                        break;
+                    default:
+                        throw new InvalidDataException($"Unrecognizable packet id: 0x{packet.PacketId:X}.");
+                }
+                if (br.BaseStream.Position != br.BaseStream.Length)
+                    throw new InvalidDataException($"Packet data is not fully consumed.");
+                return innerPacket;
+            }
+        }
+
+        private Task DispatchPacket(ClientSettings packet)
+        {
+            return Task.CompletedTask;
+        }
+
+        private Task DispatchPacket(ServerboundPluginMessage packet)
+        {
+            return Task.CompletedTask;
+        }
+
+        private Task DispatchPacket(ServerboundKeepAlive packet)
+        {
+            _player.KeepAlive(packet.KeepAliveId).Ignore();
+            return Task.CompletedTask;
+        }
+
+        private Task DispatchPacket(UseEntity packet)
+        {
+            var player = _player;
+            player.UseEntity((uint)packet.Target, (EntityUsage)packet.Type, packet.TargetX.HasValue ?
+                new Vector3?(new Vector3(packet.TargetX.Value, packet.TargetY.Value, packet.TargetZ.Value)) : null, (EntityInteractHand?)packet.Hand).Ignore();
+            return Task.CompletedTask;
+        }
+    }
+}

+ 9 - 0
src/MineCase.Server.Grains/Network/PacketRouterGrain.cs

@@ -7,12 +7,14 @@ using Orleans.Concurrency;
 using Orleans;
 using MineCase.Protocol.Handshaking;
 using System.IO;
+using MineCase.Server.Player;
 
 namespace MineCase.Server.Network
 {
     partial class PacketRouterGrain : Grain, IPacketRouter
     {
         private SessionState _state;
+        private IPlayer _player;
 
         public async Task SendPacket(UncompressedPacket packet)
         {
@@ -29,6 +31,7 @@ namespace MineCase.Server.Network
                     innerPacket = DeserializeLoginPacket(ref packet);
                     break;
                 case SessionState.Play:
+                    innerPacket = DeserializePlayPacket(ref packet);
                     break;
                 case SessionState.Closed:
                     break;
@@ -56,6 +59,12 @@ namespace MineCase.Server.Network
             return Task.CompletedTask;
         }
 
+        public Task BindToPlayer(IPlayer player)
+        {
+            _player = player;
+            return Task.CompletedTask;
+        }
+
         public enum SessionState
         {
             Handshaking,

+ 24 - 5
src/MineCase.Server.Grains/Network/Play/ClientPlayPacketGenerator.cs

@@ -1,4 +1,6 @@
-using System;
+using MineCase.Protocol.Play;
+using MineCase.Server.Game;
+using System;
 using System.Collections.Generic;
 using System.Text;
 using System.Threading.Tasks;
@@ -7,16 +9,33 @@ namespace MineCase.Server.Network.Play
 {
     class ClientPlayPacketGenerator
     {
-        private readonly IClientboundPacketSink _sink;
+        public IClientboundPacketSink Sink { get; }
 
         public ClientPlayPacketGenerator(IClientboundPacketSink sink)
         {
-            _sink = sink;
+            Sink = sink;
         }
 
-        public Task JoinGame(uint eid, byte gameMode, int dimension, byte difficulty, byte maxPlayers, string levelType, bool reducedDebugInfo)
+        public Task JoinGame(uint eid, GameMode gameMode, Dimension dimension, Difficulty difficulty, byte maxPlayers, string levelType, bool reducedDebugInfo)
         {
-            return _sink.SendPacket(new )
+            return Sink.SendPacket(new JoinGame
+            {
+                EID = (int)eid,
+                GameMode = (byte)(((uint)gameMode.ModeClass) | (gameMode.IsHardcore ? 0b100u : 0u)),
+                Dimension = (int)dimension,
+                Difficulty = (byte)difficulty,
+                LevelType = levelType,
+                MaxPlayers = maxPlayers,
+                ReducedDebugInfo = reducedDebugInfo
+            });
+        }
+
+        public Task KeepAlive(uint id)
+        {
+            return Sink.SendPacket(new ClientboundKeepAlive
+            {
+                KeepAliveId = id
+            });
         }
     }
 }

+ 78 - 1
src/MineCase.Server.Grains/Player/PlayerGrain.cs

@@ -5,16 +5,39 @@ using System.Text;
 using MineCase.Server.Game;
 using MineCase.Server.World;
 using System.Threading.Tasks;
+using MineCase.Server.Network;
+using System.Numerics;
+using MineCase.Server.Network.Play;
 
 namespace MineCase.Server.Player
 {
     class PlayerGrain : Grain, IPlayer
     {
         private string _worldId;
+        private uint _eid;
+        private IClientboundPacketSink _sink;
+        private ClientPlayPacketGenerator _generator;
+        private IDisposable _sendKeepAliveTimer;
+        public HashSet<uint> _keepAliveWaiters;
+
+        private readonly Random _keepAliveIdRand = new Random();
+        private const int ClientKeepInterval = 6;
+        private bool _isOnline = false;
+
+        public Task<IClientboundPacketSink> GetClientPacketSink()
+        {
+            return Task.FromResult(_sink);
+        }
+
+        public async Task<IGameSession> GetGameSession()
+        {
+            var world = await GetWorld();
+            return GrainFactory.GetGrain<IGameSession>(world.GetPrimaryKeyString());
+        }
 
         public async Task<IWorld> GetWorld()
         {
-            if(string.IsNullOrEmpty(_worldId))
+            if (string.IsNullOrEmpty(_worldId))
             {
                 var world = await GrainFactory.GetGrain<IWorldAccessor>(0).GetDefaultWorld();
                 _worldId = world.GetPrimaryKeyString();
@@ -22,5 +45,59 @@ namespace MineCase.Server.Player
             }
             return await GrainFactory.GetGrain<IWorldAccessor>(0).GetWorld(_worldId);
         }
+
+        public Task SetClientPacketSink(IClientboundPacketSink sink)
+        {
+            _sink = sink;
+            _generator = new ClientPlayPacketGenerator(sink);
+
+            _isOnline = true;
+            _keepAliveWaiters = new HashSet<uint>();
+            _sendKeepAliveTimer = RegisterTimer(OnSendKeepAliveRequests, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
+            return Task.CompletedTask;
+        }
+
+        public Task SetEntityId(uint eid)
+        {
+            _eid = eid;
+            return Task.CompletedTask;
+        }
+
+        public Task UseEntity(uint targetEid, EntityUsage type, Vector3? targetPosition, EntityInteractHand? hand)
+        {
+            return Task.CompletedTask;
+        }
+
+        private async Task OnSendKeepAliveRequests(object state)
+        {
+            if (_isOnline && _keepAliveWaiters.Count >= ClientKeepInterval)
+            {
+                _isOnline = false;
+                _sendKeepAliveTimer.Dispose();
+                _sendKeepAliveTimer = null;
+
+                KickPlayer().Ignore();
+            }
+            else
+            {
+                var id = (uint)_keepAliveIdRand.Next();
+                _keepAliveWaiters.Add(id);
+                await _generator.KeepAlive(id);
+            }
+        }
+
+        public Task KeepAlive(uint keepAliveId)
+        {
+            _keepAliveWaiters.Remove(keepAliveId);
+            return Task.CompletedTask;
+        }
+
+        private async Task KickPlayer()
+        {
+            var game = await GetGameSession();
+            await game.LeaveGame(this);
+            await _sink.Close();
+            DeactivateOnIdle();
+        }
     }
 }

+ 25 - 0
src/MineCase.Server.Grains/World/WorldGrain.cs

@@ -2,10 +2,35 @@
 using System;
 using System.Collections.Generic;
 using System.Text;
+using MineCase.Server.Game;
+using System.Threading.Tasks;
 
 namespace MineCase.Server.World
 {
     class WorldGrain : Grain, IWorld
     {
+        private Dictionary<uint, IEntity> _entities;
+        private uint _nextAvailEId;
+
+        public override Task OnActivateAsync()
+        {
+            _nextAvailEId = 0;
+            _entities = new Dictionary<uint, IEntity>();
+            return base.OnActivateAsync();
+        }
+
+        public Task<uint> AttachEntity(IEntity entity)
+        {
+            var id = _nextAvailEId++;
+            _entities.Add(id, entity);
+            return Task.FromResult(id);
+        }
+
+        public Task<IEntity> FindEntity(uint eid)
+        {
+            if (_entities.TryGetValue(eid, out var entity))
+                return Task.FromResult(entity);
+            return Task.FromException<IEntity>(new EntityNotFoundException());
+        }
     }
 }

+ 14 - 0
src/MineCase.Server.Interfaces/Game/Difficulty.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game
+{
+    public enum Difficulty : byte
+    {
+        Peaceful = 0,
+        Easy = 1,
+        Normal = 2,
+        Hard = 3
+    }
+}

+ 13 - 0
src/MineCase.Server.Interfaces/Game/Dimension.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game
+{
+    public enum Dimension : int
+    {
+        Nether = -1,
+        Overworld = 0,
+        End = 1
+    }
+}

+ 10 - 0
src/MineCase.Server.Interfaces/Game/EntityNotFoundException.cs

@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game
+{
+    public class EntityNotFoundException : Exception
+    {
+    }
+}

+ 19 - 0
src/MineCase.Server.Interfaces/Game/EntityUsage.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game
+{
+    public enum EntityUsage : int
+    {
+        Interact = 0,
+        Attack = 1,
+        InteractAt = 2
+    }
+
+    public enum EntityInteractHand : int
+    {
+        MainHand = 0,
+        OffHand = 1
+    }
+}

+ 21 - 0
src/MineCase.Server.Interfaces/Game/GameMode.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game
+{
+    public struct GameMode
+    {
+        public enum Class : byte
+        {
+            Survival = 0,
+            Creative = 1,
+            Adventure = 2,
+            Spectator = 3
+        }
+
+        public Class ModeClass { get; set; }
+
+        public bool IsHardcore { get; set; }
+    }
+}

+ 13 - 0
src/MineCase.Server.Interfaces/Game/IEntity.cs

@@ -0,0 +1,13 @@
+using Orleans;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MineCase.Server.Game
+{
+    public interface IEntity : IGrain
+    {
+        Task SetEntityId(uint eid);
+    }
+}

+ 1 - 0
src/MineCase.Server.Interfaces/Game/IGameSession.cs

@@ -10,5 +10,6 @@ namespace MineCase.Server.Game
     public interface IGameSession : IGrainWithStringKey
     {
         Task JoinGame(IPlayer player);
+        Task LeaveGame(IPlayer player);
     }
 }

+ 15 - 0
src/MineCase.Server.Interfaces/Game/LevelTYpes.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game
+{
+    public static class LevelTypes
+    {
+        public const string Default = "default";
+        public const string Flat = "flat";
+        public const string LargeBiomes = "largeBiomes";
+        public const string Amplified = "amplified";
+        public const string Default_1_1 = "default_1_1";
+    }
+}

+ 1 - 0
src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj

@@ -8,6 +8,7 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.Orleans.Core" Version="2.0.0-preview2-20170724" />
+    <PackageReference Include="System.Numerics.Vectors" Version="4.3.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 2 - 0
src/MineCase.Server.Interfaces/Network/IPacketRouter.cs

@@ -1,4 +1,5 @@
 using MineCase.Protocol;
+using MineCase.Server.Player;
 using Orleans;
 using System;
 using System.Collections.Generic;
@@ -9,6 +10,7 @@ namespace MineCase.Server.Network
 {
     public interface IPacketRouter : IGrainWithGuidKey
     {
+        Task BindToPlayer(IPlayer player);
         Task SendPacket(UncompressedPacket packet);
         Task Close();
         Task Play();

+ 6 - 1
src/MineCase.Server.Interfaces/Player/IPlayer.cs

@@ -4,15 +4,20 @@ using MineCase.Server.World;
 using Orleans;
 using System;
 using System.Collections.Generic;
+using System.Numerics;
 using System.Text;
 using System.Threading.Tasks;
 
 namespace MineCase.Server.Player
 {
-    public interface IPlayer : IGrainWithGuidKey
+    public interface IPlayer : IGrainWithGuidKey, IEntity
     {
         Task<IWorld> GetWorld();
+        Task<IGameSession> GetGameSession();
         Task SetClientPacketSink(IClientboundPacketSink sink);
         Task<IClientboundPacketSink> GetClientPacketSink();
+
+        Task KeepAlive(uint keepAliveId);
+        Task UseEntity(uint targetEid, EntityUsage type, Vector3? targetPosition, EntityInteractHand? hand);
     }
 }

+ 5 - 1
src/MineCase.Server.Interfaces/World/IWorld.cs

@@ -1,11 +1,15 @@
-using Orleans;
+using MineCase.Server.Game;
+using Orleans;
 using System;
 using System.Collections.Generic;
 using System.Text;
+using System.Threading.Tasks;
 
 namespace MineCase.Server.World
 {
     public interface IWorld : IGrainWithStringKey
     {
+        Task<uint> AttachEntity(IEntity entity);
+        Task<IEntity> FindEntity(uint eid);
     }
 }

+ 1 - 0
src/MineCase.Server/MineCase.Server.csproj

@@ -15,6 +15,7 @@
     <PackageReference Include="Microsoft.Extensions.Options" Version="1.1.2" />
     <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" />
     <PackageReference Include="Microsoft.Orleans.Server" Version="2.0.0-preview2-20170724" />
+    <PackageReference Include="System.Numerics.Vectors" Version="4.3.0" />
   </ItemGroup>
 
   <ItemGroup>