using Barotrauma.Extensions; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace Barotrauma { partial class AbandonedOutpostMission : Mission { private readonly XElement characterConfig; protected readonly List characters = new List(); private readonly Dictionary> characterItems = new Dictionary>(); protected readonly HashSet requireKill = new HashSet(); protected readonly HashSet requireRescue = new HashSet(); private readonly Identifier itemTag; private readonly XElement itemConfig; private readonly List items = new List(); protected const int HostagesKilledState = 5; private readonly LocalizedString hostagesKilledMessage; private const float EndDelay = 5.0f; private float endTimer; private bool allowOrderingRescuees; public override bool AllowRespawn => false; public override bool AllowUndocking { get { if (GameMain.GameSession.GameMode is CampaignMode) { return true; } return state > 0; } } public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels { get { if (State == 0) { return Targets.Select(t => (Prefab.SonarLabel, t.WorldPosition)); } else { return Enumerable.Empty<(LocalizedString Label, Vector2 Position)>(); } } } private IEnumerable Targets { get { if (State > 0) { return Enumerable.Empty(); } else { if (items.Any()) { return items.Where(it => !it.Removed && it.Condition > 0.0f).Cast().Concat(requireKill.Where(c => !c.Removed && !c.IsDead)).Concat(requireRescue); } else { return requireKill.Concat(requireRescue); } } } } protected bool wasDocked; public AbandonedOutpostMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub) { characterConfig = prefab.ConfigElement.GetChildElement("Characters"); allowOrderingRescuees = prefab.ConfigElement.GetAttributeBool(nameof(allowOrderingRescuees), true); string msgTag = prefab.ConfigElement.GetAttributeString("hostageskilledmessage", ""); hostagesKilledMessage = TextManager.Get(msgTag).Fallback(msgTag); itemConfig = prefab.ConfigElement.GetChildElement("Items"); itemTag = prefab.ConfigElement.GetAttributeIdentifier("targetitem", Identifier.Empty); } protected override void StartMissionSpecific(Level level) { failed = false; endTimer = 0.0f; characters.Clear(); characterItems.Clear(); requireKill.Clear(); requireRescue.Clear(); items.Clear(); #if SERVER spawnedItems.Clear(); #endif var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub; InitItems(submarine); if (!IsClient) { InitCharacters(submarine); } wasDocked = Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost); } private void InitItems(Submarine submarine) { if (!itemTag.IsEmpty) { var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag)); if (!itemsToDestroy.Any()) { DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".", contentPackage: Prefab.ContentPackage); } else { items.AddRange(itemsToDestroy); } } if (itemConfig != null && !IsClient) { foreach (XElement element in itemConfig.Elements()) { Identifier itemIdentifier = element.GetAttributeIdentifier("identifier", Identifier.Empty); if (MapEntityPrefab.FindByIdentifier(itemIdentifier) is not ItemPrefab itemPrefab) { DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found", contentPackage: Prefab.ContentPackage); continue; } Identifier[] moduleFlags = element.GetAttributeIdentifierArray("moduleflags", null); Identifier[] spawnPointTags = element.GetAttributeIdentifierArray("spawnpointtags", null); ISpatialEntity spawnPoint = SpawnAction.GetSpawnPos( SpawnAction.SpawnLocationType.Outpost, SpawnType.Human | SpawnType.Enemy, moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false)); spawnPoint ??= submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced(); Vector2 spawnPos = spawnPoint.WorldPosition; if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100) { spawnPos = new Vector2( MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 201), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50), wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f); } var item = new Item(itemPrefab, spawnPos, null); items.Add(item); #if SERVER spawnedItems.Add(item); #endif } } } private void InitCharacters(Submarine submarine) { characters.Clear(); characterItems.Clear(); if (characterConfig != null) { foreach (XElement element in characterConfig.Elements()) { if (GameMain.NetworkMember == null && element.GetAttributeBool("multiplayeronly", false)) { continue; } int defaultCount = element.GetAttributeInt("count", -1); if (defaultCount < 0) { defaultCount = element.GetAttributeInt("amount", 1); } int min = Math.Min(element.GetAttributeInt("min", defaultCount), 255); int max = Math.Min(Math.Max(min, element.GetAttributeInt("max", defaultCount)), 255); int count = Rand.Range(min, max + 1); if (element.Attribute("identifier") != null && element.Attribute("from") != null) { HumanPrefab humanPrefab = GetHumanPrefabFromElement(element); if (humanPrefab == null) { DebugConsole.ThrowError($"Couldn't spawn a human character for abandoned outpost mission: human prefab \"{element.GetAttributeString("identifier", string.Empty)}\" not found", contentPackage: Prefab.ContentPackage); continue; } for (int i = 0; i < count; i++) { LoadHuman(humanPrefab, element, submarine); } } else { Identifier speciesName = element.GetAttributeIdentifier("character", element.GetAttributeIdentifier("identifier", Identifier.Empty)); var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName); if (characterPrefab == null) { DebugConsole.ThrowError($"Couldn't spawn a character for abandoned outpost mission: character prefab \"{speciesName}\" not found", contentPackage: Prefab.ContentPackage); continue; } for (int i = 0; i < count; i++) { LoadMonster(characterPrefab, element, submarine); } } } } } private void LoadHuman(HumanPrefab humanPrefab, XElement element, Submarine submarine) { Identifier[] moduleFlags = element.GetAttributeIdentifierArray("moduleflags", null); Identifier[] spawnPointTags = element.GetAttributeIdentifierArray("spawnpointtags", null); var spawnPointType = element.GetAttributeEnum("spawnpointtype", SpawnType.Human); ISpatialEntity spawnPos = SpawnAction.GetSpawnPos( SpawnAction.SpawnLocationType.Outpost, spawnPointType, moduleFlags ?? humanPrefab.GetModuleFlags(), spawnPointTags ?? humanPrefab.GetSpawnPointTags(), element.GetAttributeBool("asfaraspossible", false)); spawnPos ??= submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced(); bool requiresRescue = element.GetAttributeBool("requirerescue", false); var teamId = element.GetAttributeEnum("teamid", requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None); Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, teamId, spawnPos); if (Level.Loaded?.StartOutpost?.Info is { } outPostInfo) { outPostInfo.AddOutpostNPCIdentifierOrTag(spawnedCharacter, humanPrefab.Identifier); foreach (Identifier tag in humanPrefab.GetTags()) { outPostInfo.AddOutpostNPCIdentifierOrTag(spawnedCharacter, tag); } } if (spawnPos is WayPoint wp) { spawnedCharacter.GiveIdCardTags(wp); } if (requiresRescue) { requireRescue.Add(spawnedCharacter); #if CLIENT if (allowOrderingRescuees) { GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter); } #endif } else if (TimesAttempted > 0 && spawnedCharacter.AIController is HumanAIController humanAi) { var order = OrderPrefab.Prefabs["fightintruders"] .CreateInstance(OrderPrefab.OrderTargetType.Entity, orderGiver: spawnedCharacter) .WithManualPriority(CharacterInfo.HighestManualOrderPriority); spawnedCharacter.SetOrder(order, isNewOrder: true, speak: false); } if (element.GetAttributeBool("requirekill", false)) { requireKill.Add(spawnedCharacter); } } private void LoadMonster(CharacterPrefab monsterPrefab, XElement element, Submarine submarine) { Identifier[] moduleFlags = element.GetAttributeIdentifierArray("moduleflags", null); Identifier[] spawnPointTags = element.GetAttributeIdentifierArray("spawnpointtags", null); ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost, SpawnType.Enemy, moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false)); spawnPos ??= submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced(); Character spawnedCharacter = Character.Create(monsterPrefab.Identifier, spawnPos.WorldPosition, ToolBox.RandomSeed(8), createNetworkEvent: false); characters.Add(spawnedCharacter); if (element.GetAttributeBool("requirekill", false)) { requireKill.Add(spawnedCharacter); } if (spawnedCharacter.Inventory != null) { characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true)); } if (submarine != null && spawnedCharacter.AIController is EnemyAIController enemyAi) { enemyAi.UnattackableSubmarines.Add(submarine); enemyAi.UnattackableSubmarines.Add(Submarine.MainSub); foreach (Submarine sub in Submarine.MainSub.DockedTo) { enemyAi.UnattackableSubmarines.Add(sub); } } } protected override void UpdateMissionSpecific(float deltaTime) { if (State != HostagesKilledState) { if (requireRescue.Any(r => r.Removed || r.IsDead)) { State = HostagesKilledState; return; } } else { endTimer += deltaTime; if (endTimer > EndDelay) { #if SERVER if (!(GameMain.GameSession.GameMode is CampaignMode) && GameMain.Server != null) { GameMain.Server.EndGame(); } #endif } } switch (state) { case 0: if (items.All(it => it.Removed || it.Condition <= 0.0f) && requireKill.All(c => c.Removed || c.IsDead || (c.LockHands && c.Submarine == Submarine.MainSub)) && requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player)) { State = 1; } break; #if SERVER case 1: if (!(GameMain.GameSession.GameMode is CampaignMode) && GameMain.Server != null) { if (!Submarine.MainSub.AtStartExit || (wasDocked && !Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost))) { GameMain.Server.EndGame(); State = 2; } } break; #endif } } protected override bool DetermineCompleted() { return State > 0 && State != HostagesKilledState; } protected override void EndMissionSpecific(bool completed) { failed = !completed && requireRescue.Any(r => r.Removed || r.IsDead); } } }