Kaynağa Gözat

implement player respawn after death

JasonWang 6 yıl önce
ebeveyn
işleme
34089ae836
33 değiştirilmiş dosya ile 512 ekleme ve 108 silme
  1. 12 12
      appveyor.yml
  2. 8 8
      src/Common/Engine/DependencyObject.cs
  3. 2 2
      src/Common/Engine/PropertyMetadata.cs
  4. 3 3
      src/MineCase.Algorithm/World/Biomes/Biome.cs
  5. 78 33
      src/MineCase.Core/Block/BlockStates.cs
  6. 31 8
      src/MineCase.Core/Block/BlockType.cs
  7. 5 1
      src/MineCase.Core/World/ChunkColumnCompactStorage.cs
  8. 47 0
      src/MineCase.Protocol/Protocol/Play/EntityTeleport.cs
  9. 1 1
      src/MineCase.Protocol/Protocol/Play/PlayerPosition.cs
  10. 46 0
      src/MineCase.Protocol/Protocol/Play/Respawn.cs
  11. 36 0
      src/MineCase.Protocol/Protocol/Play/UpdateViewPosition.cs
  12. 1 0
      src/MineCase.Server.Grains/Components/WorldComponent.cs
  13. 60 0
      src/MineCase.Server.Grains/Game/Entities/Components/DeathComponent.cs
  14. 8 8
      src/MineCase.Server.Grains/Game/Entities/Components/FoodComponent.cs
  15. 8 8
      src/MineCase.Server.Grains/Game/Entities/Components/HealthComponent.cs
  16. 55 0
      src/MineCase.Server.Grains/Game/Entities/Components/OutOfWorldComponent.cs
  17. 26 0
      src/MineCase.Server.Grains/Game/Entities/Components/SyncPlayerStateComponent.cs
  18. 10 0
      src/MineCase.Server.Grains/Game/Entities/Components/TeleportComponent.cs
  19. 1 1
      src/MineCase.Server.Grains/Game/Entities/MobGrain.cs
  20. 2 2
      src/MineCase.Server.Grains/Game/Entities/MonsterGrain.cs
  21. 2 2
      src/MineCase.Server.Grains/Game/Entities/PassiveMobGrain.cs
  22. 11 2
      src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs
  23. 4 1
      src/MineCase.Server.Grains/Game/GameSession.cs
  24. 40 4
      src/MineCase.Server.Grains/Network/Play/ClientPlayPacketGenerator.cs
  25. 2 0
      src/MineCase.Server.Grains/Network/Play/ServerboundPacketComponent.cs
  26. 3 3
      src/MineCase.Server.Grains/World/Decoration/Biomes/BiomeDecoratorGrain.cs
  27. 2 2
      src/MineCase.Server.Grains/World/Decoration/Plants/AbstractTreeGeneratorGrain.cs
  28. 1 0
      src/MineCase.Server.Interfaces/Game/Entities/IPlayer.cs
  29. 2 2
      src/MineCase.sln
  30. 1 1
      src/docker/docker-compose.ci.build.yml
  31. 1 1
      src/docker/docker-compose.dcproj
  32. 1 1
      src/docker/docker-compose.yml
  33. 2 2
      tests/UnitTest/BlockStateTest.cs

+ 12 - 12
appveyor.yml

@@ -30,15 +30,15 @@ test_script:
     codecov -f "test_coverage.xml"
 deploy_script:
 - ps: >-
-    docker tag minecase.gateway sunnycase/minecase.gateway:ci-latest-nanoserver
-    
-    docker tag minecase.server sunnycase/minecase.server:ci-latest-nanoserver
-
-    IF ($env:APPVEYOR_REPO_BRANCH -eq "master")
-    {
-        docker login -u="$env:DOCKER_USER" -p="$env:DOCKER_PASS"
-    
-        docker push sunnycase/minecase.gateway
-    
-        docker push sunnycase/minecase.server
-    }
+    # docker tag minecase.gateway sunnycase/minecase.gateway:ci-latest-nanoserver
+    # 
+    # docker tag minecase.server sunnycase/minecase.server:ci-latest-nanoserver
+    # 
+    # IF ($env:APPVEYOR_REPO_BRANCH -eq "master")
+    # {
+    #     docker login -u="$env:DOCKER_USER" -p="$env:DOCKER_PASS"
+    # 
+    #     docker push sunnycase/minecase.gateway
+    # 
+    #     docker push sunnycase/minecase.server
+    # }

+ 8 - 8
src/Common/Engine/DependencyObject.cs

@@ -167,11 +167,11 @@ namespace MineCase.Engine
         }
 
         /// <summary>
-        /// 注册属性变更处理器
+        /// 注册属性变更处理器.
         /// </summary>
-        /// <typeparam name="T">值类型</typeparam>
-        /// <param name="property">依赖属性</param>
-        /// <param name="handler">处理器</param>
+        /// <typeparam name="T">值类型.</typeparam>
+        /// <param name="property">依赖属性.</param>
+        /// <param name="handler">处理器.</param>
         public void RegisterPropertyChangedHandler<T>(DependencyProperty<T> property, EventHandler<PropertyChangedEventArgs<T>> handler)
         {
             if (_propertyChangedHandlers.TryGetValue(property, out var newHandler))
@@ -182,11 +182,11 @@ namespace MineCase.Engine
         }
 
         /// <summary>
-        /// 删除属性变更处理器
+        /// 删除属性变更处理器.
         /// </summary>
