﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using System;
using System.IO;
using UnityEditor;

namespace T2
{
    public static class Const
    {
        public const int MAP_CHUNK_ROOT_VERSION = 0;
        public const int MAP_CHUNK_ROOT_SKYLAYERS = 1;
        public const int MAP_CHUNK_ROOT_COLLISION = 2;
        public const int MAP_CHUNK_ROOT_GRIDBOUNDS = 3;
        public const int MAP_CHUNK_ROOT_GRIDSECTIONS = 4;
        public const int MAP_CHUNK_ROOT_ACTORS = 5;
        public const int MAP_CHUNK_ROOT_VISIBILITY = 6;
        public const int MAP_CHUNK_ROOT_LIGHTMAPS = 7;
        public const int MAP_CHUNK_ROOT_REJECTS = 8;
        public const int MAP_CHUNK_ROOT_COUNT = 9;

        public const int MAP_CHUNK_SKYLAYER_MATERIALS = 0;
        public const int MAP_CHUNK_SKYLAYER_INFO = 1;
        public const int MAP_CHUNK_SKYLAYER_COUNT = 2;

        public const int MAP_CHUNK_COLLISION_VERTICES = 0;
        public const int MAP_CHUNK_COLLISION_REGIONSETS = 1;
        public const int MAP_CHUNK_COLLISION_REGIONS = 2;
        public const int MAP_CHUNK_COLLISION_COUNT = 3;

        public const int MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE = 0;
        public const int MAP_CHUNK_GRIDBOUNDS_MIN = 1;
        public const int MAP_CHUNK_GRIDBOUNDS_MAX = 2;
        public const int MAP_CHUNK_GRIDBOUNDS_COUNT = 3;

        public const int MAP_CHUNK_GRIDSECTIONS_MESHES = 0;
        public const int MAP_CHUNK_GRIDSECTIONS_MODELFILES = 1;
        public const int MAP_CHUNK_GRIDSECTIONS_COUNT = 2;

        public const int MAP_CHUNK_ACTOR_INFO = 0;
        public const int MAP_CHUNK_ACTOR_COMPONENTS = 1;
        public const int MAP_CHUNK_ACTOR_COUNT = 2;

        public const int MAP_CHUNK_ACTORCOMPONENT_NAME = 0;
        public const int MAP_CHUNK_ACTORCOMPONENT_KEYVALUES = 1;
        public const int MAP_CHUNK_ACTORCOMPONENT_COUNT = 2;

        public const int MAP_CHUNK_LIGHTMAP_SECTIONS = 0;
        public const int MAP_CHUNK_LIGHTMAP_TEXTURES = 1;
        public const int MAP_CHUNK_LIGHTMAP_COUNT = 2;

        public const int MAP_CHUNK_LIGHTMAPSECTION_LOOKUPS = 0;
        public const int MAP_CHUNK_LIGHTMAPSECTION_VERTICES = 1;
        public const int MAP_CHUNK_LIGHTMAPSECTION_TRIANGLES = 2;
        public const int MAP_CHUNK_LIGHTMAPSECTION_COUNT = 3;

        public const int SIZEOF_VERSION = 4;
        public const int SIZEOF_SKYINFO = 44;
        public const int SIZEOF_COLLISION_VERTICE = 16;
        public const int SIZEOF_COLLISION_REGIONSET = 96;
        public const int SIZEOF_COLLISION_REGION = 18;
        public const int SIZEOF_STATICMESH = 88;
        public const int SIZEOF_ACTORINFO = 48;
        public const int SIZEOF_LIGHTMAP_VERTICE = 20;
    }

    public class T2MapMono : MonoBehaviour
    {
        public Map map;
    }

    [Serializable]
    public class Map
    {
        [HideInInspector]
        public int version;
        public SkyInfo[] skyInfos;
        [HideInInspector]
        public Vector4[] collisionVertices;
        [HideInInspector]
        public RegionSet[] collisionRegionSets;
        [HideInInspector]
        public Region[] collisionRegions;
        [HideInInspector]
        public GridBounds gridBounds;
        public ActorInfo[] actors;
        [HideInInspector]
        public Visibility[] visibility; //uint16's | size: uint16 to each bitmask lookups for staticmeshes, count: number of regions (which staticmeshes are visible from this region)
        [HideInInspector]
        public Rejects[] rejects; //uint16's | size: uint16 to each bitmask lookups for regions, count: number of regions (which regions can a region see)
        [HideInInspector]
        public LightMapTexture[] lightMapTextures;

        public Map()
        {
            Clear();
        }

        public void Clear()
        {
            version = 2;
            skyInfos = new SkyInfo[0];
            collisionVertices = new Vector4[0];
            collisionRegionSets = new RegionSet[0];
            collisionRegions = new Region[0];
            gridBounds = new GridBounds();
            actors = new ActorInfo[0];
            visibility = new Visibility[0];
            rejects = new Rejects[0];
            lightMapTextures = new LightMapTexture[0];
        }

