private static MapFile ParseMapData(BinaryReader reader) { var assetNames = AssetNameCollection.Parse(reader); var result = new MapFile(); var context = new MapParseContext(assetNames); context.PushAsset(nameof(MapFile), reader.BaseStream.Length); Asset.ParseAssets(reader, context, assetName => { switch (assetName) { case AssetList.AssetName: result.AssetList = AssetList.Parse(reader, context); break; case GlobalVersion.AssetName: result.GlobalVersion = GlobalVersion.Parse(reader, context); break; case HeightMapData.AssetName: result.HeightMapData = HeightMapData.Parse(reader, context); break; case BlendTileData.AssetName: result.BlendTileData = BlendTileData.Parse(reader, context, result.HeightMapData); break; case WorldInfo.AssetName: result.WorldInfo = WorldInfo.Parse(reader, context); break; case MPPositionList.AssetName: result.MPPositionList = MPPositionList.Parse(reader, context); break; case SidesList.AssetName: result.SidesList = SidesList.Parse(reader, context, result.AssetList != null); break; case LibraryMapLists.AssetName: result.LibraryMapLists = LibraryMapLists.Parse(reader, context); break; case Teams.AssetName: result.Teams = Teams.Parse(reader, context); break; case PlayerScriptsList.AssetName: result.PlayerScriptsList = PlayerScriptsList.Parse(reader, context); break; case BuildLists.AssetName: result.BuildLists = BuildLists.Parse(reader, context, result.AssetList != null); break; case ObjectsList.AssetName: result.ObjectsList = ObjectsList.Parse(reader, context); break; case PolygonTriggers.AssetName: result.PolygonTriggers = PolygonTriggers.Parse(reader, context); break; case TriggerAreas.AssetName: result.TriggerAreas = TriggerAreas.Parse(reader, context); break; case GlobalWaterSettings.AssetName: result.GlobalWaterSettings = GlobalWaterSettings.Parse(reader, context); break; case FogSettings.AssetName: result.FogSettings = FogSettings.Parse(reader, context); break; case MissionHotSpots.AssetName: result.MissionHotSpots = MissionHotSpots.Parse(reader, context); break; case MissionObjectives.AssetName: result.MissionObjectives = MissionObjectives.Parse(reader, context); break; case StandingWaterAreas.AssetName: result.StandingWaterAreas = StandingWaterAreas.Parse(reader, context); break; case RiverAreas.AssetName: result.RiverAreas = RiverAreas.Parse(reader, context); break; case StandingWaveAreas.AssetName: result.StandingWaveAreas = StandingWaveAreas.Parse(reader, context); break; case GlobalLighting.AssetName: result.GlobalLighting = GlobalLighting.Parse(reader, context); break; case PostEffectsChunk.AssetName: result.PostEffectsChunk = PostEffectsChunk.Parse(reader, context); break; case EnvironmentData.AssetName: result.EnvironmentData = EnvironmentData.Parse(reader, context); break; case NamedCameras.AssetName: result.NamedCameras = NamedCameras.Parse(reader, context); break; case CameraAnimationList.AssetName: result.CameraAnimationList = CameraAnimationList.Parse(reader, context); break; case CastleTemplates.AssetName: result.CastleTemplates = CastleTemplates.Parse(reader, context); break; case WaypointsList.AssetName: result.WaypointsList = WaypointsList.Parse(reader, context); break; case SkyboxSettings.AssetName: result.SkyboxSettings = SkyboxSettings.Parse(reader, context); break; default: throw new NotImplementedException(assetName); } }); context.PopAsset(); return(result); }
private static void SetupSkirmishGameSides( Game game, MapFile mapFile, PlayerSetting[] playerSettings, GameType gameType, List <Player> mapPlayers, List <Team> mapTeams, List <ScriptList> mapScriptLists) { var originalMapPlayers = mapFile.SidesList.Players; // Neutral player. mapPlayers.Add(originalMapPlayers[0]); // Civilian player. It isn't necessarily the second player. // TODO: There might be more than one civilian player. mapPlayers.Add(originalMapPlayers.FirstOrDefault(x => (string)x.Properties["playerFaction"].Value == "FactionCivilian")); //var hasAIPlayer = false; for (var i = 0; i < playerSettings.Length; i++) { var playerSetting = playerSettings[i]; var factionPlayer = originalMapPlayers.Single(x => (string)x.Properties["playerFaction"].Value == playerSetting.SideName); var isHuman = playerSetting.Owner == PlayerOwner.Player; var playerName = isHuman ? $"player{i}" : $"{factionPlayer.Properties["playerName"].Value}{i}"; //if (!isHuman) //{ // hasAIPlayer = true; //} var playerProperties = new AssetPropertyCollection(); playerProperties.AddAsciiString("playerFaction", (string)factionPlayer.Properties["playerFaction"].Value); playerProperties.AddAsciiString("playerName", playerName); playerProperties.AddAsciiString("playerDisplayName", (string)factionPlayer.Properties["playerDisplayName"].Value); playerProperties.AddBoolean("playerIsHuman", isHuman); playerProperties.AddInteger("playerColor", playerSetting.Color.ToUInt32()); // TODO: Other player properties. mapPlayers.Add(new Player { Properties = playerProperties, BuildList = factionPlayer.BuildList, }); } // Setup player relationships. var playerAllies = new string[playerSettings.Length]; var playerEnemies = new string[playerSettings.Length]; for (var i = 0; i < playerSettings.Length; i++) { playerAllies[i] = ""; playerEnemies[i] = ""; } for (var i = 0; i < playerSettings.Length; i++) { var outerPlayer = playerSettings[i]; for (var j = i + 1; j < playerSettings.Length; j++) { var innerPlayer = playerSettings[j]; if (outerPlayer.Team == innerPlayer.Team && outerPlayer.Team != 0) { playerAllies[i] += mapPlayers[j + 2].Properties["playerName"].Value + " "; playerAllies[j] += mapPlayers[i + 2].Properties["playerName"].Value + " "; } else { playerEnemies[i] += mapPlayers[j + 2].Properties["playerName"].Value + " "; playerEnemies[j] += mapPlayers[i + 2].Properties["playerName"].Value + " "; } } } for (var i = 0; i < playerSettings.Length; i++) { mapPlayers[i + 2].Properties.AddAsciiString("playerAllies", playerAllies[i]); mapPlayers[i + 2].Properties.AddAsciiString("playerEnemies", playerEnemies[i]); } var originalMapScriptLists = mapFile.SidesList.PlayerScripts.ScriptLists; var playerNames = mapPlayers .Select(p => p.Properties.GetPropOrNull("playerName")?.Value.ToString()) .ToArray(); while (mapScriptLists.Count < mapPlayers.Count) { mapScriptLists.Add(null); } // Copy neutral player scripts. var neutralPlayerName = (string)mapPlayers[0].Properties["playerName"].Value; CopyScripts( originalMapScriptLists, playerNames, neutralPlayerName, mapScriptLists, 0, appendIndex: false); // Copy civilian player scripts. var civilianPlayerName = (string)mapPlayers[1].Properties["playerName"].Value; CopyScripts( originalMapScriptLists, playerNames, civilianPlayerName, mapScriptLists, 1, appendIndex: false); var skirmishScriptsEntry = game.ContentManager.GetScriptEntry(@"Data\Scripts\SkirmishScripts.scb"); // TODO: Generals and ZH use SkirmishScripts.scb, // but later games use "libraries". if (skirmishScriptsEntry != null) { using var stream = skirmishScriptsEntry.Open(); var skirmishScripts = ScbFile.FromStream(stream); // This probably isn't right, but it does make the teams match those in .sav files. // We first add human player(s) teams, then the replay observer team, // then neutral and civilian teams, and then finally AI skirmish players. var skirmishScriptsPlayerNames = skirmishScripts.ScriptsPlayers.Players.Select(p => p.Name).ToArray(); // Skip neutral and civilian players. for (var i = 2; i < mapPlayers.Count; i++) { if ((bool)mapPlayers[i].Properties["playerIsHuman"].Value) { // Copy the scripts from the civilian player to all human players. CopyScripts( skirmishScripts.PlayerScripts.ScriptLists, skirmishScriptsPlayerNames, civilianPlayerName, mapScriptLists, i, appendIndex: true); mapTeams.Add(CreateDefaultTeam((string)mapPlayers[i].Properties["playerName"].Value)); } } mapTeams.Add(CreateDefaultTeam("ReplayObserver")); mapTeams.Add(CreateDefaultTeam(neutralPlayerName)); mapTeams.Add(CreateDefaultTeam(civilianPlayerName)); // Skip neutral and civilian players. for (var i = 2; i < mapPlayers.Count; i++) { if (!(bool)mapPlayers[i].Properties["playerIsHuman"].Value) { var playerSide = game.AssetStore.PlayerTemplates.GetByName(playerSettings[i - 2].SideName).Side; var sourcePlayerName = $"Skirmish{playerSide}"; // Copy the scripts from the according skirmish player for all AI players. CopyScripts( skirmishScripts.PlayerScripts.ScriptLists, skirmishScriptsPlayerNames, sourcePlayerName, mapScriptLists, i, appendIndex: true); // TODO: Not sure about the order the teams are added. foreach (var team in skirmishScripts.Teams.Teams) { var teamOwner = (string)team.Properties["teamOwner"].Value; if (teamOwner == sourcePlayerName) { var teamName = $"{team.Properties["teamName"].Value}{i}"; mapTeams.Add(CreateTeam( teamName, (string)mapPlayers[i].Properties["playerName"].Value, (bool)team.Properties["teamIsSingleton"].Value)); } } } } } if (playerSettings.Length > 1) { var multiplayerScriptsEntry = game.ContentManager.GetScriptEntry(@"Data\Scripts\MultiplayerScripts.scb"); if (multiplayerScriptsEntry != null) { using var stream = multiplayerScriptsEntry.Open(); var multiplayerScripts = ScbFile.FromStream(stream); // TODO: This is a bit hardcoded. mapScriptLists[0] = multiplayerScripts.PlayerScripts.ScriptLists[0]; } } }
private MapCache BuildMapCache(FileSystemEntry fileSystemEntry, FileInfo fileInfo, AssetStore assetStore) { var timestamp = fileInfo.LastWriteTime.ToFileTime(); var mapCache = new MapCache() { FileSize = (int)fileInfo.Length, TimestampLo = (int)timestamp, TimestampHi = (int)(timestamp >> 32), IsOfficial = false, NameLookupTag = null }; var mapFile = MapFile.FromFileSystemEntry(fileSystemEntry); var border = mapFile.HeightMapData.Borders[0]; mapCache.ExtentMin = new Vector3(border.Corner1X, border.Corner1Y, 0f); mapCache.ExtentMax = new Vector3(border.Corner2X, border.Corner2Y, 0f); byte startingPositionsFound = 0; foreach (var mapObject in mapFile.ObjectsList.Objects) { // ignore roads if ((mapObject.RoadType & RoadType.PrimaryType) == RoadType.None) { // handle special waypoints if (mapObject.TypeName == Waypoint.ObjectTypeName) { var waypointName = (string)mapObject.Properties["waypointName"].Value; switch (waypointName) { case "InitialCameraPosition": mapCache.InitialCameraPosition = mapObject.Position; break; case "Player_1_Start": mapCache.Player1Start = mapObject.Position; startingPositionsFound |= 1 << 0; break; case "Player_2_Start": mapCache.Player2Start = mapObject.Position; startingPositionsFound |= 1 << 1; break; case "Player_3_Start": mapCache.Player3Start = mapObject.Position; startingPositionsFound |= 1 << 2; break; case "Player_4_Start": mapCache.Player4Start = mapObject.Position; startingPositionsFound |= 1 << 3; break; case "Player_5_Start": mapCache.Player5Start = mapObject.Position; startingPositionsFound |= 1 << 4; break; case "Player_6_Start": mapCache.Player6Start = mapObject.Position; startingPositionsFound |= 1 << 5; break; case "Player_7_Start": mapCache.Player7Start = mapObject.Position; startingPositionsFound |= 1 << 6; break; case "Player_8_Start": mapCache.Player8Start = mapObject.Position; startingPositionsFound |= 1 << 7; break; } } else { // check "normal" objects var definition = assetStore.ObjectDefinitions.GetByName(mapObject.TypeName); if (definition?.KindOf?.Get(ObjectKinds.SupplySourceOnPreview) ?? false) { mapCache.SupplyPositions.Add(mapObject.Position); } if (definition?.KindOf?.Get(ObjectKinds.TechBuilding) ?? false) { mapCache.TechPositions.Add(mapObject.Position); } } } } // Only consecutive numbers are recognized, so if there are // starting positions 1, 2 and 4, the game treats it as a // 2 player map. // There is always at least one player though, even if there // are no starting positions (single player maps). mapCache.NumPlayers = 1; while ((startingPositionsFound & (1 << mapCache.NumPlayers)) > 0) { mapCache.NumPlayers++; } mapCache.IsMultiplayer = mapCache.NumPlayers > 1; // TODO // mapCache.DisplayName // mapCache.Description // mapCache.FileCrc // mapCache.IsScenarioMP // mapCache.PlayerPositions return(mapCache); }
private static void SetupSkirmishGameSides( Game game, MapFile mapFile, PlayerSetting[] playerSettings, GameType gameType, List <Player> mapPlayers, List <Team> mapTeams, List <ScriptList> mapScriptLists) { var originalMapPlayers = mapFile.SidesList.Players; // Neutral player. mapPlayers.Add(originalMapPlayers[0]); // Civilian player. It isn't necessarily the second player. // TODO: There might be more than one civilian player. mapPlayers.Add(originalMapPlayers.FirstOrDefault(x => (string)x.Properties["playerFaction"].Value == "FactionCivilian")); //var hasAIPlayer = false; for (var i = 0; i < playerSettings.Length; i++) { var playerSetting = playerSettings[i]; var factionPlayer = originalMapPlayers.First(x => (string)x.Properties["playerFaction"].Value == playerSetting.SideName); var isHuman = playerSetting.Owner == PlayerOwner.Player; var playerName = isHuman ? $"player{i}" : $"{factionPlayer.Properties["playerName"].Value}{i}"; //if (!isHuman) //{ // hasAIPlayer = true; //} var playerProperties = new AssetPropertyCollection(); playerProperties.AddAsciiString("playerFaction", (string)factionPlayer.Properties["playerFaction"].Value); playerProperties.AddAsciiString("playerName", playerName); playerProperties.AddAsciiString("playerDisplayName", (string)factionPlayer.Properties["playerDisplayName"].Value); playerProperties.AddBoolean("playerIsHuman", isHuman); playerProperties.AddInteger("playerColor", playerSetting.Color.ToUInt32()); // TODO: Other player properties. mapPlayers.Add(new Player { Properties = playerProperties, BuildList = factionPlayer.BuildList, }); } // Setup player relationships. var playerAllies = new string[playerSettings.Length]; var playerEnemies = new string[playerSettings.Length]; for (var i = 0; i < playerSettings.Length; i++) { playerAllies[i] = ""; playerEnemies[i] = ""; } for (var i = 0; i < playerSettings.Length; i++) { var outerPlayer = playerSettings[i]; for (var j = i + 1; j < playerSettings.Length; j++) { var innerPlayer = playerSettings[j]; if (outerPlayer.Team == innerPlayer.Team && outerPlayer.Team != 0) { playerAllies[i] += mapPlayers[j + 2].Properties["playerName"].Value + " "; playerAllies[j] += mapPlayers[i + 2].Properties["playerName"].Value + " "; } else { playerEnemies[i] += mapPlayers[j + 2].Properties["playerName"].Value + " "; playerEnemies[j] += mapPlayers[i + 2].Properties["playerName"].Value + " "; } } } for (var i = 0; i < playerSettings.Length; i++) { mapPlayers[i + 2].Properties.AddAsciiString("playerAllies", playerAllies[i]); mapPlayers[i + 2].Properties.AddAsciiString("playerEnemies", playerEnemies[i]); } var originalMapScriptLists = mapFile.GetPlayerScriptsList().ScriptLists; var playerNames = mapPlayers .Select(p => p.Properties.GetPropOrNull("playerName")?.Value.ToString()) .ToArray(); while (mapScriptLists.Count < mapPlayers.Count) { mapScriptLists.Add(null); } // Copy neutral player scripts. var neutralPlayerName = (string)mapPlayers[0].Properties["playerName"].Value; CopyScripts( originalMapScriptLists, playerNames, neutralPlayerName, mapScriptLists, 0, appendIndex: false); // Copy civilian player scripts. var civilianPlayerName = (string)mapPlayers[1].Properties["playerName"].Value; CopyScripts( originalMapScriptLists, playerNames, civilianPlayerName, mapScriptLists, 1, appendIndex: false); switch (game.SageGame) { case SageGame.CncGenerals: case SageGame.CncGeneralsZeroHour: CreateTeamsFromScbFile(game, mapPlayers, mapTeams, mapScriptLists, playerSettings, neutralPlayerName, civilianPlayerName); break; case SageGame.Bfme: case SageGame.Bfme2: case SageGame.Bfme2Rotwk: mapTeams.AddRange(mapFile.GetTeams()); break; } if (playerSettings.Length > 1) { var multiplayerScriptsEntry = game.ContentManager.GetScriptEntry(@"Data\Scripts\MultiplayerScripts.scb"); if (multiplayerScriptsEntry != null) { using var stream = multiplayerScriptsEntry.Open(); var multiplayerScripts = ScbFile.FromStream(stream); // TODO: This is a bit hardcoded. mapScriptLists[0] = multiplayerScripts.PlayerScripts.ScriptLists[0]; } } }