Răsfoiți Sursa

重构 分离出Entity

SunnyCase 8 ani în urmă
părinte
comite
1daafa29e6
32 a modificat fișierele cu 532 adăugiri și 68 ștergeri
  1. 28 0
      src/MineCase.Protocol/Protocol/Play/SetExperience.cs
  2. 24 0
      src/MineCase.Protocol/Protocol/Play/TimeUpdate.cs
  3. 28 0
      src/MineCase.Protocol/Protocol/Play/UpdateHealth.cs
  4. 51 0
      src/MineCase.Protocol/Protocol/Play/WindowItems.cs
  5. 20 1
      src/MineCase.Protocol/Serialization/BinaryWriterExtensions.cs
  6. 2 1
      src/MineCase.Protocol/Serialization/SerializeAsAttribute.cs
  7. 3 3
      src/MineCase.Server.Grains/Game/Entities/EntitiesModule.cs
  8. 23 0
      src/MineCase.Server.Grains/Game/Entities/EntityGrain.cs
  9. 55 0
      src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs
  10. 9 7
      src/MineCase.Server.Grains/Game/GameSession.cs
  11. 11 0
      src/MineCase.Server.Grains/Game/Windows/InventoryWindowGrain.cs
  12. 23 0
      src/MineCase.Server.Grains/Game/Windows/WindowGrain.cs
  13. 15 0
      src/MineCase.Server.Grains/Game/Windows/WindowsModule.cs
  14. 4 4
      src/MineCase.Server.Grains/Network/Login/LoginFlowGrain.cs
  15. 4 4
      src/MineCase.Server.Grains/Network/PacketRouterGrain.Play.cs
  16. 4 4
      src/MineCase.Server.Grains/Network/PacketRouterGrain.cs
  17. 48 0
      src/MineCase.Server.Grains/Network/Play/ClientPlayPacketGenerator.cs
  18. 2 2
      src/MineCase.Server.Grains/User/NonAuthenticatedUserGrain.cs
  19. 50 24
      src/MineCase.Server.Grains/User/UserGrain.cs
  20. 15 0
      src/MineCase.Server.Grains/User/UserModule.cs
  21. 17 4
      src/MineCase.Server.Grains/World/WorldGrain.cs
  22. 19 0
      src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs
  23. 24 3
      src/MineCase.Server.Interfaces/Game/IEntity.cs
  24. 3 3
      src/MineCase.Server.Interfaces/Game/IGameSession.cs
  25. 11 0
      src/MineCase.Server.Interfaces/Game/Slot.cs
  26. 11 0
      src/MineCase.Server.Interfaces/Game/Windows/IInventoryWindow.cs
  27. 14 0
      src/MineCase.Server.Interfaces/Game/Windows/IWindow.cs
  28. 1 0
      src/MineCase.Server.Interfaces/MineCase.Server.Interfaces.csproj
  29. 2 2
      src/MineCase.Server.Interfaces/Network/IPacketRouter.cs
  30. 2 2
      src/MineCase.Server.Interfaces/User/INonAuthenticatedPlayer.cs
  31. 6 3
      src/MineCase.Server.Interfaces/User/IUser.cs
  32. 3 1
      src/MineCase.Server.Interfaces/World/IWorld.cs

+ 28 - 0
src/MineCase.Protocol/Protocol/Play/SetExperience.cs

@@ -0,0 +1,28 @@
+using MineCase.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MineCase.Protocol.Play
+{
+    [Packet(0x3F)]
+    public sealed class SetExperience : ISerializablePacket
+    {
+        [SerializeAs(DataType.Float)]
+        public float ExperienceBar;
+
+        [SerializeAs(DataType.VarInt)]
+        public uint Level;
+
+        [SerializeAs(DataType.VarInt)]
+        public uint TotalExperience;
+
+        public void Serialize(BinaryWriter bw)
+        {
+            bw.WriteAsFloat(ExperienceBar);
+            bw.WriteAsVarInt(Level, out _);
+            bw.WriteAsVarInt(TotalExperience, out _);
+        }
+    }
+}