        public void Save(string path)
        {
            //-----Create Archives of this map structure for writing
            ArchiveIndex mapArc = new ArchiveIndex(0, Const.MAP_CHUNK_ROOT_COUNT);

            //Version
            mapArc.SetBlock(Const.MAP_CHUNK_ROOT_VERSION, null, 8);

            //SkyLayers
            ArchiveIndex skyLayersArc = new ArchiveIndex(mapArc[Const.MAP_CHUNK_ROOT_SKYLAYERS], Const.MAP_CHUNK_SKYLAYER_COUNT);

            //Sky Materials
            ArchiveIndex skyMatsArc = new ArchiveIndex(skyLayersArc[Const.MAP_CHUNK_SKYLAYER_MATERIALS], skyInfos.Length);
            for (int i = 0; i < skyInfos.Length; i++)
            {
                skyMatsArc.SetBlock(i, ArchiveDataSet.FromString(skyMatsArc[i], skyInfos[i].material));
            }
            skyLayersArc.SetBlock(Const.MAP_CHUNK_SKYLAYER_MATERIALS, skyMatsArc);

            //Sky Infos
            ArchiveDataSet skyInfosArc = new ArchiveDataSet(skyLayersArc[Const.MAP_CHUNK_SKYLAYER_INFO], skyInfos.Length, Const.SIZEOF_SKYINFO);
            skyLayersArc.SetBlock(Const.MAP_CHUNK_SKYLAYER_INFO, skyInfosArc);

            mapArc.SetBlock(Const.MAP_CHUNK_ROOT_SKYLAYERS, skyLayersArc);

            //Collision
            ArchiveIndex collisionArc = new ArchiveIndex(mapArc[Const.MAP_CHUNK_ROOT_COLLISION], Const.MAP_CHUNK_COLLISION_COUNT);

            //Collision Vertices
            ArchiveDataSet collisionVertsArc = new ArchiveDataSet(collisionArc[Const.MAP_CHUNK_COLLISION_VERTICES], collisionVertices.Length, Const.SIZEOF_COLLISION_VERTICE);
            collisionArc.SetBlock(Const.MAP_CHUNK_COLLISION_VERTICES, collisionVertsArc);

            //Collision RegionSets
            ArchiveDataSet collisionRegionSetsArc = new ArchiveDataSet(collisionArc[Const.MAP_CHUNK_COLLISION_REGIONSETS], collisionRegionSets.Length, Const.SIZEOF_COLLISION_REGIONSET);
            collisionArc.SetBlock(Const.MAP_CHUNK_COLLISION_REGIONSETS, collisionRegionSetsArc);

            //Collision Regions
            ArchiveDataSet collisionRegionsArc = new ArchiveDataSet(collisionArc[Const.MAP_CHUNK_COLLISION_REGIONS], collisionRegions.Length, Const.SIZEOF_COLLISION_REGION);
            collisionArc.SetBlock(Const.MAP_CHUNK_COLLISION_REGIONS, collisionRegionsArc);

            mapArc.SetBlock(Const.MAP_CHUNK_ROOT_COLLISION, collisionArc);

            //GridBounds
            ArchiveIndex gridBoundsArc = new ArchiveIndex(mapArc[Const.MAP_CHUNK_ROOT_GRIDBOUNDS], Const.MAP_CHUNK_GRIDBOUNDS_COUNT);

            //GridBounds BlockSize
            ArchiveDataSet gridBoundsBlockSizeArc = new ArchiveDataSet(gridBoundsArc[Const.MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE], 2, 2);
            gridBoundsArc.SetBlock(Const.MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE, gridBoundsBlockSizeArc);

            //GridBounds Mins
            ArchiveDataSet gridBoundsMinsArc = new ArchiveDataSet(gridBoundsArc[Const.MAP_CHUNK_GRIDBOUNDS_MIN], gridBounds.sections.Length, 8);
            gridBoundsArc.SetBlock(Const.MAP_CHUNK_GRIDBOUNDS_MIN, gridBoundsMinsArc);

            //GridBounds Maxs
            ArchiveDataSet gridBoundsMaxsArc = new ArchiveDataSet(gridBoundsArc[Const.MAP_CHUNK_GRIDBOUNDS_MAX], gridBounds.sections.Length, 8);
            gridBoundsArc.SetBlock(Const.MAP_CHUNK_GRIDBOUNDS_MAX, gridBoundsMaxsArc);

            mapArc.SetBlock(Const.MAP_CHUNK_ROOT_GRIDBOUNDS, gridBoundsArc);

            //GridSections
            ArchiveIndex gridSectionsArc = new ArchiveIndex(mapArc[Const.MAP_CHUNK_ROOT_GRIDSECTIONS], gridBounds.sections.Length);
            for (int i = 0; i < gridSectionsArc.count; i++)
            {
                ArchiveIndex gSectionArc;
                if (gridBounds.sections[i].staticMeshes.Length > 0)
                {
                    gSectionArc = new ArchiveIndex(gridSectionsArc[i], Const.MAP_CHUNK_GRIDSECTIONS_COUNT);
                    //StaticMeshes
                    ArchiveDataSet staticMeshesArc = new ArchiveDataSet(gSectionArc[Const.MAP_CHUNK_GRIDSECTIONS_MESHES], gridBounds.sections[i].staticMeshes.Length, Const.SIZEOF_STATICMESH);
                    gSectionArc.SetBlock(Const.MAP_CHUNK_GRIDSECTIONS_MESHES, staticMeshesArc);

                    //ModelFiles
                    ArchiveIndex modelFilesArc = new ArchiveIndex(gSectionArc[Const.MAP_CHUNK_GRIDSECTIONS_MODELFILES], gridBounds.sections[i].staticMeshes.Length);
                    for (int j = 0; j < gridBounds.sections[i].staticMeshes.Length; j++)
                    {
                        ArchiveDataSet modelNameArc = ArchiveDataSet.FromString(modelFilesArc[j], gridBounds.sections[i].staticMeshes[j].modelPath);
                        modelFilesArc.SetBlock(j, modelNameArc);
                    }
                    gSectionArc.SetBlock(Const.MAP_CHUNK_GRIDSECTIONS_MODELFILES, modelFilesArc);
                }
                else
                {
                    gSectionArc = new ArchiveIndex(gridSectionsArc[i], 0);
                }

                gridSectionsArc.SetBlock(i, gSectionArc);
            }

            mapArc.SetBlock(Const.MAP_CHUNK_ROOT_GRIDSECTIONS, gridSectionsArc);

            //Actors
            ArchiveIndex actorsArc = new ArchiveIndex(mapArc[Const.MAP_CHUNK_ROOT_ACTORS], actors.Length);
            for (int i = 0; i < actorsArc.count; i++)
            {
                ArchiveIndex actorArc = new ArchiveIndex(actorsArc[i], Const.MAP_CHUNK_ACTOR_COUNT);

                //Actor Info
                ArchiveDataSet infoArc = new ArchiveDataSet(actorArc[Const.MAP_CHUNK_ACTOR_INFO], 1, Const.SIZEOF_ACTORINFO);
                actorArc.SetBlock(Const.MAP_CHUNK_ACTOR_INFO, infoArc);

                //Actor Components
                ArchiveIndex componentsArc = new ArchiveIndex(actorArc[Const.MAP_CHUNK_ACTOR_COMPONENTS], actors[i].components.Length);
                for (int j = 0; j < componentsArc.count; j++)
                {
                    ArchiveIndex componentArc = new ArchiveIndex(componentsArc[j], Const.MAP_CHUNK_ACTORCOMPONENT_COUNT);

                    //Component Name
                    ArchiveDataSet compNameArc = ArchiveDataSet.FromString(componentArc[Const.MAP_CHUNK_ACTORCOMPONENT_NAME], actors[i].components[j].name);
                    componentArc.SetBlock(Const.MAP_CHUNK_ACTORCOMPONENT_NAME, compNameArc);

                    //Component Key/Value
                    ArchiveIndex keyValuesArc = new ArchiveIndex(componentArc[Const.MAP_CHUNK_ACTORCOMPONENT_KEYVALUES], actors[i].components[j].keyValues.Count * 2);
                    int n = 0;
                    foreach (KeyValuePair<string, string> pair in actors[i].components[j].keyValues)
                    {
                        ArchiveDataSet keyArc = ArchiveDataSet.FromString(keyValuesArc[n], pair.Key);
                        keyValuesArc.SetBlock(n, keyArc);
                        n++;
                        ArchiveDataSet valueArc = ArchiveDataSet.FromString(keyValuesArc[n], pair.Value);
                        keyValuesArc.SetBlock(n, valueArc);
                        n++;
                    }
                    componentArc.SetBlock(Const.MAP_CHUNK_ACTORCOMPONENT_KEYVALUES, keyValuesArc);

                    componentsArc.SetBlock(j, componentArc);
                }
                actorArc.SetBlock(Const.MAP_CHUNK_ACTOR_COMPONENTS, componentsArc);

                actorsArc.SetBlock(i, actorArc);
            }

            mapArc.SetBlock(Const.MAP_CHUNK_ROOT_ACTORS, actorsArc);

            //Visibility
            ArchiveDataSet visArc;
            if (visibility.Length == 0) //then there are no visiblity tables
            {
                visArc = new ArchiveDataSet(mapArc[Const.MAP_CHUNK_ROOT_VISIBILITY], 0, 0);
            }
            else
            {
                //visibility.Length should be same length as regions length
                //int nStaticMeshes = 0;
                //for (int i = 0; i < gridBounds.sections.Length; i++)
                //    nStaticMeshes += gridBounds.sections[i].staticMeshes.Length;
                //int visSize = MathUtilsEx.CeilDivide(nStaticMeshes, 16) * 2; //div by 16 bits for number of ushorts needed. * 2 for ushort size.
                visArc = new ArchiveDataSet(mapArc[Const.MAP_CHUNK_ROOT_VISIBILITY], visibility.Length, visibility[0].staticMeshes.Length * 2);
            }

            mapArc.SetBlock(Const.MAP_CHUNK_ROOT_VISIBILITY, visArc);

            //Lightmaps
            int nLightmaps = 0;
            for (int i = 0; i < gridBounds.sections.Length; i++)
                nLightmaps += gridBounds.sections[i].lightMapMeshes.Length;
            ArchiveIndex lightMapArc;
            if (nLightmaps == 0) //if no lightmaps have been built
            {
                lightMapArc = new ArchiveIndex(mapArc[Const.MAP_CHUNK_ROOT_LIGHTMAPS], 0);
            }
            else
            {
                lightMapArc = new ArchiveIndex(mapArc[Const.MAP_CHUNK_ROOT_LIGHTMAPS], Const.MAP_CHUNK_LIGHTMAP_COUNT);

                //Lightmap Sections
                ArchiveIndex lightmapSectionsArc = new ArchiveIndex(lightMapArc[Const.MAP_CHUNK_LIGHTMAP_SECTIONS], gridBounds.sections.Length);
                for (int i = 0; i < gridBounds.sections.Length; i++)
                {
                    ArchiveIndex sectionArc = new ArchiveIndex(lightmapSectionsArc[i], Const.MAP_CHUNK_LIGHTMAPSECTION_COUNT);

                    //Section Lookups
                    //which textureID(index starts at 0) does each count (lightmap model) use. size is always uint 4 bytes. each dataset is each lightmap model.
                    ArchiveDataSet lookupsArc = new ArchiveDataSet(sectionArc[Const.MAP_CHUNK_LIGHTMAPSECTION_LOOKUPS], gridBounds.sections[i].lightMapMeshes.Length, 4);
                    sectionArc.SetBlock(Const.MAP_CHUNK_LIGHTMAPSECTION_LOOKUPS, lookupsArc);

                    //Section Meshes Vertices
                    ArchiveIndex vertsMeshesArc = new ArchiveIndex(sectionArc[Const.MAP_CHUNK_LIGHTMAPSECTION_VERTICES], gridBounds.sections[i].lightMapMeshes.Length);
                    for (int j = 0; j < gridBounds.sections[i].lightMapMeshes.Length; j++)
                    {
                        ArchiveDataSet vertsArc = new ArchiveDataSet(vertsMeshesArc[j], gridBounds.sections[i].lightMapMeshes[j].verts.Length, Const.SIZEOF_LIGHTMAP_VERTICE);
                        vertsMeshesArc.SetBlock(j, vertsArc);
                    }
                    sectionArc.SetBlock(Const.MAP_CHUNK_LIGHTMAPSECTION_VERTICES, vertsMeshesArc);

                    //Section Meshes Triangles
                    ArchiveIndex trisMeshesArc = new ArchiveIndex(sectionArc[Const.MAP_CHUNK_LIGHTMAPSECTION_TRIANGLES], gridBounds.sections[i].lightMapMeshes.Length);
                    for (int j = 0; j < gridBounds.sections[i].lightMapMeshes.Length; j++)
                    {
                        ArchiveDataSet trisArc = new ArchiveDataSet(trisMeshesArc[j], gridBounds.sections[i].lightMapMeshes[j].tris.Length, 2);
                        trisMeshesArc.SetBlock(j, trisArc);
                    }
                    sectionArc.SetBlock(Const.MAP_CHUNK_LIGHTMAPSECTION_TRIANGLES, trisMeshesArc);

                    lightmapSectionsArc.SetBlock(i, sectionArc);
                }
                lightMapArc.SetBlock(Const.MAP_CHUNK_LIGHTMAP_SECTIONS, lightmapSectionsArc);

                //Lightmap Textures
                ArchiveIndex lightmapTexturesArc = new ArchiveIndex(lightMapArc[Const.MAP_CHUNK_LIGHTMAP_TEXTURES], lightMapTextures.Length);
                for (int i = 0; i < lightMapTextures.Length; i++)
                {
                    lightmapTexturesArc.SetBlock(i, null, lightMapTextures[i].data.Length);
                }
                lightMapArc.SetBlock(Const.MAP_CHUNK_LIGHTMAP_TEXTURES, lightmapTexturesArc);

            }

            mapArc.SetBlock(Const.MAP_CHUNK_ROOT_LIGHTMAPS, lightMapArc);

            //Rejects
            ArchiveDataSet rejectsArc;
            if (rejects.Length == 0) //then there are no reject tables
            {
                rejectsArc = new ArchiveDataSet(mapArc[Const.MAP_CHUNK_ROOT_REJECTS], 0, 0);
            }
            else
            {
                rejectsArc = new ArchiveDataSet(mapArc[Const.MAP_CHUNK_ROOT_REJECTS], rejects.Length, rejects[0].regions.Length * 2);
            }

            mapArc.SetBlock(Const.MAP_CHUNK_ROOT_REJECTS, rejectsArc);


            //----------Start Write----------
            IOUtils.writeBuffer = new byte[mapArc.TotalSize];

            mapArc.WriteHeader();

            //Write Version
            IOUtils.writeIndex = mapArc[Const.MAP_CHUNK_ROOT_VERSION];
            IOUtils.WriteInt32(version);

            skyLayersArc.WriteHeader();
            //Write Sky Materials
            skyMatsArc.WriteHeader();
            for (int i = 0; i < skyMatsArc.count; i++)
            {
                ArchiveDataSet matArc = skyMatsArc.archives[i] as ArchiveDataSet;
                matArc.WriteHeader();
                IOUtils.writeIndex = matArc[0];
                IOUtils.WriteString(skyInfos[i].material);
            }

            //Write Sky Infos
            skyInfosArc.WriteHeader();
            for (int i = 0; i < skyInfosArc.count; i++)
            {
                skyInfos[i].Write(skyInfosArc[i]);
            }

            collisionArc.WriteHeader();
            //Write Collision Vertices
            collisionVertsArc.WriteHeader();
            for (int i = 0; i < collisionVertsArc.count; i++)
            {
                IOUtils.writeIndex = collisionVertsArc[i];
                IOUtils.WriteVector4(collisionVertices[i]);
            }

            //Write Collision RegionsSets
            collisionRegionSetsArc.WriteHeader();
            for (int i = 0; i < collisionRegionSetsArc.count; i++)
            {
                collisionRegionSets[i].Write(collisionRegionSetsArc[i]);
            }

            //Write Collision Regions
            collisionRegionsArc.WriteHeader();
            for (int i = 0; i < collisionRegionsArc.count; i++)
            {
                collisionRegions[i].Write(collisionRegionsArc[i]);
            }

            gridBoundsArc.WriteHeader();
            //Write Gridbounds Block Size
            gridBoundsBlockSizeArc.WriteHeader();
            IOUtils.writeIndex = gridBoundsBlockSizeArc[0];
            IOUtils.WriteInt16(gridBounds.width);
            IOUtils.writeIndex = gridBoundsBlockSizeArc[1];
            IOUtils.WriteInt16(gridBounds.height);

            //Write Gridbounds Mins
            gridBoundsMinsArc.WriteHeader();
            for (int i = 0; i < gridBoundsMinsArc.count; i++)
            {
                IOUtils.writeIndex = gridBoundsMinsArc[i];
                IOUtils.WriteFloat(gridBounds.sections[i].boundsMin.x);
                IOUtils.WriteFloat(gridBounds.sections[i].boundsMin.y);
            }

            //Write Gridbounds Maxs
            gridBoundsMaxsArc.WriteHeader();
            for (int i = 0; i < gridBoundsMaxsArc.count; i++)
            {
                IOUtils.writeIndex = gridBoundsMaxsArc[i];
                IOUtils.WriteFloat(gridBounds.sections[i].boundsMax.x);
                IOUtils.WriteFloat(gridBounds.sections[i].boundsMax.y);
            }

            //Write GridSections
            gridSectionsArc.WriteHeader();
            for (int i = 0; i < gridSectionsArc.count; i++)
            {
                if (gridBounds.sections[i].staticMeshes.Length > 0)
                {
                    ArchiveIndex sectionArc = gridSectionsArc.archives[i] as ArchiveIndex;
                    sectionArc.WriteHeader();

                    //StaticMeshes
                    ArchiveDataSet staticMeshesArc = sectionArc.archives[Const.MAP_CHUNK_GRIDSECTIONS_MESHES] as ArchiveDataSet;
                    staticMeshesArc.WriteHeader();
                    for (int j = 0; j < staticMeshesArc.count; j++)
                    {
                        gridBounds.sections[i].staticMeshes[j].Write(staticMeshesArc[j]);
                    }

                    //ModelFiles
                    ArchiveIndex modelFilesArc = sectionArc.archives[Const.MAP_CHUNK_GRIDSECTIONS_MODELFILES] as ArchiveIndex;
                    modelFilesArc.WriteHeader();
                    for (int j = 0; j < modelFilesArc.count; j++)
                    {
                        ArchiveDataSet modelNameArc = modelFilesArc.archives[j] as ArchiveDataSet;
                        modelNameArc.WriteHeader();
                        IOUtils.writeIndex = modelNameArc[0];
                        IOUtils.WriteString(gridBounds.sections[i].staticMeshes[j].modelPath);
                    }
                }
            }

            //Write Actors
            actorsArc.WriteHeader();
            for (int i = 0; i < actorsArc.count; i++)
            {
                ArchiveIndex actorArc = actorsArc.archives[i] as ArchiveIndex;
                actorArc.WriteHeader();

                //Actor Info
                ArchiveDataSet infoArc = actorArc.archives[Const.MAP_CHUNK_ACTOR_INFO] as ArchiveDataSet;
                infoArc.WriteHeader();
                actors[i].Write(infoArc[0]);

                //Actor Components
                ArchiveIndex componentsArc = actorArc.archives[Const.MAP_CHUNK_ACTOR_COMPONENTS] as ArchiveIndex;
                componentsArc.WriteHeader();
                for (int j = 0; j < componentsArc.count; j++)
                {
                    ArchiveIndex compArc = componentsArc.archives[j] as ArchiveIndex;
                    compArc.WriteHeader();

                    //Component Name
                    ArchiveDataSet compNameArc = compArc.archives[Const.MAP_CHUNK_ACTORCOMPONENT_NAME] as ArchiveDataSet;
                    compNameArc.WriteHeader();
                    IOUtils.writeIndex = compNameArc[0];
                    IOUtils.WriteString(actors[i].components[j].name);

                    //Component Key/Value
                    ArchiveIndex keyValuesArc = compArc.archives[Const.MAP_CHUNK_ACTORCOMPONENT_KEYVALUES] as ArchiveIndex;
                    keyValuesArc.WriteHeader();
                    int n = 0;
                    foreach (KeyValuePair<string, string> pair in actors[i].components[j].keyValues)
                    {
                        ArchiveDataSet keyArc = keyValuesArc.archives[n] as ArchiveDataSet;
                        keyArc.WriteHeader();
                        IOUtils.writeIndex = keyArc[0];
                        IOUtils.WriteString(pair.Key);
                        n++;
                        ArchiveDataSet valueArc = keyValuesArc.archives[n] as ArchiveDataSet;
                        valueArc.WriteHeader();
                        IOUtils.writeIndex = valueArc[0];
                        IOUtils.WriteString(pair.Value);
                        n++;
                    }

                }

            }

            //Write Visibility
            visArc.WriteHeader();
            for (int i = 0; i < visibility.Length; i++)
            {
                IOUtils.writeIndex = visArc[i];
                for (int j = 0; j < visibility[i].staticMeshes.Length; j++)
                {
                    IOUtils.WriteUInt16(visibility[i].staticMeshes[j]);
                }
            }

            //Write Lightmaps
            lightMapArc.WriteHeader();
            if (!lightMapArc.IsEmpty)
            {
                ArchiveIndex lightmapSectionsArc = lightMapArc.archives[Const.MAP_CHUNK_LIGHTMAP_SECTIONS] as ArchiveIndex;
                lightmapSectionsArc.WriteHeader();
                for (int i = 0; i < gridBounds.sections.Length; i++)
                {
                    ArchiveIndex sectionArc = lightmapSectionsArc.archives[i] as ArchiveIndex;
                    sectionArc.WriteHeader();

                    //Lightmap Lookups
                    ArchiveDataSet lookupsArc = sectionArc.archives[Const.MAP_CHUNK_LIGHTMAPSECTION_LOOKUPS] as ArchiveDataSet;
                    lookupsArc.WriteHeader();
                    for (int j = 0; j < gridBounds.sections[i].lightMapMeshes.Length; j++)
                    {
                        IOUtils.writeIndex = lookupsArc[j];
                        IOUtils.WriteUInt32(gridBounds.sections[i].lightMapMeshes[j].textureIndex);
                    }

                    //Lightmap Vertices
                    ArchiveIndex meshesVertsArc = sectionArc.archives[Const.MAP_CHUNK_LIGHTMAPSECTION_VERTICES] as ArchiveIndex;
                    meshesVertsArc.WriteHeader();
                    for (int j = 0; j < gridBounds.sections[i].lightMapMeshes.Length; j++)
                    {
                        ArchiveDataSet vertsArc = meshesVertsArc.archives[j] as ArchiveDataSet;
                        vertsArc.WriteHeader();
                        for (int n = 0; n < gridBounds.sections[i].lightMapMeshes[j].verts.Length; n++)
                        {
                            gridBounds.sections[i].lightMapMeshes[j].verts[n].Write(vertsArc[n]);
                        }
                    }

                    //Lightmap Triangles
                    ArchiveIndex meshesTrisArc = sectionArc.archives[Const.MAP_CHUNK_LIGHTMAPSECTION_TRIANGLES] as ArchiveIndex;
                    meshesTrisArc.WriteHeader();
                    for (int j = 0; j < gridBounds.sections[i].lightMapMeshes.Length; j++)
                    {
                        ArchiveDataSet trisArc = meshesTrisArc.archives[j] as ArchiveDataSet;
                        trisArc.WriteHeader();
                        for (int n = 0; n < gridBounds.sections[i].lightMapMeshes[j].tris.Length; n++)
                        {
                            IOUtils.writeIndex = trisArc[n];
                            IOUtils.WriteUInt16(gridBounds.sections[i].lightMapMeshes[j].tris[n]);
                        }
                    }
                }

                //Lightmap Textures
                ArchiveIndex lightmapTexturesArc = lightMapArc.archives[Const.MAP_CHUNK_LIGHTMAP_TEXTURES] as ArchiveIndex;
                lightmapTexturesArc.WriteHeader();
                for (int i = 0; i < lightMapTextures.Length; i++)
                {
                    IOUtils.writeIndex = lightmapTexturesArc[i];
                    IOUtils.WriteBytes(lightMapTextures[i].data);
                }
            }

            //Write Rejects
            rejectsArc.WriteHeader();
            for (int i = 0; i < rejects.Length; i++)
            {
                IOUtils.writeIndex = rejectsArc[i];
                for (int j = 0; j < rejects[i].regions.Length; j++)
                {
                    IOUtils.WriteUInt16(rejects[i].regions[j]);
                }
            }

            File.WriteAllBytes(path, IOUtils.writeBuffer);
        }

