using Barotrauma.Extensions; using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using PositionType = Barotrauma.Level.PositionType; namespace Barotrauma { partial class MineralMission : Mission { private readonly Dictionary resourceAmounts = new Dictionary(); private readonly Dictionary> spawnedResources = new Dictionary>(); private readonly Dictionary relevantLevelResources = new Dictionary(); private readonly List<(Identifier Identifier, Vector2 Position)> missionClusterPositions = new List<(Identifier Identifier, Vector2 Position)>(); private readonly HashSet caves = new HashSet(); private readonly PositionType positionType = PositionType.Cave; /// /// The list order is important. /// It defines the order in which we "override" in case no valid position types are found /// in the level when generating them in . /// public static readonly ImmutableArray ValidPositionTypes = new PositionType[] { PositionType.Cave, PositionType.SidePath, PositionType.MainPath, PositionType.AbyssCave, }.ToImmutableArray(); /// /// Percentage. Value between 0 and 1. /// private readonly float resourceHandoverAmount; public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels { get { return missionClusterPositions .Where(p => spawnedResources.ContainsKey(p.Identifier) && AnyAreUncollected(spawnedResources[p.Identifier])) .Select(p => (ModifyMessage(Prefab.SonarLabel, color: false), p.Position)); } } public override LocalizedString SuccessMessage => ModifyMessage(base.SuccessMessage); public override LocalizedString FailureMessage => ModifyMessage(base.FailureMessage); public override LocalizedString Description => ModifyMessage(description); public override LocalizedString Name => ModifyMessage(base.Name, false); public MineralMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub) { var positionType = prefab.ConfigElement.GetAttributeEnum("PositionType", in this.positionType); if (ValidPositionTypes.Contains(positionType)) { this.positionType = positionType; } float handoverAmount = prefab.ConfigElement.GetAttributeFloat("ResourceHandoverAmount", 0.0f); resourceHandoverAmount = Math.Clamp(handoverAmount, 0.0f, 1.0f); var configElement = prefab.ConfigElement.GetChildElement("Items"); foreach (var c in configElement.GetChildElements("Item")) { var identifier = c.GetAttributeIdentifier("identifier", Identifier.Empty); if (identifier.IsEmpty) { continue; } if (resourceAmounts.ContainsKey(identifier)) { resourceAmounts[identifier]++; } else { resourceAmounts.Add(identifier, 1); } } } protected override void StartMissionSpecific(Level level) { if (spawnedResources.Any()) { #if DEBUG throw new Exception($"SpawnedResources.Count > 0 ({spawnedResources.Count})"); #else DebugConsole.AddWarning("Spawned resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds."); spawnedResources.Clear(); #endif } if (relevantLevelResources.Any()) { #if DEBUG throw new Exception($"RelevantLevelResources.Count > 0 ({relevantLevelResources.Count})"); #else DebugConsole.AddWarning("Relevant level resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds."); relevantLevelResources.Clear(); #endif } if (missionClusterPositions.Any()) { #if DEBUG throw new Exception($"MissionClusterPositions.Count > 0 ({missionClusterPositions.Count})"); #else DebugConsole.AddWarning("Mission cluster positions list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds."); missionClusterPositions.Clear(); #endif } caves.Clear(); if (IsClient) { return; } foreach ((Identifier identifier, int amount) in resourceAmounts) { if (MapEntityPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab) { DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")", contentPackage: Prefab.ContentPackage); continue; } var spawnedResources = level.GenerateMissionResources(prefab, amount, positionType, caves); if (spawnedResources.Count < amount) { DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{amount} of {prefab.Name}", contentPackage: Prefab.ContentPackage); } if (spawnedResources.None()) { continue; } this.spawnedResources.Add(identifier, spawnedResources); foreach (var cave in Level.Loaded.Caves) { foreach (var spawnedResource in spawnedResources) { if (cave.Area.Contains(spawnedResource.WorldPosition)) { cave.MissionsToDisplayOnSonar.Add(this); caves.Add(cave); break; } } } } CalculateMissionClusterPositions(); FindRelevantLevelResources(); } protected override void UpdateMissionSpecific(float deltaTime) { if (IsClient) { return; } switch (State) { case 0: if (!EnoughHaveBeenCollected()) { return; } State = 1; break; case 1: if (!Submarine.MainSub.AtEitherExit) { return; } State = 2; break; } } protected override bool DetermineCompleted() { return EnoughHaveBeenCollected(); } protected override void EndMissionSpecific(bool completed) { failed = !completed && state > 0; if (completed) { if (!IsClient) { // When mission is completed successfully, half of the resources will be removed from the player (i.e. given to the outpost as a part of the mission) var handoverResources = new List(); foreach (Identifier identifier in resourceAmounts.Keys) { if (relevantLevelResources.TryGetValue(identifier, out var availableResources)) { var collectedResources = availableResources.Where(HasBeenCollected); if (!collectedResources.Any()) { continue; } int handoverCount = (int)MathF.Round(resourceHandoverAmount * collectedResources.Count()); for (int i = 0; i < handoverCount; i++) { handoverResources.Add(collectedResources.ElementAt(i)); } } } foreach (var resource in handoverResources) { resource.Remove(); } } } foreach (var kvp in spawnedResources) { foreach (var i in kvp.Value) { if (i != null && !i.Removed && !HasBeenCollected(i)) { i.Remove(); } } } spawnedResources.Clear(); relevantLevelResources.Clear(); missionClusterPositions.Clear(); } private void FindRelevantLevelResources() { relevantLevelResources.Clear(); foreach (var identifier in resourceAmounts.Keys) { var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier && i.Submarine == null && i.ParentInventory == null && (i.GetComponent() is not Holdable h || (h.Attachable && h.Attached))) .ToArray(); relevantLevelResources.Add(identifier, items); } } private bool EnoughHaveBeenCollected() { foreach (var kvp in resourceAmounts) { if (relevantLevelResources.TryGetValue(kvp.Key, out var availableResources)) { var collected = availableResources.Count(HasBeenCollected); var needed = kvp.Value; if (collected < needed) { return false; } } else { return false; } } return true; } private bool HasBeenCollected(Item item) { if (item == null) { return false; } if (item.Removed) { return false; } var owner = item.GetRootInventoryOwner(); if (owner.Submarine != null && owner.Submarine.Info.Type == SubmarineType.Player) { return true; } else if (owner is Character c) { return c.Info != null && GameMain.GameSession.CrewManager.GetCharacterInfos().Contains(c.Info); } return false; } private bool AnyAreUncollected(IEnumerable items) => items.Any(i => !HasBeenCollected(i)); private void CalculateMissionClusterPositions() { missionClusterPositions.Clear(); foreach (var kvp in spawnedResources) { if (kvp.Value.None()) { continue; } var pos = Vector2.Zero; var itemCount = 0; foreach (var i in kvp.Value.Where(i => i != null && !i.Removed)) { pos += i.WorldPosition; itemCount++; } pos /= itemCount; missionClusterPositions.Add((kvp.Key, pos)); } } protected override LocalizedString ModifyMessage(LocalizedString message, bool color = true) { int i = 1; foreach ((Identifier identifier, int amount) in resourceAmounts) { Replace($"[resourcename{i}]", ItemPrefab.FindByIdentifier(identifier)?.Name.Value ?? ""); Replace($"[resourcequantity{i}]", amount.ToString()); i++; } Replace("[handoverpercentage]", ToolBox.GetFormattedPercentage(resourceHandoverAmount)); return message; void Replace(string find, string replace) { if (color) { replace = $"‖color:gui.orange‖{replace}‖end‖"; } message = message.Replace(find, replace); } } } }