+ 24 - 0
src/MineCase.Protocol/Protocol/Play/TimeUpdate.cs

@@ -0,0 +1,24 @@
+using MineCase.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MineCase.Protocol.Play
+{
+    [Packet(0x46)]
+    public sealed class TimeUpdate : ISerializablePacket
+    {
+        [SerializeAs(DataType.Long)]
+        public long WorldAge;
+
+        [SerializeAs(DataType.Long)]
+        public long TimeOfDay;
+
+        public void Serialize(BinaryWriter bw)
+        {
+            bw.WriteAsLong(WorldAge);
+            bw.WriteAsLong(TimeOfDay);
+        }
+    }
+}

+ 28 - 0
src/MineCase.Protocol/Protocol/Play/UpdateHealth.cs

@@ -0,0 +1,28 @@
+using MineCase.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MineCase.Protocol.Play
+{
+    [Packet(0x40)]
+    public sealed class UpdateHealth : ISerializablePacket
+    {
+        [SerializeAs(DataType.Float)]
+        public float Health;
+
+        [SerializeAs(DataType.VarInt)]
+        public uint Food;
+
+        [SerializeAs(DataType.Float)]
+        public float FoodSaturation;
+
+        public void Serialize(BinaryWriter bw)
+        {
+            bw.WriteAsFloat(Health);
+            bw.WriteAsVarInt(Food, out _);
+            bw.WriteAsFloat(FoodSaturation);
+        }
+    }
+}

+ 51 - 0
src/MineCase.Protocol/Protocol/Play/WindowItems.cs

@@ -0,0 +1,51 @@
+using MineCase.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+
+namespace MineCase.Protocol.Play
+{
+    public sealed class Slot : ISerializablePacket
+    {
+        [SerializeAs(DataType.Short)]
+        public short BlockId;
+
+        [SerializeAs(DataType.Byte)]
+        public byte? ItemCount;
+
+        [SerializeAs(DataType.Short)]
+        public short? ItemDamage;
+
+        public void Serialize(BinaryWriter bw)
+        {
+            bw.WriteAsShort(BlockId);
+            if(BlockId != -1)
+            {
+                bw.WriteAsByte(ItemCount.Value);
+                bw.WriteAsShort(ItemDamage.Value);
+                bw.WriteAsByte(0);
+            }
+        }
+    }
+
+    [Packet(0x14)]
+    public sealed class WindowItems : ISerializablePacket
+    {
+        [SerializeAs(DataType.Byte)]
+        public byte WindowId;
+
+        [SerializeAs(DataType.Short)]
+        public short Count;
+
+        [SerializeAs(DataType.Array)]
+        public Slot[] Slots;
+
+        public void Serialize(BinaryWriter bw)
+        {
+            bw.WriteAsByte(WindowId);
+            bw.WriteAsShort(Count);
+            bw.WriteAsArray(Slots);
+        }
+    }
+}

+ 20 - 1
src/MineCase.Protocol/Serialization/BinaryWriterExtensions.cs

@@ -1,6 +1,8 @@
-using System;
+using MineCase.Protocol;
+using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Runtime.CompilerServices;
 using System.Text;
 
 namespace MineCase.Serialization
@@ -39,6 +41,11 @@ namespace MineCase.Serialization
             bw.Write(bytes);
         }
 
+        public static void WriteAsShort(this BinaryWriter bw, short value)
+        {
+            bw.Write(((ushort)value).ToBigEndian());
+        }
+
         public static void WriteAsInt(this BinaryWriter bw, int value)
         {
             bw.Write(((uint)value).ToBigEndian());
@@ -48,6 +55,18 @@ namespace MineCase.Serialization
         {
             bw.Write(((ulong)value).ToBigEndian());
         }
+
+        public static void WriteAsFloat(this BinaryWriter bw, float value)
+        {
+            var uintValue = Unsafe.As<float, uint>(ref value);
+            bw.Write(uintValue.ToBigEndian());
+        }
+
+        public static void WriteAsArray<T>(this BinaryWriter bw, IReadOnlyList<T> array) where T : ISerializablePacket
+        {
+            foreach (var item in array)
+                item.Serialize(bw);
+        }
     }
 
     internal static class DataTypeSizeExtensions

