// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 using System; using System.Collections.Generic; using ClassicalSharp.Map; using ClassicalSharp.Events; #if USE16_BIT using BlockID = System.UInt16; #else using BlockID = System.Byte; #endif namespace ClassicalSharp.Singleplayer { public class PhysicsBase { Game game; World map; Random rnd = new Random(); BlockInfo info; int width, length, height, oneY; public const uint tickMask = 0xF8000000; public const uint posMask = 0x07FFFFFF; public const int tickShift = 27; FallingPhysics falling; TNTPhysics tnt; FoliagePhysics foliage; LiquidPhysics liquid; OtherPhysics other; bool enabled = true; public bool Enabled { get { return enabled; } set { enabled = value; liquid.Clear(); } } public Action[] OnActivate = new Action[Block.Count]; public Action[] OnRandomTick = new Action[Block.Count]; public Action[] OnPlace = new Action[Block.Count]; public Action[] OnDelete = new Action[Block.Count]; public PhysicsBase(Game game) { this.game = game; map = game.World; info = game.BlockInfo; game.WorldEvents.OnNewMapLoaded += ResetMap; game.UserEvents.BlockChanged += BlockChanged; enabled = Options.GetBool(OptionsKey.SingleplayerPhysics, true); falling = new FallingPhysics(game, this); tnt = new TNTPhysics(game, this); foliage = new FoliagePhysics(game, this); liquid = new LiquidPhysics(game, this); other = new OtherPhysics(game, this); } internal static 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; } int tickCount = 0; public void Tick() { if (!Enabled || game.World.IsNotLoaded) return; //if ((tickCount % 5) == 0) { liquid.TickLava(); liquid.TickWater(); //} tickCount++; TickRandomBlocks(); } void BlockChanged(object sender, BlockChangedEventArgs e) { if (!Enabled) return; Vector3I p = e.Coords; int index = (p.Y * length + p.Z) * width + p.X; BlockID block = e.Block; if (block == Block.Air && IsEdgeWater(p.X, p.Y, p.Z)) { block = Block.StillWater; game.UpdateBlock(p.X, p.Y, p.Z, Block.StillWater); } if (e.Block == 0) { Action delete = OnDelete[e.OldBlock]; if (delete != null) delete(index, e.OldBlock); } else { Action place = OnPlace[block]; if (place != null) place(index, block); } ActivateNeighbours(p.X, p.Y, p.Z, index); } /// Activates the direct neighbouring blocks of the given coordinates. public void ActivateNeighbours(int x, int y, int z, int index) { if (x > 0) Activate(index - 1); if (x < map.Width - 1) Activate(index + 1); if (z > 0) Activate(index - map.Width); if (z < map.Length - 1) Activate(index + map.Width); if (y > 0) Activate(index - oneY); if (y < map.Height - 1) Activate(index + oneY); } /// Activates the block at the particular packed coordinates. public void Activate(int index) { BlockID block = map.blocks[index]; Action activate = OnActivate[block]; if (activate != null) activate(index, block); } bool IsEdgeWater(int x, int y, int z) { WorldEnv env = map.Env; if (!(env.EdgeBlock == Block.Water || env.EdgeBlock == Block.StillWater)) return false; return y >= env.SidesHeight && y < env.EdgeHeight && (x == 0 || z == 0 || x == (map.Width - 1) || z == (map.Length - 1)); } void ResetMap(object sender, EventArgs e) { falling.ResetMap(); liquid.ResetMap(); width = map.Width; height = map.Height; length = map.Length; oneY = width * length; } public void Dispose() { game.WorldEvents.OnNewMapLoaded -= ResetMap; game.UserEvents.BlockChanged -= BlockChanged; } void TickRandomBlocks() { int xMax = width - 1, yMax = height - 1, zMax = length - 1; for (int y = 0; y < height; y += 16) for (int z = 0; z < length; z += 16) for (int x = 0; x < width; x += 16) { int lo = (y * length + z) * width + x; int hi = (Math.Min(yMax, y + 15) * length + Math.Min(zMax, z + 15)) * width + Math.Min(xMax, x + 15); // Inlined 3 random ticks for this chunk int index = rnd.Next(lo, hi); BlockID block = map.blocks[index]; Action tick = OnRandomTick[block]; if (tick != null) tick(index, block); index = rnd.Next(lo, hi); block = map.blocks[index]; tick = OnRandomTick[block]; if (tick != null) tick(index, block); index = rnd.Next(lo, hi); block = map.blocks[index]; tick = OnRandomTick[block]; if (tick != null) tick(index, block); } } } }