        public void Load(string path)
        {
            Clear();
            IOUtils.readData = File.ReadAllBytes(path);
            ArchiveIndex mapArc = ArchiveIndex.FromReadBuffer(0);

            //Read Root Version
            IOUtils.readByteIndex = mapArc[Const.MAP_CHUNK_ROOT_VERSION];
            version = IOUtils.ReadInt32(); //should be 2

            //Read Root SkyLayers
            ArchiveIndex skyLayersArc = ArchiveIndex.FromReadBuffer(mapArc[Const.MAP_CHUNK_ROOT_SKYLAYERS]);

            //Read SkyLayers Material Names
            ArchiveIndex skyLayersMatsArc = ArchiveIndex.FromReadBuffer(skyLayersArc[Const.MAP_CHUNK_SKYLAYER_MATERIALS]);
            skyInfos = new SkyInfo[skyLayersMatsArc.count];
            for (int i = 0; i < skyLayersMatsArc.count; i++)
            {
                skyInfos[i].material = ArchiveDataSet.FromReadBuffer(skyLayersMatsArc[i]).AsString();
            }

            //Read SkyLayers Info DataSets
            ArchiveDataSet skyLayersInfoArc = ArchiveDataSet.FromReadBuffer(skyLayersArc[Const.MAP_CHUNK_SKYLAYER_INFO]);
            for (int i = 0; i < skyLayersInfoArc.count; i++)
            {
                skyInfos[i].ReadFromOffset(skyLayersInfoArc[i]);
            }

            //Read Root Collision
            ArchiveIndex collisionArc = ArchiveIndex.FromReadBuffer(mapArc[Const.MAP_CHUNK_ROOT_COLLISION]);

            //Read Collision Vertices
            ArchiveDataSet collisionVertsArc = ArchiveDataSet.FromReadBuffer(collisionArc[Const.MAP_CHUNK_COLLISION_VERTICES]);
            collisionVertices = new Vector4[collisionVertsArc.count];
            for (int i = 0; i < collisionVertsArc.count; i++)
            {
                IOUtils.readByteIndex = collisionVertsArc[i];
                collisionVertices[i] = IOUtils.ReadVector4();
            }

            //Read Collision RegionSets
            ArchiveDataSet collisionRegionSetsArc = ArchiveDataSet.FromReadBuffer(collisionArc[Const.MAP_CHUNK_COLLISION_REGIONSETS]);
            collisionRegionSets = new RegionSet[collisionRegionSetsArc.count];
            for (int i = 0; i < collisionRegionSetsArc.count; i++)
            {
                collisionRegionSets[i].ReadFromOffset(collisionRegionSetsArc[i]);
            }

            //Read Collision Regions
            ArchiveDataSet collisionRegionsArc = ArchiveDataSet.FromReadBuffer(collisionArc[Const.MAP_CHUNK_COLLISION_REGIONS]);
            collisionRegions = new Region[collisionRegionsArc.count];
            for (int i = 0; i < collisionRegionsArc.count; i++)
            {
                collisionRegions[i].ReadFromOffset(collisionRegionsArc[i]);
            }

            //Read Root Grid Bounds and Grid Sections
            gridBounds.ReadFromGridBounds(ArchiveIndex.FromReadBuffer(mapArc[Const.MAP_CHUNK_ROOT_GRIDBOUNDS]));
            gridBounds.ReadFromGridSections(ArchiveIndex.FromReadBuffer(mapArc[Const.MAP_CHUNK_ROOT_GRIDSECTIONS]));

            //Read Root Actors
            ArchiveIndex actorsArc = ArchiveIndex.FromReadBuffer(mapArc[Const.MAP_CHUNK_ROOT_ACTORS]);
            actors = new ActorInfo[actorsArc.count];
            for (int i = 0; i < actorsArc.count; i++)
            {
                actors[i] = new ActorInfo();
                actors[i].Read(ArchiveIndex.FromReadBuffer(actorsArc[i]));
            }

            //Read Root Visibility
            ArchiveDataSet visArc = ArchiveDataSet.FromReadBuffer(mapArc[Const.MAP_CHUNK_ROOT_VISIBILITY]);
            visibility = new Visibility[visArc.count];
            for (int i = 0; i < visArc.count; i++)
            {
                IOUtils.readByteIndex = visArc[i];
                int nUShorts = visArc.size / 2; //div 2 bytes for ushort
                visibility[i] = new Visibility();
                visibility[i].staticMeshes = new ushort[nUShorts];
                for (int j = 0; j < nUShorts; j++)
                {
                    visibility[i].staticMeshes[j] = IOUtils.ReadUInt16();
                }
            }

            //Read Root LightMaps
            ArchiveIndex lightMapArc = ArchiveIndex.FromReadBuffer(mapArc[Const.MAP_CHUNK_ROOT_LIGHTMAPS]); //should be 2 (sections and textures) or 0
            if (!lightMapArc.IsEmpty)
            {
                ArchiveIndex lightMapSectionsArc = ArchiveIndex.FromReadBuffer(lightMapArc[Const.MAP_CHUNK_LIGHTMAP_SECTIONS]); 
                for (int i = 0; i < gridBounds.sections.Length; i++)
                {
                    gridBounds.sections[i].ReadFromLightMapSections(ArchiveIndex.FromReadBuffer(lightMapSectionsArc[i]));
                }
                ArchiveIndex lightMapTexturesArc = ArchiveIndex.FromReadBuffer(lightMapArc[Const.MAP_CHUNK_LIGHTMAP_TEXTURES]); //list of textures
                lightMapTextures = new LightMapTexture[lightMapTexturesArc.count];
                for (int i = 0; i < lightMapTexturesArc.count; i++)
                {
                    lightMapTextures[i] = new LightMapTexture();
                    IOUtils.readByteIndex = lightMapTexturesArc[i];
                    IOUtils.ReadBytes(lightMapTexturesArc.sizes[i], out lightMapTextures[i].data);
                }
            }

            //Read Root Header Rejects
            ArchiveDataSet rejectsArc = ArchiveDataSet.FromReadBuffer(mapArc[Const.MAP_CHUNK_ROOT_REJECTS]);
            rejects = new Rejects[rejectsArc.count];
            for (int i = 0; i < rejectsArc.count; i++)
            {
                IOUtils.readByteIndex = rejectsArc[i];
                int nUShorts = rejectsArc.size / 2; //div 2 bytes for ushort
                rejects[i] = new Rejects();
                rejects[i].regions = new ushort[nUShorts];
                for (int j = 0; j < nUShorts; j++)
                {
                    rejects[i].regions[j] = IOUtils.ReadUInt16();
                }
            }

        }
    }

