فهرست منبع

Add DropBlockLoader

JunWang 8 سال پیش
والد
کامیت
804076322e

+ 16 - 0
data/dropblock.txt

@@ -0,0 +1,16 @@
+#********************#
+# Drop Block Mapping #
+#********************#
+#
+#
+#******************************************************#
+#                Basic Notation Help
+#
+#
+#******************************************************#
+
+
+#Block DroppedBlock Hardness Tool Hand Wooden Stone Iron Diamand Golden Shears Sword 
+Barrier =           -, INF,             -, INF,    -,   -,   -,   -,   -, -, -
+Stone   = Cobblestone, 1.5, WoodenPickaxe, 7.5, 1.15, 0.6, 0.4, 0.3, 0.2, -, -
+

+ 31 - 0
src/MineCase.Algorithm/DropBlockMappingMatcher.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MineCase.Algorithm
+{
+    public class DropBlockMappingMatcher
+    {
+        private Dictionary<BlockState, DropBlockEntry> _mapping;
+
+        public DropBlockMappingMatcher(Dictionary<BlockState, DropBlockEntry> mapping)
+        {
+            _mapping = mapping;
+        }
+
+        public uint DropBlock(uint item, BlockState block)
+        {
+            if (_mapping.ContainsKey(block))
+            {
+                DropBlockEntry entry = _mapping[block];
+
+                // TODO drop test
+                return block.Id;
+            }
+            else
+            {
+                return uint.MaxValue;
+            }
+        }
+    }
+}

+ 250 - 0
src/MineCase.Core/DropBlockMappingLoader.cs

