<commit_before>using DFHack;
using MapGen;
using RemoteFortressReader;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using UnitFlags;
using UnityEngine;
using UnityEngine.UI;

// The class responsible for talking to DF and meshing the data it gets.
// Relevant vocabulary: A "map tile" is an individual square on the map.
// A "map block" is a 16x16x1 area of map tiles stored by DF; think minecraft "chunks".

public class GameMap : MonoBehaviour
{
    public Material waterMaterial;
    public Material magmaMaterial;

    public Light magmaGlowPrefab;
    public Text cursorProperties;

    public bool overheadShadows = true;

    public bool firstPerson = false;

    public int cursX = -30000;
    public int cursY = -30000;
    public int cursZ = -30000;

    public WorldMapMaker worldMap;

    public RectTransform optionsPanel;

    // Parameters managing the currently visible area of the map.
    // Tracking:
    int PosXBlock
    {
        get
        {
            return posXTile / 16;
        }
    }
    int PosYBlock
    {
        get
        {
            return posYTile / 16;
        }
    }
    public int posXTile = 0;
    public int posYTile = 0;
    public int posZ = 0;
    public int PosZ
    { // Public accessor; used from MapSelection
        get
        {
            return posZ;
        }
    }
    public int PosXTile
    {
        get
        {
            return posXTile;
        }
    }
    public int PosYTile
    {
        get
        {
            return posYTile;
        }
    }

    // Stored view information
    ViewInfo view;

    BlockMesher mesher;

    BlockMeshSet[,,] mapMeshes;

    public MeshCollider collisionTemplate;
    // Dirty flags for those meshes
    bool[,,] blockDirtyBits;
    bool[,,] blockContentBits;
    bool[] layerDirtyBits;
    UpdateSchedule[,,] blockUpdateSchedules;
    bool[,,] liquidBlockDirtyBits;
    bool[] spatterBlockDirtyBits;
    Texture2D[] spatterLayers;
    Texture2D[] terrainSplatLayers;
    Texture2D[] terrainTintLayers;
    Texture2D[] grassSplatLayers;
    Texture2D[] grassTintLayers;
    // Lights from magma.
    Light[,,] magmaGlow;

    DFCoord mapSize; //This is to keep track of changing size of the map.
    DFCoord mapPosition;

    // Stuff to let the material list & various meshes & whatnot be loaded from xml specs at runtime.
    public static Dictionary<MatPairStruct, MaterialDefinition> materials;
    public static Dictionary<MatPairStruct, MaterialDefinition> items;
    public static Dictionary<BuildingStruct, BuildingDefinition> buildings;
    public static Dictionary<MatPairStruct, MaterialDefinition> creatures;

    //Items list. Pretty simple right now.
    Dictionary<int, Item> itemInstances = new Dictionary<int, Item>();
    Dictionary<DFCoord, Item> itemPositions = new Dictionary<DFCoord, Item>();

    // Coordinate system stuff.
    public const float tileHeight = 3.0f;
    public const float floorHeight = 0.5f;
    public const float tileWidth = 2.0f;
    public const int blockSize = 16;

    static Object mapZOffsetLock = new Object();
    static int _mapZoffset = 0;
    public static int MapZOffset
    {
        get
        {
            lock (mapZOffsetLock)
            {
                return _mapZoffset;
            }
        }
        set
        {
            lock (mapZOffsetLock)
            {
                _mapZoffset = value;
            }
        }
    }
    public static Vector3 DFtoUnityCoord(int x, int y, int z)
    {
        Vector3 outCoord = new Vector3(x * tileWidth, (z + MapZOffset) * tileHeight, y * (-tileWidth));
        return outCoord;
    }
    public static Vector3 DFtoUnityCoord(DFCoord input)
    {
        Vector3 outCoord = new Vector3(input.x * tileWidth, (input.z + MapZOffset) * tileHeight, input.y * (-tileWidth));
        return outCoord;
    }
    public static Vector3 DFtoUnityTileCenter(DFCoord input)
    {
        Vector3 result = DFtoUnityCoord(input);
        result.y += tileHeight / 2;
        return result;
    }
    public static Vector3 DFtoUnityBottomCorner(DFCoord input)
    {
        Vector3 result = DFtoUnityCoord(input);
        result.x -= tileWidth / 2;
        result.z -= tileWidth / 2;
        return result;
    }
    public static DFCoord UnityToDFCoord(Vector3 input)
    {
        int x = Mathf.RoundToInt(input.x / tileWidth);
        int y = Mathf.RoundToInt(input.z / -tileWidth);
        int z = Mathf.FloorToInt(input.y / tileHeight);
        return new DFCoord(x, y, z - MapZOffset);
    }
    public static Vector3 UnityToFloatingDFCoord(Vector3 input)
    {
        float x = input.x / tileWidth;
        float y = input.z / -tileWidth;
        float z = input.y / tileHeight;
        return new Vector3(x + 0.5f, y + 0.5f, z - MapZOffset);
    }
    public static bool IsBlockCorner(DFCoord input)
    {
        return input.x % blockSize == 0 &&
               input.y % blockSize == 0;
    }

    // Does about what you'd think it does.
    void Start()
    {
        enabled = false;

        DFConnection.RegisterConnectionCallback(OnConnectToDF);

        dfScreen.SetActive(GameSettings.Instance.game.showDFScreen);

        spatterID = Shader.PropertyToID("_SpatterTex");
        terrainSplatID = Shader.PropertyToID("_Control");
        terrainTintID = Shader.PropertyToID("_Tint");
        grassSplatID = Shader.PropertyToID("_GrassControl");
        grassTintID = Shader.PropertyToID("_GrassTint");
        sharedMatBlock = new MaterialPropertyBlock();
    }

    public static GameMap Instance { get; private set; }

    // Awake is called when the script instance is being loaded
    public void Awake()
    {
        Instance = this;
    }

    void OnConnectToDF()
    {
        Debug.Log("Connected");
        enabled = true;
        mesher = BlockMesher.GetMesher(GameSettings.Instance.meshing.meshingThreads);
        // Initialize materials, if available
        if (DFConnection.Instance.NetMaterialList != null)
        {
            if (materials == null)
                materials = new Dictionary<MatPairStruct, RemoteFortressReader.MaterialDefinition>();
            materials.Clear();
            foreach (RemoteFortressReader.MaterialDefinition material in DFConnection.Instance.NetMaterialList.material_list)
            {
                materials[material.mat_pair] = material;
            }
            if (GameSettings.Instance.debug.saveMaterialList)
                SaveMaterialList(materials, "MaterialList.csv");
        }
        // Initialize items, if available
        if (DFConnection.Instance.NetItemList != null)
        {
            if (items == null)
                items = new Dictionary<MatPairStruct, RemoteFortressReader.MaterialDefinition>();
            items.Clear();
            foreach (MaterialDefinition material in DFConnection.Instance.NetItemList.material_list)
            {
                items[material.mat_pair] = material;
            }
            if (GameSettings.Instance.debug.saveItemList)
                SaveMaterialList(items, "ItemList.csv");
        }
        if (DFConnection.Instance.NetBuildingList != null)
        {
            if (buildings == null)
                buildings = new Dictionary<BuildingStruct, BuildingDefinition>();
            buildings.Clear();
            foreach (BuildingDefinition building in DFConnection.Instance.NetBuildingList.building_list)
            {
                buildings[building.building_type] = building;
            }
            if (GameSettings.Instance.debug.saveBuildingList)
                SaveBuildingList();
        }
        if (DFConnection.Instance.NetCreatureRawList != null)
        {
            if (creatures == null)
                creatures = new Dictionary<MatPairStruct, MaterialDefinition>();
            foreach (CreatureRaw creatureRaw in DFConnection.Instance.NetCreatureRawList.creature_raws)
            {
                foreach (var caste in creatureRaw.caste)
                {
                    MatPairStruct creatureCaste = new MatPairStruct(creatureRaw.index, caste.index);
                    MaterialDefinition creatureDef = new MaterialDefinition();
                    creatureDef.mat_pair = creatureCaste;
                    creatureDef.id = creatureRaw.creature_id + ":" + caste.caste_id;
                    creatureDef.name = caste.caste_name[0];
                    creatureDef.state_color = creatureRaw.color;
                    creatures[creatureCaste] = creatureDef;
                }
            }
            if (GameSettings.Instance.debug.saveCreatureList)
                SaveMaterialList(creatures, "CreatureList.csv");
        }

        if (GameSettings.Instance.debug.saveTiletypeList)
            SaveTileTypeList();

        if (GameSettings.Instance.debug.savePlantList)
            SavePlantList();

        UpdateView();

        InitializeBlocks();
    }

    public GameObject dfScreen;

    // Run once per frame.
    void Update()
    {

        if (Input.GetButtonDown("ToggleDF"))
        {
            GameSettings.Instance.game.showDFScreen = !GameSettings.Instance.game.showDFScreen;

            dfScreen.SetActive(GameSettings.Instance.game.showDFScreen);
        }

        var camera = FindObjectOfType<CameraMovement>();

        if ((camera != null && camera.following) || GameSettings.Instance.game.showDFScreen)
            UpdateView();


        if (!GameSettings.Instance.game.showDFScreen)
        {
            if (Input.GetButtonDown("ScaleUnits"))
            {
                GameSettings.Instance.units.scaleUnits = !GameSettings.Instance.units.scaleUnits;
            }
            if (Input.GetButtonDown("OverheadShadows"))
            {
                overheadShadows = !overheadShadows;
            }
            if (Input.GetButtonDown("FollowDF"))
            {
                camera.following = true;
            }

            // take screenshot on up->down transition of F9 key
            if (Input.GetButtonDown("TakeScreenshot"))
            {
                string path = Application.dataPath;
                if (Application.platform == RuntimePlatform.OSXPlayer)
                {
                    path += "/../../";
                }
                else if (Application.platform == RuntimePlatform.WindowsPlayer
                    || Application.platform == RuntimePlatform.LinuxPlayer)
                {
                    path += "/../";
                }

                string screenshotFilename;
                do
                {
                    screenshotCount++;
                    screenshotFilename = path + "screenshot" + screenshotCount + ".png";

                } while (File.Exists(screenshotFilename));

                if (Input.GetButton("Mod"))
                    Application.CaptureScreenshot(screenshotFilename, 8);
                else
                    Application.CaptureScreenshot(screenshotFilename);
            }
            if (Input.GetButtonDown("Refresh"))
            {
                Refresh();
            }
            if (Input.GetButtonDown("Cancel"))
            {
                optionsPanel.gameObject.SetActive(!optionsPanel.gameObject.activeSelf);
            }
        }

        ShowCursorInfo();
        UpdateRequestRegion();
        UpdateCreatures();
        UpdateBlocks();
        UpdateSpatters();
        UpdateTerrainTextures();
        DrawBlocks();
        DrawItems();
    }

    public void Refresh()
    {
        for (int z = 0; z < blockDirtyBits.GetLength(2); z++)
            for (int x = 0; x < blockDirtyBits.GetLength(0); x++)
                for (int y = 0; y < blockDirtyBits.GetLength(1); y++)
                {
                    if (blockContentBits[x, y, z])
                    {
                        layerDirtyBits[z] = true;
                        if (z > 0) 
                            layerDirtyBits[z - 1] = true; //For ramp edges.
                        spatterBlockDirtyBits[z] = true;
                    }
                    blockDirtyBits[x, y, z] = blockContentBits[x, y, z];
                    liquidBlockDirtyBits[x, y, z] = blockContentBits[x, y, z];
                }
    }