+ 2 - 1
src/MineCase.Protocol/Serialization/SerializeAsAttribute.cs

@@ -25,7 +25,8 @@ namespace MineCase.Serialization
         Position,
         Angle,
         UUID,
-        ByteArray
+        ByteArray,
+        Array
     }
 
     [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]

+ 3 - 3
src/MineCase.Server.Grains/Player/PlayerModule.cs → src/MineCase.Server.Grains/Game/Entities/EntitiesModule.cs

@@ -3,13 +3,13 @@ using System;
 using System.Collections.Generic;
 using System.Text;
 
-namespace MineCase.Server.Player
+namespace MineCase.Server.Game.Entities
 {
-    class PlayerModule : Module
+    class EntitiesModule : Module
     {
         protected override void Load(ContainerBuilder builder)
         {
-            builder.RegisterType<NonAuthenticatedPlayerGrain>();
+            builder.RegisterType<PlayerGrain>();
         }
     }
 }

+ 23 - 0
src/MineCase.Server.Grains/Game/Entities/EntityGrain.cs

@@ -0,0 +1,23 @@
+using MineCase.Server.World;
+using Orleans;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MineCase.Server.Game.Entities
+{
+    internal abstract class EntityGrain : Grain, IEntity
+    {
+        protected IWorld World { get; private set; }
+        protected uint EntityId { get; private set; }
+
+        public override Task OnActivateAsync()
+        {
+            var keys = this.GetWorldAndEntityId();
+            World = GrainFactory.GetGrain<IWorld>(keys.worldKey);
+            EntityId = keys.entityId;
+            return Task.CompletedTask;
+        }
+    }
+}

+ 55 - 0
src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs

@@ -0,0 +1,55 @@
+using MineCase.Server.Game.Windows;
+using MineCase.Server.Network.Play;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using MineCase.Server.User;
+using MineCase.Server.Network;
+
+namespace MineCase.Server.Game.Entities
+{
+    class PlayerGrain : EntityGrain, IPlayer
+    {
+        private ClientPlayPacketGenerator _generator;
+
+        private IInventoryWindow _inventory;
+
+        public Task<IInventoryWindow> GetInventory() => Task.FromResult(_inventory);
+
+        private uint _health;
+        private const uint MaxHealth = 20;
+        public const uint MaxFood = 20;
+        private uint _currentExp, _levelMaxExp, _totalExp;
+        private uint _level;
+
+        public override Task OnActivateAsync()
+        {
+            _inventory = GrainFactory.GetGrain<IInventoryWindow>(Guid.NewGuid());
+            _levelMaxExp = 7;
+            return base.OnActivateAsync();
+        }
+
+        public async Task SendWholeInventory()
+        {
+            var slots = await _inventory.GetSlots();
+            await _generator.WindowItems(0, slots);
+        }
+
+        public Task SetClientSink(IClientboundPacketSink sink)
+        {
+            _generator = new ClientPlayPacketGenerator(sink);
+            return Task.CompletedTask;
+        }
+
+        public async Task SendHealth()
+        {
+            await _generator.UpdateHealth(_health, MaxHealth, 20, MaxFood, 5.0f);
+        }
+
+        public async Task SendExperience()
+        {
+            await _generator.SetExperience((float)_currentExp / _levelMaxExp, _level, _totalExp);
+        }
+    }
+}

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

@@ -4,7 +4,7 @@ using System;
 using System.Collections.Generic;
 using System.Text;
 using System.Threading.Tasks;