    [Serializable]
    public class ActorComponent
    {
        public string name;
        public StringStringDictionary keyValues;
        public ActorComponent()
        {
            keyValues = new StringStringDictionary();
        }

        public void Read(ArchiveIndex componentArc)
        {
            //componentArc should have 2 blocks: Class Name, and key/value pairs

            //Read Component Name
            name = ArchiveDataSet.FromReadBuffer(componentArc[Const.MAP_CHUNK_ACTORCOMPONENT_NAME]).AsString();

            //Read Each Key/Value
            keyValues.Clear();
            ArchiveIndex keyValuesArc = ArchiveIndex.FromReadBuffer(componentArc[Const.MAP_CHUNK_ACTORCOMPONENT_KEYVALUES]);
            for (int n = 0; n < keyValuesArc.count; n += 2)
            {
                string key = ArchiveDataSet.FromReadBuffer(keyValuesArc[n]).AsString();
                string value = ArchiveDataSet.FromReadBuffer(keyValuesArc[n + 1]).AsString();
                keyValues.Add(key, value);
            }
        }
    }

    [Serializable]
    public class ActorInfo
    {
        public int actorID; //or type
        public Vector3 origin;
        public Vector3 scale;
        public float yaw;
        public float pitch;
        public float roll;
        public int TID; //or tag ID
        [EnumFlags]
        public ActorFlags flags;
        [HideInInspector]
        public byte unknown1;
        [HideInInspector]
        public byte unknown2;
        public ActorComponent[] components;