    public Mesh testMesh;

    private void SaveMeshes()
    {
        //COLLADA exportScene = new COLLADA();

        //List<geometry> geometryList = new List<geometry>();

        ////if(testMesh!= null)
        ////{
        ////    geometry geo = COLLADA.MeshToGeometry(testMesh);
        ////    if (geo != null)
        ////        geometryList.Add(geo);
        ////}

        //Debug.Log("Starting mesh export");
        //foreach (Mesh mesh in blocks)
        //{
        //    if (mesh != null)
        //    {
        //        geometry geo = (COLLADA.MeshToGeometry(mesh));
        //        if (geo != null)
        //            geometryList.Add(geo);
        //    }
        //}
        //Debug.Log("Added opaque blocks");
        //foreach (Mesh mesh in stencilBlocks)
        //{
        //    if (mesh != null)
        //    {
        //        geometry geo = (COLLADA.MeshToGeometry(mesh));
        //        if (geo != null)
        //            geometryList.Add(geo);
        //    }
        //}
        //Debug.Log("Added stencil blocks");
        //foreach (Mesh mesh in transparentBlocks)
        //{
        //    if (mesh != null)
        //    {
        //        geometry geo = (COLLADA.MeshToGeometry(mesh));
        //        if (geo != null)
        //            geometryList.Add(geo);
        //    }
        //}
        //Debug.Log("Added transparent blocks");
        //foreach (Mesh mesh in liquidBlocks)
        //{
        //    if (mesh != null)
        //    {
        //        geometry geo = (COLLADA.MeshToGeometry(mesh));
        //        if (geo != null)
        //            geometryList.Add(geo);
        //    }
        //}
        //Debug.Log("Added liquid blocks");

        //library_geometries geometryLib = new library_geometries();
        //geometryLib.geometry = geometryList.ToArray();
        //Debug.Log("Added geometry to library");

        //library_visual_scenes visualSceneLib = new library_visual_scenes();
        //visual_scene visualScene = new visual_scene();

        //visualSceneLib.visual_scene = new visual_scene[1];
        //visualSceneLib.visual_scene[0] = visualScene;

        //visualScene.id = "Map";
        //visualScene.name = "Map";
        //visualScene.node = new node[geometryList.Count];
        //for (int i = 0; i < geometryList.Count; i++)
        //{
        //    node thisNode = new node();
        //    visualScene.node[i] = thisNode;
        //    geometry thisGeometry = geometryList[i];
        //    thisNode.id = thisGeometry.id.Remove(thisGeometry.id.Length - 4);
        //    thisNode.name = thisGeometry.name.Remove(thisGeometry.name.Length - 6);
        //    thisNode.sid = thisNode.id;

        //    thisNode.Items = new object[1];
        //    thisNode.Items[0] = COLLADA.ConvertMatrix(Matrix4x4.identity);
        //    thisNode.ItemsElementName = new ItemsChoiceType2[1];
        //    thisNode.ItemsElementName[0] = ItemsChoiceType2.matrix;

        //    thisNode.instance_geometry = new instance_geometry[1];
        //    thisNode.instance_geometry[0] = new instance_geometry();
        //    thisNode.instance_geometry[0].url = "#" + thisGeometry.id;
        //}
        //Debug.Log("Added geometry to scene");

        //COLLADAScene sceneInstance = new COLLADAScene();
        //sceneInstance.instance_visual_scene = new InstanceWithExtra();
        //sceneInstance.instance_visual_scene.url = "#" + visualScene.id;

        //exportScene.scene = sceneInstance;

        //exportScene.Items = new object[2];
        //exportScene.Items[0] = geometryLib;
        //exportScene.Items[1] = visualSceneLib;

        //asset assetHeader = new asset();
        //assetHeader.unit = new assetUnit();
        //assetHeader.unit.meter = 1;
        //assetHeader.unit.name = "meter";
        //assetHeader.up_axis = UpAxisType.Y_UP;

        //exportScene.asset = assetHeader;
        //Debug.Log("Setup Scene");

        //if (File.Exists("Map.dae"))
        //    File.Delete("Map.dae");
        //exportScene.Save("Map.dae");
        //Debug.Log("Saved Scene");

        //Texture2D mainTex = (Texture2D)BasicTerrainMaterial.GetTexture("_MainTex");

        //Color[] mainTexPixels = mainTex.GetPixels();
        //Color[] diffusePixels = new Color[mainTexPixels.Length];
        //Color[] roughnessPixels = new Color[mainTexPixels.Length];

        //for (int i = 0; i < mainTexPixels.Length; i++)
        //{
        //    diffusePixels[i] = new Color(mainTexPixels[i].r, mainTexPixels[i].g, mainTexPixels[i].b, 1.0f);
        //    roughnessPixels[i] = new Color(mainTexPixels[i].a, mainTexPixels[i].a, mainTexPixels[i].a, 1.0f);
        //}

        //Texture2D diffuseTex = new Texture2D(mainTex.width, mainTex.height);
        //Texture2D roughnessTex = new Texture2D(mainTex.width, mainTex.height);

        //diffuseTex.SetPixels(diffusePixels);
        //roughnessTex.SetPixels(roughnessPixels);

        //diffuseTex.Apply();
        //roughnessTex.Apply();

        //byte[] diffuseBytes = diffuseTex.EncodeToPNG();
        //byte[] roughnessBytes = roughnessTex.EncodeToPNG();

        //File.WriteAllBytes("pattern.png", diffuseBytes);
        //File.WriteAllBytes("specular.png", roughnessBytes);
        //Debug.Log("Saved Maintex");

        //Texture2D bumpMap = (Texture2D)BasicTerrainMaterial.GetTexture("_BumpMap");

        //Color[] bumpMapPixels = bumpMap.GetPixels();
        //Color[] normalMapPixels = new Color[bumpMapPixels.Length];
        //Color[] ambientMapPixels = new Color[bumpMapPixels.Length];
        //Color[] alphaMapPixels = new Color[bumpMapPixels.Length];

        //for (int i = 0; i < bumpMapPixels.Length; i++)
        //{
        //    normalMapPixels[i] = new Color(bumpMapPixels[i].a, bumpMapPixels[i].g, Mathf.Sqrt(1 - ((bumpMapPixels[i].a * 2 - 1) * (bumpMapPixels[i].a * 2 - 1)) + ((bumpMapPixels[i].g * 2 - 1) * (bumpMapPixels[i].g * 2 - 1))));
        //    ambientMapPixels[i] = new Color(bumpMapPixels[i].r, bumpMapPixels[i].r, bumpMapPixels[i].r, 1.0f);
        //    alphaMapPixels[i] = new Color(bumpMapPixels[i].b, bumpMapPixels[i].b, bumpMapPixels[i].b, 1.0f);
        //}

        //Texture2D normalTex = new Texture2D(bumpMap.width, bumpMap.height);
        //Texture2D ambientTex = new Texture2D(bumpMap.width, bumpMap.height);
        //Texture2D alphaTex = new Texture2D(bumpMap.width, bumpMap.height);

        //normalTex.SetPixels(normalMapPixels);
        //ambientTex.SetPixels(ambientMapPixels);
        //alphaTex.SetPixels(alphaMapPixels);

        //normalTex.Apply();
        //ambientTex.Apply();
        //alphaTex.Apply();

        //byte[] normalBytes = normalTex.EncodeToPNG();
        //byte[] ambientBytes = ambientTex.EncodeToPNG();
        //byte[] alphaBytes = alphaTex.EncodeToPNG();

        //File.WriteAllBytes("normal.png", normalBytes);
        //File.WriteAllBytes("occlusion.png", ambientBytes);
        //File.WriteAllBytes("alpha.png", alphaBytes);
        //Debug.Log("Saved DetailTex");

        //Debug.Log("Saved map!");
    }

    void OnDestroy()
    {
        if (mesher != null)
        {
            mesher.Terminate();
            mesher = null;
        }
    }

    void UpdateView()
    {
        RemoteFortressReader.ViewInfo newView = DFConnection.Instance.PopViewInfoUpdate();
        if (newView == null) return;

        UnityEngine.Profiling.Profiler.BeginSample("UpdateView", this);
        //Debug.Log("Got view");
        view = newView;

        if (view.follow_unit_id != -1 && lastUnitList != null)
        {
            foreach (var unit in lastUnitList.creature_list)
            {
                if (unit.id == view.follow_unit_id)
                {
                    posXTile = unit.pos_x;
                    posYTile = unit.pos_y;
                    posZ = unit.pos_z + 1;
                    return;
                }
            }
        }

        posXTile = (view.view_pos_x + (view.view_size_x / 2));
        posYTile = (view.view_pos_y + (view.view_size_y / 2));
        posZ = view.view_pos_z + 1;
        UnityEngine.Profiling.Profiler.EndSample();
    }

    // Update the region we're requesting
    void UpdateRequestRegion()
    {
        UnityEngine.Profiling.Profiler.BeginSample("UpdateRequestRegion", this);
        DFConnection.Instance.SetRequestRegion(
            new BlockCoord(
                PosXBlock - GameSettings.Instance.rendering.drawRangeSide,
                PosYBlock - GameSettings.Instance.rendering.drawRangeSide,
                posZ - GameSettings.Instance.rendering.drawRangeDown
            ),
            new BlockCoord(
                PosXBlock + GameSettings.Instance.rendering.drawRangeSide,
                PosYBlock + GameSettings.Instance.rendering.drawRangeSide,
                posZ + GameSettings.Instance.rendering.drawRangeUp
            ));
        UnityEngine.Profiling.Profiler.EndSample();
    }
    void UpdateBlocks()
    {
        if (DFConnection.Instance.EmbarkMapSize != mapSize)
        {
            ClearMap();
            InitializeBlocks();
            mapSize = DFConnection.Instance.EmbarkMapSize;
            DFConnection.Instance.RequestMapReset();
        }
        if (DFConnection.Instance.EmbarkMapPosition != mapPosition)
        {
            ClearMap();
            mapPosition = DFConnection.Instance.EmbarkMapPosition;
            MapZOffset = DFConnection.Instance.EmbarkMapPosition.z;
            DFConnection.Instance.RequestMapReset();
        }
        if (MapDataStore.MapSize.x < 48)
            return;
        UnityEngine.Profiling.Profiler.BeginSample("UpdateBlocks", this);
        while (true)
        {
            UnityEngine.Profiling.Profiler.BeginSample("PopMapBlockUpdate", this);
            RemoteFortressReader.MapBlock block = DFConnection.Instance.PopMapBlockUpdate();
            UnityEngine.Profiling.Profiler.EndSample();
            if (block == null) break;
            bool setTiles;
            bool setLiquids;
            bool setSpatters;
            UnityEngine.Profiling.Profiler.BeginSample("StoreTiles", this);
            MapDataStore.Main.StoreTiles(block, out setTiles, out setLiquids, out setSpatters);
            UnityEngine.Profiling.Profiler.EndSample();
            if (setTiles)
            {
                addSeasonalUpdates(block, block.map_x, block.map_y, block.map_z);
                SetDirtyBlock(block.map_x, block.map_y, block.map_z);
                SetBlockContent(block.map_x, block.map_y, block.map_z);
            }
            if (setLiquids)
            {
                SetDirtyLiquidBlock(block.map_x, block.map_y, block.map_z);
                SetBlockContent(block.map_x, block.map_y, block.map_z);
            }
            if (setSpatters)
            {
                SetDirtySpatterBlock(block.map_x, block.map_y, block.map_z);
            }
            foreach (var item in block.items)
            {
                itemInstances[item.id] = item;
            }
        }
        itemPositions.Clear();
        foreach (var item in itemInstances)
        {
            itemPositions[item.Value.pos] = item.Value;
        }
        DirtySeasonalBlocks();
        UnityEngine.Profiling.Profiler.BeginSample("EnqueueMeshUpdates", this);
        EnqueueMeshUpdates();
        UnityEngine.Profiling.Profiler.EndSample();

        mesher.Poll();

        FetchNewMeshes();
        UnityEngine.Profiling.Profiler.EndSample();
    }