-using MineCase.Server.Player;
+using MineCase.Server.User;
 using MineCase.Server.Network.Play;
 
 namespace MineCase.Server.Game
@@ -12,28 +12,30 @@ namespace MineCase.Server.Game
     class GameSession : Grain, IGameSession
     {
         private IWorld _world;
-        private readonly Dictionary<IPlayer, PlayerContext> _players = new Dictionary<IPlayer, PlayerContext>();
+        private readonly Dictionary<IUser, PlayerContext> _players = new Dictionary<IUser, PlayerContext>();
 
         public override async Task OnActivateAsync()
         {
             _world = await GrainFactory.GetGrain<IWorldAccessor>(0).GetWorld(this.GetPrimaryKeyString());
         }
 
-        public async Task JoinGame(IPlayer player)
+        public async Task JoinGame(IUser user)
         {
-            var sink = await player.GetClientPacketSink();
+            var sink = await user.GetClientPacketSink();
             var generator = new ClientPlayPacketGenerator(sink);
 
-            _players[player] = new PlayerContext
+            _players[user] = new PlayerContext
             {
                 Generator = generator
             };
 
-            await generator.JoinGame(await _world.AttachEntity(player), new GameMode { ModeClass = GameMode.Class.Survival },
+            await user.JoinGame();
+            await generator.JoinGame((await user.GetPlayer()).GetEntityId(), new GameMode { ModeClass = GameMode.Class.Survival },
                  Dimension.Overworld, Difficulty.Easy, 10, LevelTypes.Default, false);
+            await user.NotifyLoggedIn();
         }
 
-        public Task LeaveGame(IPlayer player)
+        public Task LeaveGame(IUser player)
         {
             _players.Remove(player);
             return Task.CompletedTask;

+ 11 - 0
src/MineCase.Server.Grains/Game/Windows/InventoryWindowGrain.cs

@@ -0,0 +1,11 @@
+using Orleans;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game.Windows
+{
+    class InventoryWindowGrain : WindowGrain, IInventoryWindow
+    {
+    }
+}

+ 23 - 0
src/MineCase.Server.Grains/Game/Windows/WindowGrain.cs

@@ -0,0 +1,23 @@
+using Orleans;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MineCase.Server.Game.Windows
+{
+    internal abstract class WindowGrain : Grain, IWindow
+    {
+        private List<Slot> _slots = new List<Slot>();
+
+        public Task<uint> GetSlotCount()
+        {
+            return Task.FromResult((uint)_slots.Count);
+        }
+
+        public Task<IReadOnlyList<Slot>> GetSlots()
+        {
+            return Task.FromResult<IReadOnlyList<Slot>>(_slots);
+        }
+    }
+}

+ 15 - 0
src/MineCase.Server.Grains/Game/Windows/WindowsModule.cs

@@ -0,0 +1,15 @@
+using Autofac;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game.Windows
+{
+    class WindowsModule : Module
+    {
+        protected override void Load(ContainerBuilder builder)
+        {
+            builder.RegisterType<InventoryWindowGrain>();
+        }
+    }
+}

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

@@ -4,7 +4,7 @@ using System.Collections.Generic;
 using System.Text;
 using MineCase.Protocol.Login;
 using System.Threading.Tasks;
-using MineCase.Server.Player;
+using MineCase.Server.User;
 using MineCase.Server.Game;
 
 namespace MineCase.Server.Network.Login
@@ -19,12 +19,12 @@ namespace MineCase.Server.Network.Login
                 throw new NotImplementedException();
             else
             {
-                var uuid = await GrainFactory.GetGrain<INonAuthenticatedPlayer>(packet.Name).GetUUID();
+                var uuid = await GrainFactory.GetGrain<INonAuthenticatedUser>(packet.Name).GetUUID();
                 await SendLoginSuccess(packet.Name, uuid);
 
-                var player = GrainFactory.GetGrain<IPlayer>(uuid);
+                var player = GrainFactory.GetGrain<IUser>(uuid);
                 await player.SetClientPacketSink(GrainFactory.GetGrain<IClientboundPacketSink>(this.GetPrimaryKey()));
-                await GrainFactory.GetGrain<IPacketRouter>(this.GetPrimaryKey()).BindToPlayer(player);
+                await GrainFactory.GetGrain<IPacketRouter>(this.GetPrimaryKey()).BindToUser(player);
 
                 var world = await player.GetWorld();
                 var game = GrainFactory.GetGrain<IGameSession>(world.GetPrimaryKeyString());

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

@@ -53,15 +53,15 @@ namespace MineCase.Server.Network
 
         private Task DispatchPacket(ServerboundKeepAlive packet)
         {
-            _player.KeepAlive(packet.KeepAliveId).Ignore();
+            _user.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();
+            var player = _user;
+            //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;
         }
     }

+ 4 - 4
src/MineCase.Server.Grains/Network/PacketRouterGrain.cs

@@ -7,14 +7,14 @@ using Orleans.Concurrency;
 using Orleans;
 using MineCase.Protocol.Handshaking;
 using System.IO;
-using MineCase.Server.Player;
+using MineCase.Server.User;
 
 namespace MineCase.Server.Network
 {
     partial class PacketRouterGrain : Grain, IPacketRouter
     {
         private SessionState _state;
-        private IPlayer _player;
+        private IUser _user;
 
         public async Task SendPacket(UncompressedPacket packet)
         {
@@ -59,9 +59,9 @@ namespace MineCase.Server.Network
             return Task.CompletedTask;
         }
 
-        public Task BindToPlayer(IPlayer player)
+        public Task BindToUser(IUser user)
         {
-            _player = player;
+            _user = user;
             return Task.CompletedTask;
         }
 

+ 48 - 0
src/MineCase.Server.Grains/Network/Play/ClientPlayPacketGenerator.cs

@@ -2,6 +2,7 @@
 using MineCase.Server.Game;
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
@@ -37,5 +38,52 @@ namespace MineCase.Server.Network.Play
                 KeepAliveId = id
             });
         }
+
+        public Task UpdateHealth(uint health, uint maxHealth, uint food, uint maxFood, float foodSaturation)
+        {
+            return Sink.SendPacket(new UpdateHealth
+            {
+                Health = (float)health / maxHealth * 20,
+                Food = (uint)((float)food / maxFood * 20),
+                FoodSaturation = foodSaturation
+            });
+        }
+
+        public Task SetExperience(float experienceBar, uint level, uint totalExp)
+        {
+            return Sink.SendPacket(new SetExperience
+            {
+                ExperienceBar = experienceBar,
+                Level = level,
+                TotalExperience = totalExp
+            });
+        }
+
+        public Task TimeUpdate(long age, long timeOfDay)
+        {
+            return Sink.SendPacket(new TimeUpdate
+            {
+                WorldAge = age,
+                TimeOfDay = timeOfDay
+            });
+        }
+
+        public Task WindowItems(byte windowId, IReadOnlyList<Game.Slot> slots)
+        {
+            return Sink.SendPacket(new WindowItems
+            {
+                WindowId = windowId,
+                Count = (short)slots.Count,
+                Slots = slots.Select(o => TransformSlotData(o)).ToArray()
+            });
+        }
+
+        private Protocol.Play.Slot TransformSlotData(Game.Slot o)
+        {
+            return new Protocol.Play.Slot
+            {
+                BlockId = -1
+            };
+        }
     }
 }