        [Flags]
        public enum ActorFlags : uint
        {
            DIFFICULTY_EASY = 1 << 0,
            DIFFICULTY_NORMAL = 1 << 1,
            DIFFICULTY_HARD = 1 << 2,
            DIFFICULTY_HARDCORE = 1 << 3,
            CASTSHADOW = 1 << 4,
            HIDDEN = 1 << 5,
            FULLSHADOWRESOLUTION = 1 << 6,
            HIDEINCINEMATICS = 1 << 7,
            IMPORTANT = 1 << 8,
            BIT9 = 1 << 9,
            BIT10 = 1 << 10,
            BIT11 = 1 << 11,
            BIT12 = 1 << 12,
            BIT13 = 1 << 13,
            BIT14 = 1 << 14,
            BIT15 = 1 << 15,
            BIT16 = 1 << 16,
            BIT17 = 1 << 17,
            BIT18 = 1 << 18,
            BIT19 = 1 << 19,
            BIT20 = 1 << 10,
            BIT21 = 1 << 21,
            BIT22 = 1 << 22,
            BIT23 = 1 << 23,
            BIT24 = 1 << 24,
            BIT25 = 1 << 25,
            BIT26 = 1 << 26,
            BIT27 = 1 << 27,
            BIT28 = 1 << 28,
            BIT29 = 1 << 29,
            BIT30 = 1 << 30,
            BIT31 = 1u << 31,
        };

        public ActorInfo()
        {
            scale = Vector3.one;
            TID = -1;
            flags = ActorFlags.DIFFICULTY_EASY | ActorFlags.DIFFICULTY_NORMAL | ActorFlags.DIFFICULTY_HARD | ActorFlags.DIFFICULTY_HARDCORE;
            components = new ActorComponent[0];
        }

        public static ActorInfo CreateFrom(ActorInfo other)
        {
            ActorInfo actor = new ActorInfo();
            actor.actorID = other.actorID;
            actor.origin = other.origin;
            actor.scale = other.scale;
            actor.yaw = other.yaw;
            actor.pitch = other.pitch;
            actor.roll = other.roll;
            actor.TID = other.TID;
            actor.flags = other.flags;
            actor.unknown1 = other.unknown1;
            actor.unknown2 = other.unknown2;
            actor.components = new ActorComponent[other.components.Length];
            for (int i = 0; i < other.components.Length; i++)
            {
                actor.components[i] = new ActorComponent();
                actor.components[i].name = other.components[i].name;
                foreach (KeyValuePair<string, string> pair in other.components[i].keyValues)
                {
                    actor.components[i].keyValues.Add(pair.Key, pair.Value);
                }
            }

            return actor;
        }

        public void Read(ArchiveIndex actorArc)
        {
            //actorArc should have 2 blocks: Info, and Components

            //Read Actor Info
            ArchiveDataSet infoArc = ArchiveDataSet.FromReadBuffer(actorArc[Const.MAP_CHUNK_ACTOR_INFO]); //should have 1 count
            IOUtils.readByteIndex = infoArc[0];
            actorID = IOUtils.ReadInt32();
            origin = IOUtils.ReadVector3();
            scale = IOUtils.ReadVector3();
            yaw = IOUtils.ReadFloat();
            pitch = IOUtils.ReadFloat();
            roll = IOUtils.ReadFloat();
            TID = IOUtils.ReadInt32();
            flags = (ActorFlags)IOUtils.ReadUInt32();
            //flags = (ActorFlags)IOUtils.ReadUInt16();
            //unknown1 = IOUtils.ReadByte();
            //unknown2 = IOUtils.ReadByte();

            //Read Actor Components
            ArchiveIndex componentsArc = ArchiveIndex.FromReadBuffer(actorArc[Const.MAP_CHUNK_ACTOR_COMPONENTS]);
            components = new ActorComponent[componentsArc.count];
            for (int i = 0; i < componentsArc.count; i++)
            {
                components[i] = new ActorComponent();
                components[i].Read(ArchiveIndex.FromReadBuffer(componentsArc[i]));
            }
        }

        public void Write(int offset)
        {
            IOUtils.writeIndex = offset;
            IOUtils.WriteInt32(actorID);
            IOUtils.WriteVector3(origin);
            IOUtils.WriteVector3(scale);
            IOUtils.WriteFloat(yaw);
            IOUtils.WriteFloat(pitch);
            IOUtils.WriteFloat(roll);
            IOUtils.WriteInt32(TID);
            IOUtils.WriteUInt32((uint)flags);

            //IOUtils.WriteUInt16((ushort)flags);
            //IOUtils.WriteByte(unknown1);
            //IOUtils.WriteByte(unknown2);
        }
    }

    [Serializable]
    public class GridSection
    {
        public Vector2 boundsMin;
        public Vector2 boundsMax;
        public StaticMesh[] staticMeshes;
        public LightMapMesh[] lightMapMeshes;

        public GridSection()
        {
            staticMeshes = new StaticMesh[0];
            lightMapMeshes = new LightMapMesh[0];
        }

