using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using Barotrauma.IO; using System.Linq; using System.Xml.Linq; using System.Collections.Immutable; using System.Net; namespace Barotrauma { #warning TODO: MapEntityPrefab should be constrained further to not include item assemblies, as assemblies are effectively not entities at all partial class ItemAssemblyPrefab : MapEntityPrefab { public static readonly PrefabCollection Prefabs = new PrefabCollection(); private readonly XElement configElement; public readonly record struct DisplayEntity( Identifier Identifier, Rectangle Rect, float RotationRad); public readonly ImmutableArray DisplayEntities; public readonly Rectangle Bounds; public override LocalizedString Name { get; } public override Sprite Sprite => null; public override string OriginalName => Name.Value; public override ImmutableHashSet Tags { get; } public override ImmutableHashSet AllowedLinks => null; public override MapEntityCategory Category => MapEntityCategory.ItemAssembly; public override ImmutableHashSet Aliases => null; protected override Identifier DetermineIdentifier(XElement element) { return element.GetAttributeIdentifier("identifier", element.GetAttributeIdentifier("name", "")); } public ItemAssemblyPrefab(ContentXElement element, ItemAssemblyFile file) : base(element, file) { configElement = element; SerializableProperty.DeserializeProperties(this, configElement); Name = TextManager.Get($"EntityName.{Identifier}").Fallback(element.GetAttributeString("name", "")); Description = TextManager.Get($"EntityDescription.{Identifier}"); Tags = Enumerable.Empty().ToImmutableHashSet(); string description = element.GetAttributeString("description", string.Empty); if (!description.IsNullOrEmpty()) { Description = Description.Fallback(description); } List containedItemIDs = new List(); foreach (XElement entityElement in element.Elements()) { var containerElement = entityElement.GetChildElement("itemcontainer"); if (containerElement == null) { continue; } string containedString = containerElement.GetAttributeString("contained", ""); string[] itemIdStrings = containedString.Split(','); var itemIds = new List[itemIdStrings.Length]; for (int i = 0; i < itemIdStrings.Length; i++) { itemIds[i] ??= new List(); foreach (string idStr in itemIdStrings[i].Split(';')) { if (int.TryParse(idStr, out int id)) { itemIds[i].Add((ushort)id); containedItemIDs.Add((ushort)id); } } } } int minX = int.MaxValue, minY = int.MaxValue; int maxX = int.MinValue, maxY = int.MinValue; var displayEntities = new List(); foreach (XElement entityElement in element.Elements()) { ushort id = (ushort)entityElement.GetAttributeInt("ID", 0); if (id > 0 && containedItemIDs.Contains(id)) { continue; } if (entityElement.Elements().Any(e => e.Name.LocalName.Equals("wire", StringComparison.OrdinalIgnoreCase))) { continue; } Identifier identifier = entityElement.GetAttributeIdentifier("identifier", entityElement.Name.ToString().ToLowerInvariant()); Rectangle rect = entityElement.GetAttributeRect("rect", Rectangle.Empty); float rotation = MathHelper.ToRadians(entityElement.GetAttributeFloat("rotation", 0.0f)); if (!entityElement.GetAttributeBool("hideinassemblypreview", false)) { displayEntities.Add(new DisplayEntity(identifier, rect, rotation)); } minX = Math.Min(minX, rect.X); minY = Math.Min(minY, rect.Y - rect.Height); maxX = Math.Max(maxX, rect.Right); maxY = Math.Max(maxY, rect.Y); } DisplayEntities = displayEntities.ToImmutableArray(); Bounds = minX == int.MaxValue ? new Rectangle(0, 0, 1, 1) : new Rectangle(minX, minY, maxX - minX, maxY - minY); } protected override void CreateInstance(Rectangle rect) { #if CLIENT var loaded = CreateInstance(rect.Location.ToVector2(), Submarine.MainSub, selectInstance: Screen.Selected == GameMain.SubEditorScreen); if (Screen.Selected is SubEditorScreen && loaded.Any()) { SubEditorScreen.StoreCommand(new AddOrDeleteCommand(loaded, false, handleInventoryBehavior: false)); } #else var loaded = CreateInstance(rect.Location.ToVector2(), Submarine.MainSub); #endif } public List CreateInstance(Vector2 position, Submarine sub, bool selectInstance = false) { var retVal = PasteEntities(position, sub, configElement, ContentFile.Path.Value, selectInstance); #if CLIENT GameMain.SubEditorScreen?.ReconstructLayers(); #endif return retVal; } public static List PasteEntities(Vector2 position, Submarine sub, XElement configElement, string filePath = null, bool selectInstance = false) { int idOffset = Entity.FindFreeIdBlock(configElement.Elements().Count()); List entities = MapEntity.LoadAll(sub, configElement, filePath, idOffset); if (entities.Count == 0) { return entities; } Vector2 offset = sub?.HiddenSubPosition ?? Vector2.Zero; foreach (MapEntity me in entities) { me.Move(position); me.Submarine = sub; if (me is not Item item) { continue; } Wire wire = item.GetComponent(); //Vector2 subPosition = Submarine == null ? Vector2.Zero : Submarine.HiddenSubPosition; if (wire != null) { //fix wires that have been erroneously saved at the "hidden position" if (sub != null && Vector2.Distance(me.Position, sub.HiddenSubPosition) > sub.HiddenSubPosition.Length() / 2) { me.Move(position); } wire.MoveNodes(position - offset); } } MapEntity.MapLoaded(entities, true); #if CLIENT if (Screen.Selected == GameMain.SubEditorScreen && selectInstance) { MapEntity.SelectedList.Clear(); entities.ForEach(MapEntity.AddSelection); } #endif return entities; } public void Delete() { Prefabs.Remove(this); try { if (ContentPackage is { Files: { Length: 1 } } && ContentPackageManager.LocalPackages.Contains(ContentPackage)) { Directory.Delete(ContentPackage.Dir, recursive: true); ContentPackageManager.LocalPackages.Refresh(); ContentPackageManager.EnabledPackages.DisableRemovedMods(); } } catch (Exception e) { DebugConsole.ThrowErrorLocalized("Deleting item assembly \"" + Name + "\" failed.", e); } } public override void Dispose() { } } }