diff --git a/src/MineCase.Core/Slot.cs b/src/MineCase.Core/Slot.cs index ca7bbb20..8c757886 100644 --- a/src/MineCase.Core/Slot.cs +++ b/src/MineCase.Core/Slot.cs @@ -35,6 +35,17 @@ public Slot WithItemCount(byte count) }; } + public Slot AddItemCount(int count) + { + return new Slot + { + BlockId = BlockId, + ItemCount = (byte)(ItemCount + count), + ItemDamage = ItemDamage, + NBT = NBT + }; + } + public void MakeEmptyIfZero() { if (ItemCount == 0) diff --git a/src/MineCase.Server.Grains/Components/AddressByPartitionKeyComponent.cs b/src/MineCase.Server.Grains/Components/AddressByPartitionKeyComponent.cs index fa0c5d58..d89b1afc 100644 --- a/src/MineCase.Server.Grains/Components/AddressByPartitionKeyComponent.cs +++ b/src/MineCase.Server.Grains/Components/AddressByPartitionKeyComponent.cs @@ -25,31 +25,44 @@ protected override Task OnAttached() { AttachedObject.RegisterPropertyChangedHandler(WorldComponent.WorldProperty, OnWorldChanged); AttachedObject.RegisterPropertyChangedHandler(EntityWorldPositionComponent.EntityWorldPositionProperty, OnEntityWorldPositionChanged); + AttachedObject.RegisterPropertyChangedHandler(BlockWorldPositionComponent.BlockWorldPositionProperty, OnBlockWorldPositionChanged); return Task.CompletedTask; } - private Task OnWorldChanged(object sender, PropertyChangedEventArgs e) + private Task OnBlockWorldPositionChanged(object sender, PropertyChangedEventArgs e) { - if (AttachedObject.TryGetEntityWorldPosition(out var entityPos)) - { - var chunkWorldPos = entityPos.ToChunkWorldPos(); - var key = e.NewValue.MakeAddressByPartitionKey(chunkWorldPos); - AttachedObject.SetLocalValue(AddressByPartitionKeyProperty, key); - } + UpdateKey(); + return Task.CompletedTask; + } + private Task OnWorldChanged(object sender, PropertyChangedEventArgs e) + { + UpdateKey(); return Task.CompletedTask; } private Task OnEntityWorldPositionChanged(object sender, PropertyChangedEventArgs e) + { + UpdateKey(); + return Task.CompletedTask; + } + + private void UpdateKey() { if (AttachedObject.TryGetWorld(out var world)) { - var chunkWorldPos = e.NewValue.ToChunkWorldPos(); - var key = world.MakeAddressByPartitionKey(chunkWorldPos); - AttachedObject.SetLocalValue(AddressByPartitionKeyProperty, key); - } + ChunkWorldPos? chunkWorldPos = null; + if (AttachedObject.TryGetLocalValue(EntityWorldPositionComponent.EntityWorldPositionProperty, out var entityPos)) + chunkWorldPos = entityPos.ToChunkWorldPos(); + else if (AttachedObject.TryGetLocalValue(BlockWorldPositionComponent.BlockWorldPositionProperty, out var blockPos)) + chunkWorldPos = blockPos.ToChunkWorldPos(); - return Task.CompletedTask; + if (chunkWorldPos.HasValue) + { + var key = world.MakeAddressByPartitionKey(chunkWorldPos.Value); + AttachedObject.SetLocalValue(AddressByPartitionKeyProperty, key); + } + } } private static async Task OnAddressByPartitionKeyChanged(object sender, PropertyChangedEventArgs e) diff --git a/src/MineCase.Server.Grains/Components/BlockWorldPositionComponent.cs b/src/MineCase.Server.Grains/Components/BlockWorldPositionComponent.cs new file mode 100644 index 00000000..30d0bb15 --- /dev/null +++ b/src/MineCase.Server.Grains/Components/BlockWorldPositionComponent.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using MineCase.Engine; +using MineCase.World; + +namespace MineCase.Server.Components +{ + internal class BlockWorldPositionComponent : Component + { + public static readonly DependencyProperty BlockWorldPositionProperty = + DependencyProperty.Register("BlockWorldPosition", typeof(BlockWorldPositionComponent)); + + public BlockWorldPos BlockWorldPosition => AttachedObject.GetValue(BlockWorldPositionProperty); + + public BlockWorldPositionComponent(string name = "blockWorldPosition") + : base(name) + { + } + + public Task SetBlockWorldPosition(BlockWorldPos value) => + AttachedObject.SetLocalValue(BlockWorldPositionProperty, value); + } +} diff --git a/src/MineCase.Server.Grains/Components/GameTickComponent.cs b/src/MineCase.Server.Grains/Components/GameTickComponent.cs index f597f753..c003a875 100644 --- a/src/MineCase.Server.Grains/Components/GameTickComponent.cs +++ b/src/MineCase.Server.Grains/Components/GameTickComponent.cs @@ -9,7 +9,7 @@ namespace MineCase.Server.Components { - internal class GameTickComponent : Component, IHandle + internal class GameTickComponent : Component, IHandle, IHandle { public event AsyncEventHandler<(TimeSpan deltaTime, long worldAge)> Tick; @@ -49,5 +49,12 @@ Task IHandle.Handle(GameTick message) { return Tick.InvokeSerial(this, (message.DeltaTime, message.WorldAge)); } + + async Task IHandle.Handle(Disable message) + { + var key = AttachedObject.GetAddressByPartitionKey(); + if (!string.IsNullOrEmpty(key)) + await GrainFactory.GetGrain(key).Unsubscribe(AttachedObject); + } } } diff --git a/src/MineCase.Server.Grains/Game/BlockEntities/BlockEntity.cs b/src/MineCase.Server.Grains/Game/BlockEntities/BlockEntity.cs index c80b40af..96bb9f88 100644 --- a/src/MineCase.Server.Grains/Game/BlockEntities/BlockEntity.cs +++ b/src/MineCase.Server.Grains/Game/BlockEntities/BlockEntity.cs @@ -29,17 +29,17 @@ select new }).ToDictionary(o => o.BlockId, o => o.Method); } - private static IBlockEntity GetBlockEntity(IGrainFactory grainFactory, string key) + private static IBlockEntity GetBlockEntity(IGrainFactory grainFactory, Guid key) where TGrain : IBlockEntity { return grainFactory.GetGrain(key).Cast(); } - public static IBlockEntity Create(IGrainFactory grainFactory, IWorld world, BlockWorldPos position, BlockId blockId) + public static IBlockEntity Create(IGrainFactory grainFactory, BlockId blockId) { if (_blockEntityTypes.TryGetValue(blockId, out var method)) { - var key = world.MakeBlockEntityKey(position); + var key = Guid.NewGuid(); return (IBlockEntity)method.Invoke(null, new object[] { grainFactory, key }); } diff --git a/src/MineCase.Server.Grains/Game/BlockEntities/BlockEntityGrain.cs b/src/MineCase.Server.Grains/Game/BlockEntities/BlockEntityGrain.cs index fd0f9e5b..425484e5 100644 --- a/src/MineCase.Server.Grains/Game/BlockEntities/BlockEntityGrain.cs +++ b/src/MineCase.Server.Grains/Game/BlockEntities/BlockEntityGrain.cs @@ -2,34 +2,35 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using MineCase.Engine; +using MineCase.Server.Components; +using MineCase.Server.Game.BlockEntities.Components; using MineCase.Server.World; using MineCase.World; using Orleans; namespace MineCase.Server.Game.BlockEntities { - internal abstract class BlockEntityGrain : Grain, IBlockEntity + internal abstract class BlockEntityGrain : DependencyObject, IBlockEntity { - protected IWorld World { get; private set; } + public IWorld World => GetValue(WorldComponent.WorldProperty); - protected BlockWorldPos Position { get; private set; } + public BlockWorldPos Position => GetValue(BlockWorldPositionComponent.BlockWorldPositionProperty); - public override Task OnActivateAsync() + protected override async Task InitializeComponents() { - var keys = this.GetWorldAndBlockEntityPosition(); - World = GrainFactory.GetGrain(keys.worldKey); - Position = keys.position; - return base.OnActivateAsync(); + await SetComponent(new WorldComponent()); + await SetComponent(new BlockWorldPositionComponent()); + await SetComponent(new AddressByPartitionKeyComponent()); + await SetComponent(new ChunkEventBroadcastComponent()); + await SetComponent(new GameTickComponent()); + await SetComponent(new BlockEntityLiftTimeComponent()); } - public virtual Task Destroy() - { - return Task.CompletedTask; - } + Task IBlockEntity.GetWorld() => + Task.FromResult(World); - public virtual Task OnCreated() - { - return Task.CompletedTask; - } + Task IBlockEntity.GetPosition() => + Task.FromResult(Position); } } diff --git a/src/MineCase.Server.Grains/Game/BlockEntities/ChestBlockEntityGrain.cs b/src/MineCase.Server.Grains/Game/BlockEntities/ChestBlockEntityGrain.cs index 35d53f7e..2d63813f 100644 --- a/src/MineCase.Server.Grains/Game/BlockEntities/ChestBlockEntityGrain.cs +++ b/src/MineCase.Server.Grains/Game/BlockEntities/ChestBlockEntityGrain.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using MineCase.Server.Game.BlockEntities.Components; using MineCase.Server.Game.Entities; using MineCase.Server.Game.Entities.Components; using MineCase.Server.Game.Windows; @@ -15,84 +16,11 @@ namespace MineCase.Server.Game.BlockEntities [Reentrant] internal class ChestBlockEntityGrain : BlockEntityGrain, IChestBlockEntity { - private Slot[] _slots; - private IChestBlockEntity _neightborEntity; - - public override Task OnActivateAsync() - { - _slots = Enumerable.Repeat(Slot.Empty, ChestSlotArea.ChestSlotsCount).ToArray(); - return base.OnActivateAsync(); - } - - public Task GetSlot(int slotIndex) - { - return Task.FromResult(_slots[slotIndex]); - } - - public Task SetSlot(int slotIndex, Slot item) + protected override async Task InitializeComponents() { - _slots[slotIndex] = item; - return Task.CompletedTask; - } - - private IChestWindow _chestWindow; - - public async Task UseBy(IPlayer player) - { - var masterEntity = FindMasterEntity(_neightborEntity); - if (masterEntity.GetPrimaryKeyString() == this.GetPrimaryKeyString()) - { - if (_chestWindow == null) - { - _chestWindow = GrainFactory.GetGrain(Guid.NewGuid()); - await _chestWindow.SetEntities((_neightborEntity == null ? - new[] { this.AsReference() } : new[] { this.AsReference(), _neightborEntity }).AsImmutable()); - } - - await player.Tell(new OpenWindow { Window = _chestWindow }); - } - else - { - await masterEntity.UseBy(player); - } - } - - public override async Task Destroy() - { - if (_chestWindow != null) - await _chestWindow.Destroy(); - } - - public async Task ClearNeighborEntity() - { - _neightborEntity = null; - if (_chestWindow != null) - { - await _chestWindow.Destroy(); - await _chestWindow.SetEntities(new[] { this.AsReference() }.AsImmutable()); - } - } - - public async Task SetNeighborEntity(IChestBlockEntity chestEntity) - { - _neightborEntity = chestEntity; - if (_chestWindow != null) - { - await _chestWindow.Destroy(); - await _chestWindow.SetEntities(new[] { this.AsReference(), chestEntity }.AsImmutable()); - } - } - - private IChestBlockEntity FindMasterEntity(IChestBlockEntity neighborEntity) - { - if (neighborEntity == null) - return this.AsReference(); - - // 按 X, Z 排序取最小 - return (from e in new[] { this.AsReference(), neighborEntity } - let pos = e.GetBlockEntityPosition() - orderby pos.X, pos.Z - select e).First(); + await base.InitializeComponents(); + await SetComponent(new SlotContainerComponent(ChestSlotArea.ChestSlotsCount)); + await SetComponent(new ChestComponent()); } } } diff --git a/src/MineCase.Server.Grains/Game/BlockEntities/Components/BlockEntityLiftTimeComponent.cs b/src/MineCase.Server.Grains/Game/BlockEntities/Components/BlockEntityLiftTimeComponent.cs new file mode 100644 index 00000000..a4ccc8a7 --- /dev/null +++ b/src/MineCase.Server.Grains/Game/BlockEntities/Components/BlockEntityLiftTimeComponent.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using MineCase.Engine; +using MineCase.Server.Components; + +namespace MineCase.Server.Game.BlockEntities.Components +{ + internal class BlockEntityLiftTimeComponent : Component, IHandle, IHandle + { + public BlockEntityLiftTimeComponent(string name = "blockEntityLifeTime") + : base(name) + { + } + + async Task IHandle.Handle(SpawnBlockEntity message) + { + await AttachedObject.GetComponent().SetWorld(message.World); + await AttachedObject.GetComponent().SetBlockWorldPosition(message.Position); + } + + Task IHandle.Handle(DestroyBlockEntity message) + { + return AttachedObject.Tell(Disable.Default); + } + } +} diff --git a/src/MineCase.Server.Grains/Game/BlockEntities/Components/ChestComponent.cs b/src/MineCase.Server.Grains/Game/BlockEntities/Components/ChestComponent.cs new file mode 100644 index 00000000..d9a571bb --- /dev/null +++ b/src/MineCase.Server.Grains/Game/BlockEntities/Components/ChestComponent.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MineCase.Engine; +using MineCase.Server.Game.Entities.Components; +using MineCase.Server.Game.Windows; +using MineCase.World; +using Orleans; +using Orleans.Concurrency; + +namespace MineCase.Server.Game.BlockEntities.Components +{ + internal class ChestComponent : Component, IHandle, IHandle, IHandle + { + public static readonly DependencyProperty NeighborEntityProperty = + DependencyProperty.Register("NeighborEntity", typeof(ChestComponent), new PropertyMetadata(null, OnNeighborEntityChanged)); + + public static readonly DependencyProperty ChestWindowProperty = + DependencyProperty.Register("ChestWindow", typeof(ChestComponent)); + + public IBlockEntity NeighborEntity => AttachedObject.GetValue(NeighborEntityProperty); + + public IChestWindow ChestWindow => AttachedObject.GetValue(ChestWindowProperty); + + public ChestComponent(string name = "chest") + : base(name) + { + } + + Task IHandle.Handle(NeighborEntityChanged message) => + AttachedObject.SetLocalValue(NeighborEntityProperty, message.Entity); + + private static async Task OnNeighborEntityChanged(object sender, PropertyChangedEventArgs e) + { + var component = ((DependencyObject)sender).GetComponent(); + var window = component?.ChestWindow; + if (window == null) return; + if (e.NewValue == null) + { + await window.Destroy(); + await window.SetEntities(new[] { component.AttachedObject.AsReference() }.AsImmutable()); + } + else + { + await window.Destroy(); + await window.SetEntities(new[] { component.AttachedObject.AsReference(), e.NewValue.AsReference() }.AsImmutable()); + } + } + + async Task IHandle.Handle(DestroyBlockEntity message) + { + if (ChestWindow != null) + await ChestWindow.Destroy(); + } + + async Task IHandle.Handle(UseBy message) + { + var masterEntity = await FindMasterEntity(NeighborEntity); + if (masterEntity.GetPrimaryKey() == AttachedObject.GetPrimaryKey()) + { + if (ChestWindow == null) + { + await AttachedObject.SetLocalValue(ChestWindowProperty, GrainFactory.GetGrain(Guid.NewGuid())); + await ChestWindow.SetEntities((NeighborEntity == null ? + new[] { AttachedObject.AsReference() } : + new[] { AttachedObject.AsReference(), NeighborEntity }).AsImmutable()); + } + + await message.Player.Tell(new OpenWindow { Window = ChestWindow }); + } + else + { + await masterEntity.Tell(message); + } + } + + private async Task FindMasterEntity(IBlockEntity neighborEntity) + { + if (NeighborEntity == null) + return AttachedObject.AsReference(); + + async Task<(IBlockEntity entity, BlockWorldPos position)> GetPosition(IBlockEntity entity) => + (entity, await entity.GetPosition()); + + // 按 X, Z 排序取最小 + return (from e in await Task.WhenAll(new[] { GetPosition(AttachedObject.AsReference()), GetPosition(NeighborEntity) }) + orderby e.position.X, e.position.Z + select e.entity).First(); + } + } +} diff --git a/src/MineCase.Server.Grains/Game/BlockEntities/Components/FurnaceComponent.cs b/src/MineCase.Server.Grains/Game/BlockEntities/Components/FurnaceComponent.cs new file mode 100644 index 00000000..0fe1845e --- /dev/null +++ b/src/MineCase.Server.Grains/Game/BlockEntities/Components/FurnaceComponent.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using MineCase.Engine; +using MineCase.Game.Windows; +using MineCase.Server.Components; +using MineCase.Server.Game.Entities.Components; +using MineCase.Server.Game.Windows; +using MineCase.Server.World; + +namespace MineCase.Server.Game.BlockEntities.Components +{ + internal class FurnaceComponent : Component, IHandle, IHandle, IHandle, IHandle + { + private bool _isCooking; + private FurnaceRecipe _currentRecipe; + private FurnaceFuel _currentFuel; + private int _fuelLeft; + private int _maxFuelTime; + private int _cookProgress; + private int _maxProgress; + + public static readonly DependencyProperty FurnaceWindowProperty = + DependencyProperty.Register("FurnaceWindow", typeof(FurnaceComponent)); + + public IFurnaceWindow FurnaceWindow => AttachedObject.GetValue(FurnaceWindowProperty); + + public FurnaceComponent(string name = "furnace") + : base(name) + { + } + + protected override Task OnAttached() + { + _currentRecipe = null; + _isCooking = false; + _fuelLeft = 0; + _maxFuelTime = 0; + _cookProgress = 0; + _maxFuelTime = 200; + Register(); + return base.OnAttached(); + } + + private bool CanCook() => _currentRecipe != null && (_fuelLeft > 0 || _currentFuel != null); + + private async Task OnGameTick(object sender, (TimeSpan deltaTime, long worldAge) e) + { + if (CanCook()) + { + if (!_isCooking) + await StartCooking(); + if (_cookProgress == 0) + await TakeIngredient(); + if (_fuelLeft == 0) + await TakeFuel(); + + if (_currentRecipe != null) + { + _cookProgress++; + _fuelLeft--; + + if (FurnaceWindow != null && e.worldAge % 10 == 0) + { + await FurnaceWindow.SetProperty(FurnaceWindowPropertyType.ProgressArrow, (short)_cookProgress); + await FurnaceWindow.SetProperty(FurnaceWindowPropertyType.FireIcon, (short)_fuelLeft); + } + } + + if (_cookProgress == _maxProgress) + await Produce(); + } + else if (_isCooking) + { + await StopCooking(); + } + } + + async Task IHandle.Handle(SetSlot message) + { + if (_currentRecipe == null && message.Index == 0) + await UpdateRecipe(); + if (message.Index == 1) + await UpdateFuel(); + if (!_isCooking && CanCook()) + Register(); + } + + private Slot GetSlot(int index) => + AttachedObject.GetComponent().GetSlot(index); + + private Task SetSlot(int index, Slot slot) => + AttachedObject.GetComponent().SetSlot(index, slot); + + private async Task UpdateFuel() + { + _currentFuel = await GrainFactory.GetGrain(0).FindFuel(GetSlot(1)); + } + + private async Task UpdateRecipe() + { + var recipe = await GrainFactory.GetGrain(0).FindRecipe(GetSlot(0)); + if (recipe != null) + { + if (GetSlot(2).IsEmpty || GetSlot(2).CanStack(recipe.Output)) + { + _currentRecipe = recipe; + return; + } + } + + _currentRecipe = null; + } + + private async Task Produce() + { + if (GetSlot(2).IsEmpty) + await SetSlot(2, _currentRecipe.Output); + else + await SetSlot(2, GetSlot(2).AddItemCount(_currentRecipe.Output.ItemCount)); + _cookProgress = 0; + if (FurnaceWindow != null) + { + await FurnaceWindow.BroadcastSlotChanged(2, GetSlot(2)); + await FurnaceWindow.SetProperty(FurnaceWindowPropertyType.ProgressArrow, (short)_cookProgress); + } + } + + private async Task TakeFuel() + { + var slot = GetSlot(1).AddItemCount(-_currentFuel.Slot.ItemCount); + slot.MakeEmptyIfZero(); + await SetSlot(1, slot); + _maxFuelTime = _fuelLeft = _currentFuel.Time; + if (FurnaceWindow != null) + { + await FurnaceWindow.BroadcastSlotChanged(1, slot); + await FurnaceWindow.SetProperty(FurnaceWindowPropertyType.MaximumFuelBurnTime, (short)_maxFuelTime); + await FurnaceWindow.SetProperty(FurnaceWindowPropertyType.FireIcon, (short)_fuelLeft); + } + + await UpdateFuel(); + } + + private async Task TakeIngredient() + { + await UpdateRecipe(); + if (_currentRecipe != null) + { + var slot = GetSlot(0).AddItemCount(-_currentRecipe.Input.ItemCount); + slot.MakeEmptyIfZero(); + await SetSlot(0, slot); + _cookProgress = 0; + _maxProgress = _currentRecipe.Time; + if (FurnaceWindow != null) + { + await FurnaceWindow.BroadcastSlotChanged(0, slot); + await FurnaceWindow.SetProperty(FurnaceWindowPropertyType.ProgressArrow, (short)_cookProgress); + await FurnaceWindow.SetProperty(FurnaceWindowPropertyType.MaximumProgress, (short)_maxProgress); + } + } + } + + private async Task StartCooking() + { + _isCooking = true; + var meta = (await AttachedObject.World.GetBlockState(GrainFactory, AttachedObject.Position)).MetaValue; + await AttachedObject.World.SetBlockState(GrainFactory, AttachedObject.Position, new BlockState { Id = (uint)BlockId.BurningFurnace, MetaValue = meta }); + } + + private async Task StopCooking() + { + _isCooking = false; + var meta = (await AttachedObject.World.GetBlockState(GrainFactory, AttachedObject.Position)).MetaValue; + await AttachedObject.World.SetBlockState(GrainFactory, AttachedObject.Position, new BlockState { Id = (uint)BlockId.Furnace, MetaValue = meta }); + Unregister(); + } + + private void Register() + { + AttachedObject.GetComponent() + .Tick += OnGameTick; + } + + private void Unregister() + { + AttachedObject.GetComponent() + .Tick -= OnGameTick; + } + + async Task IHandle.Handle(SpawnBlockEntity message) + { + _isCooking = (await AttachedObject.World.GetBlockState(GrainFactory, AttachedObject.Position)) + .IsSameId(BlockStates.BurningFurnace()); + } + + Task IHandle.Handle(DestroyBlockEntity message) + { + _isCooking = false; + Unregister(); + return Task.CompletedTask; + } + + async Task IHandle.Handle(UseBy message) + { + if (FurnaceWindow == null) + { + await AttachedObject.SetLocalValue(FurnaceWindowProperty, GrainFactory.GetGrain(Guid.NewGuid())); + await FurnaceWindow.SetEntity(AttachedObject); + } + + await message.Player.Tell(new OpenWindow { Window = FurnaceWindow }); + } + } +} diff --git a/src/MineCase.Server.Grains/Game/BlockEntities/FurnaceBlockEntity.cs b/src/MineCase.Server.Grains/Game/BlockEntities/FurnaceBlockEntity.cs index e2f71448..571fbbf1 100644 --- a/src/MineCase.Server.Grains/Game/BlockEntities/FurnaceBlockEntity.cs +++ b/src/MineCase.Server.Grains/Game/BlockEntities/FurnaceBlockEntity.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using MineCase.Algorithm; using MineCase.Game.Windows; +using MineCase.Server.Game.BlockEntities.Components; using MineCase.Server.Game.Entities; using MineCase.Server.Game.Entities.Components; using MineCase.Server.Game.Windows; @@ -17,202 +18,11 @@ namespace MineCase.Server.Game.BlockEntities [Reentrant] internal class FurnaceBlockEntity : BlockEntityGrain, IFurnaceBlockEntity { - private Slot[] _slots; - - private bool _isCooking; - private FurnaceRecipe _currentRecipe; - private FurnaceFuel _currentFuel; - private int _fuelLeft; - private int _maxFuelTime; - private int _cookProgress; - private int _maxProgress; - - public override Task OnActivateAsync() - { - _slots = Enumerable.Repeat(Slot.Empty, FurnaceSlotArea.FurnaceSlotsCount).ToArray(); - _currentRecipe = null; - _isCooking = false; - _fuelLeft = 0; - _maxFuelTime = 0; - _cookProgress = 0; - _maxFuelTime = 200; - return base.OnActivateAsync(); - } - - public Task GetSlot(int slotIndex) - { - return Task.FromResult(_slots[slotIndex]); - } - - public async Task SetSlot(int slotIndex, Slot item) - { - _slots[slotIndex] = item; - if (_currentRecipe == null && slotIndex == 0) - await UpdateRecipe(); - if (slotIndex == 1) - await UpdateFuel(); - if (!_isCooking && CanCook()) - await Register(); - } - - private async Task UpdateFuel() - { - _currentFuel = await GrainFactory.GetGrain(0).FindFuel(_slots[1]); - } - - private async Task UpdateRecipe() - { - var recipe = await GrainFactory.GetGrain(0).FindRecipe(_slots[0]); - if (recipe != null) - { - if (_slots[2].IsEmpty || _slots[2].CanStack(recipe.Output)) - { - _currentRecipe = recipe; - return; - } - } - - _currentRecipe = null; - } - - private IFurnaceWindow _furnaceWindow; - - public async Task UseBy(IPlayer player) - { - if (_furnaceWindow == null) - { - _furnaceWindow = GrainFactory.GetGrain(Guid.NewGuid()); - await _furnaceWindow.SetEntity(this); - } - - await player.Tell(new OpenWindow { Window = _furnaceWindow }); - } - - public override async Task OnCreated() - { - _isCooking = (await World.GetBlockState(GrainFactory, Position)).IsSameId(BlockStates.BurningFurnace()); - } - - public override async Task Destroy() - { - _isCooking = false; - await Unregister(); - } - - private bool CanCook() => _currentRecipe != null && (_fuelLeft > 0 || _currentFuel != null); - - public async Task OnGameTick(TimeSpan deltaTime, long worldAge) - { - if (CanCook()) - { - if (!_isCooking) - await StartCooking(); - if (_cookProgress == 0) - await TakeIngredient(); - if (_fuelLeft == 0) - await TakeFuel(); - - if (_currentRecipe != null) - { - _cookProgress++; - _fuelLeft--; - - if (_furnaceWindow != null && worldAge % 10 == 0) - { - await _furnaceWindow.SetProperty(FurnaceWindowProperty.ProgressArrow, (short)_cookProgress); - await _furnaceWindow.SetProperty(FurnaceWindowProperty.FireIcon, (short)_fuelLeft); - } - } - - if (_cookProgress == _maxProgress) - await Produce(); - } - else if (_isCooking) - { - await StopCooking(); - } - } - - private async Task Produce() - { - if (_slots[2].IsEmpty) - _slots[2] = _currentRecipe.Output; - else - _slots[2].ItemCount += _currentRecipe.Output.ItemCount; - _cookProgress = 0; - if (_furnaceWindow != null) - { - await _furnaceWindow.BroadcastSlotChanged(2, _slots[2]); - await _furnaceWindow.SetProperty(FurnaceWindowProperty.ProgressArrow, (short)_cookProgress); - } - } - - private async Task TakeFuel() - { - _slots[1].ItemCount -= _currentFuel.Slot.ItemCount; - _slots[1].MakeEmptyIfZero(); - _maxFuelTime = _fuelLeft = _currentFuel.Time; - if (_furnaceWindow != null) - { - await _furnaceWindow.BroadcastSlotChanged(1, _slots[1]); - await _furnaceWindow.SetProperty(FurnaceWindowProperty.MaximumFuelBurnTime, (short)_maxFuelTime); - await _furnaceWindow.SetProperty(FurnaceWindowProperty.FireIcon, (short)_fuelLeft); - } - - await UpdateFuel(); - } - - private async Task TakeIngredient() - { - await UpdateRecipe(); - if (_currentRecipe != null) - { - _slots[0].ItemCount -= _currentRecipe.Input.ItemCount; - _slots[0].MakeEmptyIfZero(); - _cookProgress = 0; - _maxProgress = _currentRecipe.Time; - if (_furnaceWindow != null) - { - await _furnaceWindow.BroadcastSlotChanged(0, _slots[0]); - await _furnaceWindow.SetProperty(FurnaceWindowProperty.ProgressArrow, (short)_cookProgress); - await _furnaceWindow.SetProperty(FurnaceWindowProperty.MaximumProgress, (short)_maxProgress); - } - } - } - - private async Task StartCooking() - { - _isCooking = true; - var meta = (await World.GetBlockState(GrainFactory, Position)).MetaValue; - await World.SetBlockState(GrainFactory, Position, new BlockState { Id = (uint)BlockId.BurningFurnace, MetaValue = meta }); - } - - private async Task StopCooking() - { - _isCooking = false; - var meta = (await World.GetBlockState(GrainFactory, Position)).MetaValue; - await World.SetBlockState(GrainFactory, Position, new BlockState { Id = (uint)BlockId.Furnace, MetaValue = meta }); - await Unregister(); - } - - private Task Register() - { - /* - var chunkPos = Position.ToChunkWorldPos(); - var tracker = GrainFactory.GetGrain(World.MakeChunkTrackingHubKey(chunkPos.X, chunkPos.Z)); - await tracker.Subscribe(this); - */ - return Task.CompletedTask; - } - - private Task Unregister() + protected override async Task InitializeComponents() { - /* - var chunkPos = Position.ToChunkWorldPos(); - var tracker = GrainFactory.GetGrain(World.MakeChunkTrackingHubKey(chunkPos.X, chunkPos.Z)); - await tracker.Unsubscribe(this); - */ - return Task.CompletedTask; + await base.InitializeComponents(); + await SetComponent(new SlotContainerComponent(FurnaceSlotArea.FurnaceSlotsCount)); + await SetComponent(new FurnaceComponent()); } } } diff --git a/src/MineCase.Server.Grains/Game/Blocks/ChestBlockHandler.cs b/src/MineCase.Server.Grains/Game/Blocks/ChestBlockHandler.cs index 3df80361..2b3b2283 100644 --- a/src/MineCase.Server.Grains/Game/Blocks/ChestBlockHandler.cs +++ b/src/MineCase.Server.Grains/Game/Blocks/ChestBlockHandler.cs @@ -50,23 +50,26 @@ public override async Task CanBeAt(BlockWorldPos position, IGrainFactory g public override async Task UseBy(IPlayer player, IGrainFactory grainFactory, IWorld world, BlockWorldPos blockPosition, Vector3 cursorPosition) { - var entity = (await world.GetBlockEntity(grainFactory, blockPosition)).Cast(); - await entity.UseBy(player); + var entity = await world.GetBlockEntity(grainFactory, blockPosition); + await entity.Tell(new UseBy { Player = player }); } public override async Task OnNeighborChanged(BlockWorldPos selfPosition, BlockWorldPos neighborPosition, BlockState oldState, BlockState newState, IGrainFactory grainFactory, IWorld world) { if (oldState.Id == (uint)BlockId.Chest) { - var entity = (await world.GetBlockEntity(grainFactory, selfPosition)).Cast(); - await entity.ClearNeighborEntity(); + var entity = await world.GetBlockEntity(grainFactory, selfPosition); + await entity.Tell(NeighborEntityChanged.Empty); } if (newState.Id == (uint)BlockId.Chest) { await world.SetBlockState(grainFactory, selfPosition, newState); - var entity = (await world.GetBlockEntity(grainFactory, selfPosition)).Cast(); - await entity.SetNeighborEntity((await world.GetBlockEntity(grainFactory, neighborPosition)).Cast()); + var entity = await world.GetBlockEntity(grainFactory, selfPosition); + await entity.Tell(new NeighborEntityChanged + { + Entity = await world.GetBlockEntity(grainFactory, neighborPosition) + }); } } @@ -86,9 +89,9 @@ public override async Task OnPlaced(IPlayer player, IGrainFactory grainFactory, if (neighborPosition.HasValue) { - var entity = (await world.GetBlockEntity(grainFactory, position)).Cast(); - var neightborEntity = (await world.GetBlockEntity(grainFactory, neighborPosition.Value)).Cast(); - await entity.SetNeighborEntity(neightborEntity); + var entity = await world.GetBlockEntity(grainFactory, position); + var neightborEntity = await world.GetBlockEntity(grainFactory, neighborPosition.Value); + await entity.Tell(new NeighborEntityChanged { Entity = neightborEntity }); } } diff --git a/src/MineCase.Server.Grains/Game/Blocks/FurnaceBlockHandler.cs b/src/MineCase.Server.Grains/Game/Blocks/FurnaceBlockHandler.cs index fab62257..4331dfed 100644 --- a/src/MineCase.Server.Grains/Game/Blocks/FurnaceBlockHandler.cs +++ b/src/MineCase.Server.Grains/Game/Blocks/FurnaceBlockHandler.cs @@ -25,7 +25,7 @@ public FurnaceBlockHandler(BlockId blockId) public override async Task UseBy(IPlayer player, IGrainFactory grainFactory, IWorld world, BlockWorldPos blockPosition, Vector3 cursorPosition) { var entity = (await world.GetBlockEntity(grainFactory, blockPosition)).Cast(); - await entity.UseBy(player); + await entity.Tell(new UseBy { Player = player }); } } } diff --git a/src/MineCase.Server.Grains/Game/Entities/Components/DiscoveryRegisterComponent.cs b/src/MineCase.Server.Grains/Game/Entities/Components/DiscoveryRegisterComponent.cs new file mode 100644 index 00000000..843ee033 --- /dev/null +++ b/src/MineCase.Server.Grains/Game/Entities/Components/DiscoveryRegisterComponent.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using MineCase.Engine; +using MineCase.Server.Components; +using MineCase.Server.World; + +namespace MineCase.Server.Game.Entities.Components +{ + internal class DiscoveryRegisterComponent : Component + { + public DiscoveryRegisterComponent(string name = "discoveryRegister") + : base(name) + { + } + + protected override async Task OnAttached() + { + await base.OnAttached(); + AttachedObject.GetComponent() + .KeyChanged += AddressByPartitionKeyChanged; + } + + private async Task AddressByPartitionKeyChanged(object sender, (string oldKey, string newKey) e) + { + if (!string.IsNullOrEmpty(e.oldKey)) + await GrainFactory.GetGrain(e.oldKey).UnsubscribeDiscovery(AttachedObject); + if (!string.IsNullOrEmpty(e.newKey)) + await GrainFactory.GetGrain(e.newKey).SubscribeDiscovery(AttachedObject); + } + } +} diff --git a/src/MineCase.Server.Grains/Game/Entities/Components/EntityLifeTimeComponent.cs b/src/MineCase.Server.Grains/Game/Entities/Components/EntityLifeTimeComponent.cs index 4e3683c6..45db34c2 100644 --- a/src/MineCase.Server.Grains/Game/Entities/Components/EntityLifeTimeComponent.cs +++ b/src/MineCase.Server.Grains/Game/Entities/Components/EntityLifeTimeComponent.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using MineCase.Engine; using MineCase.Server.Components; +using MineCase.Server.Game.BlockEntities; namespace MineCase.Server.Game.Entities.Components { @@ -21,6 +22,7 @@ async Task IHandle.Handle(SpawnEntity message) var lookComponent = AttachedObject.GetComponent(); await lookComponent.SetPitch(message.Pitch); await lookComponent.SetYaw(message.Yaw); + await AttachedObject.Tell(BroadcastDiscovered.Default); } } } diff --git a/src/MineCase.Server.Grains/Game/Entities/Components/KeepAliveComponent.cs b/src/MineCase.Server.Grains/Game/Entities/Components/KeepAliveComponent.cs index 1b5de1b7..8230410f 100644 --- a/src/MineCase.Server.Grains/Game/Entities/Components/KeepAliveComponent.cs +++ b/src/MineCase.Server.Grains/Game/Entities/Components/KeepAliveComponent.cs @@ -8,7 +8,7 @@ namespace MineCase.Server.Game.Entities.Components { - internal class KeepAliveComponent : Component + internal class KeepAliveComponent : Component, IHandle, IHandle { private uint _keepAliveId = 0; public readonly HashSet _keepAliveWaiters = new HashSet(); @@ -37,22 +37,6 @@ public KeepAliveComponent(string name = "keepAlive") { } - protected override Task OnAttached() - { - AttachedObject.GetComponent() - .Tick += OnGameTick; - _isOnline = true; - return base.OnAttached(); - } - - protected override Task OnDetached() - { - AttachedObject.GetComponent() - .Tick -= OnGameTick; - _keepAliveWaiters.Clear(); - return base.OnDetached(); - } - public Task ReceiveResponse(uint keepAliveId) { _keepAliveWaiters.Remove(keepAliveId); @@ -66,9 +50,9 @@ private async Task OnGameTick(object sender, (TimeSpan deltaTime, long worldAge) if (_isOnline && _keepAliveWaiters.Count >= ClientKeepInterval) { _isOnline = false; - await AttachedObject.GetComponent().Kick(); + await AttachedObject.Tell(new KickPlayer()); } - else + else if (e.worldAge % 20 == 0) { var id = _keepAliveId++; _keepAliveWaiters.Add(id); @@ -76,5 +60,22 @@ private async Task OnGameTick(object sender, (TimeSpan deltaTime, long worldAge) await AttachedObject.GetComponent().GetGenerator().KeepAlive(id); } } + + Task IHandle.Handle(PlayerLoggedIn message) + { + _keepAliveWaiters.Clear(); + _isOnline = true; + AttachedObject.GetComponent() + .Tick += OnGameTick; + return Task.CompletedTask; + } + + Task IHandle.Handle(KickPlayer message) + { + AttachedObject.GetComponent() + .Tick -= OnGameTick; + _isOnline = false; + return Task.CompletedTask; + } } } diff --git a/src/MineCase.Server.Grains/Game/Entities/Components/PickupDiscoveryComponent.cs b/src/MineCase.Server.Grains/Game/Entities/Components/PickupDiscoveryComponent.cs new file mode 100644 index 00000000..b6922750 --- /dev/null +++ b/src/MineCase.Server.Grains/Game/Entities/Components/PickupDiscoveryComponent.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using MineCase.Engine; +using MineCase.Server.Components; +using MineCase.Server.Game.BlockEntities; +using MineCase.Server.Network; +using MineCase.Server.Network.Play; + +namespace MineCase.Server.Game.Entities.Components +{ + internal class PickupDiscoveryComponent : Component, IHandle, IHandle + { + public PickupDiscoveryComponent(string name = "pickupDiscovery") + : base(name) + { + } + + private ClientPlayPacketGenerator GetPlayerPacketGenerator(IPlayer player) => + new ClientPlayPacketGenerator(new ForwardToPlayerPacketSink(player, ServiceProvider.GetRequiredService())); + + Task IHandle.Handle(DiscoveredByPlayer message) + { + return GetPlayerPacketGenerator(message.Player) + .SpawnObject(AttachedObject.EntityId, AttachedObject.UUID, 0, AttachedObject.Position, AttachedObject.Pitch, AttachedObject.Yaw, 0); + } + + Task IHandle.Handle(BroadcastDiscovered message) + { + return AttachedObject.GetComponent().GetGenerator() + .SpawnObject(AttachedObject.EntityId, AttachedObject.UUID, 0, AttachedObject.Position, AttachedObject.Pitch, AttachedObject.Yaw, 0); + } + } +} diff --git a/src/MineCase.Server.Grains/Game/Entities/Components/PlayerDiscoveryComponent.cs b/src/MineCase.Server.Grains/Game/Entities/Components/PlayerDiscoveryComponent.cs new file mode 100644 index 00000000..8775f839 --- /dev/null +++ b/src/MineCase.Server.Grains/Game/Entities/Components/PlayerDiscoveryComponent.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using MineCase.Engine; +using MineCase.Server.Components; +using MineCase.Server.Game.BlockEntities; + +namespace MineCase.Server.Game.Entities.Components +{ + internal class PlayerDiscoveryComponent : Component, IHandle + { + public PlayerDiscoveryComponent(string name = "playerDiscovery") + : base(name) + { + } + + Task IHandle.Handle(DiscoveredByPlayer message) + { + /* + AttachedObject.GetComponent().GetGenerator() + .SpawnPlayer*/ + return Task.CompletedTask; + } + } +} diff --git a/src/MineCase.Server.Grains/Game/Entities/Components/SlotContainerComponent.cs b/src/MineCase.Server.Grains/Game/Entities/Components/SlotContainerComponent.cs index 25604640..068f52fd 100644 --- a/src/MineCase.Server.Grains/Game/Entities/Components/SlotContainerComponent.cs +++ b/src/MineCase.Server.Grains/Game/Entities/Components/SlotContainerComponent.cs @@ -43,6 +43,16 @@ public Task SetSlot(int index, Slot slot) return Task.CompletedTask; } + public async Task SetSlots(Slot[] slots) + { + if (slots.Length != _slotsCount) + throw new ArgumentException(nameof(slots)); + + await AttachedObject.SetLocalValue(SlotsProperty, slots); + for (int i = 0; i < _slotsCount; i++) + await SlotChanged.InvokeSerial(this, (i, slots[i])); + } + Task IHandle.Handle(SetSlot message) => SetSlot(message.Index, message.Slot); diff --git a/src/MineCase.Server.Grains/Game/Entities/Components/SyncPlayerStateComponent.cs b/src/MineCase.Server.Grains/Game/Entities/Components/SyncPlayerStateComponent.cs index f8133188..12b0dd9f 100644 --- a/src/MineCase.Server.Grains/Game/Entities/Components/SyncPlayerStateComponent.cs +++ b/src/MineCase.Server.Grains/Game/Entities/Components/SyncPlayerStateComponent.cs @@ -54,6 +54,7 @@ private Task OnDraggedSlotChanged(object sender, PropertyChangedEventArgs async Task IHandle.Handle(BindToUser message) { await AttachedObject.GetComponent().SetName(await message.User.GetName()); + await AttachedObject.GetComponent().SetSlots(await message.User.GetInventorySlots()); } } } diff --git a/src/MineCase.Server.Grains/Game/Entities/Components/WindowManagerComponent.cs b/src/MineCase.Server.Grains/Game/Entities/Components/WindowManagerComponent.cs index aeba2e5a..e0b6c05c 100644 --- a/src/MineCase.Server.Grains/Game/Entities/Components/WindowManagerComponent.cs +++ b/src/MineCase.Server.Grains/Game/Entities/Components/WindowManagerComponent.cs @@ -10,7 +10,7 @@ namespace MineCase.Server.Game.Entities.Components { - internal class WindowManagerComponent : Component, IHandle + internal class WindowManagerComponent : Component, IHandle, IHandle { private Dictionary _windows; @@ -73,6 +73,9 @@ private WindowContext GetWindow(byte windowId) Task IHandle.Handle(OpenWindow message) => OpenWindow(message.Window); + Task IHandle.Handle(AskWindowId message) => + Task.FromResult(_windows.First(o => o.Value.Window.GetPrimaryKey() == message.Window.GetPrimaryKey()).Key); + private class WindowContext { public IWindow Window; diff --git a/src/MineCase.Server.Grains/Game/Entities/EntityGrain.cs b/src/MineCase.Server.Grains/Game/Entities/EntityGrain.cs index b4be0946..ed516afe 100644 --- a/src/MineCase.Server.Grains/Game/Entities/EntityGrain.cs +++ b/src/MineCase.Server.Grains/Game/Entities/EntityGrain.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using MineCase.Engine; using MineCase.Server.Components; +using MineCase.Server.Game.Entities.Components; using MineCase.Server.Network.Play; using MineCase.Server.World; using MineCase.World; @@ -14,12 +15,16 @@ namespace MineCase.Server.Game.Entities { internal abstract class EntityGrain : DependencyObject, IEntity { + public Guid UUID => this.GetPrimaryKey(); + public uint EntityId => GetValue(EntityIdComponent.EntityIdProperty); public EntityWorldPos Position => GetValue(EntityWorldPositionComponent.EntityWorldPositionProperty); public IWorld World => GetValue(WorldComponent.WorldProperty); + public float Pitch => GetValue(EntityLookComponent.PitchProperty); + public float Yaw => GetValue(EntityLookComponent.YawProperty); protected override async Task InitializeComponents() diff --git a/src/MineCase.Server.Grains/Game/Entities/PickupGrain.cs b/src/MineCase.Server.Grains/Game/Entities/PickupGrain.cs index 0fab5a45..ebba2f9c 100644 --- a/src/MineCase.Server.Grains/Game/Entities/PickupGrain.cs +++ b/src/MineCase.Server.Grains/Game/Entities/PickupGrain.cs @@ -14,7 +14,10 @@ internal class PickupGrain : EntityGrain, IPickup protected override async Task InitializeComponents() { await base.InitializeComponents(); + await SetComponent(new EntityLifeTimeComponent()); await SetComponent(new PickupMetadataComponent()); + await SetComponent(new DiscoveryRegisterComponent()); + await SetComponent(new PickupDiscoveryComponent()); } /* diff --git a/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs b/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs index 473c29dc..dbedafa4 100644 --- a/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs +++ b/src/MineCase.Server.Grains/Game/Entities/PlayerGrain.cs @@ -33,9 +33,10 @@ protected override async Task InitializeComponents() await SetComponent(new ClientboundPacketComponent()); await SetComponent(new ChunkLoaderComponent()); await SetComponent(new DiggingComponent()); + await SetComponent(new DiscoveryRegisterComponent()); await SetComponent(new DraggedSlotComponent()); - await SetComponent(new EntityLifeTimeComponent()); await SetComponent(new ExperienceComponent()); + await SetComponent(new EntityLifeTimeComponent()); await SetComponent(new EntityOnGroundComponent()); await SetComponent(new FoodComponent()); await SetComponent(new HealthComponent()); @@ -44,6 +45,7 @@ protected override async Task InitializeComponents() await SetComponent(new KeepAliveComponent()); await SetComponent(new NameComponent()); await SetComponent(new PlayerListComponent()); + await SetComponent(new PlayerDiscoveryComponent()); await SetComponent(new ServerboundPacketComponent()); await SetComponent(new SlotContainerComponent(SlotArea.UserSlotsCount)); await SetComponent(new SyncPlayerStateComponent()); diff --git a/src/MineCase.Server.Grains/Game/Windows/ChestWindowGrain.cs b/src/MineCase.Server.Grains/Game/Windows/ChestWindowGrain.cs index 65289e32..c501f98f 100644 --- a/src/MineCase.Server.Grains/Game/Windows/ChestWindowGrain.cs +++ b/src/MineCase.Server.Grains/Game/Windows/ChestWindowGrain.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using MineCase.Engine; using MineCase.Server.Game.BlockEntities; using MineCase.Server.Game.Windows.SlotAreas; using Orleans.Concurrency; @@ -16,7 +17,7 @@ internal class ChestWindowGrain : WindowGrain, IChestWindow protected override Chat Title => _title; - public Task SetEntities(Immutable entities) + public Task SetEntities(Immutable entities) { SlotAreas.Clear(); diff --git a/src/MineCase.Server.Grains/Game/Windows/FurnaceWindowGrain.cs b/src/MineCase.Server.Grains/Game/Windows/FurnaceWindowGrain.cs index b519abe9..a25cad1b 100644 --- a/src/MineCase.Server.Grains/Game/Windows/FurnaceWindowGrain.cs +++ b/src/MineCase.Server.Grains/Game/Windows/FurnaceWindowGrain.cs @@ -16,10 +16,10 @@ internal class FurnaceWindowGrain : WindowGrain, IFurnaceWindow protected override Chat Title { get; } = new Chat("Furnace"); - private IFurnaceBlockEntity _furnaceEntity; - private Dictionary _properties = new Dictionary(); + private IBlockEntity _furnaceEntity; + private Dictionary _properties = new Dictionary(); - public Task SetEntity(IFurnaceBlockEntity furnaceEntity) + public Task SetEntity(IBlockEntity furnaceEntity) { SlotAreas.Clear(); _properties.Clear(); @@ -31,7 +31,7 @@ public Task SetEntity(IFurnaceBlockEntity furnaceEntity) return Task.CompletedTask; } - public Task SetProperty(FurnaceWindowProperty property, short value) + public Task SetProperty(FurnaceWindowPropertyType property, short value) { _properties[property] = value; return BroadcastWindowProperty(property, value); diff --git a/src/MineCase.Server.Grains/Game/Windows/SlotAreas/ChestSlotArea.cs b/src/MineCase.Server.Grains/Game/Windows/SlotAreas/ChestSlotArea.cs index 5706e89a..a04e7bd8 100644 --- a/src/MineCase.Server.Grains/Game/Windows/SlotAreas/ChestSlotArea.cs +++ b/src/MineCase.Server.Grains/Game/Windows/SlotAreas/ChestSlotArea.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using MineCase.Engine; using MineCase.Server.Game.BlockEntities; using MineCase.Server.Game.Entities; +using MineCase.Server.Game.Entities.Components; using Orleans; namespace MineCase.Server.Game.Windows.SlotAreas @@ -12,9 +14,9 @@ internal class ChestSlotArea : SlotArea { public const int ChestSlotsCount = 9 * 3; - private readonly IChestBlockEntity _chestEntity; + private readonly IDependencyObject _chestEntity; - public ChestSlotArea(IChestBlockEntity chestEntity, WindowGrain window, IGrainFactory grainFactory) + public ChestSlotArea(IDependencyObject chestEntity, WindowGrain window, IGrainFactory grainFactory) : base(ChestSlotsCount, window, grainFactory) { _chestEntity = chestEntity; @@ -22,12 +24,12 @@ public ChestSlotArea(IChestBlockEntity chestEntity, WindowGrain window, IGrainFa public override Task GetSlot(IPlayer player, int slotIndex) { - return _chestEntity.GetSlot(slotIndex); + return _chestEntity.Ask(new AskSlot { Index = slotIndex }); } public override async Task SetSlot(IPlayer player, int slotIndex, Slot slot) { - await _chestEntity.SetSlot(slotIndex, slot); + await _chestEntity.Tell(new SetSlot { Index = slotIndex, Slot = slot }); await BroadcastSlotChanged(slotIndex, slot); } } diff --git a/src/MineCase.Server.Grains/Game/Windows/SlotAreas/FurnaceSlotArea.cs b/src/MineCase.Server.Grains/Game/Windows/SlotAreas/FurnaceSlotArea.cs index 495ff509..b3492a4d 100644 --- a/src/MineCase.Server.Grains/Game/Windows/SlotAreas/FurnaceSlotArea.cs +++ b/src/MineCase.Server.Grains/Game/Windows/SlotAreas/FurnaceSlotArea.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using MineCase.Server.Game.BlockEntities; using MineCase.Server.Game.Entities; +using MineCase.Server.Game.Entities.Components; using Orleans; namespace MineCase.Server.Game.Windows.SlotAreas @@ -12,9 +13,9 @@ internal class FurnaceSlotArea : SlotArea { public const int FurnaceSlotsCount = 3; - private readonly IFurnaceBlockEntity _furnaceEntity; + private readonly IBlockEntity _furnaceEntity; - public FurnaceSlotArea(IFurnaceBlockEntity furnaceEntity, WindowGrain window, IGrainFactory grainFactory) + public FurnaceSlotArea(IBlockEntity furnaceEntity, WindowGrain window, IGrainFactory grainFactory) : base(FurnaceSlotsCount, window, grainFactory) { _furnaceEntity = furnaceEntity; @@ -22,12 +23,12 @@ public FurnaceSlotArea(IFurnaceBlockEntity furnaceEntity, WindowGrain window, IG public override Task GetSlot(IPlayer player, int slotIndex) { - return _furnaceEntity.GetSlot(slotIndex); + return _furnaceEntity.Ask(new AskSlot { Index = slotIndex }); } public override async Task SetSlot(IPlayer player, int slotIndex, Slot slot) { - await _furnaceEntity.SetSlot(slotIndex, slot); + await _furnaceEntity.Tell(new SetSlot { Index = slotIndex, Slot = slot }); await BroadcastSlotChanged(slotIndex, slot); } } diff --git a/src/MineCase.Server.Grains/Network/Play/ClientPlayPacketGenerator.cs b/src/MineCase.Server.Grains/Network/Play/ClientPlayPacketGenerator.cs index 33da7923..e6f00a0a 100644 --- a/src/MineCase.Server.Grains/Network/Play/ClientPlayPacketGenerator.cs +++ b/src/MineCase.Server.Grains/Network/Play/ClientPlayPacketGenerator.cs @@ -344,7 +344,7 @@ public Task WindowProperty(byte windowId, T property, short value) { short PropertyToShort() { - if (typeof(T) == typeof(FurnaceWindowProperty)) + if (typeof(T) == typeof(FurnaceWindowPropertyType)) return Unsafe.As(ref property); throw new NotSupportedException(); } diff --git a/src/MineCase.Server.Grains/Network/Play/ClientboundPacketComponent.cs b/src/MineCase.Server.Grains/Network/Play/ClientboundPacketComponent.cs index c7f4ef6f..5fc02b76 100644 --- a/src/MineCase.Server.Grains/Network/Play/ClientboundPacketComponent.cs +++ b/src/MineCase.Server.Grains/Network/Play/ClientboundPacketComponent.cs @@ -4,10 +4,11 @@ using System.Threading.Tasks; using MineCase.Engine; using MineCase.Server.Game.Entities.Components; +using Orleans.Concurrency; namespace MineCase.Server.Network.Play { - internal class ClientboundPacketComponent : Component, IHandle + internal class ClientboundPacketComponent : Component, IHandle, IHandle, IHandle { private IClientboundPacketSink _sink; @@ -28,5 +29,15 @@ async Task IHandle.Handle(BindToUser message) { _sink = await message.User.GetClientPacketSink(); } + + Task IHandle.Handle(KickPlayer message) + { + return Kick(); + } + + Task IHandle.Handle(PacketForwardToPlayer message) + { + return _sink.SendPacket(message.PacketId, message.Data.AsImmutable()); + } } } diff --git a/src/MineCase.Server.Grains/World/ChunkColumnGrain.cs b/src/MineCase.Server.Grains/World/ChunkColumnGrain.cs index 455d03e7..8f25f1ac 100644 --- a/src/MineCase.Server.Grains/World/ChunkColumnGrain.cs +++ b/src/MineCase.Server.Grains/World/ChunkColumnGrain.cs @@ -69,7 +69,7 @@ public async Task SetBlockState(int x, int y, int z, BlockState blockState) if (oldState.Id != blockState.Id) { bool replaceOld = true; - var newEntity = BlockEntity.Create(GrainFactory, _world, blockWorldPos, (BlockId)blockState.Id); + var newEntity = BlockEntity.Create(GrainFactory, (BlockId)blockState.Id); // 删除旧的 BlockEntity if (_blockEntities.TryGetValue(chunkPos, out var entity)) @@ -79,7 +79,7 @@ public async Task SetBlockState(int x, int y, int z, BlockState blockState) if (replaceOld) { - await entity.Destroy(); + await entity.Tell(DestroyBlockEntity.Default); _blockEntities.Remove(chunkPos); } } @@ -88,7 +88,7 @@ public async Task SetBlockState(int x, int y, int z, BlockState blockState) if (newEntity != null && replaceOld) { _blockEntities.Add(chunkPos, newEntity); - await newEntity.OnCreated(); + await newEntity.Tell(new SpawnBlockEntity { World = _world, Position = blockWorldPos }); } } diff --git a/src/MineCase.Server.Grains/World/WorldPartitionGrain.cs b/src/MineCase.Server.Grains/World/WorldPartitionGrain.cs index 4139c127..510a7f91 100644 --- a/src/MineCase.Server.Grains/World/WorldPartitionGrain.cs +++ b/src/MineCase.Server.Grains/World/WorldPartitionGrain.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading.Tasks; +using MineCase.Server.Game.BlockEntities; using MineCase.Server.Game.Entities; +using MineCase.Server.Game.Entities.Components; using Orleans; namespace MineCase.Server.World @@ -11,11 +14,13 @@ internal class WorldPartitionGrain : AddressByPartitionGrain, IWorldPartition { private ITickEmitter _tickEmitter; private HashSet _players; + private HashSet _discoveryEntities; public override Task OnActivateAsync() { _tickEmitter = GrainFactory.GetPartitionGrain(this); _players = new HashSet(); + _discoveryEntities = new HashSet(); return base.OnActivateAsync(); } @@ -24,6 +29,10 @@ public async Task Enter(IPlayer player) var active = _players.Count == 0; _players.Add(player); + var message = new DiscoveredByPlayer { Player = player }; + await Task.WhenAll(from e in _discoveryEntities + select e.Tell(message)); + if (active) await World.ActivePartition(this); } @@ -43,5 +52,17 @@ public Task OnGameTick(TimeSpan deltaTime, long worldAge) _tickEmitter.InvokeOneWay(e => e.OnGameTick(deltaTime, worldAge)); return Task.CompletedTask; } + + Task IWorldPartition.SubscribeDiscovery(IEntity entity) + { + _discoveryEntities.Add(entity); + return Task.CompletedTask; + } + + Task IWorldPartition.UnsubscribeDiscovery(IEntity entity) + { + _discoveryEntities.Remove(entity); + return Task.CompletedTask; + } } } diff --git a/src/MineCase.Server.Interfaces/Components/EntityMessages.cs b/src/MineCase.Server.Interfaces/Components/EntityMessages.cs index f461123e..3d5410e8 100644 --- a/src/MineCase.Server.Interfaces/Components/EntityMessages.cs +++ b/src/MineCase.Server.Interfaces/Components/EntityMessages.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using MineCase.Engine; +using MineCase.Server.Game.Entities; using Orleans.Concurrency; namespace MineCase.Server.Components @@ -13,4 +14,10 @@ public sealed class GameTick : IEntityMessage public long WorldAge { get; set; } } + + [Immutable] + public sealed class Disable : IEntityMessage + { + public static readonly Disable Default = new Disable(); + } } diff --git a/src/MineCase.Server.Interfaces/Game/BlockEntities/EntityMessages.cs b/src/MineCase.Server.Interfaces/Game/BlockEntities/EntityMessages.cs new file mode 100644 index 00000000..fc6880c6 --- /dev/null +++ b/src/MineCase.Server.Interfaces/Game/BlockEntities/EntityMessages.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; +using MineCase.Engine; +using MineCase.Server.Game.Entities; +using MineCase.Server.World; +using MineCase.World; +using Orleans.Concurrency; + +namespace MineCase.Server.Game.BlockEntities +{ + [Immutable] + public sealed class SpawnBlockEntity : IEntityMessage + { + public IWorld World { get; set; } + + public BlockWorldPos Position { get; set; } + } + + [Immutable] + public sealed class DestroyBlockEntity : IEntityMessage + { + public static readonly DestroyBlockEntity Default = new DestroyBlockEntity(); + } + + [Immutable] + public sealed class NeighborEntityChanged : IEntityMessage + { + public static readonly NeighborEntityChanged Empty = new NeighborEntityChanged(); + + public IBlockEntity Entity { get; set; } + } + + [Immutable] + public sealed class UseBy : IEntityMessage + { + public IPlayer Player { get; set; } + } +} diff --git a/src/MineCase.Server.Interfaces/Game/BlockEntities/IBlockEntity.cs b/src/MineCase.Server.Interfaces/Game/BlockEntities/IBlockEntity.cs index d580703c..67995801 100644 --- a/src/MineCase.Server.Interfaces/Game/BlockEntities/IBlockEntity.cs +++ b/src/MineCase.Server.Interfaces/Game/BlockEntities/IBlockEntity.cs @@ -2,17 +2,18 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using MineCase.Engine; using MineCase.Server.World; using MineCase.World; using Orleans; namespace MineCase.Server.Game.BlockEntities { - public interface IBlockEntity : IGrainWithStringKey + public interface IBlockEntity : IDependencyObject, IGrainWithGuidKey { - Task OnCreated(); + Task GetWorld(); - Task Destroy(); + Task GetPosition(); } [AttributeUsage(AttributeTargets.Interface, Inherited = false, AllowMultiple = true)] @@ -25,24 +26,4 @@ public BlockEntityAttribute(BlockId blockId) BlockId = blockId; } } - - public static class BlockEntityExtensions - { - public static string MakeBlockEntityKey(this IWorld world, BlockWorldPos position) - { - return $"{world.GetPrimaryKeyString()},{position.X},{position.Y},{position.Z}"; - } - - public static BlockWorldPos GetBlockEntityPosition(this IBlockEntity blockEntity) - { - var key = blockEntity.GetPrimaryKeyString().Split(','); - return new BlockWorldPos(int.Parse(key[1]), int.Parse(key[2]), int.Parse(key[3])); - } - - public static (string worldKey, BlockWorldPos position) GetWorldAndBlockEntityPosition(this IBlockEntity blockEntity) - { - var key = blockEntity.GetPrimaryKeyString().Split(','); - return (key[0], new BlockWorldPos(int.Parse(key[1]), int.Parse(key[2]), int.Parse(key[3]))); - } - } } diff --git a/src/MineCase.Server.Interfaces/Game/BlockEntities/IChestBlockEntity.cs b/src/MineCase.Server.Interfaces/Game/BlockEntities/IChestBlockEntity.cs index 503200ff..9c006d9e 100644 --- a/src/MineCase.Server.Interfaces/Game/BlockEntities/IChestBlockEntity.cs +++ b/src/MineCase.Server.Interfaces/Game/BlockEntities/IChestBlockEntity.cs @@ -9,14 +9,5 @@ namespace MineCase.Server.Game.BlockEntities [BlockEntity(BlockId.Chest)] public interface IChestBlockEntity : IBlockEntity { - Task GetSlot(int slotIndex); - - Task SetSlot(int slotIndex, Slot item); - - Task UseBy(IPlayer player); - - Task ClearNeighborEntity(); - - Task SetNeighborEntity(IChestBlockEntity chestEntity); } } diff --git a/src/MineCase.Server.Interfaces/Game/BlockEntities/IFurnaceBlockEntity.cs b/src/MineCase.Server.Interfaces/Game/BlockEntities/IFurnaceBlockEntity.cs index 5ed4c6a9..1556e78d 100644 --- a/src/MineCase.Server.Interfaces/Game/BlockEntities/IFurnaceBlockEntity.cs +++ b/src/MineCase.Server.Interfaces/Game/BlockEntities/IFurnaceBlockEntity.cs @@ -5,12 +5,7 @@ namespace MineCase.Server.Game.BlockEntities { [BlockEntity(BlockId.Furnace)] [BlockEntity(BlockId.BurningFurnace)] - public interface IFurnaceBlockEntity : IBlockEntity, ITickable + public interface IFurnaceBlockEntity : IBlockEntity { - Task GetSlot(int slotIndex); - - Task SetSlot(int slotIndex, Slot item); - - Task UseBy(IPlayer player); } } diff --git a/src/MineCase.Server.Interfaces/Game/Entities/EntityMessages.cs b/src/MineCase.Server.Interfaces/Game/Entities/EntityMessages.cs index d50b4c21..0a045239 100644 --- a/src/MineCase.Server.Interfaces/Game/Entities/EntityMessages.cs +++ b/src/MineCase.Server.Interfaces/Game/Entities/EntityMessages.cs @@ -16,6 +16,12 @@ public sealed class PlayerLoggedIn : IEntityMessage public static readonly PlayerLoggedIn Default = new PlayerLoggedIn(); } + [Immutable] + public sealed class KickPlayer : IEntityMessage + { + public Chat Reason { get; set; } + } + [Immutable] public sealed class BindToUser : IEntityMessage { @@ -103,4 +109,16 @@ public sealed class TossPickup : IEntityMessage { public Slot[] Slots { get; set; } } + + [Immutable] + public sealed class DiscoveredByPlayer : IEntityMessage + { + public IPlayer Player { get; set; } + } + + [Immutable] + public sealed class BroadcastDiscovered : IEntityMessage + { + public static readonly BroadcastDiscovered Default = new BroadcastDiscovered(); + } } diff --git a/src/MineCase.Server.Interfaces/Game/Windows/IChestWindow.cs b/src/MineCase.Server.Interfaces/Game/Windows/IChestWindow.cs index fac05b88..a6fc5be0 100644 --- a/src/MineCase.Server.Interfaces/Game/Windows/IChestWindow.cs +++ b/src/MineCase.Server.Interfaces/Game/Windows/IChestWindow.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using MineCase.Engine; using MineCase.Server.Game.BlockEntities; using Orleans.Concurrency; @@ -9,6 +10,6 @@ namespace MineCase.Server.Game.Windows { public interface IChestWindow : IWindow { - Task SetEntities(Immutable entities); + Task SetEntities(Immutable entities); } } diff --git a/src/MineCase.Server.Interfaces/Game/Windows/IFurnaceWindow.cs b/src/MineCase.Server.Interfaces/Game/Windows/IFurnaceWindow.cs index f50fc4ea..a8480dd5 100644 --- a/src/MineCase.Server.Interfaces/Game/Windows/IFurnaceWindow.cs +++ b/src/MineCase.Server.Interfaces/Game/Windows/IFurnaceWindow.cs @@ -9,8 +9,8 @@ namespace MineCase.Server.Game.Windows { public interface IFurnaceWindow : IWindow { - Task SetEntity(IFurnaceBlockEntity furnaceEntity); + Task SetEntity(IBlockEntity furnaceEntity); - Task SetProperty(FurnaceWindowProperty property, short value); + Task SetProperty(FurnaceWindowPropertyType property, short value); } } diff --git a/src/MineCase.Server.Interfaces/World/IWorldPartition.cs b/src/MineCase.Server.Interfaces/World/IWorldPartition.cs index 6a7f75b2..0ad943fc 100644 --- a/src/MineCase.Server.Interfaces/World/IWorldPartition.cs +++ b/src/MineCase.Server.Interfaces/World/IWorldPartition.cs @@ -15,5 +15,9 @@ public interface IWorldPartition : IAddressByPartition [OneWay] Task OnGameTick(TimeSpan deltaTime, long worldAge); + + Task SubscribeDiscovery(IEntity entity); + + Task UnsubscribeDiscovery(IEntity entity); } } diff --git a/src/Minecase.Core/Game/Windows/WindowPropertyTypes.cs b/src/Minecase.Core/Game/Windows/WindowPropertyTypes.cs index 0725ffb6..e865eaff 100644 --- a/src/Minecase.Core/Game/Windows/WindowPropertyTypes.cs +++ b/src/Minecase.Core/Game/Windows/WindowPropertyTypes.cs @@ -4,7 +4,7 @@ namespace MineCase.Game.Windows { - public enum FurnaceWindowProperty : short + public enum FurnaceWindowPropertyType : short { FireIcon = 0, MaximumFuelBurnTime = 1,