using Barotrauma; using Barotrauma.Items.Components; using Barotrauma.MoreLevelContent.Shared.Utils; using Barotrauma.Networking; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using MoreLevelContent.Shared; using MoreLevelContent.Shared.Data; using MoreLevelContent.Shared.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace MoreLevelContent.Missions { // Shared partial class MissionNPCCollection { internal readonly List characters = new(); internal readonly Dictionary> characterItems = new(); internal bool AllHumansAlive => characters.All(c => IsAlive(c)); internal bool AnyHumanAlive => characters.Any(c => IsAlive(c)); internal bool AnyHumanSurvived => characters.Any(c => Survived(c)); internal int AliveHumans => characters.Where(c => IsAlive(c)).Count(); internal delegate void OnCharacterCreated(Character character, XElement missionCharacterConfig); internal Character this[int index] => characters[index]; internal MissionNPCCollection(Mission mission, XElement characterConfig) { this.mission = mission; this.characterConfig = characterConfig; } private readonly Mission mission; private readonly XElement characterConfig; public void Clear() { characters.Clear(); characterItems.Clear(); } public void End(bool completed) { foreach (Character character in characters) { if (character.Inventory == null) { continue; } foreach (Item item in character.Inventory.AllItemsMod) { //item didn't spawn with the characters -> drop it if (!characterItems.Any(c => c.Value.Contains(item))) { item.Drop(character); } } } // characters that survived will take their items with them, in case players tried to be crafty and steal them // this needs to run here in case players abort the mission by going back home foreach (var characterItem in characterItems) { if (Survived(characterItem.Key) || !completed) { foreach (Item item in characterItem.Value) { if (!item.Removed) { item.Remove(); } } } } } internal void CreateHumansInSubmarine(Submarine submarine, CharacterTeamType team = CharacterTeamType.FriendlyNPC, OnCharacterCreated onCharacterCreated = null) { if (characterConfig == null) { Log.Warn("No characters"); return; } WayPoint explicitStayInHullPos = WayPoint.GetRandom(SpawnType.Human, null, submarine); Rand.RandSync randSync = Rand.RandSync.Unsynced; List<(HumanPrefab prefab, XElement config)> humanPrefabsToSpawn = new List<(HumanPrefab, XElement)>(); foreach (XElement element in characterConfig.Elements()) { var humanPrefab = GetHumanPrefabFromElement(element); humanPrefabsToSpawn.Add((humanPrefab, element)); } foreach (var (prefab, config) in humanPrefabsToSpawn) { var humanPrefab = prefab; XElement characterSpecificConfig = config; if (humanPrefab == null || humanPrefab.Job.IsEmpty || humanPrefab.Job == "any") { continue; } var jobPrefab = humanPrefab.GetJobPrefab(randSync); var stayPos = explicitStayInHullPos; if (jobPrefab != null) { stayPos = WayPoint.GetRandom(SpawnType.Human, jobPrefab, submarine) ?? explicitStayInHullPos; } XElement additionalItemsElement = config?.GetChildElement("additionalitems"); ContentXElement additionalItems = new ContentXElement(null, additionalItemsElement); Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, team, stayPos, additionalItems: additionalItemsElement != null ? additionalItems.Elements() : null); spawnedCharacter.EnableDespawn = false; // don't let mission npcs despawn spawnedCharacter.GiveIdCardTags(stayPos, false); onCharacterCreated?.Invoke(spawnedCharacter, characterSpecificConfig); spawnedCharacter.MLC().NPCElement = characterSpecificConfig; #if CLIENT if (GameMain.IsSingleplayer) { if (characterSpecificConfig.GetAttributeBool("allowordering", false)) { _ = GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter); } } #endif } Log.Debug("end"); InitCharacters(); } internal Character CreateHuman(HumanPrefab humanPrefab, List characters, Dictionary> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn = null, bool giveTags = true, IEnumerable additionalItems = null) { var characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.Unsynced); characterInfo.TeamID = teamType; if (positionToStayIn == null) { positionToStayIn = WayPoint.GetRandom(SpawnType.Human, characterInfo.Job?.Prefab, submarine) ?? WayPoint.GetRandom(SpawnType.Human, null, submarine); } Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false); spawnedCharacter.HumanPrefab = humanPrefab; humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn); _ = humanPrefab.GiveItems(spawnedCharacter, submarine, null, createNetworkEvents: false); foreach (var item in spawnedCharacter.Inventory.AllItems) { IdCard card = item.GetComponent(); if (card == null) continue; card.SubmarineSpecificID = submarine.SubmarineSpecificIDTag; } if (additionalItems != null) { foreach (var additionalItem in additionalItems) { int amount = additionalItem.GetAttributeInt("amount", 1); for (int i = 0; i < amount; i++) { HumanPrefab.InitializeItem(spawnedCharacter, additionalItem, submarine, humanPrefab, createNetworkEvents: false); } } } characters.Add(spawnedCharacter); characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true)); return spawnedCharacter; } internal void GiveCharacterItem(Character character, ContentXElement itemElement, bool createNetworkEvents = true) { HumanPrefab.InitializeItem(character, itemElement, null, character.HumanPrefab, createNetworkEvents: createNetworkEvents); characterItems[character] = character.Inventory.FindAllItems(recursive: true); } internal void CreateHumansAtPosition(CharacterTeamType team, Vector2 position, OnCharacterCreated onCharacterCreated) { List<(HumanPrefab, XElement)> humanPrefabsToSpawn = new List<(HumanPrefab, XElement)>(); foreach (XElement element in characterConfig?.Elements()) { var humanPrefab = GetHumanPrefabFromElement(element); humanPrefabsToSpawn.Add((humanPrefab, element)); } foreach (var prefabToSpawn in humanPrefabsToSpawn) { var humanPrefab = prefabToSpawn.Item1; XElement characterMissionConfig = prefabToSpawn.Item2; Character character = CreateHumanAtPosition(humanPrefab, team, position); character.EnableDespawn = false; // don't let mission npcs despawn onCharacterCreated.Invoke(character, characterMissionConfig); character.MLC().NPCElement = characterMissionConfig; } InitCharacters(); } internal Character CreateHumanAtPosition(HumanPrefab humanPrefab, CharacterTeamType team, Vector2 spawnPosition) { Character character = CharacterUtils.CreateHuman(humanPrefab, characters, characterItems, team, spawnPosition, false); return character; } private HumanPrefab GetHumanPrefabFromElement(XElement element) { if (element.Attribute("name") != null) { DebugConsole.ThrowError("Error in mission \"" + mission.Prefab.Identifier + "\" - use character identifiers instead of names to configure the characters."); return null; } Identifier characterIdentifier = element.GetAttributeIdentifier("identifier", Identifier.Empty); Identifier characterFrom = element.GetAttributeIdentifier("from", Identifier.Empty); HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier); if (humanPrefab == null) { DebugConsole.ThrowError("Couldn't spawn character for mission: character prefab \"" + characterIdentifier + "\" not found"); return null; } return humanPrefab; } private void InitCharacters() { Log.Debug("characterCount"); int i = 0; foreach (XElement element in characterConfig.Elements()) { characters[i].IsEscorted = false; Color col = element.GetAttributeColor("color", Color.LightGreen); characters[i].UniqueNameColor = col; Log.Debug($"Set color to {col} for character {characters[i].Name}"); i++; } } internal static bool IsAlive(Character character) => character != null && !character.Removed && !character.IsDead; internal static bool IsCaptured(Character character) => character.LockHands; internal static bool Survived(Character character) { return IsAlive(character) && character.CurrentHull?.Submarine != null && (character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)); } internal static bool Close(Character target, Character ai, float closeDistance) { float dist = Vector2.DistanceSquared(ai.WorldPosition, target.WorldPosition); return dist < closeDistance * closeDistance; } internal static Character GetClosest(Character ai) { float closest = float.MaxValue; Character target = null; foreach (Character player in Character.CharacterList.Where(c => c.IsPlayer)) { if (player.IsDead) continue; // skip dead players float dist = Vector2.DistanceSquared(player.WorldPosition, ai.WorldPosition); if (dist < closest) { closest = dist; target = player; } } return target; } } }