+ 2 - 2
src/MineCase.Server.Grains/Player/NonAuthenticatedPlayerGrain.cs → src/MineCase.Server.Grains/User/NonAuthenticatedUserGrain.cs

@@ -4,9 +4,9 @@ using System.Collections.Generic;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace MineCase.Server.Player
+namespace MineCase.Server.User
 {
-    class NonAuthenticatedPlayerGrain : Grain, INonAuthenticatedPlayer
+    class NonAuthenticatedUserGrain : Grain, INonAuthenticatedUser
     {
         private Guid _uuid;
 

+ 50 - 24
src/MineCase.Server.Grains/Player/PlayerGrain.cs → src/MineCase.Server.Grains/User/UserGrain.cs

@@ -8,22 +8,36 @@ using System.Threading.Tasks;
 using MineCase.Server.Network;
 using System.Numerics;
 using MineCase.Server.Network.Play;
+using MineCase.Server.Game.Entities;
 
-namespace MineCase.Server.Player
+namespace MineCase.Server.User
 {
-    class PlayerGrain : Grain, IPlayer
+    class UserGrain : Grain, IUser
     {
         private string _worldId;
-        private uint _eid;
+        private IWorld _world;
         private IClientboundPacketSink _sink;
         private ClientPlayPacketGenerator _generator;
-        private IDisposable _sendKeepAliveTimer;
+        private IDisposable _sendKeepAliveTimer, _worldTimeSyncTimer;
         public HashSet<uint> _keepAliveWaiters;
 
         private readonly Random _keepAliveIdRand = new Random();
         private const int ClientKeepInterval = 6;
         private bool _isOnline = false;
 
+        private IPlayer _player;
+
+        public override async Task OnActivateAsync()
+        {
+            if (string.IsNullOrEmpty(_worldId))
+            {
+                var world = await GrainFactory.GetGrain<IWorldAccessor>(0).GetDefaultWorld();
+                _worldId = world.GetPrimaryKeyString();
+                _world = world;
+            }
+            _world = await GrainFactory.GetGrain<IWorldAccessor>(0).GetWorld(_worldId);
+        }
+
         public Task<IClientboundPacketSink> GetClientPacketSink()
         {
             return Task.FromResult(_sink);
@@ -35,32 +49,27 @@ namespace MineCase.Server.Player
             return GrainFactory.GetGrain<IGameSession>(world.GetPrimaryKeyString());
         }
 
-        public async Task<IWorld> GetWorld()
-        {
-            if (string.IsNullOrEmpty(_worldId))
-            {
-                var world = await GrainFactory.GetGrain<IWorldAccessor>(0).GetDefaultWorld();
-                _worldId = world.GetPrimaryKeyString();
-                return world;
-            }
-            return await GrainFactory.GetGrain<IWorldAccessor>(0).GetWorld(_worldId);
-        }
+        public Task<IWorld> GetWorld() => Task.FromResult(_world);
 
         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)
+        public async Task JoinGame()
         {
-            _eid = eid;
-            return Task.CompletedTask;
+            var playerEid = await _world.NewEntityId();
+            _player = GrainFactory.GetGrain<IPlayer>(_world.MakeEntityKey(playerEid));
+            await _player.SetClientSink(_sink);
+            await _world.AttachEntity(_player);
+        }
+
+        private async Task SendTimeUpdate()
+        {
+            var time = await (await GetWorld()).GetTime();
+            await _generator.TimeUpdate(time.age, time.timeOfDay);
         }
 
         public Task UseEntity(uint targetEid, EntityUsage type, Vector3? targetPosition, EntityInteractHand? hand)
@@ -73,9 +82,6 @@ namespace MineCase.Server.Player
             if (_isOnline && _keepAliveWaiters.Count >= ClientKeepInterval)
             {
                 _isOnline = false;
-                _sendKeepAliveTimer.Dispose();
-                _sendKeepAliveTimer = null;
-
                 KickPlayer().Ignore();
             }
             else
@@ -94,10 +100,30 @@ namespace MineCase.Server.Player
 
         private async Task KickPlayer()
         {
+            _sendKeepAliveTimer?.Dispose();
+            _sendKeepAliveTimer = null;
+            _worldTimeSyncTimer?.Dispose();
+            _worldTimeSyncTimer = null;
+
             var game = await GetGameSession();
             await game.LeaveGame(this);
             await _sink.Close();
             DeactivateOnIdle();
         }
+
+        public Task<IPlayer> GetPlayer() => Task.FromResult(_player);
+
+        public async Task NotifyLoggedIn()
+        {
+            _isOnline = true;
+            _keepAliveWaiters = new HashSet<uint>();
+            
+            await SendTimeUpdate();
+            await _player.SendWholeInventory();
+            await _player.SendExperience();
+
+            _sendKeepAliveTimer = RegisterTimer(OnSendKeepAliveRequests, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
+            //_worldTimeSyncTimer = RegisterTimer(OnSyncWorldTime, null, TimeSpan.Zero, )
+        }
     }
 }

+ 15 - 0
src/MineCase.Server.Grains/User/UserModule.cs

@@ -0,0 +1,15 @@
+using Autofac;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.User
+{
+    class UserModule : Module
+    {
+        protected override void Load(ContainerBuilder builder)
+        {
+            builder.RegisterType<NonAuthenticatedUserGrain>();
+        }
+    }
+}

+ 17 - 4
src/MineCase.Server.Grains/World/WorldGrain.cs

@@ -11,19 +11,20 @@ namespace MineCase.Server.World
     {
         private Dictionary<uint, IEntity> _entities;
         private uint _nextAvailEId;
+        private DateTime _worldStartTime;
 
         public override Task OnActivateAsync()
         {
             _nextAvailEId = 0;
             _entities = new Dictionary<uint, IEntity>();
+            _worldStartTime = DateTime.UtcNow;
             return base.OnActivateAsync();
         }
 
-        public Task<uint> AttachEntity(IEntity entity)
+        public Task AttachEntity(IEntity entity)
         {
-            var id = _nextAvailEId++;
-            _entities.Add(id, entity);
-            return Task.FromResult(id);
+            _entities.Add(entity.GetEntityId(), entity);
+            return Task.CompletedTask;
         }
 
         public Task<IEntity> FindEntity(uint eid)
@@ -32,5 +33,17 @@ namespace MineCase.Server.World
                 return Task.FromResult(entity);
             return Task.FromException<IEntity>(new EntityNotFoundException());
         }
+
+        public Task<(long age, long timeOfDay)> GetTime()
+        {
+            var age = (long)((DateTime.UtcNow - _worldStartTime).TotalSeconds * 20);
+            return Task.FromResult((age, age % 24000));
+        }
+
+        public Task<uint> NewEntityId()
+        {
+            var id = _nextAvailEId++;
+            return Task.FromResult(id);
+        }
     }
 }

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

