mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-01-23 17:43:08 -05:00
392 lines
15 KiB
C#
392 lines
15 KiB
C#
// 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",
|
|
};
|
|
}
|
|
|
|
/// <summary> Describes how a block is rendered in the world. </summary>
|
|
public static class DrawType {
|
|
/// <summary> Completely covers blocks behind (e.g. dirt). </summary>
|
|
public const byte Opaque = 0;
|
|
/// <summary> Blocks behind show (e.g. glass). Pixels are either fully visible or invisible. </summary>
|
|
public const byte Transparent = 1;
|
|
/// <summary> Same as Transparent, but all neighbour faces show. (e.g. leaves) </summary>
|
|
public const byte TransparentThick = 2;
|
|
/// <summary> Blocks behind show (e.g. water). Pixels blend with other blocks behind. </summary>
|
|
public const byte Translucent = 3;
|
|
/// <summary> Does not show (e.g. air). Can still be collided with. </summary>
|
|
public const byte Gas = 4;
|
|
/// <summary> Block renders as an X sprite (e.g. sapling). Pixels are either fully visible or invisible. </summary>
|
|
public const byte Sprite = 5;
|
|
}
|
|
|
|
/// <summary> Describes the interaction a block has with a player when they collide with it. </summary>
|
|
public static class CollideType {
|
|
/// <summary> No interaction when player collides. </summary>
|
|
public const byte Gas = 0;
|
|
/// <summary> 'swimming'/'bobbing' interaction when player collides. </summary>
|
|
public const byte Liquid = 1;
|
|
/// <summary> Block completely stops the player when they are moving. </summary>
|
|
public const byte Solid = 2;
|
|
/// <summary> Block is solid and partially slidable on. </summary>
|
|
public const byte Ice = 3;
|
|
/// <summary> Block is solid and fully slidable on. </summary>
|
|
public const byte SlipperyIce = 4;
|
|
/// <summary> Water style 'swimming'/'bobbing' interaction when player collides. </summary>
|
|
public const byte LiquidWater = 5;
|
|
/// <summary> Lava style 'swimming'/'bobbing' interaction when player collides. </summary>
|
|
public const byte LiquidLava = 6;
|
|
/// <summary> Rope/Ladder style climbing interaction when player collides. </summary>
|
|
public const byte ClimbRope = 7;
|
|
}
|
|
|
|
/// <summary> Stores various properties about the blocks. </summary>
|
|
/// <remarks> e.g. blocks light, height, texture IDs, etc. </remarks>
|
|
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;
|
|
}
|
|
|
|
/// <summary> 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. </summary>
|
|
public static bool IsFaceHidden(BlockID block, BlockID other, int tileSide) {
|
|
return (hidden[(block * Count) + other] & (1 << tileSide)) != 0;
|
|
}
|
|
}
|
|
}
|