    void SetMaterialBounds(Vector4 bounds)
    {
        MaterialManager.Instance.SetVector("_WorldBounds", bounds);
    }

    void InitializeBlocks()
    {
        int blockSizeX = DFConnection.Instance.EmbarkMapSize.x;
        int blockSizeY = DFConnection.Instance.EmbarkMapSize.y;
        int blockSizeZ = DFConnection.Instance.EmbarkMapSize.z;
        MapZOffset = DFConnection.Instance.EmbarkMapPosition.z;

        mapMeshes = new BlockMeshSet[blockSizeX * 16 / blockSize, blockSizeY * 16 / blockSize, blockSizeZ];

        blockDirtyBits = new bool[blockSizeX * 16 / blockSize, blockSizeY * 16 / blockSize, blockSizeZ];
        blockContentBits = new bool[blockSizeX * 16 / blockSize, blockSizeY * 16 / blockSize, blockSizeZ];
        blockUpdateSchedules = new UpdateSchedule[blockSizeX * 16 / blockSize, blockSizeY * 16 / blockSize, blockSizeZ];
        liquidBlockDirtyBits = new bool[blockSizeX * 16 / blockSize, blockSizeY * 16 / blockSize, blockSizeZ];
        magmaGlow = new Light[blockSizeX * 16, blockSizeY * 16, blockSizeZ];
        spatterBlockDirtyBits = new bool[blockSizeZ];
        layerDirtyBits = new bool[blockSizeZ];
        spatterLayers = new Texture2D[blockSizeZ];
        terrainSplatLayers = new Texture2D[blockSizeZ];
        terrainTintLayers = new Texture2D[blockSizeZ];
        grassSplatLayers = new Texture2D[blockSizeZ];
        grassTintLayers = new Texture2D[blockSizeZ];


        Vector3 min = DFtoUnityCoord(0, 0, 0) - new Vector3(tileWidth / 2, 0, -tileWidth / 2);
        Vector3 max = DFtoUnityCoord((blockSizeX * 16) - 1, (blockSizeY * 16) - 1, 0) + new Vector3(tileWidth / 2, 0, -tileWidth / 2);

        SetMaterialBounds(new Vector4(min.x, min.z, max.x, max.z));
    }

    void addSeasonalUpdates(RemoteFortressReader.MapBlock block, int mapBlockX, int mapBlockY, int mapBlockZ)
    {
        if (DFConnection.Instance.NetPlantRawList == null)
            return;
        mapBlockX /= blockSize;
        mapBlockY /= blockSize;
        if (blockUpdateSchedules[mapBlockX, mapBlockY, mapBlockZ] != null)
            blockUpdateSchedules[mapBlockX, mapBlockY, mapBlockZ].Clear();
        foreach (var material in block.materials)
        {
            if (material.mat_type != 419
                    || material.mat_index < 0
                    || DFConnection.Instance.NetPlantRawList.plant_raws.Count <= material.mat_index
                    || DFConnection.Instance.NetPlantRawList.plant_raws[material.mat_index].growths.Count == 0)
                continue;
            PlantRaw plantRaw = DFConnection.Instance.NetPlantRawList.plant_raws[material.mat_index];
            if (blockUpdateSchedules[mapBlockX, mapBlockY, mapBlockZ] == null)
                blockUpdateSchedules[mapBlockX, mapBlockY, mapBlockZ] = new UpdateSchedule();
            var schedule = blockUpdateSchedules[mapBlockX, mapBlockY, mapBlockZ];
            foreach (TreeGrowth growth in plantRaw.growths)
            {
                schedule.Add(growth.timing_start);
                schedule.Add(growth.timing_end);
                foreach (GrowthPrint print in growth.prints)
                {
                    schedule.Add(print.timing_start);
                    schedule.Add(print.timing_end);
                }
            }
        }
    }

    void SetDirtyBlock(int mapBlockX, int mapBlockY, int mapBlockZ)
    {
        mapBlockX /= blockSize;
        mapBlockY /= blockSize;
        blockDirtyBits[mapBlockX, mapBlockY, mapBlockZ] = true;
        layerDirtyBits[mapBlockZ] = true;
    }
    void SetBlockContent(int mapBlockX, int mapBlockY, int mapBlockZ)
    {
        mapBlockX /= blockSize;
        mapBlockY /= blockSize;
        blockContentBits[mapBlockX, mapBlockY, mapBlockZ] = true;
    }
    void SetDirtyLiquidBlock(int mapBlockX, int mapBlockY, int mapBlockZ)
    {
        mapBlockX /= blockSize;
        mapBlockY /= blockSize;
        liquidBlockDirtyBits[mapBlockX, mapBlockY, mapBlockZ] = true;
    }
    private void SetDirtySpatterBlock(int map_x, int map_y, int map_z)
    {
        spatterBlockDirtyBits[map_z] = true;
    }


    #region Debug Data Saving

    void PrintFullMaterialList()
    {
        int totalCount = DFConnection.Instance.NetMaterialList.material_list.Count;
        int limit = totalCount;
        if (limit >= 100)
            limit = 100;
        //Don't ever do this.
        for (int i = totalCount - limit; i < totalCount; i++)
        {
            //no really, don't.
            RemoteFortressReader.MaterialDefinition material = DFConnection.Instance.NetMaterialList.material_list[i];
            Debug.Log("{" + material.mat_pair.mat_index + "," + material.mat_pair.mat_type + "}, " + material.id + ", " + material.name);
        }
    }

    void SaveTileTypeList()
    {
        if (DFConnection.Instance.NetTiletypeList == null)
            return;
        try
        {
            File.Delete("TiletypeList.csv");
        }
        catch (IOException)
        {
            return;
        }
        using (StreamWriter writer = new StreamWriter("TiletypeList.csv"))
        {
            foreach (Tiletype item in DFConnection.Instance.NetTiletypeList.tiletype_list)
            {
                writer.WriteLine(
                    item.name + ";" +
                    item.shape + ":" +
                    item.special + ":" +
                    item.material + ":" +
                    item.variant + ":" +
                    item.direction
                    );
            }
        }
    }
    void SaveBuildingList()
    {
        if (DFConnection.Instance.NetBuildingList == null)
            return;
        try
        {
            File.Delete("BuildingList.csv");
        }
        catch (IOException)
        {
            return;
        }
        using (StreamWriter writer = new StreamWriter("BuildingList.csv"))
        {
            foreach (var item in DFConnection.Instance.NetBuildingList.building_list)
            {
                writer.WriteLine(
                    item.name + ";" +
                    item.id + ";" +
                    item.building_type.building_type + ":" +
                    item.building_type.building_subtype + ":" +
                    item.building_type.building_custom
                    );
            }
        }
    }


    void SaveMaterialList(IEnumerable<KeyValuePair<MatPairStruct, MaterialDefinition>> list, string filename)
    {
        try
        {
            File.Delete(filename);
        }
        catch (IOException)
        {
            return;
        }
        using (StreamWriter writer = new StreamWriter(filename))
        {
            foreach (var item in list)
            {
                writer.WriteLine(
                    item.Value.name + ";" +
                    item.Value.id + ";" +
                    item.Value.mat_pair.mat_type + ";" +
                    item.Value.mat_pair.mat_index
                    );
            }
        }
    }

    void SavePlantList()
    {
        if (DFConnection.Instance.NetPlantRawList == null)
            return;
        try
        {
            File.Delete("PlantList.csv");
        }
        catch (IOException)
        {
            return;
        }
        using (StreamWriter writer = new StreamWriter("PlantList.csv"))
        {
            foreach (var plant in TokenLists.PlantTokenList.GrowthIDs)
            {
                foreach (var growth in plant.Value)
                {
                    foreach (var print in growth.Value)
                    {
                        writer.Write(plant.Key);
                        writer.Write(":");
                        writer.Write(growth.Key);
                        writer.Write(":");
                        writer.Write(print.Key);
                        writer.Write(";");
                        writer.Write(print.Value);
                        writer.Write(";");
                        //var index = print.Value;
                        //if (index.building_type >= 0)
                        //{
                        //    PlantRaw plantRaw = DFConnection.Instance.NetPlantRawList.plant_raws[index.building_type];
                        //    if (index.building_type >= 0 && index.building_subtype >= 0 && index.building_custom >= 0)
                        //    {
                        //        var printRaw = plantRaw.growths[index.building_subtype].prints[index.building_custom];
                        //        writer.Write(CharacterConverter.Convert((byte)printRaw.tile));
                        //    }
                        //    else
                        //    {
                        //        writer.Write(CharacterConverter.Convert((byte)plantRaw.tile));
                        //    }
                        //}
                        writer.WriteLine();
                    }
                }
            }
        }
    }
    #endregion

    void DirtySeasonalBlocks()
    {
        int xmin = Mathf.Clamp(PosXBlock - GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(0));
        int xmax = Mathf.Clamp(PosXBlock + GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(0));
        int ymin = Mathf.Clamp(PosYBlock - GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(1));
        int ymax = Mathf.Clamp(PosYBlock + GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(1));
        int zmin = Mathf.Clamp(posZ - GameSettings.Instance.rendering.drawRangeDown, 0, mapMeshes.GetLength(2));
        int zmax = Mathf.Clamp(posZ + GameSettings.Instance.rendering.drawRangeUp, 0, mapMeshes.GetLength(2));
        for (int zz = zmin; zz < zmax; zz++)
            for (int yy = ymin; yy < ymax; yy++)
                for (int xx = xmin; xx < xmax; xx++)
                {
                    if (blockUpdateSchedules[xx, yy, zz] != null && blockUpdateSchedules[xx, yy, zz].CheckUpdate(TimeHolder.DisplayedTime.CurrentYearTicks))
                        blockDirtyBits[xx, yy, zz] = true;
                }

    }

    void UpdateTerrainTextures()
    {
        if (ContentLoader.Instance == null)
            return;
        UnityEngine.Profiling.Profiler.BeginSample("UpdateTerrainTextures", this);
        for (int z = layerDirtyBits.Length - 1; z >= 0; z--)
        {
            if (layerDirtyBits[z])
            {
                GenerateTerrainTexture(z);
                GenerateGrassTexture(z);
                layerDirtyBits[z] = false;
                break; //don't do more than one set per frame.
            }
        }
        UnityEngine.Profiling.Profiler.EndSample();
    }

    void UpdateSpatters()
    {
        if (ContentLoader.Instance == null)
            return;
        UnityEngine.Profiling.Profiler.BeginSample("UpdateSpatters", this);
        for (int z = spatterBlockDirtyBits.Length - 1; z >= 0; z--)
        {
            if (spatterBlockDirtyBits[z])
            {
                GenerateSpatterTexture(z);
                spatterBlockDirtyBits[z] = false;
            }
        }
        UnityEngine.Profiling.Profiler.EndSample();
    }