        public void ReadFromGridSection(ArchiveIndex sectionArc)
        {
            //sectionArc should have 2 blocks (meshes and model file paths) or 0
            if (sectionArc.count > 0)
            {
                //Read Grid Sections Meshes
                ArchiveDataSet meshesArc = ArchiveDataSet.FromReadBuffer(sectionArc[Const.MAP_CHUNK_GRIDSECTIONS_MESHES]);
                staticMeshes = new StaticMesh[meshesArc.count];
                for (int i = 0; i < meshesArc.count; i++)
                {
                    staticMeshes[i].ReadFromOffset(meshesArc[i]);
                }

                //Read Grid Sections Model Files
                ArchiveIndex modelFilesArc = ArchiveIndex.FromReadBuffer(sectionArc[Const.MAP_CHUNK_GRIDSECTIONS_MODELFILES]); //should be same amount as meshesArc count
                for (int i = 0; i < modelFilesArc.count; i++)
                {
                    staticMeshes[i].modelPath = ArchiveDataSet.FromReadBuffer(modelFilesArc[i]).AsString();
                }
            }
        }

        public void ReadFromLightMapSections(ArchiveIndex lightMapSectionArc)
        {
            //lightMapSectionArc should have 3 blocks: lookups, verts, tris

            ArchiveDataSet lookupsArc = ArchiveDataSet.FromReadBuffer(lightMapSectionArc[Const.MAP_CHUNK_LIGHTMAPSECTION_LOOKUPS]); //looksup to what textureIndex each model uses
            lightMapMeshes = new LightMapMesh[lookupsArc.count];
            if (lookupsArc.IsEmpty) //if there are no meshes in this section then don't read the textureIDs, verts and tris
                return;

            //Set each lightmap mesh textureIndex
            for (int i = 0; i < lookupsArc.count; i++)
            {
                IOUtils.readByteIndex = lookupsArc[i];
                lightMapMeshes[i] = new LightMapMesh();
                lightMapMeshes[i].textureIndex = IOUtils.ReadUInt32();
            }

            //Set each lightmap mesh vertices
            ArchiveIndex vertsMeshesArc = ArchiveIndex.FromReadBuffer(lightMapSectionArc[Const.MAP_CHUNK_LIGHTMAPSECTION_VERTICES]); //list of meshes for verts
            for (int i = 0; i < vertsMeshesArc.count; i++)
            {
                ArchiveDataSet vertsArc = ArchiveDataSet.FromReadBuffer(vertsMeshesArc[i]);
                lightMapMeshes[i].verts = new LightMapMeshVert[vertsArc.count];
                for (int j = 0; j < vertsArc.count; j++)
                {
                    lightMapMeshes[i].verts[j].ReadFromOffset(vertsArc[j]);
                }
            }

            //Set each lightmap mesh tris
            ArchiveIndex trisMeshesArc = ArchiveIndex.FromReadBuffer(lightMapSectionArc[Const.MAP_CHUNK_LIGHTMAPSECTION_TRIANGLES]); //list of meshes for tris
            for (int i = 0; i < trisMeshesArc.count; i++)
            {
                ArchiveDataSet trisArc = ArchiveDataSet.FromReadBuffer(trisMeshesArc[i]);
                lightMapMeshes[i].tris = new ushort[trisArc.count];
                for (int j = 0; j < trisArc.count; j++)
                {
                    IOUtils.readByteIndex = trisArc[j];
                    lightMapMeshes[i].tris[j] = IOUtils.ReadUInt16();
                }
            }
        }
    }

    [Serializable]
    public class LightMapMesh
    {
        public uint textureIndex;
        public LightMapMeshVert[] verts;
        public ushort[] tris;

        public LightMapMesh()
        {
            verts = new LightMapMeshVert[0];
            tris = new ushort[0];
        }
    }

    [Serializable]
    public struct LightMapMeshVert
    {
        public Vector3 pos;
        public Vector2 uv;

        public void ReadFromOffset(int offset)
        {
            IOUtils.readByteIndex = offset;
            pos = IOUtils.ReadVector3();
            uv = IOUtils.ReadVector2();
        }

        public void Write(int offset)
        {
            IOUtils.writeIndex = offset;
            IOUtils.WriteVector3(pos);
            IOUtils.WriteVector2(uv);
        }
    }

    [Serializable]
    public class GridBounds
    {
        public short width;
        public short height;
        public GridSection[] sections;

        public GridBounds()
        {
            sections = new GridSection[0];
        }

        public void ReadFromGridBounds(ArchiveIndex gridBoundsArc)
        {
            //gridBoundsArc should have 3 blocks: blocksize, min, max

            //Read Grid Bounds Block Size (should be 2, width/height)
            ArchiveDataSet blockSizeArc = ArchiveDataSet.FromReadBuffer(gridBoundsArc[Const.MAP_CHUNK_GRIDBOUNDS_BLOCKSIZE]);
            IOUtils.readByteIndex = blockSizeArc[0];
            width = IOUtils.ReadInt16();
            IOUtils.readByteIndex = blockSizeArc[1];
            height = IOUtils.ReadInt16();

            sections = new GridSection[width * height];

            //Read Grid Bounds Sections Bounds Min
            ArchiveDataSet minsArc = ArchiveDataSet.FromReadBuffer(gridBoundsArc[Const.MAP_CHUNK_GRIDBOUNDS_MIN]); //should be same number as width*height
            for (int i = 0; i < minsArc.count; i++)
            {
                sections[i] = new GridSection();
                IOUtils.readByteIndex = minsArc[i];
                float x = IOUtils.ReadFloat();
                float y = IOUtils.ReadFloat();
                sections[i].boundsMin.Set(x, y);
            }

            //Read Grid Bounds Sections Bounds Max
            ArchiveDataSet maxsArc = ArchiveDataSet.FromReadBuffer(gridBoundsArc[Const.MAP_CHUNK_GRIDBOUNDS_MAX]); //should be same number as width*height
            for (int i = 0; i < maxsArc.count; i++)
            {
                IOUtils.readByteIndex = maxsArc[i];
                float x = IOUtils.ReadFloat();
                float y = IOUtils.ReadFloat();
                sections[i].boundsMax.Set(x, y);
            }
        }

        public void ReadFromGridSections(ArchiveIndex gridSectionsArc)
        {
            //gridSectionsArc should be have same number of blocks as as width*height
            for (int i = 0; i < gridSectionsArc.count; i++)
            {
                sections[i].ReadFromGridSection(ArchiveIndex.FromReadBuffer(gridSectionsArc[i]));
            }
        }
    }

    [Serializable]
    public struct SkyInfo
    {
        public string material;
        public Color32 whiteColor;
        public Color32 blackColor;
        public float speedScalar; //speed of sky
        public float tile; //uv tiling
        public float animSpeed;
        public float heightOffset; //height of sky from player
        public float fadeThroughDist;
        public float cameraTrack;
        public float fadeInTime;
        public float fadeOutTime;
        public byte opacity; //opacity of sky
        [HideInInspector]
        public byte flags; //unused
        [HideInInspector]
        public byte unknown1;
        [HideInInspector]
        public byte unknown2;

        public void SetToDefault()
        {
            material = "_default";
            whiteColor = new Color32(255, 255, 255, 255);
            blackColor = new Color32(0, 0, 0, 255);
            speedScalar = 1.0f;
            tile = 0.5f;
            animSpeed = 1.0f;
            heightOffset = 1536.0f;
            fadeThroughDist = 61.44f;
            cameraTrack = 0.5f;
            fadeInTime = 0.06666667f;
            fadeOutTime = 0.06666667f;
            opacity = 155;
            flags = 0;
            unknown1 = 0;
            unknown2 = 0;
        }

        //Doesn't read the array of names
        public void ReadFromOffset(int offset)
        {
            IOUtils.readByteIndex = offset;
            whiteColor = IOUtils.ReadColor();
            blackColor = IOUtils.ReadColor();
            speedScalar = IOUtils.ReadFloat();
            tile = IOUtils.ReadFloat();
            animSpeed = IOUtils.ReadFloat();
            heightOffset = IOUtils.ReadFloat();
            fadeThroughDist = IOUtils.ReadFloat();
            cameraTrack = IOUtils.ReadFloat();
            fadeInTime = IOUtils.ReadFloat();
            fadeOutTime = IOUtils.ReadFloat();
            opacity = IOUtils.ReadByte();
            flags = IOUtils.ReadByte();
            unknown1 = IOUtils.ReadByte();
            unknown2 = IOUtils.ReadByte();
        }

        public void Write(int offset)
        {
            IOUtils.writeIndex = offset;
            IOUtils.WriteColor(whiteColor);
            IOUtils.WriteColor(blackColor);
            IOUtils.WriteFloat(speedScalar);
            IOUtils.WriteFloat(tile);
            IOUtils.WriteFloat(animSpeed);
            IOUtils.WriteFloat(heightOffset);
            IOUtils.WriteFloat(fadeThroughDist);
            IOUtils.WriteFloat(cameraTrack);
            IOUtils.WriteFloat(fadeInTime);
            IOUtils.WriteFloat(fadeOutTime);
            IOUtils.WriteByte(opacity);
            IOUtils.WriteByte(flags);
            IOUtils.WriteByte(unknown1);
            IOUtils.WriteByte(unknown2);
        }
    }

