diff --git a/ClassicalSharp/Blocks/BlockInfo.cs b/ClassicalSharp/Blocks/BlockInfo.cs
index 15b4dc8e5..26e414245 100644
--- a/ClassicalSharp/Blocks/BlockInfo.cs
+++ b/ClassicalSharp/Blocks/BlockInfo.cs
@@ -90,7 +90,7 @@ namespace ClassicalSharp {
SetEmitsLight( Block.Magma, true ); SetEmitsLight( Block.Fire, true );
SetupCullingCache();
}
-
+
public void SetDefaultBlockPermissions( bool[] canPlace, bool[] canDelete ) {
for( int tile = (int)Block.Stone; tile <= (int)Block.Obsidian; tile++ ) {
canPlace[tile] = true;
diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj
index 3ac7eeee8..a3c2e5202 100644
--- a/ClassicalSharp/ClassicalSharp.csproj
+++ b/ClassicalSharp/ClassicalSharp.csproj
@@ -167,6 +167,7 @@
+
diff --git a/ClassicalSharp/Game/Game.InputHandling.cs b/ClassicalSharp/Game/Game.InputHandling.cs
index 75148f0bc..6f12cd9c1 100644
--- a/ClassicalSharp/Game/Game.InputHandling.cs
+++ b/ClassicalSharp/Game/Game.InputHandling.cs
@@ -157,18 +157,18 @@ namespace ClassicalSharp {
Vector3I pos = SelectedPos.BlockPos;
byte block = Map.GetBlock( pos );
if( block != 0 && CanDelete[block] ) {
- ParticleManager.BreakBlockEffect( pos, block );
- Network.SendSetBlock( pos.X, pos.Y, pos.Z, false, (byte)HeldBlock );
+ ParticleManager.BreakBlockEffect( pos, block );
UpdateBlock( pos.X, pos.Y, pos.Z, 0 );
+ Network.SendSetBlock( pos.X, pos.Y, pos.Z, false, (byte)HeldBlock );
}
} else if( right ) {
Vector3I pos = SelectedPos.TranslatedPos;
if( !Map.IsValidPos( pos ) ) return;
Block block = HeldBlock;
- if( !CanPick( Map.GetBlock( pos ) ) && CanPlace[(int)block] ) {
- Network.SendSetBlock( pos.X, pos.Y, pos.Z, true, (byte)block );
+ if( !CanPick( Map.GetBlock( pos ) ) && CanPlace[(int)block] ) {
UpdateBlock( pos.X, pos.Y, pos.Z, (byte)block );
+ Network.SendSetBlock( pos.X, pos.Y, pos.Z, true, (byte)block );
}
}
}
diff --git a/ClassicalSharp/Network/NetworkProcessor.cs b/ClassicalSharp/Network/NetworkProcessor.cs
index 45f7daee8..7e938260e 100644
--- a/ClassicalSharp/Network/NetworkProcessor.cs
+++ b/ClassicalSharp/Network/NetworkProcessor.cs
@@ -585,8 +585,9 @@ namespace ClassicalSharp {
byte blockId = reader.ReadUInt8();
bool canPlace = reader.ReadUInt8() != 0;
bool canDelete = reader.ReadUInt8() != 0;
+
if( blockId == 0 ) {
- for( int i = 1; i < game.CanPlace.Length; i++ ) {
+ for( int i = 1; i < BlockInfo.CpeBlocksCount; i++ ) {
game.CanPlace[i] = canPlace;
game.CanDelete[i] = canDelete;
}
diff --git a/ClassicalSharp/Singleplayer/Physics.cs b/ClassicalSharp/Singleplayer/Physics.cs
new file mode 100644
index 000000000..8ad5dcc1d
--- /dev/null
+++ b/ClassicalSharp/Singleplayer/Physics.cs
@@ -0,0 +1,282 @@
+using System;
+using System.Collections.Generic;
+using OpenTK;
+
+namespace ClassicalSharp.Singleplayer {
+
+ public class Physics {
+
+ Game game;
+ public Map map;
+ BlockInfo info;
+ int width, length, height, oneY;
+
+ const uint tickMask = 0xF8000000;
+ const uint posMask = 0x07FFFFFF;
+ const int tickShift = 27;
+
+ public Physics( Game game ) {
+ this.game = game;
+ map = game.Map;
+ info = game.BlockInfo;
+ }
+
+ bool CheckItem( Queue queue, out int posIndex ) {
+ uint packed = queue.Dequeue();
+ int tickDelay = (int)((packed & tickMask) >> tickShift);
+ posIndex = (int)(packed & posMask);
+
+ if( tickDelay > 0 ) {
+ tickDelay--;
+ queue.Enqueue( (uint)posIndex | ((uint)tickDelay << tickShift) );
+ return false;
+ }
+ return true;
+ }
+
+ bool CheckItem( Queue queue, int mask, out int posIndex, out int flags ) {
+ uint packed = queue.Dequeue();
+ flags = (int)((packed & tickMask) >> tickShift);
+ posIndex = (int)(packed & posMask);
+ int tickDelay = flags & mask;
+
+ if( tickDelay > 0 ) {
+ tickDelay--;
+ flags &= ~mask; // zero old tick delay bits
+ flags |= tickDelay; // then set them with new value
+ queue.Enqueue( (uint)posIndex | ((uint)flags << tickShift) );
+ return false;
+ }
+ return true;
+ }
+
+ public void Tick() {
+ TickLava();
+ TickWater();
+ TickFalling();
+ }
+
+ public void OnBlockPlaced( int x, int y, int z, byte block ) {
+ int index = ( y * length + z ) * width + x;
+ if( block == (byte)Block.Lava ) {
+ Lava.Enqueue( defLavaTick | (uint)index );
+ } else if( block == (byte)Block.Water ) {
+ Water.Enqueue( defWaterTick | (uint)index );
+ } else if( block == (byte)Block.Sand || block == (byte)Block.Gravel ) {
+ Falling.Enqueue( defFallingTick | (uint)index );
+ } else if( block == (byte)Block.TNT ) {
+ Explode( 4, x, y, z );
+ }
+ }
+
+ public void ResetMap() {
+ Lava.Clear();
+ Water.Clear();
+ width = map.Width;
+ length = map.Length;
+ height = map.Height;
+ oneY = height * width;
+ }
+
+ #region Lava
+
+ Queue Lava = new Queue();
+ const uint defLavaTick = 30u << tickShift;
+
+ void TickLava() {
+ int count = Lava.Count;
+ for( int i = 0; i < count; i++ ) {
+ int posIndex;
+ if( CheckItem( Lava, out posIndex ) ) {
+ byte block = map.mapData[posIndex];
+ if( !(block == (byte)Block.Lava || block == (byte)Block.StillLava) ) continue;
+
+ int x = posIndex % width;
+ int y = posIndex / oneY; // posIndex / (width * length)
+ int z = (posIndex / width) % length;
+
+ if( x > 0 ) PropagateLava( posIndex - 1, x - 1, y, z );
+ if( x < width - 1 ) PropagateLava( posIndex + 1, x + 1, y, z );
+ if( z > 0 ) PropagateLava( posIndex - width, x, y, z - 1 );
+ if( z < length - 1 ) PropagateLava( posIndex + width, x, y, z + 1 );
+ if( y > 0 ) PropagateLava( posIndex - oneY, x, y - 1, z );
+ }
+ }
+ }
+
+ void PropagateLava( int posIndex, int x, int y, int z ) {
+ byte block = map.mapData[posIndex];
+ if( block == (byte)Block.Water || block == (byte)Block.StillWater ) {
+ game.UpdateBlock( x, y, z, (byte)Block.Stone );
+ } else if( info.CollideType[block] == BlockCollideType.WalkThrough ) {
+ Lava.Enqueue( defLavaTick | (uint)posIndex );
+ game.UpdateBlock( x, y, z, (byte)Block.Lava );
+ }
+ }
+
+ #endregion
+
+ #region Water
+
+ Queue Water = new Queue();
+ const uint defWaterTick = 5u << tickShift;
+
+ void TickWater() {
+ int count = Water.Count;
+ for( int i = 0; i < count; i++ ) {
+ int posIndex;
+ if( CheckItem( Water, out posIndex ) ) {
+ byte block = map.mapData[posIndex];
+ if( !(block == (byte)Block.Water || block == (byte)Block.StillWater) ) continue;
+
+ int x = posIndex % width;
+ int y = posIndex / oneY; // posIndex / (width * length)
+ int z = (posIndex / width) % length;
+
+ if( x > 0 ) PropagateWater( posIndex - 1, x - 1, y, z );
+ if( x < width - 1 ) PropagateWater( posIndex + 1, x + 1, y, z );
+ if( z > 0 ) PropagateWater( posIndex - width, x, y, z - 1 );
+ if( z < length - 1 ) PropagateWater( posIndex + width, x, y, z + 1 );
+ if( y > 0 ) PropagateWater( posIndex - oneY, x, y - 1, z );
+ }
+ }
+ }
+
+ void PropagateWater( int posIndex, int x, int y, int z ) {
+ byte block = map.mapData[posIndex];
+ if( block == (byte)Block.Lava || block == (byte)Block.StillLava ) {
+ game.UpdateBlock( x, y, z, (byte)Block.Stone );
+ } else if( info.CollideType[block] == BlockCollideType.WalkThrough ) {
+ Water.Enqueue( defWaterTick | (uint)posIndex );
+ game.UpdateBlock( x, y, z, (byte)Block.Water );
+ }
+ }
+
+ #endregion
+
+ #region TNT
+
+ Vector3[] rayDirs;
+ Random rnd = new Random();
+ const float stepLen = 0.3f;
+ float[] hardness;
+
+ // Algorithm source: http://minecraft.gamepedia.com/Explosion
+ void Explode( float power, int x, int y, int z ) {
+ if( rayDirs == null )
+ InitExplosionCache();
+
+ game.UpdateBlock( x, y, z, 0 );
+ Vector3 basePos = new Vector3( x, y, z );
+ for( int i = 0; i < rayDirs.Length; i++ ) {
+ Vector3 dir = rayDirs[i] * stepLen;
+ Vector3 position = basePos;
+ float intensity = (float)(0.7 + rnd.NextDouble() * 0.6) * power;
+ while( intensity > 0 ) {
+ position += dir;
+ intensity -= stepLen * 0.75f;
+ Vector3I blockPos = Vector3I.Floor( position );
+ if( !map.IsValidPos( blockPos ) ) break;
+
+ byte block = map.GetBlock( blockPos );
+ intensity -= (hardness[block] / 5 + 0.3f) * stepLen; // TODO: proper block hardness table
+ if( intensity > 0 && block != 0 ) {
+ game.UpdateBlock( blockPos.X, blockPos.Y, blockPos.Z, 0 );
+ }
+ }
+ }
+ }
+
+ void InitExplosionCache() {
+ hardness = new float[] { 0, 30, 3, 2.5f, 30, 15, 0, 1.8E+07f, 500, 500, 500, 500, 2.5f,
+ 3, 15, 15, 15, 10, 1, 3, 1.5f, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0,
+ 0, 0, 30, 30, 30, 30, 30, 0, 7.5f, 30, 6000, 30, 0, 4, 0.5f, 0, 4, 4, 4, 4, 4, 2.5f,
+ // Note that the 30, 500, 15, 15 are guesses (CeramicTile --> Crate)
+ 30, 500, 15, 15, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ rayDirs = new Vector3[1352];
+ int index = 0;
+
+ // Y bottom and top planes
+ for( float x = -1; x <= 1.001f; x += 2f/15) {
+ for( float z = -1; z <= 1.001f; z += 2f/15) {
+ rayDirs[index++] = Vector3.Normalize( x, -1, z );
+ rayDirs[index++] = Vector3.Normalize( x, +1, z );
+ }
+ }
+ // Z planes
+ for( float y = -1 + 2f/15; y <= 1.001f - 2f/15; y += 2f/15) {
+ for( float x = -1; x <= 1.001f; x += 2f/15) {
+ rayDirs[index++] = Vector3.Normalize( x, y, -1 );
+ rayDirs[index++] = Vector3.Normalize( x, y, +1 );
+ }
+ }
+ // X planes
+ for( float y = -1 + 2f/15; y <= 1.001f - 2f/15; y += 2f/15) {
+ for( float z = -1 + 2f/15; z <= 1.001f- 2f/15; z += 2f/15) {
+ rayDirs[index++] = Vector3.Normalize( -1, y, z );
+ rayDirs[index++] = Vector3.Normalize( +1, y, z );
+ }
+ }
+ }
+
+ #endregion
+
+ #region Sapling
+ // TODO: tree growing
+ #endregion
+
+ #region Grass
+ // TODO: grass growing
+ #endregion
+
+ #region Sand/Gravel
+
+ Queue Falling = new Queue();
+ const uint defFallingTick = 2u << tickShift;
+
+ void TickFalling() {
+ int count = Falling.Count;
+ for( int i = 0; i < count; i++ ) {
+ int posIndex, flags;
+ if( CheckItem( Falling, 0x2, out posIndex, out flags ) ) {
+ byte block = map.mapData[posIndex];
+ if( !(block == (byte)Block.Sand || block == (byte)Block.Gravel ) ) continue;
+
+ int x = posIndex % width;
+ int y = posIndex / oneY; // posIndex / (width * length)
+ int z = (posIndex / width) % length;
+ if( y > 0 ) PropagateFalling( posIndex - oneY, x, y - 1, z, block, flags );
+ }
+ }
+ }
+
+ void PropagateFalling( int posIndex, int x, int y, int z, byte block, int flags ) {
+ byte newBlock = map.mapData[posIndex];
+ if( newBlock == 0 || info.IsLiquid[newBlock] ) {
+ uint newFlags = MakeFallingFlags( newBlock ) << tickShift;
+ Falling.Enqueue( newFlags | (uint)posIndex );
+ game.UpdateBlock( x, y, z, oldBlock[flags >> 2] );
+ game.UpdateBlock( x, y - 1, z, block );
+ }
+ }
+
+ static byte[] oldBlock = new byte[] { (byte)Block.Air, (byte)Block.StillWater,
+ (byte)Block.Water, (byte)Block.StillLava, (Byte)Block.Lava };
+ static uint MakeFallingFlags( byte above ) {
+ byte flags = 2;
+ if( above == (byte)Block.StillWater ) flags |= 0x04; // 1
+ else if( above == (byte)Block.Water ) flags |= 0x08; // 2
+ else if( above == (byte)Block.StillLava ) flags |= 0x0C; // 3
+ else if( above == (byte)Block.Lava ) flags |= 0x10; // 4
+ return flags;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/ClassicalSharp/Singleplayer/Server.cs b/ClassicalSharp/Singleplayer/Server.cs
index f1227a9dc..36ba5be42 100644
--- a/ClassicalSharp/Singleplayer/Server.cs
+++ b/ClassicalSharp/Singleplayer/Server.cs
@@ -8,8 +8,10 @@ namespace ClassicalSharp.Singleplayer {
public sealed class SinglePlayerServer : INetworkProcessor {
Game game;
+ Physics physics;
public SinglePlayerServer( Game window ) {
game = window;
+ physics = new Physics( game );
}
public override bool IsSinglePlayer {
@@ -39,6 +41,8 @@ namespace ClassicalSharp.Singleplayer {
}
public override void SendSetBlock( int x, int y, int z, bool place, byte block ) {
+ if( place )
+ physics.OnBlockPlaced( x, y, z, block );
}
public override void SendPlayerClick( MouseButton button, bool buttonDown, byte targetId, PickedPos pos ) {
@@ -49,6 +53,7 @@ namespace ClassicalSharp.Singleplayer {
public override void Tick( double delta ) {
if( Disconnected ) return;
+ physics.Tick();
}
internal void NewMap() {
@@ -73,6 +78,7 @@ namespace ClassicalSharp.Singleplayer {
ResetPlayerPosition();
game.AddChat( "&ePlaying single player", CpeMessage.Status1 );
GC.Collect();
+ physics.ResetMap();
}
unsafe void MapSet( int width, int length, byte* ptr, int yStart, int yEnd, byte block ) {
diff --git a/OpenTK/Math/Vector3.cs b/OpenTK/Math/Vector3.cs
index 6c000a7e2..98fc3e748 100644
--- a/OpenTK/Math/Vector3.cs
+++ b/OpenTK/Math/Vector3.cs
@@ -145,6 +145,11 @@ namespace OpenTK {
vec.Z *= scale;
return vec;
}
+
+ public static Vector3 Normalize(float x, float y, float z) {
+ float scale = 1f / (float)Math.Sqrt( x * x + y * y + z * z );
+ return new Vector3( x * scale, y * scale, z * scale );
+ }
public static void Normalize(ref Vector3 vec, out Vector3 result) {
float scale = 1f / vec.Length;