    // Have the mesher mesh all dirty tiles in the region
    void EnqueueMeshUpdates()
    {
        int xmin = Mathf.Clamp(PosXBlock - GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(0));
        int xmax = Mathf.Clamp(PosXBlock + GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(0));
        int ymin = Mathf.Clamp(PosYBlock - GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(1));
        int ymax = Mathf.Clamp(PosYBlock + GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(1));
        int zmin = Mathf.Clamp(posZ - GameSettings.Instance.rendering.drawRangeDown, 0, mapMeshes.GetLength(2));
        int zmax = Mathf.Clamp(posZ + GameSettings.Instance.rendering.drawRangeUp, 0, mapMeshes.GetLength(2));
        for (int zz = posZ - 1; zz >= zmin; zz--)
        {
            if (zz >= mapMeshes.GetLength(2))
                continue;

            for (int yy = ymin; yy < ymax; yy++)
                for (int xx = xmin; xx < xmax; xx++)
                {
                    if (!blockDirtyBits[xx, yy, zz] && !liquidBlockDirtyBits[xx, yy, zz])
                    {
                        continue;
                    }

                    //If we were not able to add it to the queue, don't try any more till next fame.
                    if (!mesher.Enqueue(new DFCoord(xx * 16, yy * 16, zz), blockDirtyBits[xx, yy, zz], liquidBlockDirtyBits[xx, yy, zz]))
                        return;
                    blockDirtyBits[xx, yy, zz] = false;
                    liquidBlockDirtyBits[xx, yy, zz] = false;
                    return;
                }
        }
        for (int zz = posZ; zz < zmax; zz++)
        {
            if (zz < 0)
                continue;
            for (int yy = ymin; yy < ymax; yy++)
                for (int xx = xmin; xx < xmax; xx++)
                {
                    if (!blockDirtyBits[xx, yy, zz] && !liquidBlockDirtyBits[xx, yy, zz])
                    {
                        continue;
                    }

                    //If we were not able to add it to the queue, don't try any more till next fame.
                    if (!mesher.Enqueue(new DFCoord(xx * 16, yy * 16, zz), blockDirtyBits[xx, yy, zz], liquidBlockDirtyBits[xx, yy, zz]))
                        return;
                    blockDirtyBits[xx, yy, zz] = false;
                    liquidBlockDirtyBits[xx, yy, zz] = false;
                    return;
                }
        }
    }

    // Get new meshes from the mesher
    void FetchNewMeshes()
    {
        UnityEngine.Profiling.Profiler.BeginSample("FetchNewMeshes", this);
        if (mesher.HasNewMeshes)
        {
            var newMeshes = mesher.Dequeue().Value;
            int block_x = newMeshes.location.x / blockSize;
            int block_y = newMeshes.location.y / blockSize;
            int block_z = newMeshes.location.z;
            if (mapMeshes[block_x, block_y, block_z] == null)
                mapMeshes[block_x, block_y, block_z] = new BlockMeshSet();

            var meshSet = mapMeshes[block_x, block_y, block_z];

            meshSet.LoadMeshes(newMeshes, string.Format("{0}_{1}_{2}", block_x, block_y, block_z));

            if (newMeshes.collisionMesh != null)
            {
                if (meshSet.collisionBlocks == null)
                {
                    meshSet.collisionBlocks = Instantiate(collisionTemplate);
                    meshSet.collisionBlocks.name = string.Format("collisionBlock_{0}_{1}_{2}", block_x, block_y, block_z);
                    meshSet.collisionBlocks.transform.position = DFtoUnityCoord(block_x * blockSize, block_y * blockSize, block_z);
                    meshSet.collisionBlocks.transform.parent = transform;
                }
                Mesh collisionMesh = new Mesh();
                collisionMesh.name = string.Format("block_collision_{0}_{1}_{2}", block_x, block_y, block_z);
                newMeshes.collisionMesh.CopyToMesh(collisionMesh);
                meshSet.collisionBlocks.sharedMesh = collisionMesh;
            }
        }
        UnityEngine.Profiling.Profiler.EndSample();
    }

    void GenerateGrassTexture(int z)
    {
        if (ContentLoader.Instance == null)
            return;
        UnityEngine.Profiling.Profiler.BeginSample("GenerateGrassTexture", this);

        Color[] grassColors = new Color[MapDataStore.MapSize.x * MapDataStore.MapSize.y];
        Color[] grassIndices = new Color[MapDataStore.MapSize.x * MapDataStore.MapSize.y];

        int grassTiles = 0;

        for (int x = 0; x < MapDataStore.MapSize.x; x++)
            for (int y = 0; y < MapDataStore.MapSize.y; y++)
            {
                int index = x + (y * MapDataStore.MapSize.x);
                var tile = MapDataStore.Main[x, y, z];
                if (tile == null)
                {
                    grassIndices[index].r = ContentLoader.Instance.DefaultMatTexIndex;
                    grassIndices[index].g = ContentLoader.Instance.DefaultShapeTexIndex;
                    grassColors[index] = new Color(0,0,0,0);
                    continue;
                }
                if (!(tile.tiletypeMaterial == TiletypeMaterial.GRASS_DARK
                    || tile.tiletypeMaterial == TiletypeMaterial.GRASS_LIGHT
                    || tile.tiletypeMaterial == TiletypeMaterial.GRASS_DEAD
                    || tile.tiletypeMaterial == TiletypeMaterial.GRASS_DRY
                    ))
                    continue;

                grassTiles++;

                GrassContent grassTexture;
                if (ContentLoader.Instance.GrassTextureConfiguration.GetValue(tile, MeshLayer.StaticMaterial, out grassTexture))
                {
                    grassIndices[index].r = grassTexture.MaterialTexture.StorageIndex;
                    grassIndices[index].g = grassTexture.ShapeTexture.StorageIndex;
                }
                else
                {
                    grassIndices[index].r = ContentLoader.Instance.DefaultMatTexIndex;
                    grassIndices[index].g = ContentLoader.Instance.DefaultShapeTexIndex;
                }

                ColorContent colorContent;
                if (ContentLoader.Instance.ColorConfiguration.GetValue(tile, MeshLayer.StaticMaterial, out colorContent))
                    grassColors[index] = colorContent.color;
                else
                    grassColors[index] = Color.gray;

            }
        if (grassTiles == 0)
            return;

        if (grassSplatLayers[z] == null)
        {
            grassSplatLayers[z] = new Texture2D(MapDataStore.MapSize.x, MapDataStore.MapSize.y, TextureFormat.RGHalf, false, true);
            grassSplatLayers[z].filterMode = FilterMode.Point;
            grassSplatLayers[z].wrapMode = TextureWrapMode.Clamp;
        }
        if (grassSplatLayers[z].width != MapDataStore.MapSize.x || terrainSplatLayers[z].height != MapDataStore.MapSize.y)
            grassSplatLayers[z].Resize(MapDataStore.MapSize.x, MapDataStore.MapSize.y);

        grassSplatLayers[z].SetPixels(grassIndices);
        grassSplatLayers[z].Apply();

        if (grassTintLayers[z] == null)
        {
            grassTintLayers[z] = new Texture2D(MapDataStore.MapSize.x, MapDataStore.MapSize.y, TextureFormat.RGBA32, false, true);
            grassTintLayers[z].filterMode = FilterMode.Point;
            grassTintLayers[z].wrapMode = TextureWrapMode.Clamp;
        }
        if (grassTintLayers[z].width != MapDataStore.MapSize.x || terrainTintLayers[z].height != MapDataStore.MapSize.y)
            grassTintLayers[z].Resize(MapDataStore.MapSize.x, MapDataStore.MapSize.y);

        grassTintLayers[z].SetPixels(grassColors);
        grassTintLayers[z].Apply();

        UnityEngine.Profiling.Profiler.EndSample();
    }