-        /// <typeparam name="T">值类型</typeparam>
-        /// <param name="property">依赖属性</param>
-        /// <param name="handler">处理器</param>
+        /// <typeparam name="T">值类型.</typeparam>
+        /// <param name="property">依赖属性.</param>
+        /// <param name="handler">处理器.</param>
         public void RemovePropertyChangedHandler<T>(DependencyProperty<T> property, EventHandler<PropertyChangedEventArgs<T>> handler)
         {
             if (_propertyChangedHandlers.TryGetValue(property, out var newHandler))

+ 2 - 2
src/Common/Engine/PropertyMetadata.cs

@@ -32,8 +32,8 @@ namespace MineCase.Engine
         /// <summary>
         /// Initializes a new instance of the <see cref="PropertyMetadata{T}"/> class.
         /// </summary>
-        /// <param name="defaultValue">默认值</param>
-        /// <param name="propertyChangedHandler">属性更改处理器</param>
+        /// <param name="defaultValue">默认值.</param>
+        /// <param name="propertyChangedHandler">属性更改处理器.</param>
         public PropertyMetadata(T defaultValue, EventHandler<PropertyChangedEventArgs<T>> propertyChangedHandler = null)
         {
             _defaultValue = defaultValue;

+ 3 - 3
src/MineCase.Algorithm/World/Biomes/Biome.cs

@@ -104,13 +104,13 @@ namespace MineCase.Algorithm.World.Biomes
                 BlockStates.Gravel(),
                 genSettings.GravelSize);
             _graniteGen = new MinableGenerator(
-                BlockStates.Stone(StoneType.Granite),
+                BlockStates.Granite(),
                 genSettings.GraniteSize);
             _dioriteGen = new MinableGenerator(
-                BlockStates.Stone(StoneType.Diorite),
+                BlockStates.Diorite(),
                 genSettings.DioriteSize);
             _andesiteGen = new MinableGenerator(
-                BlockStates.Stone(StoneType.Andesite),
+                BlockStates.Andesite(),
                 genSettings.AndesiteSize);
             _coalGen = new MinableGenerator(
                 BlockStates.CoalOre(),

+ 78 - 33
src/MineCase.Core/Block/BlockStates.cs

@@ -15,116 +15,161 @@ namespace MineCase.Block
             };
         }
 
-        public static BlockState Stone(StoneType type = StoneType.Stone)
+        public static BlockState Stone()
         {
             return new BlockState
             {
                 Id = (uint)BlockId.Stone,
-                MetaValue = (uint)type
+                MetaValue = 0,
             };
         }
 
-        public static BlockState GrassBlock()
+        public static BlockState Granite()
         {
             return new BlockState
             {
-                Id = (uint)BlockId.GrassBlock,
-                MetaValue = 0
+                Id = (uint)BlockId.Granite,
+                MetaValue = 0,
             };
         }
 
-        public static BlockState Dirt(DirtType type = DirtType.Dirt)
+        public static BlockState PolishedGranite()
         {
             return new BlockState
             {
-                Id = (uint)BlockId.Dirt,
-                MetaValue = (uint)type
+                Id = (uint)BlockId.PolishedGranite,
+                MetaValue = 0,
             };
         }
 
-        public static BlockState Cobblestone()
+        public static BlockState Diorite()
         {
             return new BlockState
             {
-                Id = (uint)BlockId.Cobblestone,
-                MetaValue = 0
+                Id = (uint)BlockId.Diorite,
+                MetaValue = 0,
             };
         }
 
-        public static BlockState WoodPlanks(WoodPlankType type = WoodPlankType.Oak)
+        public static BlockState PolishedDiorite()
         {
             return new BlockState
             {
-                Id = (uint)BlockId.OakPlanks,
-                MetaValue = (uint)type
+                Id = (uint)BlockId.PolishedDiorite,
+                MetaValue = 0,
             };
         }
 
-        public static BlockState Sapling(SaplingsType type = SaplingsType.Oak)
+        public static BlockState Andesite()
         {
             return new BlockState
             {
-                Id = (uint)BlockId.OakSapling,
-                MetaValue = (uint)type
+                Id = (uint)BlockId.Andesite,
+                MetaValue = 0,
             };
         }
 
-        public static BlockState Bedrock()
+        public static BlockState PolishedAndesite()
         {
             return new BlockState
             {
-                Id = (uint)BlockId.Bedrock,
-                MetaValue = 0
+                Id = (uint)BlockId.PolishedAndesite,
+                MetaValue = 0,
             };
         }
 
-        public static BlockState Water(FluidType type = FluidType.FallingFlag)
+        public static BlockState GrassBlock(GrassBlockSnowy snowy = GrassBlockSnowy.False)
         {
             return new BlockState
             {
-                Id = (uint)BlockId.Water,
-                MetaValue = (uint)type
+                Id = (uint)BlockId.GrassBlock,
+                MetaValue = (uint)snowy
             };
         }
 
-        [ObsoleteAttribute]
-        public static BlockState StationaryWater(FluidType type = FluidType.FallingFlag)
+        public static BlockState Dirt()
         {
             return new BlockState
             {
-                Id = (uint)BlockId.Water,
+                Id = (uint)BlockId.Dirt,
+                MetaValue = 0,
+            };
+        }
+
+        public static BlockState Cobblestone()
+        {
+            return new BlockState
+            {
+                Id = (uint)BlockId.Cobblestone,
+                MetaValue = 0
+            };
+        }
+
+        public static BlockState WoodPlanks(WoodPlankType type = WoodPlankType.Oak)
+        {
+            return new BlockState
+            {
+                Id = (uint)BlockId.OakPlanks,
                 MetaValue = (uint)type
             };
         }
 
