using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Xml.Linq; using Microsoft.Xna.Framework; namespace Barotrauma { [Flags] public enum MissionType { None = 0x0, Salvage = 0x1, Monster = 0x2, Cargo = 0x4, Beacon = 0x8, Nest = 0x10, Mineral = 0x20, Combat = 0x40, AbandonedOutpost = 0x80, Escort = 0x100, Pirate = 0x200, GoTo = 0x400, ScanAlienRuins = 0x800, ClearAlienRuins = 0x1000, All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo | ScanAlienRuins | ClearAlienRuins } partial class MissionPrefab { public static readonly List List = new List(); public static readonly Dictionary CoOpMissionClasses = new Dictionary() { { MissionType.Salvage, typeof(SalvageMission) }, { MissionType.Monster, typeof(MonsterMission) }, { MissionType.Cargo, typeof(CargoMission) }, { MissionType.Beacon, typeof(BeaconMission) }, { MissionType.Nest, typeof(NestMission) }, { MissionType.Mineral, typeof(MineralMission) }, { MissionType.AbandonedOutpost, typeof(AbandonedOutpostMission) }, { MissionType.Escort, typeof(EscortMission) }, { MissionType.Pirate, typeof(PirateMission) }, { MissionType.GoTo, typeof(GoToMission) }, { MissionType.ScanAlienRuins, typeof(ScanMission) }, { MissionType.ClearAlienRuins, typeof(AlienRuinMission) } }; public static readonly Dictionary PvPMissionClasses = new Dictionary() { { MissionType.Combat, typeof(CombatMission) } }; public static readonly HashSet HiddenMissionClasses = new HashSet() { MissionType.GoTo }; private readonly ConstructorInfo constructor; public readonly MissionType Type; public readonly bool MultiplayerOnly, SingleplayerOnly; public readonly string Identifier; public readonly string TextIdentifier; private readonly string[] tags; public IEnumerable Tags { get { return tags; } } public readonly string Name; public readonly string Description; public readonly string SuccessMessage; public readonly string FailureMessage; public readonly string SonarLabel; public readonly string SonarIconIdentifier; public readonly string AchievementIdentifier; public readonly Dictionary ReputationRewards = new Dictionary(); public readonly List> DataRewards = new List>(); public readonly int Commonness; public readonly int? Difficulty; public const int MinDifficulty = 1, MaxDifficulty = 4; public readonly int Reward; public readonly List Headers; public readonly List Messages; public readonly bool AllowRetry; public readonly bool IsSideObjective; public readonly bool RequireWreck; /// /// The mission can only be received when travelling from Pair.First to Pair.Second /// public readonly List> AllowedConnectionTypes; /// /// The mission can only be received in these location types /// public readonly List AllowedLocationTypes = new List(); /// /// Show entities belonging to these sub categories when the mission starts /// public readonly List UnhideEntitySubCategories = new List(); public class TriggerEvent { [Serialize("", true)] public string EventIdentifier { get; private set; } [Serialize(0, true)] public int State { get; private set; } [Serialize(0.0f, true)] public float Delay { get; private set; } [Serialize(false, true)] public bool CampaignOnly { get; private set; } public TriggerEvent(XElement element) { SerializableProperty.DeserializeProperties(this, element); } } public readonly List TriggerEvents = new List(); public LocationTypeChange LocationTypeChangeOnCompleted; public readonly XElement ConfigElement; public static void Init() { List.Clear(); var files = GameMain.Instance.GetFilesOfType(ContentType.Missions); foreach (ContentFile file in files) { XDocument doc = XMLExtensions.TryLoadXml(file.Path); if (doc == null) { continue; } bool allowOverride = false; var mainElement = doc.Root; if (mainElement.IsOverride()) { allowOverride = true; mainElement = mainElement.FirstElement(); } foreach (XElement sourceElement in mainElement.Elements()) { var element = sourceElement.IsOverride() ? sourceElement.FirstElement() : sourceElement; var identifier = element.GetAttributeString("identifier", string.Empty); var duplicate = List.Find(m => m.Identifier == identifier); if (duplicate != null) { if (allowOverride || sourceElement.IsOverride()) { DebugConsole.NewMessage($"Overriding a mission with the identifier '{identifier}' using the file '{file.Path}'", Color.Yellow); List.Remove(duplicate); } else { DebugConsole.ThrowError($"Duplicate mission found with the identifier '{identifier}' in file '{file.Path}'! Add tags as the parent of the mission definition to allow overriding."); // TODO: Don't allow adding duplicates when the issue with multiple missions is solved. //continue; } } List.Add(new MissionPrefab(element)); } } } public MissionPrefab(XElement element) { ConfigElement = element; Identifier = element.GetAttributeString("identifier", ""); TextIdentifier = element.GetAttributeString("textidentifier", null) ?? Identifier; tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true); Name = TextManager.Get("MissionName." + TextIdentifier, true) ?? element.GetAttributeString("name", ""); Description = TextManager.Get("MissionDescription." + TextIdentifier, true) ?? element.GetAttributeString("description", ""); Reward = element.GetAttributeInt("reward", 1); AllowRetry = element.GetAttributeBool("allowretry", false); IsSideObjective = element.GetAttributeBool("sideobjective", false); RequireWreck = element.GetAttributeBool("requirewreck", false); Commonness = element.GetAttributeInt("commonness", 1); if (element.GetAttribute("difficulty") != null) { int difficulty = element.GetAttributeInt("difficulty", MinDifficulty); Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty); } SuccessMessage = TextManager.Get("MissionSuccess." + TextIdentifier, true) ?? element.GetAttributeString("successmessage", "Mission completed successfully"); FailureMessage = TextManager.Get("MissionFailure." + TextIdentifier, true) ?? ""; if (string.IsNullOrEmpty(FailureMessage) && TextManager.ContainsTag("missionfailed")) { FailureMessage = TextManager.Get("missionfailed", returnNull: true) ?? ""; } if (string.IsNullOrEmpty(FailureMessage) && GameMain.Config.Language == "English") { FailureMessage = element.GetAttributeString("failuremessage", ""); } SonarLabel = TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ?? TextManager.Get("MissionSonarLabel." + element.GetAttributeString("sonarlabel", ""), true) ?? element.GetAttributeString("sonarlabel", ""); SonarIconIdentifier = element.GetAttributeString("sonaricon", ""); MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false); SingleplayerOnly = element.GetAttributeBool("singleplayeronly", false); AchievementIdentifier = element.GetAttributeString("achievementidentifier", ""); UnhideEntitySubCategories = element.GetAttributeStringArray("unhideentitysubcategories", new string[0]).ToList(); Headers = new List(); Messages = new List(); AllowedConnectionTypes = new List>(); for (int i = 0; i < 100; i++) { string header = TextManager.Get("MissionHeader" + i + "." + TextIdentifier, true); string message = TextManager.Get("MissionMessage" + i + "." + TextIdentifier, true); if (!string.IsNullOrEmpty(message)) { Headers.Add(header); Messages.Add(message); } } int messageIndex = 0; foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "message": if (messageIndex > Headers.Count - 1) { Headers.Add(string.Empty); Messages.Add(string.Empty); } Headers[messageIndex] = TextManager.Get("MissionHeader" + messageIndex + "." + TextIdentifier, true) ?? subElement.GetAttributeString("header", ""); Messages[messageIndex] = TextManager.Get("MissionMessage" + messageIndex + "." + TextIdentifier, true) ?? subElement.GetAttributeString("text", ""); messageIndex++; break; case "locationtype": case "connectiontype": if (subElement.Attribute("identifier") != null) { AllowedLocationTypes.Add(subElement.GetAttributeString("identifier", "")); } else { AllowedConnectionTypes.Add(new Pair( subElement.GetAttributeString("from", ""), subElement.GetAttributeString("to", ""))); } break; case "locationtypechange": LocationTypeChangeOnCompleted = new LocationTypeChange(subElement.GetAttributeString("from", ""), subElement, requireChangeMessages: false, defaultProbability: 1.0f); break; case "reputation": case "reputationreward": string factionIdentifier = subElement.GetAttributeString("identifier", ""); float amount = subElement.GetAttributeFloat("amount", 0.0f); if (ReputationRewards.ContainsKey(factionIdentifier)) { DebugConsole.ThrowError($"Error in mission prefab \"{Identifier}\". Multiple reputation changes defined for the identifier \"{factionIdentifier}\"."); continue; } ReputationRewards.Add(factionIdentifier, amount); if (!factionIdentifier.Equals("location", StringComparison.OrdinalIgnoreCase)) { if (FactionPrefab.Prefabs != null && !FactionPrefab.Prefabs.Any(p => p.Identifier.Equals(factionIdentifier, StringComparison.OrdinalIgnoreCase))) { DebugConsole.ThrowError($"Error in mission prefab \"{Identifier}\". Could not find a faction with the identifier \"{factionIdentifier}\"."); } } break; case "metadata": string identifier = subElement.GetAttributeString("identifier", string.Empty); string stringValue = subElement.GetAttributeString("value", string.Empty); if (!string.IsNullOrWhiteSpace(stringValue) && !string.IsNullOrWhiteSpace(identifier)) { object value = SetDataAction.ConvertXMLValue(stringValue); SetDataAction.OperationType operation = SetDataAction.OperationType.Set; string operatingString = subElement.GetAttributeString("operation", string.Empty); if (!string.IsNullOrWhiteSpace(operatingString)) { operation = (SetDataAction.OperationType) Enum.Parse(typeof(SetDataAction.OperationType), operatingString); } DataRewards.Add(Tuple.Create(identifier, value, operation)); } break; case "triggerevent": TriggerEvents.Add(new TriggerEvent(subElement)); break; } } string missionTypeName = element.GetAttributeString("type", ""); //backwards compatibility if (missionTypeName.Equals("outpostdestroy", StringComparison.OrdinalIgnoreCase) || missionTypeName.Equals("outpostrescue", StringComparison.OrdinalIgnoreCase)) { missionTypeName = "AbandonedOutpost"; } if (!Enum.TryParse(missionTypeName, out Type)) { DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - \"" + missionTypeName + "\" is not a valid mission type."); return; } if (Type == MissionType.None) { DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - mission type cannot be none."); return; } if (CoOpMissionClasses.ContainsKey(Type)) { constructor = CoOpMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]), typeof(Submarine) }); } else if (PvPMissionClasses.ContainsKey(Type)) { constructor = PvPMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]), typeof(Submarine) }); } else { DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - unsupported mission type \"" + Type.ToString() + "\""); } if (constructor == null) { DebugConsole.ThrowError($"Failed to find a constructor for the mission type \"{Type}\"!"); } InitProjSpecific(element); } partial void InitProjSpecific(XElement element); public bool IsAllowed(Location from, Location to) { if (from == to) { return AllowedLocationTypes.Any(lt => lt.Equals("any", StringComparison.OrdinalIgnoreCase)) || AllowedLocationTypes.Any(lt => lt.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase)); } foreach (Pair allowedConnectionType in AllowedConnectionTypes) { if (allowedConnectionType.First.Equals("any", StringComparison.OrdinalIgnoreCase) || allowedConnectionType.First.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase)) { if (allowedConnectionType.Second.Equals("any", StringComparison.OrdinalIgnoreCase) || allowedConnectionType.Second.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase)) { return true; } } } if (Type == MissionType.Beacon) { var connection = from.Connections.Find(c => c.Locations.Contains(from) && c.Locations.Contains(to)); if (connection?.LevelData == null || !connection.LevelData.HasBeaconStation || connection.LevelData.IsBeaconActive) { return false; } } else if (Type == MissionType.ScanAlienRuins || Type == MissionType.ClearAlienRuins) { var connection = from.Connections.Find(c => c.Locations.Contains(from) && c.Locations.Contains(to)); if (connection?.LevelData == null || connection.LevelData.GenerationParams.RuinCount < 1) { return false; } } return false; } public Mission Instantiate(Location[] locations, Submarine sub) { return constructor?.Invoke(new object[] { this, locations, sub }) as Mission; } } }