    void GenerateTerrainTexture(int z)
    {
        if (ContentLoader.Instance == null)
            return;
        UnityEngine.Profiling.Profiler.BeginSample("GenerateSpatterTexture", this);

        Color[] terrainColors = new Color[MapDataStore.MapSize.x * MapDataStore.MapSize.y];
        Color[] terrainIndices = new Color[MapDataStore.MapSize.x * MapDataStore.MapSize.y];

        for (int x = 0; x < MapDataStore.MapSize.x; x++)
            for (int y = 0; y < MapDataStore.MapSize.y; y++)
            {
                int index = x + (y * MapDataStore.MapSize.x);
                var tile = MapDataStore.Main[x, y, z];
                if (IsNullOrEmpty(tile))
                {
                    if (!IsNullOrEmpty(MapDataStore.Main[x - 1, y, z]))
                        tile = MapDataStore.Main[x - 1, y, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x, y - 1, z]))
                        tile = MapDataStore.Main[x, y - 1, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x + 1, y, z]))
                        tile = MapDataStore.Main[x + 1, y, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x, y + 1, z]))
                        tile = MapDataStore.Main[x, y + 1, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x - 1, y - 1, z]))
                        tile = MapDataStore.Main[x - 1, y - 1, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x - 1, y + 1, z]))
                        tile = MapDataStore.Main[x - 1, y + 1, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x + 1, y - 1, z]))
                        tile = MapDataStore.Main[x + 1, y - 1, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x + 1, y + 1, z]))
                        tile = MapDataStore.Main[x + 1, y + 1, z];
                    else
                    {
                        terrainIndices[index].r = ContentLoader.Instance.DefaultMatTexIndex;
                        terrainIndices[index].g = ContentLoader.Instance.DefaultShapeTexIndex;
                        terrainColors[index] = Color.gray;
                        continue;
                    }
                }
                if (tile.shape == TiletypeShape.RAMP_TOP && tile.Down != null)
                    tile = tile.Down;

                var layer = MeshLayer.BaseMaterial;
                if (tile.tiletypeMaterial == TiletypeMaterial.GRASS_DARK
                || tile.tiletypeMaterial == TiletypeMaterial.GRASS_DEAD
                || tile.tiletypeMaterial == TiletypeMaterial.GRASS_DRY
                || tile.tiletypeMaterial == TiletypeMaterial.GRASS_LIGHT
                )
                    layer = MeshLayer.LayerMaterial;


                NormalContent normalContent;
                if (ContentLoader.Instance.TerrainShapeTextureConfiguration.GetValue(tile, layer, out normalContent))
                    terrainIndices[index].g = normalContent.StorageIndex;
                else
                    terrainIndices[index].g = ContentLoader.Instance.DefaultShapeTexIndex;

                TextureContent materialContent;
                if (ContentLoader.Instance.MaterialTextureConfiguration.GetValue(tile, layer, out materialContent))
                    terrainIndices[index].r = materialContent.StorageIndex;
                else
                    terrainIndices[index].r = ContentLoader.Instance.DefaultMatTexIndex;

                ColorContent colorContent;
                if (ContentLoader.Instance.ColorConfiguration.GetValue(tile, layer, out colorContent))
                    terrainColors[index] = colorContent.color;
                else
                    terrainColors[index] = Color.gray;

            }

        if(terrainSplatLayers[z] == null)
        {
            terrainSplatLayers[z] = new Texture2D(MapDataStore.MapSize.x, MapDataStore.MapSize.y, TextureFormat.RGHalf, false, true);<commit_msg>Don't put plant materials onto the ground.<commit_after>using DFHack;
using MapGen;
using RemoteFortressReader;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using UnitFlags;
using UnityEngine;
using UnityEngine.UI;

// The class responsible for talking to DF and meshing the data it gets.
// Relevant vocabulary: A "map tile" is an individual square on the map.
// A "map block" is a 16x16x1 area of map tiles stored by DF; think minecraft "chunks".

public class GameMap : MonoBehaviour
{
    public Material waterMaterial;
    public Material magmaMaterial;

    public Light magmaGlowPrefab;
    public Text cursorProperties;

    public bool overheadShadows = true;

    public bool firstPerson = false;

    public int cursX = -30000;
    public int cursY = -30000;
    public int cursZ = -30000;

    public WorldMapMaker worldMap;

    public RectTransform optionsPanel;

    // Parameters managing the currently visible area of the map.
    // Tracking:
    int PosXBlock
    {
        get
        {
            return posXTile / 16;
        }
    }
    int PosYBlock
    {
        get
        {
            return posYTile / 16;
        }
    }
    public int posXTile = 0;
    public int posYTile = 0;
    public int posZ = 0;
    public int PosZ
    { // Public accessor; used from MapSelection
        get
        {
            return posZ;
        }
    }
    public int PosXTile
    {
        get
        {
            return posXTile;
        }
    }
    public int PosYTile
    {
        get
        {
            return posYTile;
        }
    }

    // Stored view information
    ViewInfo view;

    BlockMesher mesher;

    BlockMeshSet[,,] mapMeshes;

    public MeshCollider collisionTemplate;
    // Dirty flags for those meshes
    bool[,,] blockDirtyBits;
    bool[,,] blockContentBits;
    bool[] layerDirtyBits;
    UpdateSchedule[,,] blockUpdateSchedules;
    bool[,,] liquidBlockDirtyBits;
    bool[] spatterBlockDirtyBits;
    Texture2D[] spatterLayers;
    Texture2D[] terrainSplatLayers;
    Texture2D[] terrainTintLayers;
    Texture2D[] grassSplatLayers;
    Texture2D[] grassTintLayers;
    // Lights from magma.
    Light[,,] magmaGlow;

    DFCoord mapSize; //This is to keep track of changing size of the map.
    DFCoord mapPosition;

    // Stuff to let the material list & various meshes & whatnot be loaded from xml specs at runtime.
    public static Dictionary<MatPairStruct, MaterialDefinition> materials;
    public static Dictionary<MatPairStruct, MaterialDefinition> items;
    public static Dictionary<BuildingStruct, BuildingDefinition> buildings;
    public static Dictionary<MatPairStruct, MaterialDefinition> creatures;

    //Items list. Pretty simple right now.
    Dictionary<int, Item> itemInstances = new Dictionary<int, Item>();
    Dictionary<DFCoord, Item> itemPositions = new Dictionary<DFCoord, Item>();

    // Coordinate system stuff.
    public const float tileHeight = 3.0f;
    public const float floorHeight = 0.5f;
    public const float tileWidth = 2.0f;
    public const int blockSize = 16;

    static Object mapZOffsetLock = new Object();
    static int _mapZoffset = 0;
    public static int MapZOffset
    {
        get
        {
            lock (mapZOffsetLock)
            {
                return _mapZoffset;
            }
        }
        set
        {
            lock (mapZOffsetLock)
            {
                _mapZoffset = value;
            }
        }
    }
    public static Vector3 DFtoUnityCoord(int x, int y, int z)
    {
        Vector3 outCoord = new Vector3(x * tileWidth, (z + MapZOffset) * tileHeight, y * (-tileWidth));
        return outCoord;
    }
    public static Vector3 DFtoUnityCoord(DFCoord input)
    {
        Vector3 outCoord = new Vector3(input.x * tileWidth, (input.z + MapZOffset) * tileHeight, input.y * (-tileWidth));
        return outCoord;
    }
    public static Vector3 DFtoUnityTileCenter(DFCoord input)
    {
        Vector3 result = DFtoUnityCoord(input);
        result.y += tileHeight / 2;
        return result;
    }
    public static Vector3 DFtoUnityBottomCorner(DFCoord input)
    {
        Vector3 result = DFtoUnityCoord(input);
        result.x -= tileWidth / 2;
        result.z -= tileWidth / 2;
        return result;
    }
    public static DFCoord UnityToDFCoord(Vector3 input)
    {
        int x = Mathf.RoundToInt(input.x / tileWidth);
        int y = Mathf.RoundToInt(input.z / -tileWidth);
        int z = Mathf.FloorToInt(input.y / tileHeight);
        return new DFCoord(x, y, z - MapZOffset);
    }
    public static Vector3 UnityToFloatingDFCoord(Vector3 input)
    {
        float x = input.x / tileWidth;
        float y = input.z / -tileWidth;
        float z = input.y / tileHeight;
        return new Vector3(x + 0.5f, y + 0.5f, z - MapZOffset);
    }
    public static bool IsBlockCorner(DFCoord input)
    {
        return input.x % blockSize == 0 &&
               input.y % blockSize == 0;
    }

    // Does about what you'd think it does.
    void Start()
    {
        enabled = false;

        DFConnection.RegisterConnectionCallback(OnConnectToDF);

        dfScreen.SetActive(GameSettings.Instance.game.showDFScreen);

        spatterID = Shader.PropertyToID("_SpatterTex");
        terrainSplatID = Shader.PropertyToID("_Control");
        terrainTintID = Shader.PropertyToID("_Tint");
        grassSplatID = Shader.PropertyToID("_GrassControl");
        grassTintID = Shader.PropertyToID("_GrassTint");
        sharedMatBlock = new MaterialPropertyBlock();
    }

    public static GameMap Instance { get; private set; }

    // Awake is called when the script instance is being loaded
    public void Awake()
    {
        Instance = this;
    }

    void OnConnectToDF()
    {
        Debug.Log("Connected");
        enabled = true;
        mesher = BlockMesher.GetMesher(GameSettings.Instance.meshing.meshingThreads);
        // Initialize materials, if available
        if (DFConnection.Instance.NetMaterialList != null)
        {
            if (materials == null)
                materials = new Dictionary<MatPairStruct, RemoteFortressReader.MaterialDefinition>();
            materials.Clear();
            foreach (RemoteFortressReader.MaterialDefinition material in DFConnection.Instance.NetMaterialList.material_list)
            {
                materials[material.mat_pair] = material;
            }
            if (GameSettings.Instance.debug.saveMaterialList)
                SaveMaterialList(materials, "MaterialList.csv");
        }
        // Initialize items, if available
        if (DFConnection.Instance.NetItemList != null)
        {
            if (items == null)
                items = new Dictionary<MatPairStruct, RemoteFortressReader.MaterialDefinition>();
            items.Clear();
            foreach (MaterialDefinition material in DFConnection.Instance.NetItemList.material_list)
            {
                items[material.mat_pair] = material;
            }
            if (GameSettings.Instance.debug.saveItemList)
                SaveMaterialList(items, "ItemList.csv");
        }
        if (DFConnection.Instance.NetBuildingList != null)
        {
            if (buildings == null)
                buildings = new Dictionary<BuildingStruct, BuildingDefinition>();
            buildings.Clear();
            foreach (BuildingDefinition building in DFConnection.Instance.NetBuildingList.building_list)
            {
                buildings[building.building_type] = building;
            }
            if (GameSettings.Instance.debug.saveBuildingList)
                SaveBuildingList();
        }
        if (DFConnection.Instance.NetCreatureRawList != null)
        {
            if (creatures == null)
                creatures = new Dictionary<MatPairStruct, MaterialDefinition>();
            foreach (CreatureRaw creatureRaw in DFConnection.Instance.NetCreatureRawList.creature_raws)
            {
                foreach (var caste in creatureRaw.caste)
                {
                    MatPairStruct creatureCaste = new MatPairStruct(creatureRaw.index, caste.index);
                    MaterialDefinition creatureDef = new MaterialDefinition();
                    creatureDef.mat_pair = creatureCaste;
                    creatureDef.id = creatureRaw.creature_id + ":" + caste.caste_id;
                    creatureDef.name = caste.caste_name[0];
                    creatureDef.state_color = creatureRaw.color;
                    creatures[creatureCaste] = creatureDef;
                }
            }
            if (GameSettings.Instance.debug.saveCreatureList)
                SaveMaterialList(creatures, "CreatureList.csv");
        }

        if (GameSettings.Instance.debug.saveTiletypeList)
            SaveTileTypeList();

        if (GameSettings.Instance.debug.savePlantList)
            SavePlantList();

        UpdateView();

        InitializeBlocks();
    }

    public GameObject dfScreen;

    // Run once per frame.
    void Update()
    {

        if (Input.GetButtonDown("ToggleDF"))
        {
            GameSettings.Instance.game.showDFScreen = !GameSettings.Instance.game.showDFScreen;

            dfScreen.SetActive(GameSettings.Instance.game.showDFScreen);
        }

        var camera = FindObjectOfType<CameraMovement>();

        if ((camera != null && camera.following) || GameSettings.Instance.game.showDFScreen)
            UpdateView();


        if (!GameSettings.Instance.game.showDFScreen)
        {
            if (Input.GetButtonDown("ScaleUnits"))
            {
                GameSettings.Instance.units.scaleUnits = !GameSettings.Instance.units.scaleUnits;
            }
            if (Input.GetButtonDown("OverheadShadows"))
            {
                overheadShadows = !overheadShadows;
            }
            if (Input.GetButtonDown("FollowDF"))
            {
                camera.following = true;
            }

            // take screenshot on up->down transition of F9 key
            if (Input.GetButtonDown("TakeScreenshot"))
            {
                string path = Application.dataPath;
                if (Application.platform == RuntimePlatform.OSXPlayer)
                {
                    path += "/../../";
                }
                else if (Application.platform == RuntimePlatform.WindowsPlayer
                    || Application.platform == RuntimePlatform.LinuxPlayer)
                {
                    path += "/../";
                }

                string screenshotFilename;
                do
                {
                    screenshotCount++;
                    screenshotFilename = path + "screenshot" + screenshotCount + ".png";

                } while (File.Exists(screenshotFilename));

                if (Input.GetButton("Mod"))
                    Application.CaptureScreenshot(screenshotFilename, 8);
                else
                    Application.CaptureScreenshot(screenshotFilename);
            }
            if (Input.GetButtonDown("Refresh"))
            {
                Refresh();
            }
            if (Input.GetButtonDown("Cancel"))
            {
                optionsPanel.gameObject.SetActive(!optionsPanel.gameObject.activeSelf);
            }
        }

        ShowCursorInfo();
        UpdateRequestRegion();
        UpdateCreatures();
        UpdateBlocks();
        UpdateSpatters();
        UpdateTerrainTextures();
        DrawBlocks();
        DrawItems();
    }

    public void Refresh()
    {
        for (int z = 0; z < blockDirtyBits.GetLength(2); z++)
            for (int x = 0; x < blockDirtyBits.GetLength(0); x++)
                for (int y = 0; y < blockDirtyBits.GetLength(1); y++)
                {
                    if (blockContentBits[x, y, z])
                    {
                        layerDirtyBits[z] = true;
                        if (z > 0) 
                            layerDirtyBits[z - 1] = true; //For ramp edges.
                        spatterBlockDirtyBits[z] = true;
                    }
                    blockDirtyBits[x, y, z] = blockContentBits[x, y, z];
                    liquidBlockDirtyBits[x, y, z] = blockContentBits[x, y, z];
                }
    }

    public Mesh testMesh;

    private void SaveMeshes()
    {
        //COLLADA exportScene = new COLLADA();

        //List<geometry> geometryList = new List<geometry>();

        ////if(testMesh!= null)
        ////{
        ////    geometry geo = COLLADA.MeshToGeometry(testMesh);
        ////    if (geo != null)
        ////        geometryList.Add(geo);
        ////}

        //Debug.Log("Starting mesh export");
        //foreach (Mesh mesh in blocks)
        //{
        //    if (mesh != null)
        //    {
        //        geometry geo = (COLLADA.MeshToGeometry(mesh));
        //        if (geo != null)
        //            geometryList.Add(geo);
        //    }
        //}
        //Debug.Log("Added opaque blocks");
        //foreach (Mesh mesh in stencilBlocks)
        //{
        //    if (mesh != null)
        //    {
        //        geometry geo = (COLLADA.MeshToGeometry(mesh));
        //        if (geo != null)
        //            geometryList.Add(geo);
        //    }
        //}
        //Debug.Log("Added stencil blocks");
        //foreach (Mesh mesh in transparentBlocks)
        //{
        //    if (mesh != null)
        //    {
        //        geometry geo = (COLLADA.MeshToGeometry(mesh));
        //        if (geo != null)
        //            geometryList.Add(geo);
        //    }
        //}
        //Debug.Log("Added transparent blocks");
        //foreach (Mesh mesh in liquidBlocks)
        //{
        //    if (mesh != null)
        //    {
        //        geometry geo = (COLLADA.MeshToGeometry(mesh));
        //        if (geo != null)
        //            geometryList.Add(geo);
        //    }
        //}
        //Debug.Log("Added liquid blocks");

        //library_geometries geometryLib = new library_geometries();
        //geometryLib.geometry = geometryList.ToArray();
        //Debug.Log("Added geometry to library");

        //library_visual_scenes visualSceneLib = new library_visual_scenes();
        //visual_scene visualScene = new visual_scene();

        //visualSceneLib.visual_scene = new visual_scene[1];
        //visualSceneLib.visual_scene[0] = visualScene;

        //visualScene.id = "Map";
        //visualScene.name = "Map";
        //visualScene.node = new node[geometryList.Count];
        //for (int i = 0; i < geometryList.Count; i++)
        //{
        //    node thisNode = new node();
        //    visualScene.node[i] = thisNode;
        //    geometry thisGeometry = geometryList[i];
        //    thisNode.id = thisGeometry.id.Remove(thisGeometry.id.Length - 4);
        //    thisNode.name = thisGeometry.name.Remove(thisGeometry.name.Length - 6);
        //    thisNode.sid = thisNode.id;

        //    thisNode.Items = new object[1];
        //    thisNode.Items[0] = COLLADA.ConvertMatrix(Matrix4x4.identity);
        //    thisNode.ItemsElementName = new ItemsChoiceType2[1];
        //    thisNode.ItemsElementName[0] = ItemsChoiceType2.matrix;

        //    thisNode.instance_geometry = new instance_geometry[1];
        //    thisNode.instance_geometry[0] = new instance_geometry();
        //    thisNode.instance_geometry[0].url = "#" + thisGeometry.id;
        //}
        //Debug.Log("Added geometry to scene");

        //COLLADAScene sceneInstance = new COLLADAScene();
        //sceneInstance.instance_visual_scene = new InstanceWithExtra();
        //sceneInstance.instance_visual_scene.url = "#" + visualScene.id;

        //exportScene.scene = sceneInstance;

        //exportScene.Items = new object[2];
        //exportScene.Items[0] = geometryLib;
        //exportScene.Items[1] = visualSceneLib;

        //asset assetHeader = new asset();
        //assetHeader.unit = new assetUnit();
        //assetHeader.unit.meter = 1;
        //assetHeader.unit.name = "meter";
        //assetHeader.up_axis = UpAxisType.Y_UP;

        //exportScene.asset = assetHeader;
        //Debug.Log("Setup Scene");

        //if (File.Exists("Map.dae"))
        //    File.Delete("Map.dae");
        //exportScene.Save("Map.dae");
        //Debug.Log("Saved Scene");

        //Texture2D mainTex = (Texture2D)BasicTerrainMaterial.GetTexture("_MainTex");

        //Color[] mainTexPixels = mainTex.GetPixels();
        //Color[] diffusePixels = new Color[mainTexPixels.Length];
        //Color[] roughnessPixels = new Color[mainTexPixels.Length];

        //for (int i = 0; i < mainTexPixels.Length; i++)
        //{
        //    diffusePixels[i] = new Color(mainTexPixels[i].r, mainTexPixels[i].g, mainTexPixels[i].b, 1.0f);
        //    roughnessPixels[i] = new Color(mainTexPixels[i].a, mainTexPixels[i].a, mainTexPixels[i].a, 1.0f);
        //}

        //Texture2D diffuseTex = new Texture2D(mainTex.width, mainTex.height);
        //Texture2D roughnessTex = new Texture2D(mainTex.width, mainTex.height);

        //diffuseTex.SetPixels(diffusePixels);
        //roughnessTex.SetPixels(roughnessPixels);

        //diffuseTex.Apply();
        //roughnessTex.Apply();

        //byte[] diffuseBytes = diffuseTex.EncodeToPNG();
        //byte[] roughnessBytes = roughnessTex.EncodeToPNG();

        //File.WriteAllBytes("pattern.png", diffuseBytes);
        //File.WriteAllBytes("specular.png", roughnessBytes);
        //Debug.Log("Saved Maintex");

        //Texture2D bumpMap = (Texture2D)BasicTerrainMaterial.GetTexture("_BumpMap");

        //Color[] bumpMapPixels = bumpMap.GetPixels();
        //Color[] normalMapPixels = new Color[bumpMapPixels.Length];
        //Color[] ambientMapPixels = new Color[bumpMapPixels.Length];
        //Color[] alphaMapPixels = new Color[bumpMapPixels.Length];

        //for (int i = 0; i < bumpMapPixels.Length; i++)
        //{
        //    normalMapPixels[i] = new Color(bumpMapPixels[i].a, bumpMapPixels[i].g, Mathf.Sqrt(1 - ((bumpMapPixels[i].a * 2 - 1) * (bumpMapPixels[i].a * 2 - 1)) + ((bumpMapPixels[i].g * 2 - 1) * (bumpMapPixels[i].g * 2 - 1))));
        //    ambientMapPixels[i] = new Color(bumpMapPixels[i].r, bumpMapPixels[i].r, bumpMapPixels[i].r, 1.0f);
        //    alphaMapPixels[i] = new Color(bumpMapPixels[i].b, bumpMapPixels[i].b, bumpMapPixels[i].b, 1.0f);
        //}

        //Texture2D normalTex = new Texture2D(bumpMap.width, bumpMap.height);
        //Texture2D ambientTex = new Texture2D(bumpMap.width, bumpMap.height);
        //Texture2D alphaTex = new Texture2D(bumpMap.width, bumpMap.height);

        //normalTex.SetPixels(normalMapPixels);
        //ambientTex.SetPixels(ambientMapPixels);
        //alphaTex.SetPixels(alphaMapPixels);

        //normalTex.Apply();
        //ambientTex.Apply();
        //alphaTex.Apply();

        //byte[] normalBytes = normalTex.EncodeToPNG();
        //byte[] ambientBytes = ambientTex.EncodeToPNG();
        //byte[] alphaBytes = alphaTex.EncodeToPNG();

        //File.WriteAllBytes("normal.png", normalBytes);
        //File.WriteAllBytes("occlusion.png", ambientBytes);
        //File.WriteAllBytes("alpha.png", alphaBytes);
        //Debug.Log("Saved DetailTex");

        //Debug.Log("Saved map!");
    }

    void OnDestroy()
    {
        if (mesher != null)
        {
            mesher.Terminate();
            mesher = null;
        }
    }

    void UpdateView()
    {
        RemoteFortressReader.ViewInfo newView = DFConnection.Instance.PopViewInfoUpdate();
        if (newView == null) return;

        UnityEngine.Profiling.Profiler.BeginSample("UpdateView", this);
        //Debug.Log("Got view");
        view = newView;

        if (view.follow_unit_id != -1 && lastUnitList != null)
        {
            foreach (var unit in lastUnitList.creature_list)
            {
                if (unit.id == view.follow_unit_id)
                {
                    posXTile = unit.pos_x;
                    posYTile = unit.pos_y;
                    posZ = unit.pos_z + 1;
                    return;
                }
            }
        }

        posXTile = (view.view_pos_x + (view.view_size_x / 2));
        posYTile = (view.view_pos_y + (view.view_size_y / 2));
        posZ = view.view_pos_z + 1;
        UnityEngine.Profiling.Profiler.EndSample();
    }

    // Update the region we're requesting
    void UpdateRequestRegion()
    {
        UnityEngine.Profiling.Profiler.BeginSample("UpdateRequestRegion", this);
        DFConnection.Instance.SetRequestRegion(
            new BlockCoord(
                PosXBlock - GameSettings.Instance.rendering.drawRangeSide,
                PosYBlock - GameSettings.Instance.rendering.drawRangeSide,
                posZ - GameSettings.Instance.rendering.drawRangeDown
            ),
            new BlockCoord(
                PosXBlock + GameSettings.Instance.rendering.drawRangeSide,
                PosYBlock + GameSettings.Instance.rendering.drawRangeSide,
                posZ + GameSettings.Instance.rendering.drawRangeUp
            ));
        UnityEngine.Profiling.Profiler.EndSample();
    }
    void UpdateBlocks()
    {
        if (DFConnection.Instance.EmbarkMapSize != mapSize)
        {
            ClearMap();
            InitializeBlocks();
            mapSize = DFConnection.Instance.EmbarkMapSize;
            DFConnection.Instance.RequestMapReset();
        }
        if (DFConnection.Instance.EmbarkMapPosition != mapPosition)
        {
            ClearMap();
            mapPosition = DFConnection.Instance.EmbarkMapPosition;
            MapZOffset = DFConnection.Instance.EmbarkMapPosition.z;
            DFConnection.Instance.RequestMapReset();
        }
        if (MapDataStore.MapSize.x < 48)
            return;
        UnityEngine.Profiling.Profiler.BeginSample("UpdateBlocks", this);
        while (true)
        {
            UnityEngine.Profiling.Profiler.BeginSample("PopMapBlockUpdate", this);
            RemoteFortressReader.MapBlock block = DFConnection.Instance.PopMapBlockUpdate();
            UnityEngine.Profiling.Profiler.EndSample();
            if (block == null) break;
            bool setTiles;
            bool setLiquids;
            bool setSpatters;
            UnityEngine.Profiling.Profiler.BeginSample("StoreTiles", this);
            MapDataStore.Main.StoreTiles(block, out setTiles, out setLiquids, out setSpatters);
            UnityEngine.Profiling.Profiler.EndSample();
            if (setTiles)
            {
                addSeasonalUpdates(block, block.map_x, block.map_y, block.map_z);
                SetDirtyBlock(block.map_x, block.map_y, block.map_z);
                SetBlockContent(block.map_x, block.map_y, block.map_z);
            }
            if (setLiquids)
            {
                SetDirtyLiquidBlock(block.map_x, block.map_y, block.map_z);
                SetBlockContent(block.map_x, block.map_y, block.map_z);
            }
            if (setSpatters)
            {
                SetDirtySpatterBlock(block.map_x, block.map_y, block.map_z);
            }
            foreach (var item in block.items)
            {
                itemInstances[item.id] = item;
            }
        }
        itemPositions.Clear();
        foreach (var item in itemInstances)
        {
            itemPositions[item.Value.pos] = item.Value;
        }
        DirtySeasonalBlocks();
        UnityEngine.Profiling.Profiler.BeginSample("EnqueueMeshUpdates", this);
        EnqueueMeshUpdates();
        UnityEngine.Profiling.Profiler.EndSample();

        mesher.Poll();

        FetchNewMeshes();
        UnityEngine.Profiling.Profiler.EndSample();
    }

    void SetMaterialBounds(Vector4 bounds)
    {
        MaterialManager.Instance.SetVector("_WorldBounds", bounds);
    }

    void InitializeBlocks()
    {
        int blockSizeX = DFConnection.Instance.EmbarkMapSize.x;
        int blockSizeY = DFConnection.Instance.EmbarkMapSize.y;
        int blockSizeZ = DFConnection.Instance.EmbarkMapSize.z;
        MapZOffset = DFConnection.Instance.EmbarkMapPosition.z;

        mapMeshes = new BlockMeshSet[blockSizeX * 16 / blockSize, blockSizeY * 16 / blockSize, blockSizeZ];

        blockDirtyBits = new bool[blockSizeX * 16 / blockSize, blockSizeY * 16 / blockSize, blockSizeZ];
        blockContentBits = new bool[blockSizeX * 16 / blockSize, blockSizeY * 16 / blockSize, blockSizeZ];
        blockUpdateSchedules = new UpdateSchedule[blockSizeX * 16 / blockSize, blockSizeY * 16 / blockSize, blockSizeZ];
        liquidBlockDirtyBits = new bool[blockSizeX * 16 / blockSize, blockSizeY * 16 / blockSize, blockSizeZ];
        magmaGlow = new Light[blockSizeX * 16, blockSizeY * 16, blockSizeZ];
        spatterBlockDirtyBits = new bool[blockSizeZ];
        layerDirtyBits = new bool[blockSizeZ];
        spatterLayers = new Texture2D[blockSizeZ];
        terrainSplatLayers = new Texture2D[blockSizeZ];
        terrainTintLayers = new Texture2D[blockSizeZ];
        grassSplatLayers = new Texture2D[blockSizeZ];
        grassTintLayers = new Texture2D[blockSizeZ];


        Vector3 min = DFtoUnityCoord(0, 0, 0) - new Vector3(tileWidth / 2, 0, -tileWidth / 2);
        Vector3 max = DFtoUnityCoord((blockSizeX * 16) - 1, (blockSizeY * 16) - 1, 0) + new Vector3(tileWidth / 2, 0, -tileWidth / 2);

        SetMaterialBounds(new Vector4(min.x, min.z, max.x, max.z));
    }

    void addSeasonalUpdates(RemoteFortressReader.MapBlock block, int mapBlockX, int mapBlockY, int mapBlockZ)
    {
        if (DFConnection.Instance.NetPlantRawList == null)
            return;
        mapBlockX /= blockSize;
        mapBlockY /= blockSize;
        if (blockUpdateSchedules[mapBlockX, mapBlockY, mapBlockZ] != null)
            blockUpdateSchedules[mapBlockX, mapBlockY, mapBlockZ].Clear();
        foreach (var material in block.materials)
        {
            if (material.mat_type != 419
                    || material.mat_index < 0
                    || DFConnection.Instance.NetPlantRawList.plant_raws.Count <= material.mat_index
                    || DFConnection.Instance.NetPlantRawList.plant_raws[material.mat_index].growths.Count == 0)
                continue;
            PlantRaw plantRaw = DFConnection.Instance.NetPlantRawList.plant_raws[material.mat_index];
            if (blockUpdateSchedules[mapBlockX, mapBlockY, mapBlockZ] == null)
                blockUpdateSchedules[mapBlockX, mapBlockY, mapBlockZ] = new UpdateSchedule();
            var schedule = blockUpdateSchedules[mapBlockX, mapBlockY, mapBlockZ];
            foreach (TreeGrowth growth in plantRaw.growths)
            {
                schedule.Add(growth.timing_start);
                schedule.Add(growth.timing_end);
                foreach (GrowthPrint print in growth.prints)
                {
                    schedule.Add(print.timing_start);
                    schedule.Add(print.timing_end);
                }
            }
        }
    }

    void SetDirtyBlock(int mapBlockX, int mapBlockY, int mapBlockZ)
    {
        mapBlockX /= blockSize;
        mapBlockY /= blockSize;
        blockDirtyBits[mapBlockX, mapBlockY, mapBlockZ] = true;
        layerDirtyBits[mapBlockZ] = true;
    }
    void SetBlockContent(int mapBlockX, int mapBlockY, int mapBlockZ)
    {
        mapBlockX /= blockSize;
        mapBlockY /= blockSize;
        blockContentBits[mapBlockX, mapBlockY, mapBlockZ] = true;
    }
    void SetDirtyLiquidBlock(int mapBlockX, int mapBlockY, int mapBlockZ)
    {
        mapBlockX /= blockSize;
        mapBlockY /= blockSize;
        liquidBlockDirtyBits[mapBlockX, mapBlockY, mapBlockZ] = true;
    }
    private void SetDirtySpatterBlock(int map_x, int map_y, int map_z)
    {
        spatterBlockDirtyBits[map_z] = true;
    }


    #region Debug Data Saving

    void PrintFullMaterialList()
    {
        int totalCount = DFConnection.Instance.NetMaterialList.material_list.Count;
        int limit = totalCount;
        if (limit >= 100)
            limit = 100;
        //Don't ever do this.
        for (int i = totalCount - limit; i < totalCount; i++)
        {
            //no really, don't.
            RemoteFortressReader.MaterialDefinition material = DFConnection.Instance.NetMaterialList.material_list[i];
            Debug.Log("{" + material.mat_pair.mat_index + "," + material.mat_pair.mat_type + "}, " + material.id + ", " + material.name);
        }
    }

    void SaveTileTypeList()
    {
        if (DFConnection.Instance.NetTiletypeList == null)
            return;
        try
        {
            File.Delete("TiletypeList.csv");
        }
        catch (IOException)
        {
            return;
        }
        using (StreamWriter writer = new StreamWriter("TiletypeList.csv"))
        {
            foreach (Tiletype item in DFConnection.Instance.NetTiletypeList.tiletype_list)
            {
                writer.WriteLine(
                    item.name + ";" +
                    item.shape + ":" +
                    item.special + ":" +
                    item.material + ":" +
                    item.variant + ":" +
                    item.direction
                    );
            }
        }
    }
    void SaveBuildingList()
    {
        if (DFConnection.Instance.NetBuildingList == null)
            return;
        try
        {
            File.Delete("BuildingList.csv");
        }
        catch (IOException)
        {
            return;
        }
        using (StreamWriter writer = new StreamWriter("BuildingList.csv"))
        {
            foreach (var item in DFConnection.Instance.NetBuildingList.building_list)
            {
                writer.WriteLine(
                    item.name + ";" +
                    item.id + ";" +
                    item.building_type.building_type + ":" +
                    item.building_type.building_subtype + ":" +
                    item.building_type.building_custom
                    );
            }
        }
    }


    void SaveMaterialList(IEnumerable<KeyValuePair<MatPairStruct, MaterialDefinition>> list, string filename)
    {
        try
        {
            File.Delete(filename);
        }
        catch (IOException)
        {
            return;
        }
        using (StreamWriter writer = new StreamWriter(filename))
        {
            foreach (var item in list)
            {
                writer.WriteLine(
                    item.Value.name + ";" +
                    item.Value.id + ";" +
                    item.Value.mat_pair.mat_type + ";" +
                    item.Value.mat_pair.mat_index
                    );
            }
        }
    }

    void SavePlantList()
    {
        if (DFConnection.Instance.NetPlantRawList == null)
            return;
        try
        {
            File.Delete("PlantList.csv");
        }
        catch (IOException)
        {
            return;
        }
        using (StreamWriter writer = new StreamWriter("PlantList.csv"))
        {
            foreach (var plant in TokenLists.PlantTokenList.GrowthIDs)
            {
                foreach (var growth in plant.Value)
                {
                    foreach (var print in growth.Value)
                    {
                        writer.Write(plant.Key);
                        writer.Write(":");
                        writer.Write(growth.Key);
                        writer.Write(":");
                        writer.Write(print.Key);
                        writer.Write(";");
                        writer.Write(print.Value);
                        writer.Write(";");
                        //var index = print.Value;
                        //if (index.building_type >= 0)
                        //{
                        //    PlantRaw plantRaw = DFConnection.Instance.NetPlantRawList.plant_raws[index.building_type];
                        //    if (index.building_type >= 0 && index.building_subtype >= 0 && index.building_custom >= 0)
                        //    {
                        //        var printRaw = plantRaw.growths[index.building_subtype].prints[index.building_custom];
                        //        writer.Write(CharacterConverter.Convert((byte)printRaw.tile));
                        //    }
                        //    else
                        //    {
                        //        writer.Write(CharacterConverter.Convert((byte)plantRaw.tile));
                        //    }
                        //}
                        writer.WriteLine();
                    }
                }
            }
        }
    }
    #endregion

    void DirtySeasonalBlocks()
    {
        int xmin = Mathf.Clamp(PosXBlock - GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(0));
        int xmax = Mathf.Clamp(PosXBlock + GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(0));
        int ymin = Mathf.Clamp(PosYBlock - GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(1));
        int ymax = Mathf.Clamp(PosYBlock + GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(1));
        int zmin = Mathf.Clamp(posZ - GameSettings.Instance.rendering.drawRangeDown, 0, mapMeshes.GetLength(2));
        int zmax = Mathf.Clamp(posZ + GameSettings.Instance.rendering.drawRangeUp, 0, mapMeshes.GetLength(2));
        for (int zz = zmin; zz < zmax; zz++)
            for (int yy = ymin; yy < ymax; yy++)
                for (int xx = xmin; xx < xmax; xx++)
                {
                    if (blockUpdateSchedules[xx, yy, zz] != null && blockUpdateSchedules[xx, yy, zz].CheckUpdate(TimeHolder.DisplayedTime.CurrentYearTicks))
                        blockDirtyBits[xx, yy, zz] = true;
                }

    }

    void UpdateTerrainTextures()
    {
        if (ContentLoader.Instance == null)
            return;
        UnityEngine.Profiling.Profiler.BeginSample("UpdateTerrainTextures", this);
        for (int z = layerDirtyBits.Length - 1; z >= 0; z--)
        {
            if (layerDirtyBits[z])
            {
                GenerateTerrainTexture(z);
                GenerateGrassTexture(z);
                layerDirtyBits[z] = false;
                break; //don't do more than one set per frame.
            }
        }
        UnityEngine.Profiling.Profiler.EndSample();
    }

    void UpdateSpatters()
    {
        if (ContentLoader.Instance == null)
            return;
        UnityEngine.Profiling.Profiler.BeginSample("UpdateSpatters", this);
        for (int z = spatterBlockDirtyBits.Length - 1; z >= 0; z--)
        {
            if (spatterBlockDirtyBits[z])
            {
                GenerateSpatterTexture(z);
                spatterBlockDirtyBits[z] = false;
            }
        }
        UnityEngine.Profiling.Profiler.EndSample();
    }

    // Have the mesher mesh all dirty tiles in the region
    void EnqueueMeshUpdates()
    {
        int xmin = Mathf.Clamp(PosXBlock - GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(0));
        int xmax = Mathf.Clamp(PosXBlock + GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(0));
        int ymin = Mathf.Clamp(PosYBlock - GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(1));
        int ymax = Mathf.Clamp(PosYBlock + GameSettings.Instance.rendering.drawRangeSide, 0, mapMeshes.GetLength(1));
        int zmin = Mathf.Clamp(posZ - GameSettings.Instance.rendering.drawRangeDown, 0, mapMeshes.GetLength(2));
        int zmax = Mathf.Clamp(posZ + GameSettings.Instance.rendering.drawRangeUp, 0, mapMeshes.GetLength(2));
        for (int zz = posZ - 1; zz >= zmin; zz--)
        {
            if (zz >= mapMeshes.GetLength(2))
                continue;

            for (int yy = ymin; yy < ymax; yy++)
                for (int xx = xmin; xx < xmax; xx++)
                {
                    if (!blockDirtyBits[xx, yy, zz] && !liquidBlockDirtyBits[xx, yy, zz])
                    {
                        continue;
                    }

                    //If we were not able to add it to the queue, don't try any more till next fame.
                    if (!mesher.Enqueue(new DFCoord(xx * 16, yy * 16, zz), blockDirtyBits[xx, yy, zz], liquidBlockDirtyBits[xx, yy, zz]))
                        return;
                    blockDirtyBits[xx, yy, zz] = false;
                    liquidBlockDirtyBits[xx, yy, zz] = false;
                    return;
                }
        }
        for (int zz = posZ; zz < zmax; zz++)
        {
            if (zz < 0)
                continue;
            for (int yy = ymin; yy < ymax; yy++)
                for (int xx = xmin; xx < xmax; xx++)
                {
                    if (!blockDirtyBits[xx, yy, zz] && !liquidBlockDirtyBits[xx, yy, zz])
                    {
                        continue;
                    }

                    //If we were not able to add it to the queue, don't try any more till next fame.
                    if (!mesher.Enqueue(new DFCoord(xx * 16, yy * 16, zz), blockDirtyBits[xx, yy, zz], liquidBlockDirtyBits[xx, yy, zz]))
                        return;
                    blockDirtyBits[xx, yy, zz] = false;
                    liquidBlockDirtyBits[xx, yy, zz] = false;
                    return;
                }
        }
    }

    // Get new meshes from the mesher
    void FetchNewMeshes()
    {
        UnityEngine.Profiling.Profiler.BeginSample("FetchNewMeshes", this);
        if (mesher.HasNewMeshes)
        {
            var newMeshes = mesher.Dequeue().Value;
            int block_x = newMeshes.location.x / blockSize;
            int block_y = newMeshes.location.y / blockSize;
            int block_z = newMeshes.location.z;
            if (mapMeshes[block_x, block_y, block_z] == null)
                mapMeshes[block_x, block_y, block_z] = new BlockMeshSet();

            var meshSet = mapMeshes[block_x, block_y, block_z];

            meshSet.LoadMeshes(newMeshes, string.Format("{0}_{1}_{2}", block_x, block_y, block_z));

            if (newMeshes.collisionMesh != null)
            {
                if (meshSet.collisionBlocks == null)
                {
                    meshSet.collisionBlocks = Instantiate(collisionTemplate);
                    meshSet.collisionBlocks.name = string.Format("collisionBlock_{0}_{1}_{2}", block_x, block_y, block_z);
                    meshSet.collisionBlocks.transform.position = DFtoUnityCoord(block_x * blockSize, block_y * blockSize, block_z);
                    meshSet.collisionBlocks.transform.parent = transform;
                }
                Mesh collisionMesh = new Mesh();
                collisionMesh.name = string.Format("block_collision_{0}_{1}_{2}", block_x, block_y, block_z);
                newMeshes.collisionMesh.CopyToMesh(collisionMesh);
                meshSet.collisionBlocks.sharedMesh = collisionMesh;
            }
        }
        UnityEngine.Profiling.Profiler.EndSample();
    }

    void GenerateGrassTexture(int z)
    {
        if (ContentLoader.Instance == null)
            return;
        UnityEngine.Profiling.Profiler.BeginSample("GenerateGrassTexture", this);

        Color[] grassColors = new Color[MapDataStore.MapSize.x * MapDataStore.MapSize.y];
        Color[] grassIndices = new Color[MapDataStore.MapSize.x * MapDataStore.MapSize.y];

        int grassTiles = 0;

        for (int x = 0; x < MapDataStore.MapSize.x; x++)
            for (int y = 0; y < MapDataStore.MapSize.y; y++)
            {
                int index = x + (y * MapDataStore.MapSize.x);
                var tile = MapDataStore.Main[x, y, z];
                if (tile == null)
                {
                    grassIndices[index].r = ContentLoader.Instance.DefaultMatTexIndex;
                    grassIndices[index].g = ContentLoader.Instance.DefaultShapeTexIndex;
                    grassColors[index] = new Color(0,0,0,0);
                    continue;
                }
                if (!(tile.tiletypeMaterial == TiletypeMaterial.GRASS_DARK
                    || tile.tiletypeMaterial == TiletypeMaterial.GRASS_LIGHT
                    || tile.tiletypeMaterial == TiletypeMaterial.GRASS_DEAD
                    || tile.tiletypeMaterial == TiletypeMaterial.GRASS_DRY
                    ))
                    continue;

                grassTiles++;

                GrassContent grassTexture;
                if (ContentLoader.Instance.GrassTextureConfiguration.GetValue(tile, MeshLayer.StaticMaterial, out grassTexture))
                {
                    grassIndices[index].r = grassTexture.MaterialTexture.StorageIndex;
                    grassIndices[index].g = grassTexture.ShapeTexture.StorageIndex;
                }
                else
                {
                    grassIndices[index].r = ContentLoader.Instance.DefaultMatTexIndex;
                    grassIndices[index].g = ContentLoader.Instance.DefaultShapeTexIndex;
                }

                ColorContent colorContent;
                if (ContentLoader.Instance.ColorConfiguration.GetValue(tile, MeshLayer.StaticMaterial, out colorContent))
                    grassColors[index] = colorContent.color;
                else
                    grassColors[index] = Color.gray;

            }
        if (grassTiles == 0)
            return;

        if (grassSplatLayers[z] == null)
        {
            grassSplatLayers[z] = new Texture2D(MapDataStore.MapSize.x, MapDataStore.MapSize.y, TextureFormat.RGHalf, false, true);
            grassSplatLayers[z].filterMode = FilterMode.Point;
            grassSplatLayers[z].wrapMode = TextureWrapMode.Clamp;
        }
        if (grassSplatLayers[z].width != MapDataStore.MapSize.x || terrainSplatLayers[z].height != MapDataStore.MapSize.y)
            grassSplatLayers[z].Resize(MapDataStore.MapSize.x, MapDataStore.MapSize.y);

        grassSplatLayers[z].SetPixels(grassIndices);
        grassSplatLayers[z].Apply();

        if (grassTintLayers[z] == null)
        {
            grassTintLayers[z] = new Texture2D(MapDataStore.MapSize.x, MapDataStore.MapSize.y, TextureFormat.RGBA32, false, true);
            grassTintLayers[z].filterMode = FilterMode.Point;
            grassTintLayers[z].wrapMode = TextureWrapMode.Clamp;
        }
        if (grassTintLayers[z].width != MapDataStore.MapSize.x || terrainTintLayers[z].height != MapDataStore.MapSize.y)
            grassTintLayers[z].Resize(MapDataStore.MapSize.x, MapDataStore.MapSize.y);

        grassTintLayers[z].SetPixels(grassColors);
        grassTintLayers[z].Apply();

        UnityEngine.Profiling.Profiler.EndSample();
    }

    void GenerateTerrainTexture(int z)
    {
        if (ContentLoader.Instance == null)
            return;
        UnityEngine.Profiling.Profiler.BeginSample("GenerateSpatterTexture", this);

        Color[] terrainColors = new Color[MapDataStore.MapSize.x * MapDataStore.MapSize.y];
        Color[] terrainIndices = new Color[MapDataStore.MapSize.x * MapDataStore.MapSize.y];

        for (int x = 0; x < MapDataStore.MapSize.x; x++)
            for (int y = 0; y < MapDataStore.MapSize.y; y++)
            {
                int index = x + (y * MapDataStore.MapSize.x);
                var tile = MapDataStore.Main[x, y, z];
                if (IsNullOrEmpty(tile))
                {
                    if (!IsNullOrEmpty(MapDataStore.Main[x - 1, y, z]))
                        tile = MapDataStore.Main[x - 1, y, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x, y - 1, z]))
                        tile = MapDataStore.Main[x, y - 1, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x + 1, y, z]))
                        tile = MapDataStore.Main[x + 1, y, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x, y + 1, z]))
                        tile = MapDataStore.Main[x, y + 1, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x - 1, y - 1, z]))
                        tile = MapDataStore.Main[x - 1, y - 1, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x - 1, y + 1, z]))
                        tile = MapDataStore.Main[x - 1, y + 1, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x + 1, y - 1, z]))
                        tile = MapDataStore.Main[x + 1, y - 1, z];
                    else if (!IsNullOrEmpty(MapDataStore.Main[x + 1, y + 1, z]))
                        tile = MapDataStore.Main[x + 1, y + 1, z];
                    else
                    {
                        terrainIndices[index].r = ContentLoader.Instance.DefaultMatTexIndex;
                        terrainIndices[index].g = ContentLoader.Instance.DefaultShapeTexIndex;
                        terrainColors[index] = Color.gray;
                        continue;
                    }
                }
                if (tile.shape == TiletypeShape.RAMP_TOP && tile.Down != null)
                    tile = tile.Down;

                var layer = MeshLayer.BaseMaterial;
                if (tile.tiletypeMaterial == TiletypeMaterial.GRASS_DARK
                || tile.tiletypeMaterial == TiletypeMaterial.GRASS_DEAD
                || tile.tiletypeMaterial == TiletypeMaterial.GRASS_DRY
                || tile.tiletypeMaterial == TiletypeMaterial.GRASS_LIGHT
                || tile.tiletypeMaterial == TiletypeMaterial.PLANT
                )
                    layer = MeshLayer.LayerMaterial;


                NormalContent normalContent;
                if (ContentLoader.Instance.TerrainShapeTextureConfiguration.GetValue(tile, layer, out normalContent))
                    terrainIndices[index].g = normalContent.StorageIndex;
                else
                    terrainIndices[index].g = ContentLoader.Instance.DefaultShapeTexIndex;

                TextureContent materialContent;
                if (ContentLoader.Instance.MaterialTextureConfiguration.GetValue(tile, layer, out materialContent))
                    terrainIndices[index].r = materialContent.StorageIndex;
                else
                    terrainIndices[index].r = ContentLoader.Instance.DefaultMatTexIndex;

                ColorContent colorContent;
                if (ContentLoader.Instance.ColorConfiguration.GetValue(tile, layer, out colorContent))
                    terrainColors[index] = colorContent.color;
                else
                    terrainColors[index] = Color.gray;

            }

        if(terrainSplatLayers[z] == null)
        {
            terrainSplatLayers[z] = new Texture2D(MapDataStore.MapSize.x, MapDataStore.MapSize.y, TextureFormat.RGHalf, false, true);
{'fraction_non_alphanumeric': 0.1058115801912128, 'fraction_numerical': 0.006187560425394779, 'mean_word_length': 1.7803297294068454, 'pattern_counts': {'":': 18, '<': 83, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1843455', 'n_tokens_mistral': 27416, 'n_tokens_neox': 25973, 'n_words': 5053}