diff --git a/Chraft/Chraft.csproj b/Chraft/Chraft.csproj index 63ca6bec..09ab0a69 100644 --- a/Chraft/Chraft.csproj +++ b/Chraft/Chraft.csproj @@ -256,6 +256,9 @@ + + + diff --git a/Chraft/World/Blocks/BlockBase.cs b/Chraft/World/Blocks/BlockBase.cs index 41148935..522462e8 100644 --- a/Chraft/World/Blocks/BlockBase.cs +++ b/Chraft/World/Blocks/BlockBase.cs @@ -157,6 +157,25 @@ public virtual void Destroy(EntityBase entity, StructBlock block) NotifyNearbyBlocks(entity, block); } + /// + /// Removes the block from the world. Don't drop anything. + /// + /// block that is being removed + public virtual void Remove(StructBlock block) + { + UpdateOnDestroy(block); + NotifyNearbyBlocks(null, block); + } + + /// + /// Spawns the block in the world (not placed by the player) + /// + /// block that is being spawned + public virtual void Spawn(StructBlock block) + { + UpdateOnPlace(block); + } + /// /// Notifies the nearby block that the current block has been destroyed /// May be used by recipient block to start the physic simulation etc diff --git a/Chraft/World/Blocks/BlockGravel.cs b/Chraft/World/Blocks/BlockGravel.cs index 31c70676..7fe685ef 100644 --- a/Chraft/World/Blocks/BlockGravel.cs +++ b/Chraft/World/Blocks/BlockGravel.cs @@ -5,6 +5,7 @@ using Chraft.Entity; using Chraft.Interfaces; using Chraft.Plugins.Events.Args; +using Chraft.World.Blocks.Physics; namespace Chraft.World.Blocks { @@ -39,5 +40,41 @@ protected override void DropItems(EntityBase entity, StructBlock block) } base.DropItems(entity, block); } + + public override void NotifyDestroy(EntityBase entity, StructBlock sourceBlock, StructBlock targetBlock) + { + if ((targetBlock.Coords.WorldY - sourceBlock.Coords.WorldY) == 1 && + targetBlock.Coords.WorldX == sourceBlock.Coords.WorldX && + targetBlock.Coords.WorldZ == sourceBlock.Coords.WorldZ) + { + StartPhysics(targetBlock); + } + base.NotifyDestroy(entity, sourceBlock, targetBlock); + } + + public override void Place(EntityBase entity, StructBlock block, StructBlock targetBlock, BlockFace face) + { + if (!CanBePlacedOn(entity, block, targetBlock, face)) + return; + + if (!RaisePlaceEvent(entity, block)) + return; + + UpdateOnPlace(block); + + RemoveItem(entity); + + if (block.Coords.WorldY > 1) + if (block.World.GetBlockId(block.Coords.WorldX, block.Coords.WorldY - 1, block.Coords.WorldZ) == (byte)BlockData.Blocks.Air) + StartPhysics(block); + } + + protected void StartPhysics(StructBlock block) + { + Remove(block); + FallingGravel fgBlock = new FallingGravel(block.World, new Location(block.Coords.WorldX + 0.5, block.Coords.WorldY + 0.5, block.Coords.WorldZ + 0.5)); + fgBlock.Start(); + block.World.PhysicsBlocks.TryAdd(fgBlock.EntityId, fgBlock); + } } } diff --git a/Chraft/World/Blocks/BlockSand.cs b/Chraft/World/Blocks/BlockSand.cs index 2091fb2b..2b7ab1c2 100644 --- a/Chraft/World/Blocks/BlockSand.cs +++ b/Chraft/World/Blocks/BlockSand.cs @@ -5,6 +5,7 @@ using Chraft.Entity; using Chraft.Interfaces; using Chraft.Plugins.Events.Args; +using Chraft.World.Blocks.Physics; namespace Chraft.World.Blocks { @@ -17,5 +18,41 @@ public BlockSand() IsSolid = true; LootTable.Add(new ItemStack((short)Type, 1)); } + + public override void NotifyDestroy(EntityBase entity, StructBlock sourceBlock, StructBlock targetBlock) + { + if ((targetBlock.Coords.WorldY - sourceBlock.Coords.WorldY) == 1 && + targetBlock.Coords.WorldX == sourceBlock.Coords.WorldX && + targetBlock.Coords.WorldZ == sourceBlock.Coords.WorldZ) + { + StartPhysics(targetBlock); + } + base.NotifyDestroy(entity, sourceBlock, targetBlock); + } + + public override void Place(EntityBase entity, StructBlock block, StructBlock targetBlock, BlockFace face) + { + if (!CanBePlacedOn(entity, block, targetBlock, face)) + return; + + if (!RaisePlaceEvent(entity, block)) + return; + + UpdateOnPlace(block); + + RemoveItem(entity); + + if (block.Coords.WorldY > 1) + if (block.World.GetBlockId(block.Coords.WorldX, block.Coords.WorldY - 1, block.Coords.WorldZ) == (byte)BlockData.Blocks.Air) + StartPhysics(block); + } + + protected void StartPhysics(StructBlock block) + { + Remove(block); + FallingSand fsBlock = new FallingSand(block.World, new Location(block.Coords.WorldX + 0.5, block.Coords.WorldY + 0.5, block.Coords.WorldZ + 0.5)); + fsBlock.Start(); + block.World.PhysicsBlocks.TryAdd(fsBlock.EntityId, fsBlock); + } } } diff --git a/Chraft/World/Blocks/Physics/BlockBasePhysics.cs b/Chraft/World/Blocks/Physics/BlockBasePhysics.cs new file mode 100644 index 00000000..aaad6f13 --- /dev/null +++ b/Chraft/World/Blocks/Physics/BlockBasePhysics.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Chraft.Net.Packets; +using Chraft.Utils; + +namespace Chraft.World.Blocks.Physics +{ + public abstract class BlockBasePhysics + { + public int EntityId { get; protected set; } + public WorldManager World { get; protected set; } + public Location Position { get; protected set; } + public bool IsPlaying { get; protected set; } + public Vector3 Velocity { get; protected set; } + public AddObjectVehiclePacket.ObjectType Type; + + protected BlockBasePhysics(WorldManager world, Location pos) + { + World = world; + Position = pos; + EntityId = world.Server.AllocateEntity(); + + CreateEntityPacket entity = new CreateEntityPacket { EntityId = EntityId }; + foreach (var nearbyPlayer in World.Server.GetNearbyPlayers(World, new AbsWorldCoords(Position.X, Position.Y, Position.Z))) + { + nearbyPlayer.SendPacket(entity); + } + } + + public virtual void Start() + { + if (IsPlaying) + return; + AddObjectVehiclePacket obj = new AddObjectVehiclePacket + { + EntityId = EntityId, + Type = Type, + UnknownFlag = 0, + X = Position.X, + Y = Position.Y, + Z = Position.Z + }; + foreach (var nearbyPlayer in World.Server.GetNearbyPlayers(World, new AbsWorldCoords(Position.X, Position.Y, Position.Z))) + { + nearbyPlayer.SendPacket(obj); + } + IsPlaying = true; + } + + public virtual void Simulate() + { + } + + public virtual void Stop() + { + IsPlaying = false; + BlockBasePhysics unused = null; + World.PhysicsBlocks.TryRemove(EntityId, out unused); + DestroyEntityPacket entity = new DestroyEntityPacket { EntityId = EntityId }; + foreach (var nearbyPlayer in World.Server.GetNearbyPlayers(World, new AbsWorldCoords(Position.X, Position.Y, Position.Z))) + { + nearbyPlayer.SendPacket(entity); + } + OnStop(); + } + + protected virtual void OnStop() + { + } + } +} diff --git a/Chraft/World/Blocks/Physics/FallingGravel.cs b/Chraft/World/Blocks/Physics/FallingGravel.cs new file mode 100644 index 00000000..2ac03d53 --- /dev/null +++ b/Chraft/World/Blocks/Physics/FallingGravel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Chraft.Utils; + +namespace Chraft.World.Blocks.Physics +{ + public class FallingGravel : FallingSand + { + public FallingGravel(WorldManager world, Location pos) : base(world, pos) + { + Type = Net.Packets.AddObjectVehiclePacket.ObjectType.FallingGravel; + BlockId = (byte) BlockData.Blocks.Gravel; + } + } +} diff --git a/Chraft/World/Blocks/Physics/FallingSand.cs b/Chraft/World/Blocks/Physics/FallingSand.cs new file mode 100644 index 00000000..3d0eb605 --- /dev/null +++ b/Chraft/World/Blocks/Physics/FallingSand.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Chraft.Utils; + +namespace Chraft.World.Blocks.Physics +{ + public class FallingSand : BlockBasePhysics + { + protected byte BlockId; + + public FallingSand(WorldManager world, Location pos) : base(world, pos) + { + Type = Net.Packets.AddObjectVehiclePacket.ObjectType.FallingSand; + BlockId = (byte) BlockData.Blocks.Sand; + Velocity = new Vector3(0, -0.4D, 0); + } + + public override void Simulate() + { + int x = MathHelper.floor_double(Position.X); + int y = MathHelper.floor_double(Position.Y); + int z = MathHelper.floor_double(Position.Z); + byte blockId = World.GetBlockId(x, y, z); + if (blockId != (byte)BlockData.Blocks.Air) + { + Stop(); + return; + } + + if (Position.Y <= 1) + { + Stop(); + return; + } + + Position.Vector += Velocity; + } + + protected override void OnStop() + { + UniversalCoords uc = UniversalCoords.FromWorld(MathHelper.floor_double(Position.X), MathHelper.floor_double(Position.Y) + 1, MathHelper.floor_double(Position.Z)); + StructBlock block = new StructBlock(uc, BlockId, 0, World); + BlockHelper.Instance(BlockId).Spawn(block); + base.OnStop(); + } + } +} diff --git a/Chraft/World/WorldManager.cs b/Chraft/World/WorldManager.cs index ea205f5c..1ba8c2b5 100644 --- a/Chraft/World/WorldManager.cs +++ b/Chraft/World/WorldManager.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Threading; using Chraft.Net; @@ -7,6 +8,7 @@ using System.IO; using Chraft.Entity; using Chraft.World.Blocks; +using Chraft.World.Blocks.Physics; using Chraft.World.Weather; using Chraft.Plugins.Events.Args; using System.Threading.Tasks; @@ -40,6 +42,9 @@ public partial class WorldManager : IDisposable public ConcurrentQueue ChunksToRecalculate; + public ConcurrentDictionary PhysicsBlocks; + private Task _PhysicsSimulationTask; + private readonly object ChunkLightUpdateLock = new object(); private Task _GrowStuffTask; private Task _CollectTask; @@ -175,6 +180,7 @@ public bool Load() _ChunkProvider = new ChunkProvider(this); Generator = _ChunkProvider.GetNewGenerator(GeneratorType.Custom, GetSeed()); ChunkManager = new WorldChunkManager(this); + PhysicsBlocks = new ConcurrentDictionary(); InitializeSpawn(); InitializeThreads(); @@ -354,7 +360,12 @@ private void GlobalTickProc(object state) } } - + if (_PhysicsSimulationTask == null || _PhysicsSimulationTask.IsCompleted) + { + _PhysicsSimulationTask = new Task(PhysicsProc); + _PhysicsSimulationTask.Start(); + } + } public Chunk GetChunkFromPosition(int x, int z) @@ -421,6 +432,14 @@ private void GrowProc() } } + private void PhysicsProc() + { + foreach (var physicsBlock in PhysicsBlocks) + { + physicsBlock.Value.Simulate(); + } + } + private void EntityMoverStart() { Thread thread = new Thread(MovementThread); @@ -705,21 +724,6 @@ private void UpdatePhysics(UniversalCoords coords, bool updateClients = true) { BlockData.Blocks type = (BlockData.Blocks)GetBlockId(coords); UniversalCoords oneDown = UniversalCoords.FromWorld(coords.WorldX, coords.WorldY - 1, coords.WorldZ); - if (type == BlockData.Blocks.Sand && coords.WorldY > 0 && GetBlockId(oneDown) == 0) - { - SetBlockAndData(coords, 0, 0); - SetBlockAndData(oneDown, (byte)BlockData.Blocks.Sand, 0); - Update(oneDown, updateClients); - return; - } - - if (type == BlockData.Blocks.Gravel && coords.WorldY > 0 && GetBlockId(UniversalCoords.FromWorld(coords.WorldX, coords.WorldY - 1, coords.WorldZ)) == 0) - { - SetBlockAndData(coords, 0, 0); - SetBlockAndData(oneDown, (byte)BlockData.Blocks.Gravel, 0); - Update(oneDown, updateClients); - return; - } if (type == BlockData.Blocks.Water) {