using Unity.Netcode; /// /// Compact block state struct for chunk-based storage. /// 3 bytes per block: type (1), health (1), flags (1) /// public struct BlockData : INetworkSerializable { /// /// Block type: 0=empty/air, 1=normal stone, 2=resource ore /// public byte blockType; /// /// Block health: 0-255 (0 = destroyed) /// public byte health; /// /// Block flags: bit 0 = isDiscovered (fog of war) /// public byte flags; // Block type constants public const byte TYPE_EMPTY = 0; public const byte TYPE_NORMAL = 1; public const byte TYPE_RESOURCE = 2; // Flag bit masks public const byte FLAG_DISCOVERED = 1 << 0; // Has been seen at least once (networked/permanent) /// /// Whether this block is empty (air or destroyed) /// public bool IsEmpty => blockType == TYPE_EMPTY || health == 0; /// /// Whether this block has been discovered by players /// public bool IsDiscovered { get => (flags & FLAG_DISCOVERED) != 0; set { if (value) flags |= FLAG_DISCOVERED; else flags &= unchecked((byte)~FLAG_DISCOVERED); } } /// /// Whether this block is a resource block /// public bool IsResource => blockType == TYPE_RESOURCE; /// /// Create an empty block /// public static BlockData Empty => new BlockData { blockType = TYPE_EMPTY, health = 0, flags = 0 }; /// /// Create a normal stone block with full health /// public static BlockData Normal(byte maxHealth = 100) => new BlockData { blockType = TYPE_NORMAL, health = maxHealth, flags = 0 }; /// /// Create a resource ore block with full health /// public static BlockData Resource(byte maxHealth = 150) => new BlockData { blockType = TYPE_RESOURCE, health = maxHealth, flags = 0 }; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref blockType); serializer.SerializeValue(ref health); serializer.SerializeValue(ref flags); } } /// /// Network-serializable container for entire chunk state. /// Used for initial sync when clients connect. /// public struct ChunkState : INetworkSerializable { public BlockData[] blocks; public const int CHUNK_SIZE = 4; public const int BLOCKS_PER_CHUNK = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; // 64 public ChunkState(int size) { blocks = new BlockData[size]; } /// /// Ensure blocks array is initialized /// private void EnsureInitialized() { if (blocks == null) { blocks = new BlockData[BLOCKS_PER_CHUNK]; } } /// /// Get block at local coordinates within the chunk /// public BlockData GetBlock(int x, int y, int z) { if (blocks == null) return BlockData.Empty; int index = x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE; if (index < 0 || index >= blocks.Length) return BlockData.Empty; return blocks[index]; } /// /// Set block at local coordinates within the chunk /// public void SetBlock(int x, int y, int z, BlockData block) { EnsureInitialized(); int index = x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE; if (index >= 0 && index < blocks.Length) { blocks[index] = block; } } /// /// Convert local index to local 3D coordinates /// public static (int x, int y, int z) IndexToLocal(int index) { int x = index % CHUNK_SIZE; int y = (index / CHUNK_SIZE) % CHUNK_SIZE; int z = index / (CHUNK_SIZE * CHUNK_SIZE); return (x, y, z); } /// /// Convert local 3D coordinates to index /// public static int LocalToIndex(int x, int y, int z) { return x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE; } public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { // Serialize array length first int length = BLOCKS_PER_CHUNK; serializer.SerializeValue(ref length); if (serializer.IsReader) { blocks = new BlockData[length]; } else if (blocks == null) { blocks = new BlockData[length]; } for (int i = 0; i < length; i++) { blocks[i].NetworkSerialize(serializer); } } /// /// Create a default initialized ChunkState /// public static ChunkState CreateEmpty() { return new ChunkState(BLOCKS_PER_CHUNK); } }