diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs index b993f7deb..697d8f31d 100644 --- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs @@ -773,20 +773,12 @@ namespace Barotrauma commands.Add(new Command("checkcrafting", "checkcrafting: Checks item deconstruction & crafting recipes for inconsistencies.", (string[] args) => { - List fabricableItems = new List(); + List fabricableItems = new List(); foreach (MapEntityPrefab mapEntityPrefab in MapEntityPrefab.List) { if (mapEntityPrefab is ItemPrefab itemPrefab) { - var fabricatorElement = itemPrefab.ConfigElement.Element("Fabricator"); - if (fabricatorElement == null) { continue; } - - foreach (XElement element in fabricatorElement.Elements()) - { - if (element.Name.ToString().ToLowerInvariant() != "fabricableitem") { continue; } - fabricableItems.Add(new FabricableItem(element)); - } - + fabricableItems.AddRange(itemPrefab.FabricationRecipes); } } foreach (MapEntityPrefab mapEntityPrefab in MapEntityPrefab.List) @@ -962,8 +954,6 @@ namespace Barotrauma } } } - element.Value = lines[i]; - i++; } }, isCheat: false)); #endif diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs index cbb751f50..ec5b88851 100644 --- a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs +++ b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs @@ -656,13 +656,6 @@ namespace Barotrauma msg.Timer -= deltaTime; msg.Pos += msg.Velocity * deltaTime; } - - foreach (GUIMessage msg in messages) - { - if (!msg.WorldSpace) continue; - msg.Timer -= deltaTime; - msg.Pos += msg.Velocity * deltaTime; - } } messages.RemoveAll(m => m.Timer <= 0.0f); diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs index c053856a6..e074796e4 100644 --- a/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Fabricator.cs @@ -20,7 +20,7 @@ namespace Barotrauma.Items.Components private GUIComponent inputInventoryHolder, outputInventoryHolder; private GUICustomComponent inputInventoryOverlay, outputInventoryOverlay; - private FabricableItem selectedItem; + private FabricationRecipe selectedItem; private GUIComponent inSufficientPowerWarning; @@ -38,7 +38,7 @@ namespace Barotrauma.Items.Components { OnSelected = (GUIComponent component, object userdata) => { - selectedItem = userdata as FabricableItem; + selectedItem = userdata as FabricationRecipe; if (selectedItem != null) { SelectItem(Character.Controlled, selectedItem); } return true; } @@ -59,7 +59,7 @@ namespace Barotrauma.Items.Components CanBeFocused = false }; - foreach (FabricableItem fi in fabricableItems) + foreach (FabricationRecipe fi in fabricationRecipes) { GUIFrame frame = new GUIFrame(new RectTransform(new Point(itemList.Rect.Width, 50), itemList.Content.RectTransform), style: null) { @@ -115,13 +115,13 @@ namespace Barotrauma.Items.Components partial void SelectProjSpecific(Character character) { - var nonItems = itemList.Content.Children.Where(c => !(c.UserData is FabricableItem)).ToList(); + var nonItems = itemList.Content.Children.Where(c => !(c.UserData is FabricationRecipe)).ToList(); nonItems.ForEach(i => itemList.Content.RemoveChild(i)); itemList.Content.RectTransform.SortChildren((c1, c2) => { - var item1 = c1.GUIComponent.UserData as FabricableItem; - var item2 = c2.GUIComponent.UserData as FabricableItem; + var item1 = c1.GUIComponent.UserData as FabricationRecipe; + var item2 = c2.GUIComponent.UserData as FabricationRecipe; bool hasSkills1 = DegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f; bool hasSkills2 = DegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f; @@ -144,7 +144,7 @@ namespace Barotrauma.Items.Components { CanBeFocused = false }; - var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricableItem fabricableItem && DegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f); + var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && DegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f); if (firstinSufficient != null) { insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform)); @@ -155,13 +155,13 @@ namespace Barotrauma.Items.Components { overlayComponent.RectTransform.SetAsLastChild(); - FabricableItem targetItem = fabricatedItem ?? selectedItem; + FabricationRecipe targetItem = fabricatedItem ?? selectedItem; if (targetItem != null) { int slotIndex = 0; - var missingItems = new List(); - foreach (FabricableItem.RequiredItem requiredItem in targetItem.RequiredItems) + var missingItems = new List(); + foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems) { for (int i = 0; i < requiredItem.Amount; i++) { @@ -176,7 +176,7 @@ namespace Barotrauma.Items.Components var availableIngredients = GetAvailableIngredients(); - foreach (FabricableItem.RequiredItem requiredItem in missingItems) + foreach (FabricationRecipe.RequiredItem requiredItem in missingItems) { //highlight suitable ingredients in linked inventories foreach (Item item in availableIngredients) @@ -230,7 +230,7 @@ namespace Barotrauma.Items.Components { overlayComponent.RectTransform.SetAsLastChild(); - FabricableItem targetItem = fabricatedItem ?? selectedItem; + FabricationRecipe targetItem = fabricatedItem ?? selectedItem; if (targetItem != null) { var itemIcon = targetItem.TargetItem.InventoryIcon ?? targetItem.TargetItem.sprite; @@ -257,7 +257,7 @@ namespace Barotrauma.Items.Components } } - private bool SelectItem(Character user, FabricableItem selectedItem) + private bool SelectItem(Character user, FabricationRecipe selectedItem) { selectedItemFrame.ClearChildren(); @@ -354,7 +354,7 @@ namespace Barotrauma.Items.Components { foreach (GUIComponent child in itemList.Content.Children) { - var itemPrefab = child.UserData as FabricableItem; + var itemPrefab = child.UserData as FabricationRecipe; if (itemPrefab == null) continue; bool canBeFabricated = CanBeFabricated(itemPrefab, availableIngredients); @@ -371,13 +371,13 @@ namespace Barotrauma.Items.Components public void ClientWrite(NetBuffer msg, object[] extraData = null) { - int itemIndex = fabricatedItem == null ? -1 : fabricableItems.IndexOf(fabricatedItem); - msg.WriteRangedInteger(-1, fabricableItems.Count - 1, itemIndex); + int itemIndex = fabricatedItem == null ? -1 : fabricationRecipes.IndexOf(fabricatedItem); + msg.WriteRangedInteger(-1, fabricationRecipes.Count - 1, itemIndex); } public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) { - int itemIndex = msg.ReadRangedInteger(-1, fabricableItems.Count - 1); + int itemIndex = msg.ReadRangedInteger(-1, fabricationRecipes.Count - 1); UInt16 userID = msg.ReadUInt16(); Character user = Entity.FindEntityByID(userID) as Character; @@ -388,11 +388,11 @@ namespace Barotrauma.Items.Components else { //if already fabricating the selected item, return - if (fabricatedItem != null && fabricableItems.IndexOf(fabricatedItem) == itemIndex) return; - if (itemIndex < 0 || itemIndex >= fabricableItems.Count) return; + if (fabricatedItem != null && fabricationRecipes.IndexOf(fabricatedItem) == itemIndex) return; + if (itemIndex < 0 || itemIndex >= fabricationRecipes.Count) return; - SelectItem(user, fabricableItems[itemIndex]); - StartFabricating(fabricableItems[itemIndex], user); + SelectItem(user, fabricationRecipes[itemIndex]); + StartFabricating(fabricationRecipes[itemIndex], user); } } } diff --git a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Fabricator.cs index bdd1935a7..9203d533a 100644 --- a/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaServer/Source/Items/Components/Machines/Fabricator.cs @@ -12,7 +12,7 @@ namespace Barotrauma.Items.Components { public void ServerRead(ClientNetObject type, NetBuffer msg, Client c) { - int itemIndex = msg.ReadRangedInteger(-1, fabricableItems.Count - 1); + int itemIndex = msg.ReadRangedInteger(-1, fabricationRecipes.Count - 1); item.CreateServerEvent(this); @@ -25,17 +25,17 @@ namespace Barotrauma.Items.Components else { //if already fabricating the selected item, return - if (fabricatedItem != null && fabricableItems.IndexOf(fabricatedItem) == itemIndex) return; - if (itemIndex < 0 || itemIndex >= fabricableItems.Count) return; + if (fabricatedItem != null && fabricationRecipes.IndexOf(fabricatedItem) == itemIndex) return; + if (itemIndex < 0 || itemIndex >= fabricationRecipes.Count) return; - StartFabricating(fabricableItems[itemIndex], c.Character); + StartFabricating(fabricationRecipes[itemIndex], c.Character); } } public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null) { - int itemIndex = fabricatedItem == null ? -1 : fabricableItems.IndexOf(fabricatedItem); - msg.WriteRangedInteger(-1, fabricableItems.Count - 1, itemIndex); + int itemIndex = fabricatedItem == null ? -1 : fabricationRecipes.IndexOf(fabricatedItem); + msg.WriteRangedInteger(-1, fabricationRecipes.Count - 1, itemIndex); UInt16 userID = fabricatedItem == null || user == null ? (UInt16)0 : user.ID; msg.Write(userID); } diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs index 42567d00b..2dafda0cc 100644 --- a/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/EnemyAIController.cs @@ -1216,6 +1216,43 @@ namespace Barotrauma targetingTag = "room"; } if (door != null) + { + // If there's not a more specific tag for the door + if (string.IsNullOrEmpty(targetingTag) || targetingTag == "room") + { + targetingTag = "door"; + } + bool isOutdoor = door.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom; + bool isOpen = door.IsOpen || door.Item.Condition <= 0.0f; + //increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside + if (aggressiveBoarding) + { + if (character.CurrentHull == null) + { + valueModifier = isOutdoor ? 1 : 0; + valueModifier *= isOpen ? 5 : 1; + } + } + else + { + // Ignore disabled walls + bool isDisabled = true; + for (int i = 0; i < s.Sections.Length; i++) + { + valueModifier = isOutdoor ? 0 : 1; + valueModifier *= isOpen ? 0 : 1; + } + if (isDisabled) + { + valueModifier = 0; + } + } + } + else + { + targetingTag = "room"; + } + if (door != null) { // If there's not a more specific tag for the door if (string.IsNullOrEmpty(targetingTag) || targetingTag == "room") @@ -1242,6 +1279,10 @@ namespace Barotrauma { continue; } + else if (isOpen) //ignore broken and open doors + { + continue; + } } else if (target.Entity is IDamageable targetDamageable && targetDamageable.Health <= 0.0f) { diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs index a3af545f4..562cd475f 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs @@ -7,132 +7,15 @@ using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components -{ - class FabricableItem - { - public class RequiredItem - { - public readonly ItemPrefab ItemPrefab; - public int Amount; - public readonly float MinCondition; - public readonly bool UseCondition; - - public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, bool useCondition) - { - ItemPrefab = itemPrefab; - Amount = amount; - MinCondition = minCondition; - UseCondition = useCondition; - } - } - - public readonly ItemPrefab TargetItem; - - public readonly string DisplayName; - - public readonly List RequiredItems; - - public readonly float RequiredTime; - - public readonly float OutCondition; //Percentage-based from 0 to 1 - - public readonly List RequiredSkills; - - public FabricableItem(XElement element) - { - if (element.Attribute("name") != null) - { - string name = element.Attribute("name").Value; - DebugConsole.ThrowError("Error in fabricable item config (" + name + ") - use item identifiers instead of names"); - TargetItem = MapEntityPrefab.Find(name) as ItemPrefab; - if (TargetItem == null) - { - DebugConsole.ThrowError("Error in fabricable item config - item prefab \"" + name + "\" not found."); - return; - } - } - else - { - string identifier = element.GetAttributeString("identifier", ""); - TargetItem = MapEntityPrefab.Find(null, identifier) as ItemPrefab; - if (TargetItem == null) - { - DebugConsole.ThrowError("Error in fabricable item config - item prefab \"" + identifier + "\" not found."); - return; - } - } - - string displayName = element.GetAttributeString("displayname", ""); - DisplayName = string.IsNullOrEmpty(displayName) ? TargetItem.Name : TextManager.Get($"DisplayName.{displayName}"); - - RequiredSkills = new List(); - RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f); - OutCondition = element.GetAttributeFloat("outcondition", 1.0f); - RequiredItems = new List(); - - foreach (XElement subElement in element.Elements()) - { - switch (subElement.Name.ToString().ToLowerInvariant()) - { - case "requiredskill": - if (subElement.Attribute("name") != null) - { - DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Use skill identifiers instead of names."); - continue; - } - - RequiredSkills.Add(new Skill( - subElement.GetAttributeString("identifier", ""), - subElement.GetAttributeInt("level", 0))); - break; - case "item": - case "requireditem": - string requiredItemIdentifier = subElement.GetAttributeString("identifier", ""); - if (string.IsNullOrWhiteSpace(requiredItemIdentifier)) - { - DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! One of the required items has no identifier."); - continue; - } - - float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f); - //Substract mincondition from required item's condition or delete it regardless? - bool useCondition = subElement.GetAttributeBool("usecondition", true); - int count = subElement.GetAttributeInt("count", 1); - - - ItemPrefab requiredItem = MapEntityPrefab.Find(null, requiredItemIdentifier.Trim()) as ItemPrefab; - if (requiredItem == null) - { - DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Required item \"" + requiredItemIdentifier + "\" not found."); - continue; - } - - var existing = RequiredItems.Find(r => r.ItemPrefab == requiredItem); - if (existing == null) - { - RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition)); - } - else - { - - RequiredItems.Remove(existing); - RequiredItems.Add(new RequiredItem(requiredItem, existing.Amount + count, minCondition, useCondition)); - } - - break; - } - } - - } - } +{ partial class Fabricator : Powered, IServerSerializable, IClientSerializable { public const float SkillIncreaseMultiplier = 0.5f; - private List fabricableItems; + private List fabricationRecipes = new List(); - private FabricableItem fabricatedItem; + private FabricationRecipe fabricatedItem; private float timeUntilReady; private float requiredTime; @@ -145,21 +28,30 @@ namespace Barotrauma.Items.Components public Fabricator(Item item, XElement element) : base(item, element) { - fabricableItems = new List(); - foreach (XElement subElement in element.Elements()) { - if (subElement.Name.ToString().ToLowerInvariant() != "fabricableitem") continue; + if (subElement.Name.ToString().ToLowerInvariant() == "fabricableitem") + { + DebugConsole.ThrowError("Error in item " + item.Name + "! Fabrication recipes should be defined in the craftable item's xml, not in the fabricator."); + break; + } + } - FabricableItem fabricableItem = new FabricableItem(subElement); - if (fabricableItem.TargetItem == null) + foreach (MapEntityPrefab me in MapEntityPrefab.List) + { + if (!(me is ItemPrefab itemPrefab)) { continue; } + + foreach (FabricationRecipe recipe in itemPrefab.FabricationRecipes) { - DebugConsole.ThrowError("Error in item " + item.Name + "! Fabricable item \"" + subElement.GetAttributeString("name", "") + "\" not found."); + if (recipe.SuitableFabricatorIdentifiers.Length > 0) + { + if (!recipe.SuitableFabricatorIdentifiers.Any(i => item.prefab.Identifier == i || item.HasTag(i))) + { + continue; + } + } + fabricationRecipes.Add(recipe); } - else - { - fabricableItems.Add(fabricableItem); - } } InitProjSpecific(); @@ -176,13 +68,13 @@ namespace Barotrauma.Items.Components inputContainer = containers[0]; outputContainer = containers[1]; - - foreach (FabricableItem fabricableItem in fabricableItems) + + foreach (var recipe in fabricationRecipes) { - int ingredientCount = fabricableItem.RequiredItems.Sum(it => it.Amount); + int ingredientCount = recipe.RequiredItems.Sum(it => it.Amount); if (ingredientCount > inputContainer.Capacity) { - DebugConsole.ThrowError("Error in item \"" + item.Name + "\": There's not enough room in the input inventory for the ingredients of \"" + fabricableItem.TargetItem.Name + "\"!"); + DebugConsole.ThrowError("Error in item \"" + item.Name + "\": There's not enough room in the input inventory for the ingredients of \"" + recipe.TargetItem.Name + "\"!"); } } @@ -207,7 +99,7 @@ namespace Barotrauma.Items.Components return (picker != null); } - private void StartFabricating(FabricableItem selectedItem, Character user) + private void StartFabricating(FabricationRecipe selectedItem, Character user) { if (selectedItem == null) return; if (!outputContainer.Inventory.IsEmpty()) return; @@ -292,7 +184,7 @@ namespace Barotrauma.Items.Components if (timeUntilReady > 0.0f) { return; } var availableIngredients = GetAvailableIngredients(); - foreach (FabricableItem.RequiredItem ingredient in fabricatedItem.RequiredItems) + foreach (FabricationRecipe.RequiredItem ingredient in fabricatedItem.RequiredItems) { for (int i = 0; i < ingredient.Amount; i++) { @@ -335,17 +227,17 @@ namespace Barotrauma.Items.Components CancelFabricating(null); } - private bool CanBeFabricated(FabricableItem fabricableItem) + private bool CanBeFabricated(FabricationRecipe fabricableItem) { if (fabricableItem == null) { return false; } List availableIngredients = GetAvailableIngredients(); return CanBeFabricated(fabricableItem, availableIngredients); } - private bool CanBeFabricated(FabricableItem fabricableItem, IEnumerable availableIngredients) + private bool CanBeFabricated(FabricationRecipe fabricableItem, IEnumerable availableIngredients) { if (fabricableItem == null) { return false; } - foreach (FabricableItem.RequiredItem requiredItem in fabricableItem.RequiredItems) + foreach (FabricationRecipe.RequiredItem requiredItem in fabricableItem.RequiredItems) { if (availableIngredients.Count(it => IsItemValidIngredient(it, requiredItem)) < requiredItem.Amount) { @@ -355,7 +247,7 @@ namespace Barotrauma.Items.Components return true; } - private float GetRequiredTime(FabricableItem fabricableItem, Character user) + private float GetRequiredTime(FabricationRecipe fabricableItem, Character user) { float degreeOfSuccess = DegreeOfSuccess(user, fabricableItem.RequiredSkills); @@ -397,7 +289,7 @@ namespace Barotrauma.Items.Components /// Move the items required for fabrication into the input container. /// The method assumes that all the required ingredients are available either in the input container or linked containers. /// - private void MoveIngredientsToInputContainer(FabricableItem targetItem) + private void MoveIngredientsToInputContainer(FabricationRecipe targetItem) { //required ingredients that are already present in the input container List usedItems = new List(); @@ -428,7 +320,7 @@ namespace Barotrauma.Items.Components } } - private bool IsItemValidIngredient(Item item, FabricableItem.RequiredItem requiredItem) + private bool IsItemValidIngredient(Item item, FabricationRecipe.RequiredItem requiredItem) { return item != null && diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs index 09f0991c3..b0a1c7005 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs @@ -491,6 +491,9 @@ namespace Barotrauma case "levelcommonness": case "suitabletreatment": case "containedsprite": + case "fabricate": + case "fabricable": + case "fabricableitem": break; case "staticbody": StaticBodyConfig = subElement; diff --git a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs index e900ed7f7..24f360850 100644 --- a/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/Source/Items/ItemPrefab.cs @@ -31,6 +31,100 @@ namespace Barotrauma } } + class FabricationRecipe + { + public class RequiredItem + { + public readonly ItemPrefab ItemPrefab; + public int Amount; + public readonly float MinCondition; + public readonly bool UseCondition; + + public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, bool useCondition) + { + ItemPrefab = itemPrefab; + Amount = amount; + MinCondition = minCondition; + UseCondition = useCondition; + } + } + + public readonly ItemPrefab TargetItem; + public readonly string DisplayName; + public readonly List RequiredItems; + public readonly string[] SuitableFabricatorIdentifiers; + public readonly float RequiredTime; + public readonly float OutCondition; //Percentage-based from 0 to 1 + public readonly List RequiredSkills; + + public FabricationRecipe(XElement element, ItemPrefab itemPrefab) + { + TargetItem = itemPrefab; + string displayName = element.GetAttributeString("displayname", ""); + DisplayName = string.IsNullOrEmpty(displayName) ? itemPrefab.Name : TextManager.Get($"DisplayName.{displayName}"); + + SuitableFabricatorIdentifiers = element.GetAttributeStringArray("suitablefabricators", new string[0]); + + RequiredSkills = new List(); + RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f); + OutCondition = element.GetAttributeFloat("outcondition", 1.0f); + RequiredItems = new List(); + + foreach (XElement subElement in element.Elements()) + { + switch (subElement.Name.ToString().ToLowerInvariant()) + { + case "requiredskill": + if (subElement.Attribute("name") != null) + { + DebugConsole.ThrowError("Error in fabricable item " + itemPrefab.Name + "! Use skill identifiers instead of names."); + continue; + } + + RequiredSkills.Add(new Skill( + subElement.GetAttributeString("identifier", ""), + subElement.GetAttributeInt("level", 0))); + break; + case "item": + case "requireditem": + string requiredItemIdentifier = subElement.GetAttributeString("identifier", ""); + if (string.IsNullOrWhiteSpace(requiredItemIdentifier)) + { + DebugConsole.ThrowError("Error in fabricable item " + itemPrefab.Name + "! One of the required items has no identifier."); + continue; + } + + float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f); + //Substract mincondition from required item's condition or delete it regardless? + bool useCondition = subElement.GetAttributeBool("usecondition", true); + int count = subElement.GetAttributeInt("count", 1); + + + ItemPrefab requiredItem = MapEntityPrefab.Find(null, requiredItemIdentifier.Trim()) as ItemPrefab; + if (requiredItem == null) + { + DebugConsole.ThrowError("Error in fabricable item " + itemPrefab.Name + "! Required item \"" + requiredItemIdentifier + "\" not found."); + continue; + } + + var existing = RequiredItems.Find(r => r.ItemPrefab == requiredItem); + if (existing == null) + { + RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition)); + } + else + { + + RequiredItems.Remove(existing); + RequiredItems.Add(new RequiredItem(requiredItem, existing.Amount + count, minCondition, useCondition)); + } + + break; + } + } + } + } + partial class ItemPrefab : MapEntityPrefab { private readonly string configFile; @@ -48,6 +142,8 @@ namespace Barotrauma //the construction can be Activated() by a Character inside the area public List Triggers; + private List fabricationRecipeElements = new List(); + public string ConfigFile { get { return configFile; } @@ -65,6 +161,12 @@ namespace Barotrauma private set; } + public List FabricationRecipes + { + get; + private set; + } + public float DeconstructTime { get; @@ -309,6 +411,18 @@ namespace Barotrauma new ItemPrefab(doc.Root, filePath); } } + + //initialize item requirements for fabrication recipes + //(has to be done after all the item prefabs have been loaded, because the + //recipe ingredients may not have been loaded yet when loading the prefab) + foreach (MapEntityPrefab me in List) + { + if (!(me is ItemPrefab itemPrefab)) { continue; } + foreach (XElement fabricationRecipe in itemPrefab.fabricationRecipeElements) + { + itemPrefab.FabricationRecipes.Add(new FabricationRecipe(fabricationRecipe, itemPrefab)); + } + } } public ItemPrefab(XElement element, string filePath) @@ -333,6 +447,7 @@ namespace Barotrauma Triggers = new List(); DeconstructItems = new List(); + FabricationRecipes = new List(); DeconstructTime = 1.0f; Tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true).ToHashSet(); @@ -464,6 +579,11 @@ namespace Barotrauma DeconstructItems.Add(new DeconstructItem(deconstructItem)); } + break; + case "fabricate": + case "fabricable": + case "fabricableitem": + fabricationRecipeElements.Add(subElement); break; case "trigger": Rectangle trigger = new Rectangle(0, 0, 10, 10) @@ -1148,6 +1268,39 @@ namespace Barotrauma AllowedLinks = element.GetAttributeStringArray("allowedlinks", new string[0], convertToLowerInvariant: true).ToList(); + if (sprite == null) + { + DebugConsole.ThrowError("Item \"" + Name + "\" has no sprite!"); +#if SERVER + sprite = new Sprite("", Vector2.Zero); + sprite.SourceRect = new Rectangle(0, 0, 32, 32); +#else + sprite = new Sprite(TextureLoader.PlaceHolderTexture, null, null) + { + Origin = TextureLoader.PlaceHolderTexture.Bounds.Size.ToVector2() / 2 + }; +#endif + size = sprite.size; + sprite.EntityID = identifier; + } + + if (!category.HasFlag(MapEntityCategory.Legacy) && string.IsNullOrEmpty(identifier)) + { + DebugConsole.ThrowError( + "Item prefab \"" + name + "\" has no identifier. All item prefabs have a unique identifier string that's used to differentiate between items during saving and loading."); + } + if (!string.IsNullOrEmpty(identifier)) + { + MapEntityPrefab existingPrefab = List.Find(e => e.Identifier == identifier); + if (existingPrefab != null) + { + DebugConsole.ThrowError( + "Map entity prefabs \"" + name + "\" and \"" + existingPrefab.Name + "\" have the same identifier!"); + } + } + + AllowedLinks = element.GetAttributeStringArray("allowedlinks", new string[0], convertToLowerInvariant: true).ToList(); + List.Add(this); }