@@ -0,0 +1,19 @@
+using MineCase.Server.Game.Windows;
+using MineCase.Server.Network;
+using MineCase.Server.User;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MineCase.Server.Game.Entities
+{
+    public interface IPlayer : IEntity
+    {
+        Task SetClientSink(IClientboundPacketSink sink);
+
+        Task<IInventoryWindow> GetInventory();
+        Task SendWholeInventory();
+        Task SendExperience();
+    }
+}

+ 24 - 3
src/MineCase.Server.Interfaces/Game/IEntity.cs

@@ -1,4 +1,5 @@
-using Orleans;
+using MineCase.Server.World;
+using Orleans;
 using System;
 using System.Collections.Generic;
 using System.Text;
@@ -6,8 +7,28 @@ using System.Threading.Tasks;
 
 namespace MineCase.Server.Game
 {
-    public interface IEntity : IGrain
+    public interface IEntity : IGrainWithStringKey
     {
-        Task SetEntityId(uint eid);
+
+    }
+
+    public static class EntityExtensions
+    {
+        public static string MakeEntityKey(this IWorld world, uint eid)
+        {
+            return $"{world.GetPrimaryKeyString()},{eid}";
+        }
+
+        public static uint GetEntityId(this IEntity entity)
+        {
+            var key = entity.GetPrimaryKeyString();
+            return uint.Parse(key.Split(',')[1]);
+        }
+
+        public static (string worldKey, uint entityId) GetWorldAndEntityId(this IEntity entity)
+        {
+            var key = entity.GetPrimaryKeyString().Split(',');
+            return (key[0], uint.Parse(key[1]));
+        }
     }
 }

