// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using System.Collections.Generic;
using ClassicalSharp.Blocks;
using OpenTK;
using BlockID = System.UInt16;
using TexLoc = System.UInt16;
namespace ClassicalSharp {
public static class SoundType {
public const byte None = 0; public const byte Wood = 1;
public const byte Gravel = 2; public const byte Grass = 3;
public const byte Stone = 4; public const byte Metal = 5;
public const byte Glass = 6; public const byte Cloth = 7;
public const byte Sand = 8; public const byte Snow = 9;
public const byte Count = 10;
public static string[] Names = new string[Count] {
"none", "wood", "gravel", "grass", "stone",
"metal", "glass", "cloth", "sand", "snow",
};
}
/// Describes how a block is rendered in the world.
public static class DrawType {
/// Completely covers blocks behind (e.g. dirt).
public const byte Opaque = 0;
/// Blocks behind show (e.g. glass). Pixels are either fully visible or invisible.
public const byte Transparent = 1;
/// Same as Transparent, but all neighbour faces show. (e.g. leaves)
public const byte TransparentThick = 2;
/// Blocks behind show (e.g. water). Pixels blend with other blocks behind.
public const byte Translucent = 3;
/// Does not show (e.g. air). Can still be collided with.
public const byte Gas = 4;
/// Block renders as an X sprite (e.g. sapling). Pixels are either fully visible or invisible.
public const byte Sprite = 5;
}
/// Describes the interaction a block has with a player when they collide with it.
public static class CollideType {
/// No interaction when player collides.
public const byte Gas = 0;
/// 'swimming'/'bobbing' interaction when player collides.
public const byte Liquid = 1;
/// Block completely stops the player when they are moving.
public const byte Solid = 2;
/// Block is solid and partially slidable on.
public const byte Ice = 3;
/// Block is solid and fully slidable on.
public const byte SlipperyIce = 4;
/// Water style 'swimming'/'bobbing' interaction when player collides.
public const byte LiquidWater = 5;
/// Lava style 'swimming'/'bobbing' interaction when player collides.
public const byte LiquidLava = 6;
/// Rope/Ladder style climbing interaction when player collides.
public const byte ClimbRope = 7;
}
/// Stores various properties about the blocks.
/// e.g. blocks light, height, texture IDs, etc.
public static partial class BlockInfo {
public static bool[] IsLiquid, BlocksLight, FullBright;
public static bool[] CanPlace, CanDelete, Tinted, FullOpaque;
public static byte[] Collide, ExtendedCollide, hidden;
public static byte[] LightOffset, Draw, SpriteOffset, CanStretch;
public static byte[] DigSounds, StepSounds;
public static string[] Name;
public static float[] FogDensity, SpeedMultiplier;
public static TexLoc[] textures;
public static PackedCol[] FogCol;
public static Vector3[] MinBB, MaxBB, RenderMinBB, RenderMaxBB;
static uint[] DefinedCustomBlocks;
public static int MaxUsed, Count, IDMask;
public static void Allocate(int count) {
IsLiquid = new bool[count];
BlocksLight = new bool[count];
FullBright = new bool[count];
CanPlace = new bool[count];
CanDelete = new bool[count];
Tinted = new bool[count];
FullOpaque = new bool[count];
Collide = new byte[count];
ExtendedCollide = new byte[count];
textures = new TexLoc[count * Side.Sides];
hidden = new byte[count * count];
LightOffset = new byte[count];
Draw = new byte[count];
SpriteOffset = new byte[count];
CanStretch = new byte[count];
DigSounds = new byte[count];
StepSounds = new byte[count];
Name = new string[count];
FogDensity = new float[count];
SpeedMultiplier = new float[count];
FogCol = new PackedCol[count];
MinBB = new Vector3[count];
MaxBB = new Vector3[count];
RenderMinBB = new Vector3[count];
RenderMaxBB = new Vector3[count];
DefinedCustomBlocks = new uint[count >> 5];
Count = count;
SetMaxUsed(count - 1);
}
public static void SetMaxUsed(int max) {
MaxUsed = max;
IDMask = Utils.NextPowerOf2(max + 1) - 1;
}
public static void Reset() {
Init();
RecalculateSpriteBB();
}
public static void Init() {
for (int i = 0; i < DefinedCustomBlocks.Length; i++) {
DefinedCustomBlocks[i] = 0;
}
for (int b = 0; b < Count; b++) {
ResetBlockProps((BlockID)b);
}
UpdateCulling();
}
public static void SetDefaultPerms() {
for (int b = Block.Air; b < Count; b++) {
CanPlace[b] = true;
CanDelete[b] = true;
}
CanPlace[Block.Air] = false; CanDelete[Block.Air] = false;
CanPlace[Block.Lava] = false; CanDelete[Block.Lava] = false;
CanPlace[Block.Water] = false; CanDelete[Block.Water] = false;
CanPlace[Block.StillLava] = false; CanDelete[Block.StillLava] = false;
CanPlace[Block.StillWater] = false; CanDelete[Block.StillWater] = false;
CanPlace[Block.Bedrock] = false; CanDelete[Block.Bedrock] = false;
}
public static bool IsCustomDefined(BlockID block) {
return (DefinedCustomBlocks[block >> 5] & (1u << (block & 0x1F))) != 0;
}
public static void SetCustomDefined(BlockID block, bool defined) {
if (defined) {
DefinedCustomBlocks[block >> 5] |= (1u << (block & 0x1F));
} else {
DefinedCustomBlocks[block >> 5] &= ~(1u << (block & 0x1F));
}
}
public static void DefineCustom(Game game, BlockID block) {
SetBlockDraw(block, Draw[block]);
CalcRenderBounds(block);
UpdateCulling(block);
Tinted[block] = FogCol[block] != PackedCol.Black && Name[block].IndexOf('#') >= 0;
CalcLightOffset(block);
game.Inventory.AddDefault(block);
SetCustomDefined(block, true);
Events.RaiseBlockDefinitionChanged();
}
static void RecalcIsLiquid(BlockID block) {
byte collide = ExtendedCollide[block];
IsLiquid[block] =
(collide == CollideType.LiquidWater && Draw[block] == DrawType.Translucent) ||
(collide == CollideType.LiquidLava && Draw[block] == DrawType.Transparent);
}
public static void SetCollide(BlockID block, byte collide) {
// necessary for cases where servers redefined core blocks before extended types were introduced
collide = DefaultSet.MapOldCollide(block, collide);
ExtendedCollide[block] = collide;
RecalcIsLiquid(block);
// Reduce extended collision types to their simpler forms
if (collide == CollideType.Ice) collide = CollideType.Solid;
if (collide == CollideType.SlipperyIce) collide = CollideType.Solid;
if (collide == CollideType.LiquidWater) collide = CollideType.Liquid;
if (collide == CollideType.LiquidLava) collide = CollideType.Liquid;
Collide[block] = collide;
}
public static void SetBlockDraw(BlockID block, byte draw) {
if (draw == DrawType.Opaque && Collide[block] != CollideType.Solid)
draw = DrawType.Transparent;
Draw[block] = draw;
RecalcIsLiquid(block);
FullOpaque[block] = draw == DrawType.Opaque
&& MinBB[block] == Vector3.Zero && MaxBB[block] == Vector3.One;
}
public static void ResetBlockProps(BlockID block) {
BlocksLight[block] = DefaultSet.BlocksLight(block);
FullBright[block] = DefaultSet.FullBright(block);
FogCol[block] = DefaultSet.FogColour(block);
FogDensity[block] = DefaultSet.FogDensity(block);
SetCollide(block, DefaultSet.Collide(block));
DigSounds[block] = DefaultSet.DigSound(block);
StepSounds[block] = DefaultSet.StepSound(block);
SpeedMultiplier[block] = 1;
Name[block] = DefaultName(block);
Tinted[block] = false;
SpriteOffset[block] = 0;
Draw[block] = DefaultSet.Draw(block);
if (Draw[block] == DrawType.Sprite) {
MinBB[block] = new Vector3(2.50f/16f, 0, 2.50f/16f);
MaxBB[block] = new Vector3(13.5f/16f, 1, 13.5f/16f);
} else {
MinBB[block] = Vector3.Zero;
MaxBB[block] = Vector3.One;
MaxBB[block].Y = DefaultSet.Height(block);
}
SetBlockDraw(block, Draw[block]);
CalcRenderBounds(block);
CalcLightOffset(block);
if (block >= Block.CpeCount) {
SetTex(0, Side.Top, block);
SetTex(0, Side.Bottom, block);
SetSide(0, block);
} else {
SetTex(topTex[block], Side.Top, block);
SetTex(bottomTex[block], Side.Bottom, block);
SetSide(sideTex[block], block);
}
}
public static int FindID(string name) {
for (int b = 0; b < Count; b++) {
if (Utils.CaselessEq(Name[b], name)) return b;
}
return -1;
}
public static int Parse(string str) {
int b;
if (int.TryParse(str, out b) && b < Count) return b;
return FindID(str);
}
const string RawNames = "Air_Stone_Grass_Dirt_Cobblestone_Wood_Sapling_Bedrock_Water_Still water_Lava" +
"_Still lava_Sand_Gravel_Gold ore_Iron ore_Coal ore_Log_Leaves_Sponge_Glass_Red_Orange_Yellow_Lime_Green" +
"_Teal_Aqua_Cyan_Blue_Indigo_Violet_Magenta_Pink_Black_Gray_White_Dandelion_Rose_Brown mushroom_Red mushroom" +
"_Gold_Iron_Double slab_Slab_Brick_TNT_Bookshelf_Mossy rocks_Obsidian_Cobblestone slab_Rope_Sandstone" +
"_Snow_Fire_Light pink_Forest green_Brown_Deep blue_Turquoise_Ice_Ceramic tile_Magma_Pillar_Crate_Stone brick";
static string DefaultName(BlockID block) {
if (block >= Block.CpeCount) return "Invalid";
// Find start and end of this particular block name
int start = 0;
for (int i = 0; i < block; i++)
start = RawNames.IndexOf('_', start) + 1;
int end = RawNames.IndexOf('_', start);
if (end == -1) end = RawNames.Length;
return RawNames.Substring(start, end - start);
}
internal static void SetSide(TexLoc texLoc, BlockID blockId) {
textures[blockId * Side.Sides + Side.Left] = texLoc;
textures[blockId * Side.Sides + Side.Right] = texLoc;
textures[blockId * Side.Sides + Side.Front] = texLoc;
textures[blockId * Side.Sides + Side.Back] = texLoc;
}
internal static void SetTex(TexLoc texLoc, int face, BlockID blockId) {
textures[blockId * Side.Sides + face] = texLoc;
}
public static int GetTextureLoc(BlockID block, int face) {
return textures[block * Side.Sides + face];
}
static byte[] topTex = new byte[] { 0, 1, 0, 2, 16, 4, 15, 17, 14, 14,
30, 30, 18, 19, 32, 33, 34, 21, 22, 48, 49, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 13, 12, 29, 28, 24, 23, 6, 6, 7, 9, 4,
36, 37, 16, 11, 25, 50, 38, 80, 81, 82, 83, 84, 51, 54, 86, 26, 53, 52, };
static byte[] sideTex = new byte[] { 0, 1, 3, 2, 16, 4, 15, 17, 14, 14,
30, 30, 18, 19, 32, 33, 34, 20, 22, 48, 49, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 13, 12, 29, 28, 40, 39, 5, 5, 7, 8, 35,
36, 37, 16, 11, 41, 50, 38, 80, 81, 82, 83, 84, 51, 54, 86, 42, 53, 52, };
static byte[] bottomTex = new byte[] { 0, 1, 2, 2, 16, 4, 15, 17, 14, 14,
30, 30, 18, 19, 32, 33, 34, 21, 22, 48, 49, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 13, 12, 29, 28, 56, 55, 6, 6, 7, 10, 4,
36, 37, 16, 11, 57, 50, 38, 80, 81, 82, 83, 84, 51, 54, 86, 58, 53, 52 };
internal static void UpdateCulling() {
for (int block = 0; block < Count; block++) {
CalcStretch((BlockID)block);
for (int neighbour = 0; neighbour < Count; neighbour++) {
CalcCulling((BlockID)block, (BlockID)neighbour);
}
}
}
internal static void UpdateCulling(BlockID block) {
CalcStretch(block);
for (int other = 0; other < Count; other++) {
CalcCulling(block, (BlockID)other);
CalcCulling((BlockID)other, block);
}
}
static void CalcStretch(BlockID block) {
// faces which can be stretched on X axis
if (MinBB[block].X == 0 && MaxBB[block].X == 1) {
CanStretch[block] |= 0x3C;
} else {
CanStretch[block] &= 0xC3; // ~0x3C
}
// faces which can be stretched on Z axis
if (MinBB[block].Z == 0 && MaxBB[block].Z == 1) {
CanStretch[block] |= 0x03;
} else {
CanStretch[block] &= 0xFC; // ~0x03
}
}
static void CalcCulling(BlockID block, BlockID other) {
if (!IsHidden(block, other)) {
// Block is not hidden at all, so we can just entirely skip per-face check
BlockInfo.hidden[(block * Count) + other] = 0;
} else {
Vector3 bMin = MinBB[block], bMax = MaxBB[block];
Vector3 oMin = MinBB[other], oMax = MaxBB[other];
if (IsLiquid[block]) bMax.Y -= 1.5f/16;
if (IsLiquid[other]) oMax.Y -= 1.5f/16;
// Don't need to care about sprites here since they never cull faces
bool bothLiquid = IsLiquid[block] && IsLiquid[other];
int f = 0; // mark all faces initially 'not hidden'
// Whether the 'texture region' of a face on block fits inside corresponding region on other block
bool occludedX = (bMin.Z >= oMin.Z && bMax.Z <= oMax.Z) && (bMin.Y >= oMin.Y && bMax.Y <= oMax.Y);
bool occludedY = (bMin.X >= oMin.X && bMax.X <= oMax.X) && (bMin.Z >= oMin.Z && bMax.Z <= oMax.Z);
bool occludedZ = (bMin.X >= oMin.X && bMax.X <= oMax.X) && (bMin.Y >= oMin.Y && bMax.Y <= oMax.Y);
f |= occludedX && oMax.X == 1 && bMin.X == 0 ? (1 << Side.Left) : 0;
f |= occludedX && oMin.X == 0 && bMax.X == 1 ? (1 << Side.Right) : 0;
f |= occludedZ && oMax.Z == 1 && bMin.Z == 0 ? (1 << Side.Front) : 0;
f |= occludedZ && oMin.Z == 0 && bMax.Z == 1 ? (1 << Side.Back) : 0;
f |= occludedY && (bothLiquid || (oMax.Y == 1 && bMin.Y == 0)) ? (1 << Side.Bottom) : 0;
f |= occludedY && (bothLiquid || (oMin.Y == 0 && bMax.Y == 1)) ? (1 << Side.Top) : 0;
BlockInfo.hidden[(block * Count) + other] = (byte)f;
}
}
static bool IsHidden(BlockID block, BlockID other) {
// Sprite blocks can never hide faces.
if (Draw[block] == DrawType.Sprite) return false;
// NOTE: Water is always culled by lava
if ((block == Block.Water || block == Block.StillWater) && (other == Block.Lava || other == Block.StillLava))
return true;
// All blocks (except for say leaves) cull with themselves.
if (block == other) return Draw[block] != DrawType.TransparentThick;
// An opaque neighbour (asides from lava) culls the face.
if (Draw[other] == DrawType.Opaque && !IsLiquid[other]) return true;
if (Draw[block] != DrawType.Translucent || Draw[other] != DrawType.Translucent) return false;
// e.g. for water / ice, don't need to draw water.
byte bType = Collide[block], oType = Collide[other];
bool canSkip = (bType == CollideType.Solid && oType == CollideType.Solid) || bType != CollideType.Solid;
return canSkip;
}
/// Returns whether the face at the given face of the block
/// should be drawn with the neighbour 'other' present on the other side of the face.
public static bool IsFaceHidden(BlockID block, BlockID other, int tileSide) {
return (hidden[(block * Count) + other] & (1 << tileSide)) != 0;
}
}
}