-        public static BlockState Lava(FluidType type = FluidType.FallingFlag)
+        public static BlockState Sapling(SaplingsType type = SaplingsType.Oak)
         {
             return new BlockState
             {
-                Id = (uint)BlockId.Lava,
+                Id = (uint)BlockId.OakSapling,
                 MetaValue = (uint)type
             };
         }
 
-        [ObsoleteAttribute]
-        public static BlockState StationaryLava(FluidType type = FluidType.FallingFlag)
+        public static BlockState Bedrock()
         {
             return new BlockState
             {
-                Id = (uint)BlockId.Lava,
+                Id = (uint)BlockId.Bedrock,
                 MetaValue = 0
             };
         }
 
-        public static BlockState Sand(SandType type = SandType.Sand)
+        public static BlockState Water(FluidType type = FluidType.Level0)
+        {
+            return new BlockState
+            {
+                Id = (uint)BlockId.Water,
+                MetaValue = (uint)type
+            };
+        }
+
+        public static BlockState Lava(FluidType type = FluidType.Level0)
         {
             return new BlockState
             {
-                Id = (uint)BlockId.Sand,
+                Id = (uint)BlockId.Lava,
                 MetaValue = (uint)type
             };
         }
 
+        public static BlockState Sand(SandType type = SandType.Sand)
+        {
+            if (type == SandType.Sand)
+            {
+                return new BlockState
+                {
+                    Id = (uint)BlockId.Sand,
+                    MetaValue = 0
+                };
+            }
+            else
+            {
+                return new BlockState
+                {
+                    Id = (uint)BlockId.RedSand,
+                    MetaValue = 0
+                };
+            }
+        }
+
         public static BlockState Gravel()
         {
             return new BlockState

+ 31 - 8
src/MineCase.Core/Block/BlockType.cs

@@ -726,6 +726,21 @@ namespace MineCase.Block
         }
     }
 