+ 3 - 3
src/MineCase.Server.Interfaces/Game/IGameSession.cs

@@ -1,4 +1,4 @@
-using MineCase.Server.Player;
+using MineCase.Server.User;
 using Orleans;
 using System;
 using System.Collections.Generic;
@@ -9,7 +9,7 @@ namespace MineCase.Server.Game
 {
     public interface IGameSession : IGrainWithStringKey
     {
-        Task JoinGame(IPlayer player);
-        Task LeaveGame(IPlayer player);
+        Task JoinGame(IUser player);
+        Task LeaveGame(IUser player);
     }
 }

+ 11 - 0
src/MineCase.Server.Interfaces/Game/Slot.cs

@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game
+{
+    public sealed class Slot
+    {
+        public bool IsEmpty { get; set; } = true;
+    }
+}

+ 11 - 0
src/MineCase.Server.Interfaces/Game/Windows/IInventoryWindow.cs

@@ -0,0 +1,11 @@
+using Orleans;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Server.Game.Windows
+{
+    public interface IInventoryWindow : IWindow
+    {
+    }
+}

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

@@ -0,0 +1,14 @@
+using Orleans;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MineCase.Server.Game.Windows
+{
+    public interface IWindow : IGrainWithGuidKey
+    {
+        Task<uint> GetSlotCount();
+        Task<IReadOnlyList<Slot>> GetSlots();
+    }
+}

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

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

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

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

