completely overhaul this code by removing special block arrays, and dont store textures locally. fetch textures and custom block models and other info from https://github.com/PrismarineJS/minecraft-assets/tree/master/data/1.20.2 . textures are stored in https://github.com/PrismarineJS/minecraft-assets/tree/master/data/1.20.2/blocks . block texture mappings are in https://github.com/PrismarineJS/minecraft-assets/blob/master/data/1.20.2/blocks_textures.json . Block models/directional texture mappings are in https://github.com/PrismarineJS/minecraft-assets/blob/master/data/1.20.2/blocks_models.json . give the full updated and compacted code

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using Newtonsoft.Json;
using System;
using PimDeWitte.UnityMainThreadDispatcher;
using System.Collections;

public class BlockFetcher : MonoBehaviour
{
private string serverUrl = "http://localhost:8000/blocks";
public string texturePath = "Assets/Textures";
public Material blockMaterial;
public string defaultTextureName = "missing_texture";
private const int MAX_VERTICES_PER_CHUNK = 65000;
private List meshChunks = new();
public string[] woodTypes;
public Mesh slabMesh, fenceMesh, stairMesh;

private class MeshChunk
{
public List vertices = new(MAX_VERTICES_PER_CHUNK);
public List triangles = new();
public List uvs = new();
public bool isFull => vertices.Count + 4 >= MAX_VERTICES_PER_CHUNK;
}

[Serializable]
public class BlockTextureConfig
{
public string blockName, top, bottom, front, back, left, right, side;
public bool isBillboard;
public Mesh customMesh;
public void SetDefaults(string defaultName)
{
top ??= defaultName;
bottom ??= defaultName;
if (!string.IsNullOrEmpty(side))
front = back = left = right = side;
else
{
front ??= defaultName;
back ??= defaultName;
left ??= defaultName;
right ??= defaultName;
}
}
}

[Serializable]
public class BlockTintConfig
{
public string blockName;
public Color tintColor;
public string maskName;
public BlockTintConfig(string blockName, Color tintColor, string maskName = null)
{
this.blockName = blockName;
this.tintColor = tintColor;
this.maskName = maskName;
}
}

public BlockTextureConfig[] blockConfigs;
public BlockTintConfig[] blockTintConfigs;

private Texture2D textureAtlas;
private Rect[] atlasRects;
private Dictionary<string, int> blockNameToTextureIndex = new(StringComparer.OrdinalIgnoreCase);
private Dictionary<string, BlockTextureConfig> blockConfigMap = new(StringComparer.OrdinalIgnoreCase);
private int defaultTextureIndex;
private Dictionary<int, Vector2[]> uvCache = new();

[Serializable]
public class BlockData
{
public string block_name;
public int x, y, z;
public string direction;
}

private static readonly Vector3[,] faceVertices = {
{ new(-0.5f, -0.5f, -0.5f), new(0.5f, -0.5f, -0.5f), new(0.5f, 0.5f, -0.5f), new(-0.5f, 0.5f, -0.5f) },
{ new(-0.5f, -0.5f, 0.5f), new(0.5f, -0.5f, 0.5f), new(0.5f, 0.5f, 0.5f), new(-0.5f, 0.5f, 0.5f) },
{ new(-0.5f, 0.5f, -0.5f), new(0.5f, 0.5f, -0.5f), new(0.5f, 0.5f, 0.5f), new(-0.5f, 0.5f, 0.5f) },
{ new(-0.5f, -0.5f, -0.5f), new(0.5f, -0.5f, -0.5f), new(0.5f, -0.5f, 0.5f), new(-0.5f, -0.5f, 0.5f) },
{ new(-0.5f, -0.5f, -0.5f), new(-0.5f, -0.5f, 0.5f), new(-0.5f, 0.5f, 0.5f), new(-0.5f, 0.5f, -0.5f) },
{ new(0.5f, -0.5f, -0.5f), new(0.5f, -0.5f, 0.5f), new(0.5f, 0.5f, 0.5f), new(0.5f, 0.5f, -0.5f) }
};

private static readonly Vector3[][] billboardVertices = {
new[] { new Vector3(-0.5f, -0.5f, -0.5f), new(0.5f, -0.5f, 0.5f), new(0.5f, 0.5f, 0.5f), new(-0.5f, 0.5f, -0.5f) },
new[] { new Vector3(-0.5f, -0.5f, 0.5f), new(0.5f, -0.5f, -0.5f), new(0.5f, 0.5f, -0.5f), new(-0.5f, 0.5f, 0.5f) }
};

void Start()
{
InitializeBlockConfigs();
BeginInitialization();
}

async void BeginInitialization()
{
await CreateTextureAtlasAsync();
await FetchAndProcessBlocksAsync();
}

private void InitializeBlockConfigs()
{
blockConfigMap.Clear();
foreach (var config in blockConfigs)
{
config.SetDefaults(defaultTextureName); // Use default texture name
blockConfigMap[config.blockName] = config;
}
}

private async Task CreateTextureAtlasAsync()
{
var maskTextures = LoadMaskTextures();


// Load all textures on the main thread to avoid Unity exceptions
var textureList = await LoadAllTexturesAsync(maskTextures);

textureAtlas = new Texture2D(2048, 2048, TextureFormat.RGBA32, false) { filterMode = FilterMode.Point };
atlasRects = textureAtlas.PackTextures(textureList.ToArray(), 0, 2048);
textureAtlas.Apply(false, true);
blockMaterial.mainTexture = textureAtlas;
}

private Dictionary<string, Texture2D> LoadMaskTextures()
{
var maskTextures = new Dictionary<string, Texture2D>(StringComparer.OrdinalIgnoreCase);
string maskPath = Path.Combine(texturePath, "Masks");
if (!Directory.Exists(maskPath)) return maskTextures;


foreach (string maskFile in Directory.GetFiles(maskPath, "*.png"))
{
    string maskName = Path.GetFileNameWithoutExtension(maskFile);
    Texture2D maskTexture = new Texture2D(2, 2, TextureFormat.RGBA32, false) { filterMode = FilterMode.Point };
    maskTexture.LoadImage(File.ReadAllBytes(maskFile));
    maskTextures[maskName] = maskTexture;
}
return maskTextures;
}

private async Task<List> LoadAllTexturesAsync(Dictionary<string, Texture2D> maskTextures)
{
List texturesList = new();
string[] textureFiles = Directory.GetFiles(texturePath, "*.png");


// Load default texture
Texture2D defaultTexture = await LoadDefaultTextureAsync();
texturesList.Add(defaultTexture);
blockNameToTextureIndex[defaultTextureName] = 0;
defaultTextureIndex = 0;

int textureIndex = 1;
foreach (string textureFile in textureFiles)
{
    string fileName = Path.GetFileNameWithoutExtension(textureFile);
    if (fileName.Equals(defaultTextureName, StringComparison.OrdinalIgnoreCase)) continue;
    Texture2D texture = await LoadAndProcessTextureAsync(textureFile, fileName, maskTextures);
    texturesList.Add(texture);
    blockNameToTextureIndex[fileName] = textureIndex++;
}

return texturesList;
}

private async Task LoadDefaultTextureAsync()
{
string defaultTexturePath = Path.Combine(texturePath, defaultTextureName + ".png");
if (File.Exists(defaultTexturePath))
{
byte[] data = await ReadFileAsync(defaultTexturePath);
return await LoadTextureOnMainThreadAsync(data);
}
else
{
Debug.LogError($"Default texture {defaultTexturePath} not found! Creating fallback texture.");
return CreateFallbackTexture();
}
}

private async Task LoadAndProcessTextureAsync(string textureFile, string textureName, Dictionary<string, Texture2D> maskTextures)
{
byte[] fileData = await ReadFileAsync(textureFile);


// Load texture on main thread to avoid Unity exceptions
Texture2D texture = await LoadTextureOnMainThreadAsync(fileData);

var tintConfig = blockTintConfigs.FirstOrDefault(c => c.blockName.Equals(textureName, StringComparison.OrdinalIgnoreCase));
if (tintConfig != null)
{
    ApplyTintToTexture(texture, tintConfig, maskTextures);
}
return texture;
}

private async Task LoadTextureOnMainThreadAsync(byte[] data)
{
TaskCompletionSource tcs = new();
UnityMainThreadDispatcher.Instance().Enqueue(() =>
{
Texture2D texture = new Texture2D(2, 2, TextureFormat.RGBA32, false) { filterMode = FilterMode.Point };
texture.LoadImage(data);
tcs.SetResult(texture);
});
return await tcs.Task;
}

private void ApplyTintToTexture(Texture2D texture, BlockTintConfig config, Dictionary<string, Texture2D> maskTextures)
{
Texture2D maskTexture = null;
if (!string.IsNullOrEmpty(config.maskName))
{
maskTextures.TryGetValue(config.maskName, out maskTexture);
}


Color[] pixels = texture.GetPixels();
Color[] maskPixels = maskTexture?.GetPixels();

for (int i = 0; i < pixels.Length; i++)
{
    if (maskPixels != null)
    {
        float maskIntensity = maskPixels[i].grayscale;
        pixels[i] = Color.Lerp(pixels[i], pixels[i] * config.tintColor, maskIntensity);
    }
    else
    {
        pixels[i] *= config.tintColor;
    }
}

texture.SetPixels(pixels);
texture.Apply();
}

private Task<byte[]> ReadFileAsync(string filePath)
{
return Task.Run(() => File.ReadAllBytes(filePath));
}

private Texture2D CreateFallbackTexture()
{
Texture2D texture = new Texture2D(16, 16, TextureFormat.RGBA32, false) { filterMode = FilterMode.Point };
Color[] colors = Enumerable.Range(0, 256).Select(i => (i / 16 + i % 16) % 2 == 0 ? Color.magenta : Color.black).ToArray();
texture.SetPixels(colors);
texture.Apply();
return texture;
}

private async Task FetchAndProcessBlocksAsync()
{
string jsonResponse = await GetBlocksDataAsync();
var blockDataList = JsonConvert.DeserializeObject<List>(jsonResponse);


// Process blocks on the main thread using a coroutine
UnityMainThreadDispatcher.Instance().Enqueue(() => StartCoroutine(ProcessBlocksCoroutine(blockDataList)));
}

private async Task GetBlocksDataAsync()
{
using UnityWebRequest webRequest = UnityWebRequest.Get(serverUrl);
var asyncOp = webRequest.SendWebRequest();


while (!asyncOp.isDone)
    await Task.Yield();

if (webRequest.result != UnityWebRequest.Result.Success)
{
    Debug.LogError("Error fetching block data: " + webRequest.error);
    return null;
}
else
{
    return webRequest.downloadHandler.text;
}
}

private IEnumerator ProcessBlocksCoroutine(List blockDataList)
{
int batchSize = 1000; // Adjust as needed
int totalBlocks = blockDataList.Count;
int processedBlocks = 0;


while (processedBlocks < totalBlocks)
{
    int blocksToProcess = Mathf.Min(batchSize, totalBlocks - processedBlocks);
    for (int i = 0; i < blocksToProcess; i++)
    {
        AddBlockFaces(blockDataList[processedBlocks + i]);
    }
    processedBlocks += blocksToProcess;

    // Optional: Update a progress indicator here

    yield return null; // Wait for the next frame
}

CreateMesh();
}

private void AddBlockFaces(BlockData block)
{
Vector3 blockPosition = new(block.x, block.y, block.z);
if (!blockConfigMap.TryGetValue(block.block_name, out var config))
{
config = new BlockTextureConfig { blockName = block.block_name };
config.SetDefaults(block.block_name); // Use block's own name
}


var customMeshes = new Dictionary<string, Mesh>
{
    { "_slab", slabMesh },
    { "_fence", fenceMesh },
    { "_stairs", stairMesh }
};

Mesh selectedMesh = config.customMesh;
string replace = null, add = null;
if (selectedMesh == null)
{
    foreach (var kvp in customMeshes)
    {
        if (config.blockName.Contains(kvp.Key))
        {
            selectedMesh = kvp.Value;
            replace = kvp.Key;
            if (woodTypes.Any(w => config.front.Contains(w)))
                add = "_planks";
            break;
        }
    }
}

if (selectedMesh != null)
    AddCustomMeshToChunk(blockPosition, selectedMesh, config.front, replace, add);
else if (config.isBillboard)
    AddBillboardFacesToChunk(blockPosition, config);
else
    AddDefaultCubeFacesToChunk(blockPosition, config);
}

private MeshChunk GetCurrentChunk()
{
if (meshChunks.Count == 0 || meshChunks[^1].isFull)
meshChunks.Add(new MeshChunk());
return meshChunks[^1];
}

private void AddCustomMeshToChunk(Vector3 blockPosition, Mesh customMesh, string textureName, string replace, string add)
{
if (replace != null) textureName = textureName.Replace(replace, "") + add;
var chunk = GetCurrentChunk();
var textureUVs = GetUVsForTexture(textureName);
int vertCount = chunk.vertices.Count;
int uvIndex = 0;


foreach (var vertex in customMesh.vertices)
{
    chunk.vertices.Add(vertex + blockPosition);
    chunk.uvs.Add(textureUVs[uvIndex % 4]);
    uvIndex++;
}
foreach (var triangle in customMesh.triangles)
    chunk.triangles.Add(vertCount + triangle);
}

private void AddBillboardFacesToChunk(Vector3 blockPosition, BlockTextureConfig config)
{
var textureUVs = GetUVsForTexture(config.front);
foreach (var plane in billboardVertices)
{
var chunk = GetCurrentChunk();
int vertCount = chunk.vertices.Count;
for (int i = 0; i < 4; i++)
{
chunk.vertices.Add(plane[i] + blockPosition);
chunk.uvs.Add(textureUVs[i]);
}
chunk.triangles.AddRange(new[] { vertCount, vertCount + 2, vertCount + 1, vertCount, vertCount + 3, vertCount + 2 });
}
}

private void AddDefaultCubeFacesToChunk(Vector3 blockPosition, BlockTextureConfig config)
{
string[] faceTextures = { config.front, config.back, config.top, config.bottom, config.left, config.right };
for (int faceIndex = 0; faceIndex < 6; faceIndex++)
{
var chunk = GetCurrentChunk();
var textureUVs = GetUVsForTexture(faceTextures[faceIndex]);
int vertCount = chunk.vertices.Count;
for (int i = 0; i < 4; i++)
{
chunk.vertices.Add(faceVertices[faceIndex, i] + blockPosition);
chunk.uvs.Add(textureUVs[i]);
}
chunk.triangles.AddRange(new[] { vertCount, vertCount + 2, vertCount + 1, vertCount, vertCount + 3, vertCount + 2 });
}
}

private int GetTextureIndexForTextureName(string textureName)
{
if (string.IsNullOrEmpty(textureName))
return defaultTextureIndex;
return blockNameToTextureIndex.TryGetValue(textureName, out int index) ? index : defaultTextureIndex;
}

private Vector2[] GetUVsForTexture(string textureName)
{
int textureIndex = GetTextureIndexForTextureName(textureName);


if (textureIndex >= atlasRects.Length || textureIndex < 0)
{
    Debug.LogWarning($"Invalid texture index: {textureIndex} for texture: {textureName}. Using default texture.");
    textureIndex = defaultTextureIndex;
}

if (!uvCache.TryGetValue(textureIndex, out Vector2[] uvs))
{
    Rect uvRect = atlasRects[textureIndex];
    uvs = new[]
    {
        new Vector2(uvRect.xMin, uvRect.yMin),
        new Vector2(uvRect.xMax, uvRect.yMin),
        new Vector2(uvRect.xMax, uvRect.yMax),
        new Vector2(uvRect.xMin, uvRect.yMax)
    };
    uvCache[textureIndex] = uvs;
}
return uvs;
}

private void CreateMesh()
{
GameObject chunksParent = new("ChunkedMeshes");
for (int i = 0; i < meshChunks.Count; i++)
{
var chunk = meshChunks[i];
GameObject chunkObject = new($"MeshChunk_{i}") { transform = { parent = chunksParent.transform } };
Mesh mesh = new() { indexFormat = UnityEngine.Rendering.IndexFormat.UInt32 };
mesh.SetVertices(chunk.vertices);
mesh.SetTriangles(chunk.triangles, 0);
mesh.SetUVs(0, chunk.uvs);
mesh.RecalculateNormals();
chunkObject.AddComponent().mesh = mesh;
chunkObject.AddComponent().material = blockMaterial;
chunkObject.AddComponent().sharedMesh = mesh;
}
}
}