using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Barotrauma.Extensions; namespace Barotrauma { [Flags] enum MapEntityCategory { None = 0, Structure = 1, Decorative = 2, Machine = 4, Medical = 8, Weapon = 16, Diving = 32, Equipment = 64, Fuel = 128, Electrical = 256, Material = 1024, Alien = 2048, Wrecked = 4096, ItemAssembly = 8192, Legacy = 16384, Misc = 32768 } abstract partial class MapEntityPrefab : PrefabWithUintIdentifier { public static IEnumerable List { get { foreach (var ep in CoreEntityPrefab.Prefabs) { yield return ep; } foreach (var ep in StructurePrefab.Prefabs) { yield return ep; } foreach (var ep in ItemPrefab.Prefabs) { yield return ep; } foreach (var ep in ItemAssemblyPrefab.Prefabs) { yield return ep; } } } //which prefab has been selected for placing public static MapEntityPrefab Selected { get; set; } //the position where the structure is being placed (needed when stretching the structure) protected static Vector2 placePosition; public static bool SelectPrefab(object selection) { if ((Selected = selection as MapEntityPrefab) != null) { placePosition = Vector2.Zero; return true; } else { return false; } } //a method that allows the GUIListBoxes to check through a delegate if the entityprefab is still selected public static object GetSelected() { return (object)Selected; } /// /// Find a matching map entity prefab /// /// The name of the item (can be omitted when searching based on identifier) /// The identifier of the item (if null, the identifier is ignored and the search is done only based on the name) [Obsolete("Prefer MapEntityPrefab.FindByIdentifier or MapEntityPrefab.FindByName")] public static MapEntityPrefab Find(string name, string identifier = null, bool showErrorMessages = true) { return Find(name, (identifier ?? "").ToIdentifier(), showErrorMessages); } [Obsolete("Prefer MapEntityPrefab.FindByIdentifier or MapEntityPrefab.FindByName")] public static MapEntityPrefab Find(string name, Identifier identifier, bool showErrorMessages = true) { //try to search based on identifier first if (string.IsNullOrEmpty(name) && !identifier.IsEmpty) { if (CoreEntityPrefab.Prefabs.ContainsKey(identifier)) { return CoreEntityPrefab.Prefabs[identifier]; } if (StructurePrefab.Prefabs.ContainsKey(identifier)) { return StructurePrefab.Prefabs[identifier]; } if (ItemPrefab.Prefabs.ContainsKey(identifier)) { return ItemPrefab.Prefabs[identifier]; } if (ItemAssemblyPrefab.Prefabs.ContainsKey(identifier)) { return ItemAssemblyPrefab.Prefabs[identifier]; } } foreach (MapEntityPrefab prefab in List) { if (!identifier.IsEmpty) { if (prefab.Identifier != identifier) { if (prefab.Aliases != null && prefab.Aliases.Any(a => a == identifier)) { return prefab; } continue; } else { if (string.IsNullOrEmpty(name)) { return prefab; } } } if (!string.IsNullOrEmpty(name)) { if (prefab.Name.Equals(name, StringComparison.OrdinalIgnoreCase) || prefab.OriginalName.Equals(name, StringComparison.OrdinalIgnoreCase) || (prefab.Aliases != null && prefab.Aliases.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase)))) { return prefab; } } } if (showErrorMessages) { DebugConsole.ThrowError("Failed to find a matching MapEntityPrefab (name: \"" + name + "\", identifier: \"" + identifier + "\").\n" + Environment.StackTrace.CleanupStackTrace()); } return null; } public static MapEntityPrefab GetRandom(Predicate predicate, Rand.RandSync sync) { return List.GetRandom(p => predicate(p), sync); } /// /// Find a matching map entity prefab /// /// A predicate that returns true on the desired prefab. public static MapEntityPrefab Find(Predicate predicate) { return List.FirstOrDefault(p => predicate(p)); } public static MapEntityPrefab FindByName(string name) { if (name.IsNullOrEmpty()) { throw new ArgumentException($"{nameof(name)} must not be null or empty"); } var matches = List.Where(prefab => prefab.Name.Equals(name, StringComparison.OrdinalIgnoreCase) || prefab.OriginalName.Equals(name, StringComparison.OrdinalIgnoreCase) || (prefab.Aliases != null && prefab.Aliases.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase)))); //if there's multiple matches, prefer ones that aren't hidden in menus and base items //(hidden ones and variants are often e.g. some kind of special ones used in an event or versions unlockable with talents) if (matches.Count() > 1) { var bestMatch = matches.FirstOrDefault(prefab => !prefab.HideInMenus) ?? matches.FirstOrDefault(prefab => prefab is ItemPrefab ip && ip.VariantOf.IsEmpty); if (bestMatch != null) { return bestMatch; } } return matches.FirstOrDefault(); } public static MapEntityPrefab FindByIdentifier(Identifier identifier) => CoreEntityPrefab.Prefabs.TryGet(identifier, out var corePrefab) ? corePrefab : ItemPrefab.Prefabs.TryGet(identifier, out var itemPrefab) ? itemPrefab : StructurePrefab.Prefabs.TryGet(identifier, out var structurePrefab) ? structurePrefab : ItemAssemblyPrefab.Prefabs.TryGet(identifier, out var itemAssemblyPrefab) ? itemAssemblyPrefab : (MapEntityPrefab)null; public abstract Sprite Sprite { get; } public virtual bool CanSpriteFlipX { get; } = false; public virtual bool CanSpriteFlipY { get; } = false; public abstract string OriginalName { get; } public abstract LocalizedString Name { get; } public abstract ImmutableHashSet Tags { get; } /// /// Links defined to identifiers. /// public abstract ImmutableHashSet AllowedLinks { get; } public abstract MapEntityCategory Category { get; } //If a matching prefab is not found when loading a sub, the game will attempt to find a prefab with a matching alias. //(allows changing names while keeping backwards compatibility with older sub files) public abstract ImmutableHashSet Aliases { get; } //is it possible to stretch the entity horizontally/vertically [Serialize(false, IsPropertySaveable.No)] public bool ResizeHorizontal { get; protected set; } [Serialize(false, IsPropertySaveable.No)] public bool ResizeVertical { get; protected set; } [Serialize("", IsPropertySaveable.No)] public LocalizedString Description { get; protected set; } [Serialize("", IsPropertySaveable.No)] public string AllowedUpgrades { get; protected set; } [Serialize(false, IsPropertySaveable.No)] public bool HideInMenus { get; protected set; } [Serialize(false, IsPropertySaveable.No)] public bool HideInEditors { get; protected set; } [Serialize("", IsPropertySaveable.No)] public string Subcategory { get; protected set; } [Serialize(false, IsPropertySaveable.No)] public bool Linkable { get; protected set; } [Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.No)] public Color SpriteColor { get; protected set; } [Serialize(1f, IsPropertySaveable.Yes), Editable(0.1f, 10f, DecimalCount = 3)] public float Scale { get; protected set; } protected MapEntityPrefab(Identifier identifier) : base(null, identifier) { } public MapEntityPrefab(ContentXElement element, ContentFile file) : base(file, element) { } public string GetItemNameTextId() { var textId = $"entityname.{Identifier}"; return TextManager.ContainsTag(textId) ? textId : null; } public string GetHullNameTextId() { var textId = $"roomname.{Identifier}"; return TextManager.ContainsTag(textId) ? textId : null; } private string cachedAllowedUpgrades = ""; private ImmutableHashSet allowedUpgradeSet; public IEnumerable GetAllowedUpgrades() { if (string.IsNullOrWhiteSpace(AllowedUpgrades)) { return Enumerable.Empty(); } if (allowedUpgradeSet is null || cachedAllowedUpgrades != AllowedUpgrades) { allowedUpgradeSet = AllowedUpgrades.Split(",").ToIdentifiers().ToImmutableHashSet(); cachedAllowedUpgrades = AllowedUpgrades; } return allowedUpgradeSet; } public bool HasSubCategory(string subcategory) { return subcategory?.Equals(this.Subcategory, StringComparison.OrdinalIgnoreCase) ?? false; } protected abstract void CreateInstance(Rectangle rect); #if DEBUG public void DebugCreateInstance() { Rectangle rect = new Rectangle(new Point((int)Screen.Selected.Cam.WorldViewCenter.X, (int)Screen.Selected.Cam.WorldViewCenter.Y), new Point((int)Submarine.GridSize.X, (int)Submarine.GridSize.Y)); CreateInstance(rect); } #endif /// /// Check if the name or any of the aliases of this prefab match the given name. /// public bool NameMatches(string name, StringComparison comparisonType) => OriginalName.Equals(name, comparisonType) || (Aliases != null && Aliases.Any(a => a.Equals(name, comparisonType))); public bool NameMatches(IEnumerable allowedNames, StringComparison comparisonType) => allowedNames.Any(n => NameMatches(n, comparisonType)); public bool IsLinkAllowed(MapEntityPrefab target) { if (target == null) { return false; } if (target is StructurePrefab && AllowedLinks.Contains("structure".ToIdentifier())) { return true; } if (target is ItemPrefab && AllowedLinks.Contains("item".ToIdentifier())) { return true; } if (target is LinkedSubmarinePrefab && Tags.Contains("dock".ToIdentifier())) { return true; } if (this is LinkedSubmarinePrefab && target.Tags.Contains("dock".ToIdentifier())) { return true; } return AllowedLinks.Contains(target.Identifier) || target.AllowedLinks.Contains(Identifier) || target.Tags.Any(t => AllowedLinks.Contains(t)) || Tags.Any(t => target.AllowedLinks.Contains(t)); } protected void LoadDescription(ContentXElement element) { Identifier descriptionIdentifier = element.GetAttributeIdentifier("descriptionidentifier", ""); Identifier nameIdentifier = element.GetAttributeIdentifier("nameidentifier", ""); string originalDescription = Description.Value; if (descriptionIdentifier != Identifier.Empty) { Description = TextManager.Get($"EntityDescription.{descriptionIdentifier}"); } else if (nameIdentifier == Identifier.Empty) { Description = TextManager.Get($"EntityDescription.{Identifier}"); } else { Description = TextManager.Get($"EntityDescription.{nameIdentifier}"); } if (!originalDescription.IsNullOrEmpty()) { Description = Description.Fallback(originalDescription); } } } }