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 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 VehicleSuspensionFactors(DocumentParser doc)
        {
            while (!doc.NextLineIsASection())
            {
                var sf = doc.ReadStringArray(2);

                switch (sf[0])
                {
                    case "max_compression":
                        this.maxCompression = sf[1].ToSingle();
                        break;

                    case "ride_height":
                        this.rideHeight = sf[1].ToSingle();
                        break;

                    case "max_steering_lock":
                        this.maxSteeringLock = sf[1].ToInt();
                        break;

                    case "max_extension":
                        this.maxExtension = sf[1].ToSingle();
                        break;

                    default:
                        throw new NotImplementedException("Unknown SuspensionFactor parameter: " + sf[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 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 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 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 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;
        }