using System; using Barotrauma.Extensions; using Barotrauma.Items.Components; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace Barotrauma { class HumanPrefab : PrefabWithUintIdentifier { [Serialize("any", IsPropertySaveable.No)] public Identifier Job { get; protected set; } [Serialize(1f, IsPropertySaveable.No)] public float Commonness { get; protected set; } [Serialize(1f, IsPropertySaveable.No)] public float HealthMultiplier { get; protected set; } [Serialize(1f, IsPropertySaveable.No)] public float HealthMultiplierInMultiplayer { get; protected set; } [Serialize(1f, IsPropertySaveable.No)] public float AimSpeed { get; protected set; } [Serialize(1f, IsPropertySaveable.No)] public float AimAccuracy { get; protected set; } private readonly HashSet moduleFlags = new HashSet(); [Serialize("", IsPropertySaveable.Yes, "What outpost module tags does the NPC prefer to spawn in.")] public string ModuleFlags { get => string.Join(",", moduleFlags); set { moduleFlags.Clear(); if (!string.IsNullOrWhiteSpace(value)) { string[] splitFlags = value.Split(','); foreach (var f in splitFlags) { moduleFlags.Add(f.ToIdentifier()); } } } } private readonly HashSet spawnPointTags = new HashSet(); [Serialize("", IsPropertySaveable.Yes, "Tag(s) of the spawnpoints the NPC prefers to spawn at.")] public string SpawnPointTags { get => string.Join(",", spawnPointTags); set { spawnPointTags.Clear(); if (!string.IsNullOrWhiteSpace(value)) { string[] splitTags = value.Split(','); foreach (var tag in splitTags) { spawnPointTags.Add(tag.ToIdentifier()); } } } } [Serialize(CampaignMode.InteractionType.None, IsPropertySaveable.No)] public CampaignMode.InteractionType CampaignInteractionType { get; protected set; } [Serialize(AIObjectiveIdle.BehaviorType.Passive, IsPropertySaveable.No)] public AIObjectiveIdle.BehaviorType Behavior { get; protected set; } [Serialize(float.PositiveInfinity, IsPropertySaveable.No)] public float ReportRange { get; protected set; } public Identifier[] PreferredOutpostModuleTypes { get; protected set; } public XElement Element { get; protected set; } public readonly List<(XElement element, float commonness)> ItemSets = new List<(XElement element, float commonness)>(); public readonly List<(XElement element, float commonness)> CustomCharacterInfos = new List<(XElement element, float commonness)>(); public readonly Identifier NpcSetIdentifier; public HumanPrefab(ContentXElement element, ContentFile file, Identifier npcSetIdentifier) : base(file, element.GetAttributeIdentifier("identifier", "")) { SerializableProperty.DeserializeProperties(this, element); Element = element; element.GetChildElements("itemset").ForEach(e => ItemSets.Add((e, e.GetAttributeFloat("commonness", 1)))); element.GetChildElements("character").ForEach(e => CustomCharacterInfos.Add((e, e.GetAttributeFloat("commonness", 1)))); PreferredOutpostModuleTypes = element.GetAttributeIdentifierArray("preferredoutpostmoduletypes", Array.Empty()); this.NpcSetIdentifier = npcSetIdentifier; } public IEnumerable GetModuleFlags() { return moduleFlags; } public IEnumerable GetSpawnPointTags() { return spawnPointTags; } public JobPrefab GetJobPrefab(Rand.RandSync randSync = Rand.RandSync.Unsynced, Func predicate = null) { return !Job.IsEmpty && Job != "any" ? JobPrefab.Get(Job) : JobPrefab.Random(randSync, predicate); } public void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn = null) { var humanAI = npc.AIController as HumanAIController; if (humanAI != null) { var idleObjective = humanAI.ObjectiveManager.GetObjective(); if (positionToStayIn != null && Behavior == AIObjectiveIdle.BehaviorType.StayInHull) { idleObjective.TargetHull = AIObjectiveGoTo.GetTargetHull(positionToStayIn); idleObjective.Behavior = AIObjectiveIdle.BehaviorType.StayInHull; } else { idleObjective.Behavior = Behavior; foreach (Identifier moduleType in PreferredOutpostModuleTypes) { idleObjective.PreferredOutpostModuleTypes.Add(moduleType); } } humanAI.ReportRange = ReportRange; humanAI.AimSpeed = AimSpeed; humanAI.AimAccuracy = AimAccuracy; } if (CampaignInteractionType != CampaignMode.InteractionType.None) { (GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(npc, CampaignInteractionType); if (positionToStayIn != null && humanAI != null) { humanAI.ObjectiveManager.SetForcedOrder(new AIObjectiveGoTo(positionToStayIn, npc, humanAI.ObjectiveManager, repeat: true, getDivingGearIfNeeded: false, closeEnough: 200) { DebugLogWhenFails = false }); } } } public bool GiveItems(Character character, Submarine submarine, Rand.RandSync randSync = Rand.RandSync.Unsynced, bool createNetworkEvents = true) { if (ItemSets == null || !ItemSets.Any()) { return false; } var spawnItems = ToolBox.SelectWeightedRandom(ItemSets, it => it.commonness, randSync).element; if (spawnItems != null) { foreach (XElement itemElement in spawnItems.GetChildElements("item")) { int amount = itemElement.GetAttributeInt("amount", 1); for (int i = 0; i < amount; i++) { InitializeItem(character, itemElement, submarine, this, createNetworkEvents: createNetworkEvents); } } } return true; } /// /// Creates a character info from the human prefab. If there are custom character infos defined, those are used, otherwise a randomized info is generated. /// /// /// public CharacterInfo CreateCharacterInfo(Rand.RandSync randSync = Rand.RandSync.Unsynced) { var characterElement = ToolBox.SelectWeightedRandom(CustomCharacterInfos, info => info.commonness, randSync).element; CharacterInfo characterInfo; if (characterElement == null) { characterInfo= new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: GetJobPrefab(randSync), npcIdentifier: Identifier); } else { characterInfo = new CharacterInfo(characterElement, Identifier); } characterInfo.HumanPrefabIds = (NpcSetIdentifier, Identifier); return characterInfo; } public static void InitializeItem(Character character, XElement itemElement, Submarine submarine, HumanPrefab humanPrefab, Item parentItem = null, bool createNetworkEvents = true) { ItemPrefab itemPrefab; string itemIdentifier = itemElement.GetAttributeString("identifier", ""); itemPrefab = MapEntityPrefab.FindByIdentifier(itemIdentifier.ToIdentifier()) as ItemPrefab; if (itemPrefab == null) { DebugConsole.ThrowError("Tried to spawn \"" + humanPrefab?.Identifier + "\" with the item \"" + itemIdentifier + "\". Matching item prefab not found."); return; } Item item = new Item(itemPrefab, character.Position, null); #if SERVER if (GameMain.Server != null && Entity.Spawner != null && createNetworkEvents) { if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item)) { string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created."; DebugConsole.ThrowError(errorMsg); GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item); GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item); } Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item)); } #endif if (itemElement.GetAttributeBool("equip", false)) { //if the item is both pickable and wearable, try to wear it instead of picking it up List allowedSlots = item.GetComponents().Count() > 1 ? new List(item.GetComponent()?.AllowedSlots ?? item.GetComponent().AllowedSlots) : new List(item.AllowedSlots); allowedSlots.Remove(InvSlotType.Any); character.Inventory.TryPutItem(item, null, allowedSlots); } else { character.Inventory.TryPutItem(item, null, item.AllowedSlots); } IdCard idCardComponent = item.GetComponent(); if (idCardComponent != null) { idCardComponent.Initialize(null, character); if (submarine != null && (submarine.Info.IsWreck || submarine.Info.IsOutpost)) { idCardComponent.SubmarineSpecificID = submarine.SubmarineSpecificIDTag; } var idCardTags = itemElement.GetAttributeStringArray("tags", Array.Empty()); foreach (string tag in idCardTags) { item.AddTag(tag); } } foreach (WifiComponent wifiComponent in item.GetComponents()) { wifiComponent.TeamID = character.TeamID; } parentItem?.Combine(item, user: null); foreach (XElement childItemElement in itemElement.Elements()) { int amount = childItemElement.GetAttributeInt("amount", 1); for (int i = 0; i < amount; i++) { InitializeItem(character, childItemElement, submarine, humanPrefab, item, createNetworkEvents); } } } public override void Dispose() { } } }