+ 2 - 2
src/MineCase.Server.Interfaces/Player/INonAuthenticatedPlayer.cs → src/MineCase.Server.Interfaces/User/INonAuthenticatedPlayer.cs

@@ -4,9 +4,9 @@ using System.Collections.Generic;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace MineCase.Server.Player
+namespace MineCase.Server.User
 {
-    public interface INonAuthenticatedPlayer : IGrainWithStringKey
+    public interface INonAuthenticatedUser : IGrainWithStringKey
     {
         Task<Guid> GetUUID();
     }

+ 6 - 3
src/MineCase.Server.Interfaces/Player/IPlayer.cs → src/MineCase.Server.Interfaces/User/IUser.cs

@@ -1,4 +1,5 @@
 using MineCase.Server.Game;
+using MineCase.Server.Game.Entities;
 using MineCase.Server.Network;
 using MineCase.Server.World;
 using Orleans;
@@ -8,16 +9,18 @@ using System.Numerics;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace MineCase.Server.Player
+namespace MineCase.Server.User
 {
-    public interface IPlayer : IGrainWithGuidKey, IEntity
+    public interface IUser : IGrainWithGuidKey
     {
         Task<IWorld> GetWorld();
         Task<IGameSession> GetGameSession();
+        Task<IPlayer> GetPlayer();
         Task SetClientPacketSink(IClientboundPacketSink sink);
         Task<IClientboundPacketSink> GetClientPacketSink();
 
+        Task JoinGame();
+        Task NotifyLoggedIn();
         Task KeepAlive(uint keepAliveId);
-        Task UseEntity(uint targetEid, EntityUsage type, Vector3? targetPosition, EntityInteractHand? hand);
     }
 }

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

@@ -9,7 +9,9 @@ namespace MineCase.Server.World
 {
     public interface IWorld : IGrainWithStringKey
     {
-        Task<uint> AttachEntity(IEntity entity);
+        Task<uint> NewEntityId();
+        Task AttachEntity(IEntity entity);
         Task<IEntity> FindEntity(uint eid);
+        Task<(long age, long timeOfDay)> GetTime();
     }
 }