// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 using System; using ClassicalSharp.Map; namespace ClassicalSharp.Singleplayer { public class FoilagePhysics { Game game; PhysicsBase physics; World map; Random rnd = new Random(); public FoilagePhysics(Game game, PhysicsBase physics) { this.game = game; this.physics = physics; map = game.World; physics.OnPlace[Block.Sapling] = HandleSapling; physics.OnRandomTick[Block.Sapling] = HandleSapling; physics.OnRandomTick[Block.Dirt] = HandleDirt; physics.OnRandomTick[Block.Grass] = HandleGrass; physics.OnRandomTick[Block.Dandelion] = HandleFlower; physics.OnRandomTick[Block.Rose] = HandleFlower; physics.OnRandomTick[Block.RedMushroom] = HandleMushroom; physics.OnRandomTick[Block.BrownMushroom] = HandleMushroom; } void HandleSapling(int index, byte block) { int x = index % map.Width; int y = (index / map.Width) / map.Length; int z = (index / map.Width) % map.Length; GrowTree(x, y, z); } void HandleDirt(int index, byte block) { int x = index % map.Width; int y = (index / map.Width) / map.Length; int z = (index / map.Width) % map.Length; if (game.Lighting.IsLit(x, y, z)) game.UpdateBlock(x, y, z, Block.Grass); } void HandleGrass(int index, byte block) { int x = index % map.Width; int y = (index / map.Width) / map.Length; int z = (index / map.Width) % map.Length; if (!game.Lighting.IsLit(x, y, z)) game.UpdateBlock(x, y, z, Block.Dirt); } void HandleFlower(int index, byte block) { int x = index % map.Width; int y = (index / map.Width) / map.Length; int z = (index / map.Width) % map.Length; if (!game.Lighting.IsLit(x, y, z)) { game.UpdateBlock(x, y, z, Block.Air); physics.ActivateNeighbours(x, y, z, index); return; } byte below = Block.Dirt; if (y > 0) below = map.blocks[index - map.Width * map.Length]; if (!(below == Block.Dirt || below == Block.Grass)) { game.UpdateBlock(x, y, z, Block.Air); physics.ActivateNeighbours(x, y, z, index); } } void HandleMushroom(int index, byte block) { int x = index % map.Width; int y = (index / map.Width) / map.Length; int z = (index / map.Width) % map.Length; if (game.Lighting.IsLit(x, y, z)) { game.UpdateBlock(x, y, z, Block.Air); physics.ActivateNeighbours(x, y, z, index); return; } byte below = Block.Stone; if (y > 0) below = map.blocks[index - map.Width * map.Length]; if (!(below == Block.Stone || below == Block.Cobblestone)) { game.UpdateBlock(x, y, z, Block.Air); physics.ActivateNeighbours(x, y, z, index); } } // Algorithm source: Looking at the trees generated by the default classic server. // Hence, the random thresholds may be slightly off. public void GrowTree(int x, int y, int z) { int trunkH = rnd.Next(1, 4); game.UpdateBlock(x, y, z, 0); // Can the new tree grow? if (!CheckBounds(x, x, y, y + trunkH - 1, z, z) || !CheckBounds(x - 2, x + 2, y + trunkH, y + trunkH + 1, z - 2, z + 2) || !CheckBounds(x - 1, x + 1, y + trunkH + 2, y + trunkH + 3, z - 1, z + 1)) { game.UpdateBlock(x, y, z, 0); return; } // Leaves bottom layer y += trunkH; for (int zz = -2; zz <= 2; zz++) { for (int xx = -2; xx <= 2; xx++) { if (Math.Abs(xx) == 2 && Math.Abs(zz) == 2) { if (rnd.Next(0, 5) < 4) game.UpdateBlock(x + xx, y, z + zz, Block.Leaves); if (rnd.Next(0, 5) < 2) game.UpdateBlock(x + xx, y + 1, z + zz, Block.Leaves); } else { game.UpdateBlock(x + xx, y, z + zz, Block.Leaves); game.UpdateBlock(x + xx, y + 1, z + zz, Block.Leaves); } } } // Leaves top layer y += 2; for (int zz = -1; zz <= 1; zz++) { for (int xx = -1; xx <= 1; xx++) { if (xx == 0 || zz == 0) { game.UpdateBlock(x + xx, y, z + zz, Block.Leaves); game.UpdateBlock(x + xx, y + 1, z + zz, Block.Leaves); } else if (rnd.Next(0, 5) == 0) { game.UpdateBlock(x + xx, y, z + zz, Block.Leaves); } } } // Base trunk y -= 2 + trunkH; for (int yy = 0; yy < trunkH + 3; yy++) game.UpdateBlock(x, y + yy, z, Block.Log); } bool CheckBounds(int x1, int x2, int y1, int y2, int z1, int z2) { for (int y = y1; y <= y2; y++) for (int z = z1; z <= z2; z++) for (int x = x1; x <= x2; x++) { if (!map.IsValidPos(x, y, z)) return false; byte block = map.GetBlock(x, y, z); if (!(block == 0 || block == Block.Leaves)) return false; } return true; } } }