    [Serializable]
    //links is the index to the other region that shares an edge with the current region
    //if its -1 then nothing is linked and the edge is a solid wall
    public struct Region
    {
        public short regionSetIndex;
        [EnumFlags]
        public RegionFlags flags;
        public short indice1;
        public short indice2;
        public short indice3;
        public short edgeLink1;
        public short edgeLink2;
        public short edgeLink3;
        public ushort drawOrder;

        [Flags]
        public enum RegionFlags : ushort
        {
            WATER = 1 << 0,
            BLOCK = 1 << 1,
            OPENED = 1 << 2,
            CLIFF = 1 << 3,
            CLIMB = 1 << 4,
            ONESIDED = 1 << 5,
            CEILING = 1 << 6,
            CRAWL = 1 << 7,
            ENTERCRAWL = 1 << 8,
            HIDDEN = 1 << 9,
            ENTERED = 1 << 10,
            LEAPOFFAITH = 1 << 11,
            RESTRICTED = 1 << 12,
            SLOPETEST = 1 << 13,
            DEATHPIT = 1 << 14,
            MAPPED = 1 << 15
        }

        public void ReadFromOffset(int offset)
        {
            IOUtils.readByteIndex = offset;
            regionSetIndex = IOUtils.ReadInt16();
            flags = (RegionFlags)IOUtils.ReadUInt16();
            indice1 = IOUtils.ReadInt16();
            indice2 = IOUtils.ReadInt16();
            indice3 = IOUtils.ReadInt16();
            edgeLink1 = IOUtils.ReadInt16();
            edgeLink2 = IOUtils.ReadInt16();
            edgeLink3 = IOUtils.ReadInt16();
            drawOrder = IOUtils.ReadUInt16();
        }

        public void Write(int offset)
        {
            IOUtils.writeIndex = offset;
            IOUtils.WriteInt16(regionSetIndex);
            IOUtils.WriteUInt16((ushort)flags);
            IOUtils.WriteInt16(indice1);
            IOUtils.WriteInt16(indice2);
            IOUtils.WriteInt16(indice3);
            IOUtils.WriteInt16(edgeLink1);
            IOUtils.WriteInt16(edgeLink2);
            IOUtils.WriteInt16(edgeLink3);
            IOUtils.WriteUInt16(drawOrder);
        }
    }

    [Serializable]
    public struct RegionSet
    {
        [EnumFlags]
        public RegionSetFlags1 flags;
        [EnumFlags]
        public RegionSetFlags2 flags2;
        [EnumFlags]
        public RegionSetFlags3 flags3;
        public Color32 fogColor;
        public Color32 waterColor;
        public float fogZFar;
        public float waterZFar;
        public float fogStart;
        public float waterStart;
        public float waterHeight;
        public float blendLength;
        public Vector3 jumpPadLandPosition;
        public float jumpPadHeight;
        public float cameraZOffset;
        public float cameraMovementScaler;
        public short arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8;
        public ushort skyLayers;
        public byte floorImpactID;
        public byte wallImpactID;
        public byte ambience;
        public byte mapColor; //Automap Color
        public byte music;
        public byte jumpPadTime;
        public byte currentStrength;
        public sbyte currentDirectionX;
        public sbyte currentDirectionY;
        public sbyte currentDirectionZ;
        public short tagID;

        [Flags]
        public enum RegionSetFlags1 : uint
        {
            WATER = 1 << 0,
            BLOCK = 1 << 1,
            OPENED = 1 << 2,
            CLIFF = 1 << 3,
            CLIMB = 1 << 4,
            ONESIDED = 1 << 5,
            CEILING = 1 << 6,
            CRAWL = 1 << 7,
            ENTERCRAWL = 1 << 8,
            HIDDEN = 1 << 9,
            ENTERED = 1 << 10,
            LEAPOFFAITH = 1 << 11,
            RESTRICTED = 1 << 12,
            SLOPETEST = 1 << 13,
            DEATHPIT = 1 << 14,
            MAPPED = 1 << 15,
            TREMOR = 1 << 16,
            WHISPERS = 1 << 17,  // NOTE: UNUSED!!!
            TELEPORT = 1 << 18,
            DAMAGE = 1 << 19,
            DRAWSKY = 1 << 20,
            TELEPORTAIR = 1 << 21,
            LAVA = 1 << 22,
            WHISPERSCOLLECTED = 1 << 23,  // NOTE: UNUSED!!!
            ANTIGRAVITY = 1 << 24,
            LADDER = 1 << 25,
            CHECKPOINT = 1 << 26,  // NOTE: UNUSED!!!
            SAVEGAME = 1 << 27,  // NOTE: UNUSED!!!
            WARPRETURN = 1 << 28,  // NOTE: UNUSED!!!
            SHALLOWWATER = 1 << 29,
            DRAWSUN = 1 << 30,
            STOREWARPRETURN = 1u << 31  // NOTE: UNUSED!!!
        };

        [Flags]
        public enum RegionSetFlags2 : ushort
        {
            POISONWATER = 1 << 0,  // NOTE: UNUSED!!!
            RIVEROFSOULS = 1 << 1,
            CAVE = 1 << 2,  // NOTE: UNUSED!!!
            CAVERN = 1 << 3,  // NOTE: UNUSED!!!
            FALLING = 1 << 4,  // NOTE: UNUSED!!!
            SPIKES = 1 << 5,  // NOTE: UNUSED!!!
            QUICKSAND = 1 << 6,
            SWAMP = 1 << 7,
            CURRENT = 1 << 8,
            CURRENT_SWIRL_CW = 1 << 9,
            CURRENT_SWIRL_CCW = 1 << 10,
            CAMERAAPPLYINAIR = 1 << 11,
            JUMPPAD = 1 << 12,
            ORIENTCAMERA = 1 << 13,
            ALLOW_PLAYER_CTRL = 1 << 14,
            STAYINCURRENT = 1 << 15
        }

        [Flags]
        public enum RegionSetFlags3 : ushort
        {
            QUICKWARP = 1 << 0,
            PLAYERBLOCK = 1 << 1,
            NODAMAGEAI = 1 << 2,
            DISMOUNTAREA = 1 << 3,
            JUMPPADWHISPERS = 1 << 4,

            // Remaster only
            SECRET = 1 << 5,
            LOCALTELEPORT = 1 << 6,
            MISSIONTELEPORT = 1 << 7,
            ONEWAYBRIDGE = 1 << 8
        }

        public void ReadFromOffset(int offset)
        {
            IOUtils.readByteIndex = offset;
            flags = (RegionSetFlags1)IOUtils.ReadUInt32();
            flags2 = (RegionSetFlags2)IOUtils.ReadUInt16();
            flags3 = (RegionSetFlags3)IOUtils.ReadUInt16();
            fogColor = IOUtils.ReadColor();
            waterColor = IOUtils.ReadColor();
            fogZFar = IOUtils.ReadFloat();
            waterZFar = IOUtils.ReadFloat();
            fogStart = IOUtils.ReadFloat();
            waterStart = IOUtils.ReadFloat();
            waterHeight = IOUtils.ReadFloat();
            blendLength = IOUtils.ReadFloat();
            jumpPadLandPosition = IOUtils.ReadVector3();
            jumpPadHeight = IOUtils.ReadFloat();
            cameraZOffset = IOUtils.ReadFloat();
            cameraMovementScaler = IOUtils.ReadFloat();
            arg1 = IOUtils.ReadInt16();
            arg2 = IOUtils.ReadInt16();
            arg3 = IOUtils.ReadInt16();
            arg4 = IOUtils.ReadInt16();
            arg5 = IOUtils.ReadInt16();
            arg6 = IOUtils.ReadInt16();
            arg7 = IOUtils.ReadInt16();
            arg8 = IOUtils.ReadInt16();
            skyLayers = IOUtils.ReadUInt16();
            floorImpactID = IOUtils.ReadByte();
            wallImpactID = IOUtils.ReadByte();
            ambience = IOUtils.ReadByte();
            mapColor = IOUtils.ReadByte();
            music = IOUtils.ReadByte();
            jumpPadTime = IOUtils.ReadByte();
            currentStrength = IOUtils.ReadByte();
            currentDirectionX = IOUtils.ReadSByte();
            currentDirectionY = IOUtils.ReadSByte();
            currentDirectionZ = IOUtils.ReadSByte();
            tagID = IOUtils.ReadInt16();
        }