@@ -0,0 +1,250 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MineCase
+{
+    public class DropBlockMappingLoader
+    {
+        public Dictionary<BlockState, DropBlockEntry> Mapping { get; } = new Dictionary<BlockState, DropBlockEntry>();
+
+        public async Task LoadMapping(StreamReader streamReader)
+        {
+            while (!streamReader.EndOfStream)
+            {
+                var line = await streamReader.ReadLineAsync();
+                ParseLine(line);
+            }
+        }
+
+        private unsafe void ParseLine(string line)
+        {
+            var lineSpan = line.AsSpan();
+            var commentIndex = lineSpan.IndexOf('#');
+            if (commentIndex != -1)
+                lineSpan = lineSpan.Slice(0, commentIndex);
+            if (lineSpan.IsEmpty) return;
+
+            char* buffer = stackalloc char[lineSpan.Length];
+            int bufLen = 0;
+
+            // 删除空格
+            {
+                for (int i = 0; i < lineSpan.Length; i++)
+                {
+                    if (!char.IsWhiteSpace(lineSpan[i]))
+                        buffer[bufLen++] = lineSpan[i];
+                }
+            }
+
+            var normLine = new Span<char>(buffer, bufLen);
+            if (normLine.IsEmpty) return;
+            var splitter = normLine.IndexOf('=');
+
+            var resultSpan = normLine.Slice(0, splitter);
+            var ingredientsSpan = normLine.Slice(splitter + 1);
+
+            var recipe = new CraftingRecipe();
+            var resultSplitter = resultSpan.IndexOf(',');
+            if (resultSplitter == -1)
+            {
+                ParseItem(resultSpan, ref recipe.Output);
+                recipe.Output.ItemCount = 1;
+            }
+            else
+            {
+                ParseItem(resultSpan.Slice(0, resultSplitter), ref recipe.Output);
+                recipe.Output.ItemCount = byte.Parse(ToString(resultSpan.Slice(resultSplitter + 1)));
+            }
+
+            var recipeSlots = new List<CraftingRecipeSlot>();
+            var restSpan = ingredientsSpan;
+            while (!restSpan.IsEmpty)
+            {
+                var ingredientSplitter = restSpan.IndexOf('|');
+                var ingredientSpan = ingredientSplitter == -1 ? restSpan : restSpan.Slice(0, ingredientSplitter);
+                ParseIngredient(ingredientSpan, recipeSlots);
+                if (ingredientSplitter == -1)
+                    break;
+                else
+                    restSpan = restSpan.Slice(ingredientSplitter + 1);
+            }
+
+            recipe.Inputs = recipeSlots.ToArray();
+            NormalizeIngredients(recipe);
+
+            // Recipes.Add(recipe);
+        }
+
+        private void ParseIngredient(Span<char> ingredientSpan, ICollection<CraftingRecipeSlot> recipeSlots)
+        {
+            var slot = new Slot { ItemCount = 1 };
+            var splitter = ingredientSpan.IndexOf(',');
+            ParseItem(ingredientSpan.Slice(0, splitter), ref slot);
+            var distributionSpan = ingredientSpan.Slice(splitter + 1);
+
+            do
+            {
+                var positionSplitter = distributionSpan.IndexOf(',');
+                var positionSpan = positionSplitter == -1 ? distributionSpan : distributionSpan.Slice(0, positionSplitter);
+
+                var recipeSlot = new CraftingRecipeSlot { Slot = slot };
+                if (positionSpan.Length == 1 && positionSpan[0] == '*')
+                {
+                    recipeSlot.X = -1;
+                    recipeSlot.Y = -1;
+                }
+                else
+                {
+                    splitter = positionSpan.IndexOf(':');
+
+                    int ParsePoint(ReadOnlySpan<char> span)
+                    {
+                        if (span.Length != 1)
+                            throw new ArgumentOutOfRangeException(nameof(span));
+
+                        switch (span[0])
+                        {
+                            case '1':
+                                return 0;
+                            case '2':
+                                return 1;
+                            case '3':
+                                return 2;
+                            case '*':
+                                return -1;
+                            default:
+                                throw new ArgumentOutOfRangeException(nameof(span));
+                        }
+                    }
+
+                    recipeSlot.X = ParsePoint(positionSpan.Slice(0, splitter));
+                    recipeSlot.Y = ParsePoint(positionSpan.Slice(splitter + 1));
+                }
+
+                recipeSlots.Add(recipeSlot);
+
+                if (positionSplitter == -1)
+                    break;
+                else
+                    distributionSpan = distributionSpan.Slice(positionSplitter + 1);
+            }
+            while (true);
+        }
+
+        private void NormalizeIngredients(CraftingRecipe recipe)
+        {
+            int minX = 2, minY = 2;
+            int maxX = 0, maxY = 0;
+
+            for (int i = 0; i < recipe.Inputs.Length; i++)
+            {
+                ref var recipeSlot = ref recipe.Inputs[i];
+                if (recipeSlot.X >= 0)
+                {
+                    minX = Math.Min(minX, recipeSlot.X);
+                    maxX = Math.Max(maxX, recipeSlot.X);
+                }
+
+                if (recipeSlot.Y >= 0)
+                {
+                    minY = Math.Min(minY, recipeSlot.Y);
+                    maxY = Math.Max(maxY, recipeSlot.Y);
+                }
+            }
+
+            // 移动到左上角
+            for (int i = 0; i < recipe.Inputs.Length; i++)
+            {
+                ref var recipeSlot = ref recipe.Inputs[i];
+                if (recipeSlot.X >= 0)
+                    recipeSlot.X -= minX;
+
+                if (recipeSlot.Y >= 0)
+                    recipeSlot.Y -= minY;
+            }
+
+            recipe.Width = Math.Max(1, maxX - minX + 1);
+            recipe.Height = Math.Max(1, maxY - minY + 1);
+        }
+
+        private void ParseItem(Span<char> span, ref Slot slot)
+        {
+            var metaSplitter = span.IndexOf('^');
+            var idSpan = metaSplitter == -1 ? span : span.Slice(0, metaSplitter);
+
+            bool isBlock;
+            var text = ToString(idSpan);
+            if (Enum.TryParse(text, out ItemId item))
+            {
+                slot.BlockId = (short)item;
+                isBlock = false;
+            }
+            else if (Enum.TryParse(text, out BlockId block))
+            {
+                slot.BlockId = (short)block;
+                isBlock = true;
+            }
+            else
+            {
+                throw new ArgumentOutOfRangeException($"Invalid item name: {text}.");
+            }
+
+            if (metaSplitter != -1)
+            {
+                var metaText = ToString(span.Slice(metaSplitter + 1));
+                if (short.TryParse(metaText, out var value))
+                {
+                    slot.ItemDamage = value;
+                }
+                else
+                {
+                    var itemsType = isBlock ? typeof(BlockStates) : typeof(ItemStates);
+                    var paramEnum = itemsType.GetMethod(text).GetParameters()[0].ParameterType;
+                    slot.ItemDamage = Convert.ToInt16(Enum.Parse(paramEnum, metaText));
+                }
+            }
+            else
+            {
+                slot.ItemDamage = 0;
+            }
+        }
+
+        private static unsafe string ToString(ReadOnlySpan<char> span)
+        {
+            return new string((char*)Unsafe.AsPointer(ref span.DangerousGetPinnableReference()), 0, span.Length);
+        }
+    }
+
+    public enum Tools
+    {
+        Hand,
+        Axes,
+        PickAxes,
+        Shovels,
+        Hoes
+    }
+
+    public enum ToolMaterial
+    {
+        Hand,
+        Wooden,
+        Stone,
+        Iron,
+        Diamand,
+        Golden
+    }
+
+    public class DropBlockEntry
+    {
+        public BlockState TargetBlock;
+        public BlockState DroppedBlock;
+        public float Hardness;
+        public Tools Tool;
+        public float Shears;
+        public float Sword;
+    }
+}

