protected virtual void SetMapPlayers(ScnFile file, List <string> players, MapPlayers mapPlayers)
        {
            int i = 0;

            foreach (var scnSection in file.Entries)
            {
                if (scnSection.Name != "SetStartLocation")
                {
                    continue;
                }

                int x = Convert.ToInt32(scnSection.Values[0]);
                int y = Convert.ToInt32(scnSection.Values[1]);
                if (x != 0 && y != 0)
                {
                    var multi = new PlayerReference
                    {
                        Name     = "Multi" + i,
                        Team     = i + 2,
                        Playable = true,
                        Faction  = "Random"
                    };

                    mapPlayers.Players.Add(multi.Name, multi);
                    i++;
                }
            }

            numMultiStarts = i;
        }
        protected void Run(Utility utility, string[] args)
        {
            // HACK: The engine code assumes that Game.modData is set.
            Game.ModData = ModData = utility.ModData;

            var filename = args[1];

            using (var stream = File.OpenRead(filename))
            {
                var headerString = stream.ReadASCII(4);
                var headerVers   = stream.ReadInt32();

                if (headerString != "MAP_")
                {
                    throw new ArgumentException("Map file did not start with MAP_");
                }

                if (headerVers < 0x300)
                {
                    throw new ArgumentException("Map version was too low.");
                }

                var width  = stream.ReadInt32();
                var height = stream.ReadInt32();
                stream.ReadInt32();                 // Tileset num???
                var tilesetName = "BARREN";

                filename = filename.ToLowerInvariant();

                var scnFilename = filename.Replace(".map", ".scn");
                using (var scn = File.OpenRead(scnFilename))
                {
                    var scnFile = new ScnFile(scn);
                    foreach (var scnSection in scnFile.Entries)
                    {
                        if (scnSection.Name != "SetDefaultTerrain")
                        {
                            continue;
                        }

                        tilesetName = scnSection.ValuesStr.ToUpperInvariant();
                    }
                }

                Map = new Map(ModData, ModData.DefaultTileSets[tilesetName], width + 2, height + 2)
                {
                    Title  = Path.GetFileNameWithoutExtension(filename),
                    Author = "Dark Reign",
                };

                Map.RequiresMod = ModData.Manifest.Id;

                SetBounds(Map, width + 2, height + 2);

                var byte1Hash           = new HashSet <byte>();
                var byte2Hash           = new HashSet <byte>();
                var byte3Hash           = new HashSet <byte>();
                var byte4Hash           = new HashSet <byte>();
                var byte5Hash           = new HashSet <byte>();
                var byte6Hash           = new HashSet <byte>();
                var unknownTileTypeHash = new HashSet <int>();

                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++)
                    {
                        var byte1 = stream.ReadUInt8();                         // Tile type 0-63, with art variations repeated 1-4
                        var byte2 = stream.ReadUInt8();                         // Which art variation to use. 0 = 1-4, 1 = 5-8
                        var byte3 = stream.ReadUInt8();                         // Base elevation, defaults to 2.
                        var byte4 = stream.ReadUInt8();                         // Unknown, defaults to 36. Seems to be elevation related.
                        var byte5 = stream.ReadUInt8();                         // Unknown, defaults to 73. Seems to be elevation related.
                        var byte6 = stream.ReadUInt8();                         // Unknown, defaults to 146. Seems to be elevation related.

                        if (!byte1Hash.Contains(byte1))
                        {
                            byte1Hash.Add(byte1);
                        }
                        if (!byte2Hash.Contains(byte2))
                        {
                            byte2Hash.Add(byte2);
                        }
                        if (!byte3Hash.Contains(byte3))
                        {
                            byte3Hash.Add(byte3);
                        }
                        if (!byte4Hash.Contains(byte4))
                        {
                            byte4Hash.Add(byte4);
                        }
                        if (!byte5Hash.Contains(byte5))
                        {
                            byte5Hash.Add(byte5);
                        }
                        if (!byte6Hash.Contains(byte6))
                        {
                            byte6Hash.Add(byte6);
                        }

                        var  subindex  = (byte)(byte1 / 64);
                        byte variation = (byte)(subindex * (byte2 + 1));
                        int  tileType  = byte1 % 64;

                        if (tileType >= 16)
                        {
                            unknownTileTypeHash.Add(tileType);
                            tileType = 1;                             // TODO: Handle edge sprites
                        }

                        Map.Tiles[new CPos(x + 1, y + 1)] = new TerrainTile((ushort)tileType, variation);                         // types[i, j], byte1
                    }
                }

                // What's after the tiles? Water/Taelon?
                stream.ReadInt32();                 // Always one
                stream.ReadInt32();                 // Always 256
                int length = stream.ReadInt32();    // Byte length of remaining data

                byte1Hash = new HashSet <byte>();
                var byteList = new List <byte>();
                for (int i = 0; i < length; i++)
                {
                    var byte1 = stream.ReadUInt8();
                    if (!byte1Hash.Contains(byte1))
                    {
                        byte1Hash.Add(byte1);
                    }

                    byteList.Add(byte1);
                }

                using (var scn = File.OpenRead(scnFilename))
                {
                    var scnFile = new ScnFile(scn);

                    MapPlayers = new MapPlayers(Map.Rules, 0);
                    SetMapPlayers(scnFile, Players, MapPlayers);

                    // Place start locations
                    int i = 0;
                    foreach (var scnSection in scnFile.Entries)
                    {
                        if (scnSection.Name != "SetStartLocation")
                        {
                            continue;
                        }

                        int divisor = 24;
                        int x       = Convert.ToInt32(scnSection.Values[0]) / divisor;
                        int y       = Convert.ToInt32(scnSection.Values[1]) / divisor;
                        if (x != 0 && y != 0)
                        {
                            var ar = new ActorReference("mpspawn")
                            {
                                new LocationInit(new CPos(x + 1, y + 1)),
                                new OwnerInit("Neutral")
                            };

                            Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + i++, ar.Save()));
                        }
                    }

                    // Parse map thingies
                    foreach (var scnSection in scnFile.Entries)
                    {
                        if (scnSection.Name != "AddThingAt")
                        {
                            continue;
                        }

                        string type = scnSection.Values[1];
                        int    x    = Convert.ToInt32(scnSection.Values[2]);
                        int    y    = Convert.ToInt32(scnSection.Values[3]);

                        var matchingActor = string.Empty;

                        if (thingNames.ContainsKey(type))
                        {
                            matchingActor = thingNames[type];
                        }
                        else if (!knownUnknownThings.Contains(type))
                        {
                            throw new Exception("Unknown thing name: " + type);
                        }

                        if (x >= 0 && y >= 0 && !string.IsNullOrEmpty(matchingActor))
                        {
                            var ar = new ActorReference(matchingActor)
                            {
                                new LocationInit(new CPos(x + 1, y + 1)),
                                new OwnerInit("Neutral")
                            };

                            Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + i++, ar.Save()));
                        }
                    }

                    int currentTeam = 0;

                    if (!skipActors)
                    {
                        // Units
                        foreach (var scnSection in scnFile.Entries)
                        {
                            if (scnSection.Name == "SetDefaultTeam")
                            {
                                currentTeam = Convert.ToInt32(scnSection.ValuesStr);
                                continue;
                            }

                            if (scnSection.Name != "PutUnitAt")
                            {
                                continue;
                            }

                            int playerIndex = GetMatchingPlayerIndex(currentTeam);                             // to skip creeps and neutral if necessary

                            string type = scnSection.Values[1];
                            int    x    = Convert.ToInt32(scnSection.Values[2]);
                            int    y    = Convert.ToInt32(scnSection.Values[3]);

                            var matchingActor = string.Empty;

                            if (unitNames.ContainsKey(type))
                            {
                                matchingActor = unitNames[type];
                            }
                            else if (!knownUnknownUnits.Contains(type))
                            {
                                throw new Exception("Unknown unit name: " + type);
                            }

                            if (x >= 0 && y >= 0 && !string.IsNullOrEmpty(matchingActor))
                            {
                                var ar = new ActorReference(matchingActor)
                                {
                                    new LocationInit(new CPos(x + 1, y + 1)),
                                    new OwnerInit(MapPlayers.Players.Values.First(p => p.Team == playerIndex).Name)
                                };

                                Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + i++, ar.Save()));
                            }
                        }

                        // Do buildings
                        foreach (var scnSection in scnFile.Entries)
                        {
                            if (scnSection.Name == "SetDefaultTeam")
                            {
                                currentTeam = Convert.ToInt32(scnSection.ValuesStr);
                                continue;
                            }

                            if (scnSection.Name != "AddBuildingAt")
                            {
                                continue;
                            }

                            int playerIndex = GetMatchingPlayerIndex(currentTeam);                             // to skip creeps and neutral if necessary

                            string type = scnSection.Values[1];
                            int    x    = Convert.ToInt32(scnSection.Values[2]);
                            int    y    = Convert.ToInt32(scnSection.Values[3]);

                            var matchingActor = string.Empty;

                            if (buildingNames.ContainsKey(type))
                            {
                                matchingActor = buildingNames[type];
                            }
                            else if (!knownUnknownBuildings.Contains(type))
                            {
                                throw new Exception("Unknown building name: " + type);
                            }

                            var isResource = type == "impww" || type == "impmn";
                            var ownerName  = isResource
                                                                ? "Neutral"
                                                                : MapPlayers.Players.Values.First(p => p.Team == playerIndex).Name;

                            if (isResource == true)
                            {
                                throw new ArgumentException("FART");
                            }

                            if (x >= 0 && y >= 0 && !string.IsNullOrEmpty(matchingActor))
                            {
                                var ar = new ActorReference(matchingActor)
                                {
                                    new LocationInit(new CPos(x + 1, y + 1)),
                                    new OwnerInit(ownerName)
                                };

                                Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + i++, ar.Save()));
                            }
                        }
                    }

                    // Do resources
                    foreach (var scnSection in scnFile.Entries)
                    {
                        if (scnSection.Name != "AddBuildingAt")
                        {
                            continue;
                        }

                        string type = scnSection.Values[1];
                        int    x    = Convert.ToInt32(scnSection.Values[2]);
                        int    y    = Convert.ToInt32(scnSection.Values[3]);

                        var isResource = type == "impww" || type == "impmn";
                        if (!isResource)
                        {
                            continue;
                        }

                        var matchingActor = string.Empty;

                        if (buildingNames.ContainsKey(type))
                        {
                            matchingActor = buildingNames[type];
                        }

                        if (x >= 0 && y >= 0 && !string.IsNullOrEmpty(matchingActor))
                        {
                            var ar = new ActorReference(matchingActor)
                            {
                                new LocationInit(new CPos(x + 1, y + 1)),
                                new OwnerInit("Neutral")
                            };

                            Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + i++, ar.Save()));
                        }
                    }
                }

                // Reset teams var
                foreach (var playersValue in MapPlayers.Players.Values)
                {
                    playersValue.Team = 0;
                }

                Map.PlayerDefinitions = MapPlayers.ToMiniYaml();
            }

            Map.FixOpenAreas();

            var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap";

            Map.Save(ZipFileLoader.Create(dest));
            Console.WriteLine(dest + " saved.");
        }
        protected override void SetMapPlayers(ScnFile file, List <string> players, MapPlayers mapPlayers)
        {
            var teamHasUnits = new Func <int, bool>(playerIndex =>
            {
                bool correctPlayer = false;
                foreach (var scnSection in file.Entries)
                {
                    if (scnSection.Name == "SetDefaultTeam")
                    {
                        int defaultTeam = Convert.ToInt32(scnSection.Values[0]);
                        correctPlayer   = defaultTeam == playerIndex;
                        continue;
                    }

                    if (correctPlayer && (scnSection.Name == "AddBuildingAt" || scnSection.Name == "PutUnitAt"))
                    {
                        return(true);
                    }
                }

                return(false);
            });

            // Single player, examine sides
            int teamIndex = 0;
            int sideIndex = 0;

            int[] allianceArray   = new[] { 0, 0, 0, 0, 0, 0, 0, 0 };
            var   nameFactionDict = new Dictionary <string, string>()
            {
                { "Freedom Guard", "fguard" },
                { "Imperium", "imperium" },
            };

            var factionColors = new[] { Color.FromArgb(234, 189, 25), Color.FromArgb(124, 60, 234) };
            var newPlayers    = new List <PlayerReference>();

            // Add players
            foreach (var scnSection in file.Entries)
            {
                if (scnSection.Name == "SetTeam")
                {
                    teamIndex = Convert.ToInt32(scnSection.Values[0]);                     // to skip creeps and normal
                    continue;
                }

                if (scnSection.Name == "SetTeamSide")
                {
                    sideIndex = Convert.ToInt32(scnSection.Values[0]);
                    if (sideIndex > 1)                     // No togran yet.
                    {
                        sideIndex = 0;
                    }

                    continue;
                }

                if (scnSection.Name == "SetAlliance" && teamHasUnits(teamIndex))
                {
                    if (teamIndex == 8)
                    {
                        continue;                         // It's just creeps
                    }
                    for (int allianceI = 0; allianceI < 8; allianceI++)
                    {
                        allianceArray[allianceI] = Convert.ToInt32(scnSection.Values[allianceI]);
                    }

                    var enemyIndices = new List <int>();
                    var allyIndices  = new List <int>();
                    for (int ei = 0; ei < 8; ei++)
                    {
                        if (allianceArray[ei] == 0)
                        {
                            enemyIndices.Add(ei);
                        }
                        else if (ei != teamIndex && allianceArray[ei] == 2)
                        {
                            allyIndices.Add(ei);
                        }
                    }

                    // Create player at this point
                    PlayerReference newPlayer = new PlayerReference
                    {
                        Team    = teamIndex,
                        Name    = sideIndex.ToString(),
                        Faction = nameFactionDict.Values.ToArray()[sideIndex],
                        Color   = factionColors[sideIndex],
                        Enemies = enemyIndices.Select(x => x.ToString()).ToArray(),
                        Allies  = allyIndices.Select(x => x.ToString()).ToArray(),
                    };

                    if (teamIndex == 0)
                    {
                        // First is always playable faction
                        newPlayer.Playable  = true;
                        newPlayer.AllowBots = false;
                        newPlayer.Required  = true;
                        newPlayer.LockSpawn = true;
                        newPlayer.LockTeam  = true;
                    }

                    newPlayers.Add(newPlayer);
                }
            }

            // Ensure names are unique
            var factionCountIndex = new Dictionary <string, int>()
            {
                { "fguard", 0 },
                { "imperium", 0 }
            };

            foreach (var newPlayer in newPlayers)
            {
                // Sort out unique names
                sideIndex = Convert.ToInt32(newPlayer.Name);
                var sideName            = nameFactionDict.Keys.ToArray()[sideIndex];
                int currentFactionCount = factionCountIndex[newPlayer.Faction];
                if (currentFactionCount > 0)
                {
                    sideName += " " + currentFactionCount;
                }

                currentFactionCount++;
                factionCountIndex[newPlayer.Faction] = currentFactionCount;
                newPlayer.Name = sideName;

                // And unique colors
                if (newPlayer.Team > 1 && newPlayer.Team != 8)
                {
                    int newColorIndex = newPlayer.Team - 2;
                    newPlayer.Color = namedColorMapping.Values.ToArray()[newColorIndex];
                }
            }

            foreach (var newPlayer in newPlayers)
            {
                // Sort out alliances
                teamIndex = newPlayer.Team;
                var allyNames  = new List <string>();
                var enemyNames = new List <string>();
                foreach (var allyOrEnemy in newPlayers)
                {
                    if (allyOrEnemy.Team == teamIndex)
                    {
                        continue;
                    }

                    if (newPlayer.Allies.Contains(allyOrEnemy.Team.ToString()))
                    {
                        allyNames.Add(allyOrEnemy.Name);
                    }
                    else if (newPlayer.Enemies.Contains(allyOrEnemy.Team.ToString()))
                    {
                        enemyNames.Add(allyOrEnemy.Name);
                    }
                }

                newPlayer.Allies  = allyNames.ToArray();
                newPlayer.Enemies = enemyNames.ToArray();
            }

            // Increase the team indices by two to skip creeps and neutral.
            foreach (var newPlayer in newPlayers)
            {
                newPlayer.Team += 2;
                mapPlayers.Players.Add(newPlayer.Name, newPlayer);
            }
        }