        public void Write(int offset)
        {
            IOUtils.writeIndex = offset;
            IOUtils.WriteUInt32((uint)flags);
            IOUtils.WriteUInt16((ushort)flags2);
            IOUtils.WriteUInt16((ushort)flags3);
            IOUtils.WriteColor(fogColor);
            IOUtils.WriteColor(waterColor);
            IOUtils.WriteFloat(fogZFar);
            IOUtils.WriteFloat(waterZFar);
            IOUtils.WriteFloat(fogStart);
            IOUtils.WriteFloat(waterStart);
            IOUtils.WriteFloat(waterHeight);
            IOUtils.WriteFloat(blendLength);
            IOUtils.WriteVector3(jumpPadLandPosition);
            IOUtils.WriteFloat(jumpPadHeight);
            IOUtils.WriteFloat(cameraZOffset);
            IOUtils.WriteFloat(cameraMovementScaler);
            IOUtils.WriteInt16(arg1);
            IOUtils.WriteInt16(arg2);
            IOUtils.WriteInt16(arg3);
            IOUtils.WriteInt16(arg4);
            IOUtils.WriteInt16(arg5);
            IOUtils.WriteInt16(arg6);
            IOUtils.WriteInt16(arg7);
            IOUtils.WriteInt16(arg8);
            IOUtils.WriteUInt16(skyLayers);
            IOUtils.WriteByte(floorImpactID);
            IOUtils.WriteByte(wallImpactID);
            IOUtils.WriteByte(ambience);
            IOUtils.WriteByte(mapColor);
            IOUtils.WriteByte(music);
            IOUtils.WriteByte(jumpPadTime);
            IOUtils.WriteByte(currentStrength);
            IOUtils.WriteSByte(currentDirectionX);
            IOUtils.WriteSByte(currentDirectionY);
            IOUtils.WriteSByte(currentDirectionZ);
            IOUtils.WriteInt16(tagID);
        }
    }

    [Serializable]
    public struct StaticMesh
    {
        public string modelPath;
        public Vector3 origin;
        public Vector3 scale;
        public float yaw; //in rads
        public float pitch;
        public float roll;
        public Vector3 boundsMin;
        public Vector3 boundsMax;
        public int texIndex;
        [EnumFlags]
        public StaticMeshFlags flags;
        public int regionIndex;
        public float collisionWidth;
        public float collisionHeight;
        public float collisionHeightOffset;
        public float sunLightShadowClipPlane;

        [Flags]
        public enum StaticMeshFlags : int
        {
            NODRAWCAMERA = 1 << 0,
            SOLID = 1 << 1,
            NOPOLYCOLLISION = 1 << 2,
            CASTSUNSHADOW = 1 << 3,
            NOLIGHTMAP = 1 << 4,
            HIGHRESLIGHTMAP1 = 1 << 5,
            HIGHRESLIGHTMAP2 = 1 << 6
        }

        //Doesn't set model path
        public void ReadFromOffset(int offset)
        {
            IOUtils.readByteIndex = offset;
            origin = IOUtils.ReadVector3();
            scale = IOUtils.ReadVector3();
            yaw = IOUtils.ReadFloat();
            pitch = IOUtils.ReadFloat();
            roll = IOUtils.ReadFloat();
            boundsMin = IOUtils.ReadVector3();
            boundsMax = IOUtils.ReadVector3();
            texIndex = IOUtils.ReadInt32();
            flags = (StaticMeshFlags)IOUtils.ReadInt32();
            regionIndex = IOUtils.ReadInt32();
            collisionWidth = IOUtils.ReadFloat();
            collisionHeight = IOUtils.ReadFloat();
            collisionHeightOffset = IOUtils.ReadFloat();
            sunLightShadowClipPlane = IOUtils.ReadFloat();
        }

        //Doesn't write the model path
        public void Write(int offset)
        {
            IOUtils.writeIndex = offset;
            IOUtils.WriteVector3(origin);
            IOUtils.WriteVector3(scale);
            IOUtils.WriteFloat(yaw);
            IOUtils.WriteFloat(pitch);
            IOUtils.WriteFloat(roll);
            IOUtils.WriteVector3(boundsMin);
            IOUtils.WriteVector3(boundsMax);
            IOUtils.WriteInt32(texIndex);
            IOUtils.WriteInt32((int)flags);
            IOUtils.WriteInt32(regionIndex);
            IOUtils.WriteFloat(collisionWidth);
            IOUtils.WriteFloat(collisionHeight);
            IOUtils.WriteFloat(collisionHeightOffset);
            IOUtils.WriteFloat(sunLightShadowClipPlane);
        }
    }

    [Serializable]
    public class Visibility
    {
        public ushort[] staticMeshes; //lookups for staticmeshes

        public Visibility()
        {
            staticMeshes = new ushort[0]; //bit ON = visible
        }
    }

    [Serializable]
    public class Rejects
    {
        public ushort[] regions; //lookups for regions 0 = can't see the region, 1 = can see region

        public Rejects()
        {
            regions = new ushort[0];
        }
    }

    [Serializable]
    public class LightMapTexture
    {
        public byte[] data; //compressed zlib 2048x2048 (RGBA) image

        public LightMapTexture()
        {
            data = new byte[0];
        }

        //Takes the absolute file path (example: Application.dataPath + "/lightmap0.png") and saves a png file there. Returns true if there was an error.
        public bool CreatePNG(string path)
        {
            byte[] uncompressedImage;
            bool error = Zlib.Utils.Inflate(data, out uncompressedImage);
            if (error)
                return true;
            Texture2D tex = new Texture2D(2048, 2048, TextureFormat.RGBA32, false, true);
            tex.LoadRawTextureData(uncompressedImage);
            tex.Apply();
            byte[] pngBytes = tex.EncodeToPNG();
            UnityEngine.Object.DestroyImmediate(tex);
            File.WriteAllBytes(path, pngBytes);
            return false;
        }

        //Sets the byte data from a Unity Texture2D. Returns true if there was an error.
        public bool FromTexture(Texture2D tex)
        {
            return Zlib.Utils.Deflate(tex.GetRawTextureData(), out data);
        }
    }

    [Serializable]
    public struct MapGridInfo
    {
        public Vector2Int blockSize;
        public Vector2 min;
        public Vector2 max;
        public Vector2 size;
        public Vector2 unit;

        //cWorldBounds is based on the total sum of all bounding boxes of every static mesh in the level
        public void BuildGridInfo(Bounds cWorldBounds, float fGridSize)
        {
            Assert.IsTrue(fGridSize >= 1.0f);

            min[0] = cWorldBounds.min.x;
            min[1] = cWorldBounds.min.y;
            max[0] = cWorldBounds.max.x;
            max[1] = cWorldBounds.max.y;

            size[0] = (cWorldBounds.max.x - cWorldBounds.min.x);
            size[1] = (cWorldBounds.max.y - cWorldBounds.min.y);

            blockSize[0] = (int)(size[0] / fGridSize);
            blockSize[1] = (int)(size[1] / fGridSize);

            unit[0] = Mathf.Max(Mathf.Floor((blockSize[0] / size[0]) * 2.0f), 1.0f);
            unit[1] = Mathf.Max(Mathf.Floor((blockSize[1] / size[1]) * 2.0f), 1.0f);

            Assert.IsTrue(blockSize[0] >= 0 && blockSize[1] >= 0);

            if (blockSize[0] == 0 || blockSize[1] == 0)
            {
                blockSize[0] = 1;
                blockSize[1] = 1;

                size[0] = Mathf.Infinity;
                size[1] = Mathf.Infinity;
            }
        }

        public int GetGridIndexFromOrigin(Vector3 vOrigin)
        {
            int nGrids = blockSize[0] * blockSize[1];

            for(int y = 0; y < blockSize[1]; ++y)
            {
                for(int x = 0; x < blockSize[0]; ++x)
                {
                    Vector2Int org = new Vector2Int();
                    Vector2Int pos = new Vector2Int();
                    int idx;

                    for(int i = 0; i < 2; ++i)
                    {
                        org[i] = (int)vOrigin[i] - ((int)min[i] -
                            (int)((float)blockSize[i] / unit[i]));

                        pos[i] = org[i] / (int)(size[i] / (float)blockSize[i]);
                    }

                    Mathf.Clamp(pos[0], 0, blockSize[0]-1);
                    Mathf.Clamp(pos[1], 0, blockSize[1]-1);

                    if(x != pos[0] || y != pos[1])
                    {
                        continue;
                    }

                    idx = (pos[1] * blockSize[0]) + pos[0];

                    if(idx >= nGrids)
                    {
                        idx = nGrids-1;
                    }

                    return idx;
                }
            }

            return -1;
        }
    }
}