+ 44 - 0
src/MineCase.Server.Grains/Game/DropBlockMappingGrain.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.FileProviders;
+using MineCase.Algorithm;
+using MineCase.Server.World;
+using Orleans;
+using Orleans.Concurrency;
+
+namespace MineCase.Server.Game
+{
+    [StatelessWorker]
+    internal class DropBlockMappingGrain : Grain, IDropBlockMapping
+    {
+        private const string _mappingFileName = "dropblock.txt";
+
+        private DropBlockMappingMatcher _mappingMatcher;
+        private IFileProvider _fileProvider;
+
+        public DropBlockMappingGrain()
+        {
+            _fileProvider = new PhysicalFileProvider(AppContext.BaseDirectory);
+        }
+
+        public Task<uint> DropBlock(uint itemId, BlockState block)
+        {
+            return Task.FromResult(_mappingMatcher.DropBlock(itemId, block));
+        }
+
+        public override async Task OnActivateAsync()
+        {
+            var file = _fileProvider.GetFileInfo(_mappingFileName);
+
+            var recipeLoader = new DropBlockMappingLoader();
+            using (var sr = new StreamReader(file.CreateReadStream()))
+                await recipeLoader.LoadMapping(sr);
+            _mappingMatcher = new DropBlockMappingMatcher(recipeLoader.Mapping);
+        }
+    }
+}

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

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Orleans;
+using Orleans.Concurrency;
+
+namespace MineCase.Server.Game
+{
+    public interface IDropBlockMapping : IGrainWithIntegerKey
+    {
+        Task<uint> DropBlock(uint itemId, BlockState block);
+    }
+}

+ 1 - 1
tests/UnitTest/CraftingRecipeTest.cs

@@ -69,7 +69,7 @@ namespace MineCase.UnitTest
             var recipe = matcher.FindRecipe(new Slot[,]
             {
                 { new Slot { BlockId = (short)BlockStates.WoodPlanks().Id, ItemCount = 1 }, Slot.Empty, Slot.Empty },
-                { new Slot { BlockId = (short)BlockStates.WoodPlanks().Id, ItemCount = 1 }, new Slot { BlockId = (short)ItemId.Stick, ItemCount = 1 }, new Slot { BlockId = (short)ItemId.Stick, ItemCount = 1 }},
+                { new Slot { BlockId = (short)BlockStates.WoodPlanks().Id, ItemCount = 1 }, new Slot { BlockId = (short)ItemId.Stick, ItemCount = 1 }, new Slot { BlockId = (short)ItemId.Stick, ItemCount = 1 } },
                 { new Slot { BlockId = (short)BlockStates.WoodPlanks().Id, ItemCount = 1 }, Slot.Empty, Slot.Empty }
             });
             Assert.NotNull(recipe);