public VehicleWheelMap(DocumentParser doc)
        {
            this.name = doc.ReadNextLine();
            this.wheels = new VehicleAttachmentComplicatedWheels();

            while (!doc.NextLineIsASection() && !doc.EOF())
            {
                var wm = doc.ReadStringArray(2);

                switch (wm[0])
                {
                    case "localise":
                        this.localName = wm[1];
                        break;

                    case "fl_wheel_folder_name":
                        this.wheels.FLWheel = wm[1];
                        break;

                    case "fr_wheel_folder_name":
                        this.wheels.FRWheel = wm[1];
                        break;

                    case "rl_wheel_folder_name":
                        this.wheels.RLWheel = wm[1];
                        break;

                    case "rr_wheel_folder_name":
                        this.wheels.RRWheel = wm[1];
                        break;

                    case "wheel_folder_name":
                        this.wheels.FLWheel = wm[1];
                        this.wheels.FRWheel = wm[1];
                        this.wheels.RLWheel = wm[1];
                        this.wheels.RRWheel = wm[1];
                        break;

                    default:
                        throw new NotImplementedException("Unknown WheelMap parameter: " + wm[0]);
                }
            }
        }
        public VehicleMaterialMap(DocumentParser doc)
        {
            this.name = doc.ReadNextLine();

            while (!doc.NextLineIsASection())
            {
                var mm = doc.ReadStringArray();

                switch (mm[0].ToLower())
                {
                    case "shrapnel":
                        this.shrapnel = Vector3.Parse(mm[1]);
                        break;

                    case "localise":
                        this.localName = mm[1];
                        break;

                    case "paint":
                        this.paint = mm[2];
                        break;

                    case "material_map_product_id":
                        this.appID = mm[1].ToInt();
                        break;

                    default:
                        throw new NotImplementedException("Unknown MaterialMap parameter: " + mm[0]);
                }
            }
        }
        public static VehicleSetupConfig Load(string pathToFile)
        {
            VehicleSetupConfig setup = new VehicleSetupConfig();

            using (var doc = new DocumentParser(pathToFile))
            {
                string line = doc.ReadFirstLine();

                while (line != null)
                {
                    switch (line)
                    {
                        case "[default_driver]":
                            while (!doc.NextLineIsASection() && !doc.EOF())
                            {
                                setup.Drivers.Add(doc.ReadNextLine());
                            }
                            break;

                        case "[attachment]":
                            setup.Attachments.Add(new VehicleAttachment(doc));
                            break;

                        case "[wheel_module]":
                            setup.WheelModules.Add(new VehicleWheelModule(doc));
                            break;

                        case "[suspension_factors]":
                            setup.SuspensionFactors = new VehicleSuspensionFactors(doc);
                            break;

                        case "[ai_script]":
                            setup.AIScript = doc.ReadNextLine();
                            break;

                        case "[material_map]":
                            setup.MaterialMaps.Add(new VehicleMaterialMap(doc));
                            break;

                        case "[wheel_map]":
                            setup.WheelMaps.Add(new VehicleWheelMap(doc));
                            break;

                        case "[disable_ejection]":
                            setup.EjectDriver = false;
                            break;

                        case "[stats]":
                            setup.Stats.TopSpeed = doc.ReadInt();
                            setup.Stats.Time = doc.ReadFloat();
                            setup.Stats.Weight = doc.ReadFloat();
                            setup.Stats.Toughness = doc.ReadFloat();
                            if (!doc.EOF() && !doc.NextLineIsASection()) { setup.Stats.UnlockLevel = doc.ReadFloat(); }
                            break;

                        case "[decal_points]":
                            while (!doc.NextLineIsASection() && !doc.EOF())
                            {
                                setup.DecalPoints.Add(doc.ReadVector3());
                            }
                            break;

                        default:
                            Console.WriteLine(pathToFile);
                            throw new NotImplementedException("Unexpected [SECTION]: " + line);
                    }

                    line = doc.ReadNextLine();
                }
            }

            return setup;
        }
        public VehicleWheelModule(DocumentParser doc)
        {
            string s = doc.ReadNextLine();

            switch (s)
            {
                case "SkidMarks":
                    this.wheelModuleType = WheelModuleType.SkidMarks;
                    this.skidMarkImage = doc.ReadStringArray(2)[1];
                    break;

                case "TyreParticles":
                case "TyreSmokeVFX":
                    this.wheelModuleType = WheelModuleType.TyreParticles;
                    this.tyreParticleVFX = doc.ReadStringArray(2)[1];
                    break;

                case "SkidNoise":
                    this.wheelModuleType = WheelModuleType.SkidNoise;
                    this.skidNoiseSound = doc.ReadStringArray(2)[1];
                    break;

                default:
                    throw new NotImplementedException("Unknown WheelModuleType: " + s);
            }
        }
        public VehicleAttachment(DocumentParser doc)
        {
            string s = doc.ReadNextLine();

            switch (s)
            {
                case "DynamicsWheels":
                    this.attachmentType = AttachmentType.DynamicsWheels;
                    break;

                case "ComplicatedWheels":
                    this.attachmentType = AttachmentType.ComplicatedWheels;
                    this.wheels = new VehicleAttachmentComplicatedWheels();

                    while (!doc.NextLineIsASection())
                    {
                        var cw = doc.ReadStringArray(2);

                        switch (cw[0])
                        {
                            case "fl_wheel_folder_name":
                                this.wheels.FLWheel = cw[1];
                                break;

                            case "fr_wheel_folder_name":
                                this.wheels.FRWheel = cw[1];
                                break;

                            case "rl_wheel_folder_name":
                                this.wheels.RLWheel = cw[1];
                                break;

                            case "rr_wheel_folder_name":
                                this.wheels.RRWheel = cw[1];
                                break;

                            case "wheel_folder_name":
                                this.wheels.FLWheel = cw[1];
                                this.wheels.FRWheel = cw[1];
                                this.wheels.RLWheel = cw[1];
                                this.wheels.RRWheel = cw[1];
                                break;

                            default:
                                throw new NotImplementedException("Unknown ComplicatedWheels parameter: " + cw[0]);
                        }
                    }
                    break;

                case "DynamicsFmodEngine":
                    this.attachmentType = AttachmentType.DynamicsFmodEngine;
                    this.engine = new VehicleAttachmentFModEngine();

                    while (!doc.NextLineIsASection())
                    {
                        var dfe = doc.ReadStringArray(2);

                        switch (dfe[0])
                        {
                            case "engine":
                                this.engine.Engine = dfe[1];
                                break;

                            case "rpmsmooth":
                                this.engine.RPMSmooth = Single.Parse(dfe[1], ToxicRagers.Culture);
                                break;

                            case "onloadsmooth":
                                this.engine.OnLoadSmooth = Single.Parse(dfe[1], ToxicRagers.Culture);
                                break;

                            case "offloadsmooth":
                                this.engine.OffLoadSmooth = Single.Parse(dfe[1], ToxicRagers.Culture);
                                break;

                            case "max_revs":
                                this.engine.MaxRevs = int.Parse(dfe[1]);
                                break;

                            case "min_revs":
                                this.engine.MinRevs = int.Parse(dfe[1]);
                                break;

                            default:
                                throw new NotImplementedException("Unknown DynamicsFmodEngine parameter: " + dfe[0]);
                        }
                    }
                    break;

                case "Horn":
                    this.attachmentType = AttachmentType.Horn;

                    var h = doc.ReadStringArray(2);
                    this.horn = h[1];
                    break;

                case "ExhaustParticles":
                    this.attachmentType = AttachmentType.ExhaustParticles;
                    this.exhaust = new VehicleAttachmentExhaust();

                    while (!doc.NextLineIsASection())
                    {
                        var ep = doc.ReadStringArray(2);

                        switch (ep[0])
                        {
                            case "vfx":
                                this.exhaust.VFX = ep[1];
                                break;

                            case "underwater_vfx":
                                this.exhaust.UnderwaterVFX = ep[1];
                                break;

                            case "anchor":
                                this.exhaust.Anchor = ep[1];
                                break;

                            default:
                                throw new NotImplementedException("Unknown ExhaustParticle parameter: " + ep[0]);
                        }
                    }
                    break;

                case "ReverseLightSound":
                    this.attachmentType = AttachmentType.ReverseLightSound;

                    var rl = doc.ReadStringArray(2);
                    this.reverseLightSound = rl[1];
                    break;

                case "ContinuousSound":
                    this.attachmentType = AttachmentType.ContinuousSound;

                    while (!doc.NextLineIsASection())
                    {
                        var cs = doc.ReadStringArray(2);

                        switch (cs[0])
                        {
                            case "sound":
                                this.continuousSound = cs[1];
                                break;

                            case "lump":
                                this.continuousSoundLump = cs[1];
                                break;

                            default:
                                throw new NotImplementedException("Unknown ContinuousSound parameter: " + cs[0]);
                        }
                    }
                    break;

                default:
                    throw new NotImplementedException("Unknown AttachmentType: " + s);
            }
        }
        public AccessoryShapeComponent(DocumentParser sr)
        {
            points = new List<Vector3>();

            string s = sr.ReadNextLine();
            int pointCount;

            switch (s)
            {
                case "AlignedCuboid":
                    this.componentType = ComponentType.AlignedCuboid;
                    points.Add(sr.ReadVector3());
                    points.Add(sr.ReadVector3());
                    break;

                case "Polyhedron":
                    this.componentType = ComponentType.Polyhedron;
                    pointCount = sr.ReadInt();
                    for (int i = 0; i < pointCount; i++) { points.Add(sr.ReadVector3()); }
                    break;

                case "RoundedPolyhedron":
                    this.componentType = ComponentType.RoundedPolyhedron;
                    radius = sr.ReadFloat();
                    pointCount = sr.ReadInt();
                    for (int i = 0; i < pointCount; i++) { points.Add(sr.ReadVector3()); }
                    break;

                case "Sphere":
                    points.Add(sr.ReadVector3());
                    radius = sr.ReadFloat();
                    break;

                case "TicTac":
                    points.Add(sr.ReadVector3());
                    points.Add(sr.ReadVector3());
                    radius = sr.ReadFloat();
                    break;

                default:
                    throw new NotImplementedException("Unknown ComponentType: " + s);
            }

            while (sr.ReadNextLine() == "form_collision_groups")
            {
                group = sr.ReadInt();
            }

            sr.Rewind();
        }
        public static Routes Load(string pathToFile)
        {
            Routes routes = new Routes();

            using (var doc = new DocumentParser(pathToFile))
            {
                string line = doc.ReadNextLine();

                while (line != null)
                {
                    switch (line)
                    {
                        case "[LUMP]":
                            doc.ReadNextLine();  // level
                            line = doc.ReadNextLine();
                            break;

                        case "[VERSION]":
                            doc.ReadNextLine();  // 2.500000
                            line = doc.ReadNextLine();
                            break;

                        case "[RACE_LAYERS]":
                            line = doc.SkipToNextSection();
                            break;

                        case "[LUA_SCRIPTS]":
                            line = doc.SkipToNextSection();
                            break;

                        case "[RACES]":
                            while (!doc.NextLineIsASection())
                            {
                                routes.races.Add(doc.ReadNextLine());
                            }

                            line = doc.ReadNextLine();
                            break;

                        case "[AINODE]":
                            bool bAINode = true;
                            var node = new AINode();

                            while (bAINode)
                            {
                                line = doc.ReadNextLine();

                                switch (line)
                                {
                                    case "<INDEX>":
                                        node.Index = doc.ReadInt();
                                        break;

                                    case "<TYPE>":
                                        node.Type = doc.ReadInt();
                                        break;

                                    case "<RADIUS>":
                                        node.Radius = doc.ReadFloat();
                                        break;

                                    case "<POS>":
                                        node.Position = doc.ReadVector3();
                                        break;

                                    case "<RACE_LINE>":
                                        node.RaceLine = doc.ReadVector3();
                                        break;

                                    case "<RACE_LINE_OFFSET>":
                                        node.RaceLineOffset = doc.ReadFloat();
                                        break;

                                    default:
                                        bAINode = false;
                                        routes.nodes.Add(node);
                                        if (line != null && !line.StartsWith("[")) { Console.WriteLine("Unexpected [AINODE] line: " + line); }
                                        break;
                                }
                            }
                            break;

                        case "[AILINK]":
                            bool bAILink = true;
                            var link = new AILink();

                            while (bAILink)
                            {
                                line = doc.ReadNextLine();

                                switch (line)
                                {
                                    case "<NODES>":
                                        link.NodeA = doc.ReadInt();
                                        link.NodeB = doc.ReadInt();
                                        break;

                                    case "<WIDTH>":
                                        link.Width = doc.ReadFloat();
                                        break;

                                    case "<VALUE>":
                                        link.Value = doc.ReadNextLine();
                                        break;

                                    case "<ONEWAY>":
                                        link.OneWay = true;
                                        break;

                                    case "<TYPE>":
                                        for (int i = 0; i < routes.races.Count; i++)
                                        {
                                            link.Types.Add(doc.ReadInt());
                                        }
                                        break;

                                    case "<RACE_VALUE>":
                                        link.RaceValueAmount = doc.ReadInt();
                                        link.RaceValue = doc.ReadNextLine();
                                        break;

                                    default:
                                        bAILink = false;
                                        routes.links.Add(link);
                                        if (line != null && !line.StartsWith("[")) { throw new NotImplementedException("Unexpected [AILINK] line: " + line); }
                                        break;
                                }
                            }
                            break;

                        default:
                            Console.WriteLine(pathToFile);
                            throw new NotImplementedException("Unexpected [SECTION]: " + line);
                    }
                }
            }

            return routes;
        }
        public AccessoryShape(DocumentParser sr)
        {
            name = sr.ReadNextLine();

            if (name.Replace("_", " ") != "(no shape)")
            {
                boxCount = sr.ReadInt();

                for (int i = 0; i < boxCount; i++)
                {
                    boxes.Add(new AccessoryShapeComponent(sr));
                }
            }
        }
        public AccessoryJoint(DocumentParser sr)
        {
            string line;
            if (!Accessory.TestLine("joint", sr.ReadNextLine(), out line)) { Console.WriteLine("Unexpected value: {0}", line); }

            vertexNum = sr.ReadInt();
            var f1 = sr.ReadFloat();
            var v1 = sr.ReadVector3();
            var v2 = sr.ReadVector3();
            var v3 = sr.ReadVector3();
            var v4 = sr.ReadVector3();
            var v5 = sr.ReadVector3();
            var v6 = sr.ReadVector3();
            var v7 = sr.ReadVector3();
            var v8 = sr.ReadVector3();
            var i1 = sr.ReadInt();
            for (int i = 0; i < i1; i++)
            {
                var i2 = sr.ReadInt();
                var f2 = sr.ReadFloat();
                var f3 = sr.ReadFloat();
                var v9 = sr.ReadVector3();
                var v10 = sr.ReadVector3();
                var f4 = sr.ReadFloat();
                var s1 = sr.ReadNextLine();
                var v11 = sr.ReadVector3();
                var v12 = sr.ReadVector3();
            }
            if (!Accessory.TestLine("(no_weakness)", sr.ReadNextLine(), out line))
            {
                var s3 = sr.ReadNextLine();
                var v13 = sr.ReadVector3();
                var f5 = sr.ReadFloat();
                var s4 = sr.ReadNextLine();
                var v15 = sr.ReadVector3();

                var t = sr.ReadNextLine();
                if (t.Contains(","))
                {
                    var v_1 = Vector2.Parse(t);
                }
                else
                {
                    var f6 = t.ToSingle();
                }
            }
        }
        public AccessoryDynamics(DocumentParser doc)
        {
            while (!doc.NextLineIsASection() && !doc.EOF())
            {
                string line = doc.ReadNextLine();

                switch (line)
                {
                    case "<lump_name>":
                        lump = doc.ReadNextLine();
                        break;

                    case "<mass>":
                        mass = doc.ReadFloat();
                        break;

                    case "<drivable_on>":
                        bDrivableOn = true;
                        break;

                    case "<solid>":
                        bSolid = true;
                        break;

                    case "<stop_sinking_into_ground>":
                        bStopSinkingIntoGround = true;
                        break;

                    case "<part_of_world>":
                        bPartOfWorld = true;
                        break;

                    case "<ignore_world>":
                        bIgnoreWorld = true;
                        break;

                    case "<inf_mass>":
                        bInfMass = true;
                        break;

                    case "<inf_mi>":
                        bInfMi = true;
                        break;

                    case "<buoyant>":
                        bBuouyant = true;
                        buoyancyCount = doc.ReadFloat();
                        buoyancyVector = doc.ReadVector3();
                        break;

                    case "<substance>":
                        substance = doc.ReadInt();
                        break;

                    case "<group>":
                        group = doc.ReadInt();
                        break;

                    case "<ignore_group>":
                        ignoreGroup = doc.ReadInt();
                        break;

                    case "<moments>":
                        moments = doc.ReadVector3();
                        break;

                    case "<buoyancy_relative_to_com>":
                        bBuoyancyRelativeToCOM = true;
                        break;

                    case "<centre_of_mass>":
                        centreOfMass = doc.ReadVector3();
                        break;

                    case "<sphere_rolling_resistance>":
                        sphereRollingResistance = doc.ReadFloat();
                        break;

                    case "<shape>":
                        shape = new AccessoryShape(doc);
                        break;

                    case "<breakable>":
                        breakable = new AccessoryBreak(doc);
                        break;

                    case "<world_joint>":
                        worldJoint = new AccessoryJoint(doc);
                        break;

                    case "<child_joint>":
                        childJoint = new AccessoryJoint(doc);
                        break;

                    default:
                        throw new NotImplementedException(string.Format("Unknown [DYNAMICS] setting: {0}", line));
                }
            }
        }
        public AccessoryBreak(DocumentParser sr)
        {
            bool bBreak = true;
            string[] settings;
            string line;

            if (!Accessory.TestLine("Breakable", sr.ReadNextLine(), out line)) { Console.WriteLine("Unexpected value: {0}", line); }

            while (bBreak)
            {
                line = sr.ReadNextLine();
                if (line == null) { break; }

                settings = line.Split(' ');

                switch (settings[0].ToLower())
                {
                    case "break":
                    case "break_impulse":
                        breakImpulse = settings[settings.Length - 1].ToInt();
                        break;

                    case "detach_children":
                    case "detatch_children":
                        bDetatchChildren = true;
                        break;

                    case "destroy_children":
                        bDestroyChildren = true;
                        break;

                    case "explode":
                    case "explode_force":
                        explodeForce = settings[settings.Length - 1].ToInt();
                        break;

                    case "random_rotation":
                        if (settings.Length == 2)
                        {
                            randomRotation = Vector2.Parse(settings[1]);
                        }
                        else
                        {
                            randomRotation = new Vector2(settings[1].ToSingle(), settings[2].ToSingle());
                        }
                        break;

                    case "sound":
                        sound = settings[1];
                        break;

                    case "trigger_particles":
                        bTriggerParticles = true;
                        break;

                    case "collision_with_world_will_break_me":
                        Console.WriteLine("collision_with_world_will_break_me: {0}", settings[1]);
                        break;

                    case "replace":
                        if (settings.Length == 3)
                        {
                            replacements.Add(new AccessoryBreakReplacement(settings[1], settings[2]));
                        }
                        else
                        {
                            replacements.Add(new AccessoryBreakReplacement(settings[1]));
                        }
                        break;

                    default:
                        bBreak = false;

                        if (!settings[0].StartsWith("[") && !settings[0].StartsWith("<"))
                        {
                            Console.WriteLine("Unexpected setting: {0}", settings[0]);
                        }
                        else
                        {
                            sr.Rewind();
                        }
                        break;
                }
            }
        }
        public static Accessory Load(string pathToFile)
        {
            Accessory accessory = new Accessory();

            using (var doc = new DocumentParser(pathToFile))
            {
                string line = doc.ReadNextLine();

                while (line != null)
                {
                    switch (line)
                    {
                        case "[LUMP]":
                            doc.ReadNextLine();  // level
                            line = doc.ReadNextLine();
                            break;

                        case "[VERSION]":
                            doc.ReadNextLine();  // 2.500000
                            line = doc.ReadNextLine();
                            break;

                        case "[RACE_LAYERS]":
                            line = doc.SkipToNextSection();
                            break;

                        case "[LUA_SCRIPTS]":
                            line = doc.SkipToNextSection();
                            break;

                        case "[APP_DATA]":
                            if (TestLine("<CustomAccessoryType>", doc.ReadNextLine(), out line))
                            {
                                CustomAccessoryType accessoryType;
                                line = doc.ReadNextLine();

                                if (!Enum.TryParse<CustomAccessoryType>(line, out accessoryType)) { throw new NotImplementedException("Unknown CustomAccessoryType: " + line); }
                                accessory.CustomAccessoryType = accessoryType;

                                switch (accessoryType)
                                {
                                    case CustomAccessoryType.ConveyorAccessory:
                                    case CustomAccessoryType.CopSpawn:
                                    case CustomAccessoryType.ExplodingAccessory:
                                    case CustomAccessoryType.GibletAccessoryType:
                                    case CustomAccessoryType.MultiplayerSpawn:
                                    case CustomAccessoryType.StandardAccessory:
                                    case CustomAccessoryType.StartingGrid:
                                    case CustomAccessoryType.AngularDampedAccessory:
                                    case CustomAccessoryType.Checkpoint:
                                    case CustomAccessoryType.Powerup:
                                    case CustomAccessoryType.ManagedAccessory:
                                    case CustomAccessoryType.RigidBodyAnimation:
                                    case CustomAccessoryType.RockingAccessory:
                                    case CustomAccessoryType.RotatingAccessory:
                                        while (!doc.NextLineIsASection() && !doc.EOF())
                                        {
                                            accessory.customAccessoryProperties.Add(doc.ReadNextLine().Split(' '));
                                        }
                                        break;

                                    default:
                                        Console.WriteLine("Unhandled accessory");
                                        break;
                                }

                                line = doc.ReadNextLine();

                                if (line != null && line.ToLower().StartsWith("accessoryaudio"))
                                {
                                    accessory.CustomAccessoryAudio = line.Replace("accessoryaudio ", "", StringComparison.OrdinalIgnoreCase);
                                    line = doc.ReadNextLine();
                                }
                            }
                            break;

                        case "[ACOLYTE]":
                        case "[ACOYLTE]":
                            bool bAcolyte = true;

                            while (bAcolyte)
                            {
                                line = doc.ReadNextLine();

                                switch (line)
                                {
                                    case "<powerup>":
                                        accessory.Powerup = true;
                                        break;

                                    case "<hidden>":
                                        accessory.Hidden = true;
                                        break;

                                    default:
                                        bAcolyte = false;
                                        if (line != null && !line.StartsWith("[")) { Console.WriteLine("Unexpected [ACOLYTE] line: " + line); }
                                        break;
                                }
                            }
                            break;

                        case "[ANIMATION]":
                            accessory.AnimationType = doc.ReadNextLine();
                            accessory.AnimationFile = doc.ReadNextLine();
                            accessory.AnimationSpeed = doc.ReadFloat();
                            accessory.AnimationPhase = doc.ReadFloat();
                            line = doc.ReadNextLine();
                            break;

                        case "[DYNAMICS]":
                            accessory.dynamics.Add(new AccessoryDynamics(doc));
                            line = doc.ReadNextLine();
                            break;

                        default:
                            Console.WriteLine(pathToFile);
                            throw new NotImplementedException("Unexpected [SECTION]: " + line);
                    }
                }
            }

            return accessory;
        }