+    // New enums
+    public enum GrassBlockSnowy : uint
+    {
+        True = 0,
+        False = 1,
+    }
+
+    public enum OakLogAxis : uint
+    {
+        X = 0,
+        Y = 1,
+        Z = 2,
+    }
+
+    // Old enums
     public enum WoodPlankType : uint
     {
         Oak = 0,
@@ -779,14 +794,22 @@ namespace MineCase.Block
     [Flags]
     public enum FluidType : uint
     {
-        Level1 = 0,
-        Level2 = 1,
-        Level3 = 2,
-        Level4 = 3,
-        Level5 = 4,
-        Level6 = 5,
-        Level7 = 6,
-        Level8 = 7,
+        Level0 = 0,
+        Level1 = 1,
+        Level2 = 2,
+        Level3 = 3,
+        Level4 = 4,
+        Level5 = 5,
+        Level6 = 6,
+        Level7 = 7,
+        Level8 = 8,
+        Level9 = 9,
+        Level10 = 10,
+        Level11 = 11,
+        Level12 = 12,
+        Level13 = 13,
+        Level14 = 14,
+        Level15 = 15,
 
         // 0x8 bit field : If set, this liquid is "falling" and only spreads downward
         FallingFlag = 0x8

+ 5 - 1
src/MineCase.Core/World/ChunkColumnCompactStorage.cs

@@ -140,6 +140,8 @@ namespace MineCase.World
             {
                 get
                 {
+                    if (y < 0 || y > 255)
+                        return BlockStates.Air();
                     var offset = GetOffset(x, y, z);
                     var toRead = Math.Min(_bitsPerBlock, 64 - offset.bitOffset);
                     var value = Storage[offset.indexOffset] >> offset.bitOffset;
@@ -152,6 +154,8 @@ namespace MineCase.World
 
                 set
                 {
+                    if (y < 0 || y > 255)
+                        throw new IndexOutOfRangeException("Axis y out of range");
                     var stgValue = (ulong)((value.Id + value.MetaValue) & BlockMask);
                     var offset = GetOffset(x, y, z);
                     var tmpValue = Storage[offset.indexOffset];
@@ -228,7 +232,7 @@ namespace MineCase.World
 
         public static uint ToUInt32(ref BlockState blockState)
         {
-            return blockState.Id;
+            return blockState.Id + blockState.MetaValue;
         }
     }
 }

+ 47 - 0
src/MineCase.Protocol/Protocol/Play/EntityTeleport.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using MineCase.Serialization;
+
+namespace MineCase.Protocol.Play
+{
+#if !NET46
+    [Orleans.Concurrency.Immutable]
+#endif
+    [Packet(0x57)]
+    public sealed class EntityTeleport : ISerializablePacket
+    {
+        [SerializeAs(DataType.VarInt)]
+        public uint EID;
+
+        [SerializeAs(DataType.Double)]
+        public double X;
+
+        [SerializeAs(DataType.Double)]
+        public double Y;
+
+        [SerializeAs(DataType.Double)]
+        public double Z;
+
+        [SerializeAs(DataType.Angle)]
+        public byte Yaw;
+
+        [SerializeAs(DataType.Angle)]
+        public byte Pitch;
+
+        [SerializeAs(DataType.Boolean)]
+        public bool OnGround;
+
+        public void Serialize(BinaryWriter bw)
+        {
+            bw.WriteAsVarInt(EID, out _);
+            bw.WriteAsDouble(X);
+            bw.WriteAsDouble(Y);
+            bw.WriteAsDouble(Z);
+            bw.WriteAsAngle(Yaw);
+            bw.WriteAsAngle(Pitch);
+            bw.WriteAsBoolean(OnGround);
+        }
+    }
+}

+ 1 - 1
src/MineCase.Protocol/Protocol/Play/PlayerPosition.cs

@@ -9,7 +9,7 @@ namespace MineCase.Protocol.Play
 #if !NET46
     [Orleans.Concurrency.Immutable]
 #endif
-    [Packet(0x0E)]
+    [Packet(0x11)]
     public sealed class PlayerPosition
     {
         [SerializeAs(DataType.Double)]

+ 46 - 0
src/MineCase.Protocol/Protocol/Play/Respawn.cs

@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using MineCase.Serialization;
+
+namespace MineCase.Protocol.Play
+{
+#if !NET46
+    [Orleans.Concurrency.Immutable]
+#endif
+    [Packet(0x3B)]
+    public sealed class Respawn : ISerializablePacket
+    {
+        [SerializeAs(DataType.Int)]
+        public int Dimension;
+
+        [SerializeAs(DataType.Long)]
+        public long HashedSeed;
+
+        [SerializeAs(DataType.Byte)]
+        public byte Gamemode;
+
+        [SerializeAs(DataType.String)]
+        public string LevelType;
+
+        public static Respawn Deserialize(ref SpanReader br)
+        {
+            return new Respawn
+            {
+                Dimension = br.ReadAsInt(),
+                HashedSeed = br.ReadAsLong(),
+                Gamemode = br.ReadAsByte(),
+                LevelType = br.ReadAsString()
+            };
+        }
+
+        public void Serialize(BinaryWriter bw)
+        {
+            bw.WriteAsInt(Dimension);
+            bw.WriteAsLong(HashedSeed);
+            bw.WriteAsByte(Gamemode);
+            bw.WriteAsString(LevelType);
+        }
+    }
+}

+ 36 - 0
src/MineCase.Protocol/Protocol/Play/UpdateViewPosition.cs

@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using MineCase.Serialization;
+
+namespace MineCase.Protocol.Play
+{
+#if !NET46
+    [Orleans.Concurrency.Immutable]
+#endif
+    [Packet(0x41)]
+    public sealed class UpdateViewPosition : ISerializablePacket
+    {
+        [SerializeAs(DataType.VarInt)]
+        public int ChunkX;
+
+        [SerializeAs(DataType.VarInt)]
+        public int ChunkZ;
+
+        public static UpdateViewPosition Deserialize(ref SpanReader br)
+        {
+            return new UpdateViewPosition
+            {
+                ChunkX = (int)br.ReadAsVarInt(out _),
+                ChunkZ = (int)br.ReadAsVarInt(out _),
+            };
+        }
+
+        public void Serialize(BinaryWriter bw)
+        {
+            bw.WriteAsVarInt((uint)ChunkX, out _);
+            bw.WriteAsVarInt((uint)ChunkZ, out _);
+        }
+    }
+}

+ 1 - 0
src/MineCase.Server.Grains/Components/WorldComponent.cs

@@ -4,6 +4,7 @@ using System.Text;
 using System.Threading.Tasks;
 using MineCase.Engine;
 using MineCase.Server.World;
+using MineCase.World;
 
 namespace MineCase.Server.Components
 {

+ 60 - 0
src/MineCase.Server.Grains/Game/Entities/Components/DeathComponent.cs

@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using MineCase.Engine;
+using MineCase.Server.Components;
+using MineCase.Server.Network.Play;
+using MineCase.World;
+
+namespace MineCase.Server.Game.Entities.Components
+{
+    /// <summary>
+    /// When player health is less than 0, respawn him.
+    /// </summary>
+    internal class DeathComponent : Component<PlayerGrain>
+    {
+        public static readonly DependencyProperty<bool> IsDeathProperty =
+            DependencyProperty.Register<bool>("IsDeath", typeof(DeathComponent), new PropertyMetadata<bool>(false, OnDeath));
+
+        public bool IsDeath => AttachedObject.GetValue(IsDeathProperty);
+
+        public DeathComponent(string name = "death")
+            : base(name)
+        {
+        }
+
+        protected override void OnAttached()
+        {
+        }
+
+        protected override void OnDetached()
+        {
+        }
+
+        private async Task Respawn()
+        {
+            var generator = AttachedObject.GetComponent<ClientboundPacketComponent>().GetGenerator();
+
+            var teleportComponent = AttachedObject.GetComponent<TeleportComponent>();
+            var world = AttachedObject.GetWorld();
+            var spawnPos = await world.GetSpawnPosition();
+            await teleportComponent.Teleport(spawnPos, 0, 0);
+            await generator.Respawn(Dimension.Overworld, await world.GetSeed(), new GameMode { ModeClass = GameMode.Class.Survival, IsHardcore = false }, LevelTypes.Default);
+            AttachedObject.SetLocalValue(HealthComponent.HealthProperty, AttachedObject.GetValue(HealthComponent.MaxHealthProperty));
+            AttachedObject.SetLocalValue(FoodComponent.FoodProperty, AttachedObject.GetValue(FoodComponent.MaxFoodProperty));
+            AttachedObject.SetLocalValue(DeathComponent.IsDeathProperty, false);
+        }
+
+        private void OnDeath(PropertyChangedEventArgs<bool> e)
+        {
+            Task.Run(Respawn);
+        }
+
+        private static void OnDeath(object sender, PropertyChangedEventArgs<bool> e)
+        {
+            var component = ((DependencyObject)sender).GetComponent<DeathComponent>();
+            component.OnDeath(e);
+        }
+    }
+}

+ 8 - 8
src/MineCase.Server.Grains/Game/Entities/Components/FoodComponent.cs

@@ -8,18 +8,18 @@ namespace MineCase.Server.Game.Entities.Components
 {
     internal class FoodComponent : Component
     {
-        public static readonly DependencyProperty<uint> FoodProperty =
-            DependencyProperty.Register<uint>("Food", typeof(FoodComponent));
+        public static readonly DependencyProperty<int> FoodProperty =
+            DependencyProperty.Register<int>("Food", typeof(FoodComponent));
 
-        public static readonly DependencyProperty<uint> MaxFoodProperty =
-            DependencyProperty.Register<uint>("MaxFood", typeof(FoodComponent));
+        public static readonly DependencyProperty<int> MaxFoodProperty =
+            DependencyProperty.Register<int>("MaxFood", typeof(FoodComponent));
 
         public static readonly DependencyProperty<float> FoodSaturationProperty =
             DependencyProperty.Register<float>("FoodSaturation", typeof(FoodComponent));
 
-        public uint Food => AttachedObject.GetValue(FoodProperty);
+        public int Food => AttachedObject.GetValue(FoodProperty);
 
-        public uint MaxFood => AttachedObject.GetValue(MaxFoodProperty);
+        public int MaxFood => AttachedObject.GetValue(MaxFoodProperty);
 
         public float FoodSaturation => AttachedObject.GetValue(FoodSaturationProperty);
 
@@ -28,10 +28,10 @@ namespace MineCase.Server.Game.Entities.Components
         {
         }
 
-        public void SetFood(uint value) =>
+        public void SetFood(int value) =>
             AttachedObject.SetLocalValue(FoodProperty, value);
 
-        public void SetMaxFood(uint value) =>
+        public void SetMaxFood(int value) =>
             AttachedObject.SetLocalValue(MaxFoodProperty, value);
 
         public void SetFoodSaturation(float value) =>

+ 8 - 8
src/MineCase.Server.Grains/Game/Entities/Components/HealthComponent.cs

@@ -8,25 +8,25 @@ namespace MineCase.Server.Game.Entities.Components
 {
     internal class HealthComponent : Component
     {
-        public static readonly DependencyProperty<uint> MaxHealthProperty =
-            DependencyProperty.Register<uint>("MaxHealth", typeof(HealthComponent));
+        public static readonly DependencyProperty<int> MaxHealthProperty =
+            DependencyProperty.Register<int>("MaxHealth", typeof(HealthComponent));
 
-        public static readonly DependencyProperty<uint> HealthProperty =
-            DependencyProperty.Register<uint>("Health", typeof(HealthComponent));
+        public static readonly DependencyProperty<int> HealthProperty =
+            DependencyProperty.Register<int>("Health", typeof(HealthComponent));
 
-        public uint Health => AttachedObject.GetValue(HealthProperty);
+        public int Health => AttachedObject.GetValue(HealthProperty);
 
-        public uint MaxHealth => AttachedObject.GetValue(MaxHealthProperty);
+        public int MaxHealth => AttachedObject.GetValue(MaxHealthProperty);
 
         public HealthComponent(string name = "health")
             : base(name)
         {
         }
 
-        public void SetHealth(uint value) =>
+        public void SetHealth(int value) =>
             AttachedObject.SetLocalValue(HealthProperty, value);
 
-        public void SetMaxHealth(uint value) =>
+        public void SetMaxHealth(int value) =>
             AttachedObject.SetLocalValue(MaxHealthProperty, value);
     }
 }

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

@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using MineCase.Engine;
+using MineCase.Server.Components;
+using MineCase.World;
+
+namespace MineCase.Server.Game.Entities.Components
+{
+    internal class OutOfWorldComponent : Component<PlayerGrain>
+    {
+        public OutOfWorldComponent(string name = "outOfWorld")
+            : base(name)
+        {
+        }
+
+        protected override void OnAttached()
+        {
+            Register();
+        }
+
+        protected override void OnDetached()
+        {
+            Unregister();
+        }
+
+        private void Register()
+        {
+            AttachedObject.GetComponent<GameTickComponent>()
+                .Tick += OnGameTick;
+        }
+
+        private void Unregister()
+        {
+            AttachedObject.GetComponent<GameTickComponent>()
+                .Tick -= OnGameTick;
+        }
+
+        private Task OnGameTick(object sender, GameTickArgs e)
+        {
+            // Every 40 tick, check whether entities are out of world
+            if (e.WorldAge % 40 == 0)
+            {
+                if (AttachedObject.Position.Y < -64)
+                {
+                    var health = AttachedObject.GetComponent<HealthComponent>();
+                    health.SetHealth(health.Health - 4);
+                }
+            }
+
+            return Task.CompletedTask;
+        }
+    }
+}

+ 26 - 0
src/MineCase.Server.Grains/Game/Entities/Components/SyncPlayerStateComponent.cs

@@ -60,6 +60,7 @@ namespace MineCase.Server.Game.Entities.Components
             AttachedObject.RegisterPropertyChangedHandler(EntityLookComponent.HeadYawProperty, OnEntityHeadYawChanged);
             AttachedObject.RegisterPropertyChangedHandler(EntityLookComponent.PitchProperty, OnEntityPitchChanged);
             AttachedObject.RegisterPropertyChangedHandler(EntityLookComponent.YawProperty, OnEntityYawChanged);
+            AttachedObject.RegisterPropertyChangedHandler(HealthComponent.HealthProperty, OnEntityHealthChanged);
         }
 
         private ChunkEventBroadcastComponent _broadcastComponent;
@@ -101,9 +102,22 @@ namespace MineCase.Server.Game.Entities.Components
 
         private void OnEntityWorldPositionChanged(object sender, PropertyChangedEventArgs<EntityWorldPos> e)
         {
+            var generator = AttachedObject.GetComponent<ClientboundPacketComponent>().GetGenerator();
+
+            // Update Collider
             var pos = e.NewValue;
             var box = new Cuboid(new Point3d(pos.X, pos.Z, pos.Y), new Size(0.6f, 0.6f, 1.75f));
             AttachedObject.SetLocalValue(ColliderComponent.ColliderShapeProperty, box);
+
+            // Check if we need to send UpdateViewPosition packet. If the player walk cross chunk borders, send it.
+            var oldChunkPos = e.OldValue.ToChunkWorldPos();
+            var newChunkPos = e.NewValue.ToChunkWorldPos();
+            if (oldChunkPos != newChunkPos)
+            {
+                generator.UpdateViewPosition(newChunkPos.X, newChunkPos.Z);
+            }
+
+            // Broadcast to trackers
             _broadcastComponent = _broadcastComponent ?? AttachedObject.GetComponent<ChunkEventBroadcastComponent>();
             _broadcastComponent.GetGenerator(AttachedObject)
                 .EntityRelativeMove(
@@ -114,6 +128,18 @@ namespace MineCase.Server.Game.Entities.Components
                 AttachedObject.GetValue(EntityOnGroundComponent.IsOnGroundProperty));
         }
 
+        private void OnEntityHealthChanged(object sender, PropertyChangedEventArgs<int> e)
+        {
+            var generator = AttachedObject.GetComponent<ClientboundPacketComponent>().GetGenerator();
+            var healthComponent = AttachedObject.GetComponent<HealthComponent>();
+            var foodComponent = AttachedObject.GetComponent<FoodComponent>();
+            generator.UpdateHealth(healthComponent.Health, healthComponent.MaxHealth, foodComponent.Food, foodComponent.MaxFood, foodComponent.FoodSaturation);
+            if (healthComponent.Health < 0)
+            {
+                AttachedObject.SetLocalValue(DeathComponent.IsDeathProperty, true);
+            }
+        }
+
         private static short GetDelta(float before, float after)
         {
             return (short)((after * 32 - before * 32) * 128);

+ 10 - 0
src/MineCase.Server.Grains/Game/Entities/Components/TeleportComponent.cs

@@ -3,6 +3,9 @@ using System.Collections.Generic;
 using System.Text;
 using System.Threading.Tasks;
 using MineCase.Engine;
+using MineCase.Server.Components;
+using MineCase.Server.Network.Play;
+using MineCase.World;
 
 namespace MineCase.Server.Game.Entities.Components
 {
@@ -17,6 +20,13 @@ namespace MineCase.Server.Game.Entities.Components
 
         public uint StartNew() => _teleportId++;
 
+        public Task Teleport(EntityWorldPos position, float yaw, float pitch)
+        {
+            var generator = AttachedObject.GetComponent<ClientboundPacketComponent>().GetGenerator();
+            uint teleportId = AttachedObject.GetComponent<TeleportComponent>().StartNew();
+            return generator.PositionAndLook(position.X, position.Y, position.Z, yaw, pitch, 0, teleportId);
+        }
+
         public Task ConfirmTeleport(uint teleportId)
         {
             return Task.CompletedTask;

+ 1 - 1
src/MineCase.Server.Grains/Game/Entities/MobGrain.cs

@@ -33,7 +33,7 @@ namespace MineCase.Server.Game.Entities
         public async override Task OnActivateAsync()
         {
             await base.OnActivateAsync();
-            this.SetLocalValue(HealthComponent.MaxHealthProperty, 20u);
+            this.SetLocalValue(HealthComponent.MaxHealthProperty, 20);
             this.SetLocalValue(HealthComponent.HealthProperty, GetValue(HealthComponent.MaxHealthProperty));
             this.SetLocalValue(EntityOnGroundComponent.IsOnGroundProperty, true);
         }

+ 2 - 2
src/MineCase.Server.Grains/Game/Entities/MonsterGrain.cs

@@ -31,8 +31,8 @@ namespace MineCase.Server.Game.Entities
         public async override Task OnActivateAsync()
         {
             await base.OnActivateAsync();
-            this.SetLocalValue(HealthComponent.MaxHealthProperty, 20u);
-            this.SetLocalValue(FoodComponent.MaxFoodProperty, 20u);
+            this.SetLocalValue(HealthComponent.MaxHealthProperty, 20);
+            this.SetLocalValue(FoodComponent.MaxFoodProperty, 20);
             this.SetLocalValue(HealthComponent.HealthProperty, GetValue(HealthComponent.MaxHealthProperty));
             this.SetLocalValue(FoodComponent.FoodProperty, GetValue(FoodComponent.MaxFoodProperty));
         }

+ 2 - 2
src/MineCase.Server.Grains/Game/Entities/PassiveMobGrain.cs

@@ -35,8 +35,8 @@ namespace MineCase.Server.Game.Entities
         public async override Task OnActivateAsync()
         {
             await base.OnActivateAsync();
-            this.SetLocalValue(HealthComponent.MaxHealthProperty, 20u);
-            this.SetLocalValue(FoodComponent.MaxFoodProperty, 20u);
+            this.SetLocalValue(HealthComponent.MaxHealthProperty, 20);
+            this.SetLocalValue(FoodComponent.MaxFoodProperty, 20);
             this.SetLocalValue(HealthComponent.HealthProperty, GetValue(HealthComponent.MaxHealthProperty));
             this.SetLocalValue(FoodComponent.FoodProperty, GetValue(FoodComponent.MaxFoodProperty));
         }

+ 11 - 2
src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs

@@ -25,6 +25,8 @@ namespace MineCase.Server.Game.Entities
     [Reentrant]
     internal class PlayerGrain : EntityGrain, IPlayer
     {
+        public uint ViewDistance => GetValue(ViewDistanceComponent.ViewDistanceProperty);
+
         protected override void InitializeComponents()
         {
             base.InitializeComponents();
@@ -56,13 +58,15 @@ namespace MineCase.Server.Game.Entities
             SetComponent(new CollectorComponent());
             SetComponent(new ColliderComponent());
             SetComponent(new MobSpawnerComponent());
+            SetComponent(new OutOfWorldComponent());
+            SetComponent(new DeathComponent());
         }
 
         public override async Task OnActivateAsync()
         {
             await base.OnActivateAsync();
-            this.SetLocalValue(HealthComponent.MaxHealthProperty, 20u);
-            this.SetLocalValue(FoodComponent.MaxFoodProperty, 20u);
+            this.SetLocalValue(HealthComponent.MaxHealthProperty, 20);
+            this.SetLocalValue(FoodComponent.MaxFoodProperty, 20);
             this.SetLocalValue(HealthComponent.HealthProperty, GetValue(HealthComponent.MaxHealthProperty));
             this.SetLocalValue(FoodComponent.FoodProperty, GetValue(FoodComponent.MaxFoodProperty));
         }
@@ -80,5 +84,10 @@ namespace MineCase.Server.Game.Entities
             // await _generator.SendClientAnimation(entityID, animationID);
             throw new NotImplementedException();
         }
+
+        public Task<uint> GetViewDistance()
+        {
+            return Task.FromResult(ViewDistance);
+        }
     }
 }

+ 4 - 1
src/MineCase.Server.Grains/Game/GameSession.cs

@@ -10,6 +10,7 @@ using MineCase.Engine;
 using MineCase.Protocol.Play;
 using MineCase.Server.Components;
 using MineCase.Server.Game.Entities;
+using MineCase.Server.Game.Entities.Components;
 using MineCase.Server.Network.Play;
 using MineCase.Server.Persistence.Components;
 using MineCase.Server.Settings;
@@ -66,12 +67,14 @@ namespace MineCase.Server.Game
             };
 
             await user.JoinGame();
+            var player = await user.GetPlayer();
             await generator.JoinGame(
-                await (await user.GetPlayer()).GetEntityId(),
+                await player.GetEntityId(),
                 await user.GetGameMode(),
                 Dimension.Overworld,
                 Difficulty.Easy,
                 (byte)settings.MaxPlayers,
+                await player.GetViewDistance(),
                 LevelTypes.Default,
                 false);
             await user.NotifyLoggedIn();

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

@@ -267,7 +267,7 @@ namespace MineCase.Server.Network.Play
         }
 
         // TODO update params for 1.15.2
-        public Task JoinGame(uint eid, GameMode gameMode, Dimension dimension, Difficulty difficulty, byte maxPlayers, string levelType, bool reducedDebugInfo)
+        public Task JoinGame(uint eid, GameMode gameMode, Dimension dimension, Difficulty difficulty, byte maxPlayers, uint viewDistance, string levelType, bool reducedDebugInfo)
         {
             return SendPacket(new JoinGame
             {
@@ -275,9 +275,11 @@ namespace MineCase.Server.Network.Play
                 GameMode = ToByte(gameMode),
                 Dimension = (int)dimension,
                 HashedSeed = 0, // FIXME
-                LevelType = levelType,
                 MaxPlayers = maxPlayers,
-                ReducedDebugInfo = reducedDebugInfo
+                LevelType = levelType,
+                ViewDistance = viewDistance,
+                ReducedDebugInfo = reducedDebugInfo,
+                EnableRespawnScreen = true,
             });
         }
 
@@ -335,7 +337,7 @@ namespace MineCase.Server.Network.Play
             });
         }
 
-        public Task UpdateHealth(uint health, uint maxHealth, uint food, uint maxFood, float foodSaturation)
+        public Task UpdateHealth(int health, int maxHealth, int food, int maxFood, float foodSaturation)
         {
             return SendPacket(new UpdateHealth
             {
@@ -544,6 +546,40 @@ namespace MineCase.Server.Network.Play
             });
         }
 
+        public Task EntityTeleport(uint entityId, Vector3 position, byte pitch, byte yaw, bool onGround)
+        {
+            return SendPacket(new EntityTeleport
+            {
+                EID = entityId,
+                X = position.X,
+                Y = position.Y,
+                Z = position.Z,
+                Pitch = pitch,
+                Yaw = yaw,
+                OnGround = onGround,
+            });
+        }
+
+        public Task Respawn(Dimension dimension, long hashseed, GameMode gameMode, string levelType)
+        {
+            return SendPacket(new Respawn
+            {
+                Dimension = (int)dimension,
+                HashedSeed = hashseed,
+                Gamemode = ToByte(gameMode),
+                LevelType = levelType,
+            });
+        }
+
+        public Task UpdateViewPosition(int chunkX, int chunkZ)
+        {
+            return SendPacket(new UpdateViewPosition
+            {
+                ChunkX = chunkX,
+                ChunkZ = chunkZ,
+            });
+        }
+
         public Task SendPacket(uint packetId, byte[] data)
         {
             if (Sink != null)

+ 2 - 0
src/MineCase.Server.Grains/Network/Play/ServerboundPacketComponent.cs

@@ -188,6 +188,7 @@ namespace MineCase.Server.Network.Play
 
         private Task DispatchPacket(ServerboundPositionAndLook packet)
         {
+            // TODO: check if player movement is valid
             AttachedObject.SetLocalValue(EntityWorldPositionComponent.EntityWorldPositionProperty, new EntityWorldPos((float)packet.X, (float)packet.FeetY, (float)packet.Z));
             AttachedObject.SetLocalValue(EntityLookComponent.PitchProperty, packet.Pitch);
             AttachedObject.SetLocalValue(EntityLookComponent.YawProperty, packet.Yaw);
@@ -203,6 +204,7 @@ namespace MineCase.Server.Network.Play
 
         private Task DispatchPacket(PlayerPosition packet)
         {
+            // TODO: check if player movement is valid
             AttachedObject.SetLocalValue(EntityWorldPositionComponent.EntityWorldPositionProperty, new EntityWorldPos((float)packet.X, (float)packet.FeetY, (float)packet.Z));
             AttachedObject.SetLocalValue(EntityOnGroundComponent.IsOnGroundProperty, packet.OnGround);
             return Task.CompletedTask;

+ 3 - 3
src/MineCase.Server.Grains/World/Decoration/Biomes/BiomeDecoratorGrain.cs

@@ -47,9 +47,9 @@ namespace MineCase.Server.World.Decoration.Biomes
             var oreGenerator = GrainFactory.GetGrain<IMinableGenerator>(0);
             await oreGenerator.Generate(world, chunkWorldPos, BlockStates.Dirt(), settings.DirtCount, settings.DirtSize, settings.DirtMinHeight, settings.DirtMaxHeight);
             await oreGenerator.Generate(world, chunkWorldPos, BlockStates.Gravel(), settings.GravelCount, settings.GravelSize, settings.GravelMinHeight, settings.GravelMaxHeight);
-            await oreGenerator.Generate(world, chunkWorldPos, BlockStates.Stone(StoneType.Granite), settings.GraniteCount, settings.GraniteSize, settings.GraniteMinHeight, settings.GraniteMaxHeight);
-            await oreGenerator.Generate(world, chunkWorldPos, BlockStates.Stone(StoneType.Diorite), settings.DioriteCount, settings.DioriteSize, settings.DioriteMinHeight, settings.DioriteMaxHeight);
-            await oreGenerator.Generate(world, chunkWorldPos, BlockStates.Stone(StoneType.Andesite), settings.AndesiteCount, settings.AndesiteSize, settings.AndesiteMinHeight, settings.AndesiteMaxHeight);
+            await oreGenerator.Generate(world, chunkWorldPos, BlockStates.Granite(), settings.GraniteCount, settings.GraniteSize, settings.GraniteMinHeight, settings.GraniteMaxHeight);
+            await oreGenerator.Generate(world, chunkWorldPos, BlockStates.Diorite(), settings.DioriteCount, settings.DioriteSize, settings.DioriteMinHeight, settings.DioriteMaxHeight);
+            await oreGenerator.Generate(world, chunkWorldPos, BlockStates.Andesite(), settings.AndesiteCount, settings.AndesiteSize, settings.AndesiteMinHeight, settings.AndesiteMaxHeight);
 
             await oreGenerator.Generate(world, chunkWorldPos, BlockStates.CoalOre(), settings.CoalCount, settings.CoalSize, settings.CoalMinHeight, settings.CoalMaxHeight);
             await oreGenerator.Generate(world, chunkWorldPos, BlockStates.IronOre(), settings.IronCount, settings.IronSize, settings.IronMinHeight, settings.IronMaxHeight);

+ 2 - 2
src/MineCase.Server.Grains/World/Decoration/Plants/AbstractTreeGeneratorGrain.cs

@@ -28,8 +28,8 @@ namespace MineCase.Server.World.Decoration.Plants
 
         public static bool IsSoil(BlockState state)
         {
-            if (state == BlockStates.Dirt() ||
-                state == BlockStates.GrassBlock())
+            if (state.IsId(BlockId.Dirt) ||
+                state.IsId(BlockId.GrassBlock))
             {
                 return true;
             }

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

@@ -14,5 +14,6 @@ namespace MineCase.Server.Game.Entities
 {
     public interface IPlayer : IEntity
     {
+        Task<uint> GetViewDistance();
     }
 }

+ 2 - 2
src/MineCase.sln

@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2003
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29709.97
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MineCase.Server", "MineCase.Server\MineCase.Server.csproj", "{8E71CBEC-5804-4125-B651-C78426E57C8C}"
 EndProject

+ 1 - 1
src/docker/docker-compose.ci.build.yml

@@ -1 +1 @@
-version: '3'
+version: '3.4'

+ 1 - 1
src/docker/docker-compose.dcproj

@@ -19,7 +19,7 @@
     </ProjectConfiguration>
   </ItemGroup>
   <PropertyGroup Label="Globals">
-    <ProjectVersion>2.0</ProjectVersion>
+    <ProjectVersion>2.1</ProjectVersion>
     <DockerTargetOS>Windows</DockerTargetOS>
     <ProjectGuid>2974a4be-85d2-454d-acde-b4be63993b95</ProjectGuid>
   </PropertyGroup>

+ 1 - 1
src/docker/docker-compose.yml

@@ -1,4 +1,4 @@
-version: '3'
+version: '3.4'
 
 services:
   minecase.server:

+ 2 - 2
tests/UnitTest/BlockStateTest.cs

@@ -28,12 +28,12 @@ namespace MineCase.UnitTest
         [Fact]
         public void IsIdTest()
         {
-            BlockState state1 = BlockStates.Stone(StoneType.Diorite);
+            BlockState state1 = BlockStates.Diorite();
             BlockState state2 = BlockStates.Leaves(LeaveType.Birch);
             BlockState state3 = BlockStates.Wood(WoodType.Birch);
             BlockState state4 = BlockStates.Wood(WoodType.Oak);
 
-            Assert.True(state1.IsId(BlockId.Stone));
+            Assert.True(state1.IsId(BlockId.Diorite));
 
             // Assert.True(state2.IsId(BlockId.Leaves));
             // Assert.True(state3.IsId(BlockId.Wood));