using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace Barotrauma { partial class LevelObjectPrefab : ISerializableEntity { private static List list = new List(); public static List List { get { return list; } } public class ChildObject { public List AllowedNames; public int MinCount, MaxCount; public ChildObject() { AllowedNames = new List(); MinCount = 1; MaxCount = 1; } public ChildObject(XElement element) { AllowedNames = element.GetAttributeStringArray("names", new string[0]).ToList(); MinCount = element.GetAttributeInt("mincount", 1); MaxCount = Math.Max(element.GetAttributeInt("maxcount", 1), MinCount); } } [Flags] public enum SpawnPosType { None = 0, Wall = 1, RuinWall = 2, SeaFloor = 4, MainPath = 8 } public Sprite Sprite { get; private set; } public Sprite SpecularSprite { get; private set; } public DeformableSprite DeformableSprite { get; private set; } [Serialize(1.0f, false), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)] public float MinSize { get; private set; } [Serialize(1.0f, false), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)] public float MaxSize { get; private set; } /// /// Which sides of a wall the object can appear on. /// [Serialize((Alignment.Top | Alignment.Bottom | Alignment.Left | Alignment.Right), true), Editable(ToolTip = "Which sides of a wall the object can spawn on.")] public Alignment Alignment { get; private set; } [Serialize(SpawnPosType.Wall, false), Editable()] public SpawnPosType SpawnPos { get; private set; } public XElement Config { get; private set; } public readonly List LevelTriggerElements; /// /// Overrides the commonness of the object in a specific level type. /// Key = name of the level type, value = commonness in that level type. /// public Dictionary OverrideCommonness; public XElement PhysicsBodyElement { get; private set; } public int PhysicsBodyTriggerIndex { get; private set; } [Serialize("0.0,1.0", true), Editable()] public Vector2 DepthRange { get; private set; } [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, ToolTip = "The tendency for the prefab to form clusters. Used as an exponent for perlin noise values that are used to determine the probability for an object to spawn at a specific position.")] /// /// The tendency for the prefab to form clusters. Used as an exponent for perlin noise values /// that are used to determine the probability for an object to spawn at a specific position. /// public float ClusteringAmount { get; private set; } [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, ToolTip = "A value between 0-1 that determines the z-coordinate to sample perlin noise from when determining the probability " + " for an object to spawn at a specific position. Using the same (or close) value for different objects means the objects tend " + "to form clusters in the same areas.")] /// /// A value between 0-1 that determines the z-coordinate to sample perlin noise from when /// determining the probability for an object to spawn at a specific position. /// Using the same (or close) value for different objects means the objects tend to form clusters /// in the same areas. /// public float ClusteringGroup { get; private set; } [Serialize(false, true), Editable(ToolTip = "Should the object be rotated to align it with the wall surface it spawns on.")] public bool AlignWithSurface { get; private set; } [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, ToolTip = "Minimum length of a graph edge the object can spawn on.")] /// /// Minimum length of a graph edge the object can spawn on. /// public float MinSurfaceWidth { get; private set; } private Vector2 randomRotation; [Serialize("0.0,0.0", true), Editable(ToolTip = "How much the rotation of the object can vary (min and max values in degrees).")] public Vector2 RandomRotation { get { return new Vector2(MathHelper.ToDegrees(randomRotation.X), MathHelper.ToDegrees(randomRotation.Y)); } private set { randomRotation = new Vector2(MathHelper.ToRadians(value.X), MathHelper.ToRadians(value.Y)); } } public Vector2 RandomRotationRad => randomRotation; private float swingAmount; [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 360.0f, ToolTip = "How much the object swings (in degrees).")] public float SwingAmount { get { return MathHelper.ToDegrees(swingAmount); } private set { swingAmount = MathHelper.ToRadians(value); } } public float SwingAmountRad => swingAmount; [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, ToolTip = "How fast the object swings.")] public float SwingFrequency { get; private set; } [Serialize("0.0,0.0", true), Editable(ToolTip = "How much the scale of the object oscillates on each axis. A value of 0.5,0.5 would make the object's scale oscillate from 100% to 150%.")] public Vector2 ScaleOscillation { get; private set; } [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, ToolTip = "How fast the object's scale oscillates.")] public float ScaleOscillationFrequency { get; private set; } [Serialize(1.0f, true), Editable(ToolTip = "How likely it is for the object to spawn in a level. "+ "This is relative to the commonness of the other objects - for example, having an object with "+ "a commonness of 1 and another with a commonness of 10 would mean the latter appears in levels 10 times as frequently as the former. "+ "The commonness value can be overridden on specific level types.")] public float Commonness { get; private set; } [Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f, ToolTip = "How much the object disrupts submarine's sonar.")] public float SonarDisruption { get; private set; } public string Name { get; set; } public List ChildObjects { get; private set; } public Dictionary SerializableProperties { get; private set; } /// /// A list of prefabs whose properties override this one's properties when a trigger is active. /// E.g. if a trigger in the index 1 of the trigger list is active, the properties in index 1 in this list are used (unless it's null) /// public List OverrideProperties { get; private set; } public override string ToString() { return "LevelObjectPrefab (" + Name + ")"; } public static void LoadAll() { var files = GameMain.Instance.GetFilesOfType(ContentType.LevelObjectPrefabs); if (files.Count() > 0) { foreach (var file in files) { LoadConfig(file); } } else { LoadConfig("Content/LevelObjects/LevelObject/Prefabs.xml"); } } private static void LoadConfig(string configPath) { try { XDocument doc = XMLExtensions.TryLoadXml(configPath); if (doc == null || doc.Root == null) return; foreach (XElement element in doc.Root.Elements()) { list.Add(new LevelObjectPrefab(element)); } } catch (Exception e) { DebugConsole.ThrowError(String.Format("Failed to load LevelObject prefabs from {0}", configPath), e); } } public LevelObjectPrefab(XElement element) { ChildObjects = new List(); LevelTriggerElements = new List(); OverrideProperties = new List(); OverrideCommonness = new Dictionary(); SerializableProperties = SerializableProperty.DeserializeProperties(this, element); if (element != null) { Config = element; Name = element.Name.ToString(); LoadElements(element, -1); InitProjSpecific(element); } //use the maximum width of the sprite as the minimum surface width if no value is given if (element != null && !element.Attributes("minsurfacewidth").Any()) { if (Sprite != null) MinSurfaceWidth = Sprite.size.X * MaxSize; if (DeformableSprite != null) MinSurfaceWidth = Math.Max(MinSurfaceWidth, DeformableSprite.Size.X * MaxSize); } } private void LoadElements(XElement element, int parentTriggerIndex) { foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "sprite": Sprite = new Sprite(subElement, lazyLoad: true); break; case "specularsprite": SpecularSprite = new Sprite(subElement, lazyLoad: true); break; case "deformablesprite": DeformableSprite = new DeformableSprite(subElement, lazyLoad: true); break; case "overridecommonness": string levelType = subElement.GetAttributeString("leveltype", ""); if (!OverrideCommonness.ContainsKey(levelType)) { OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f)); } break; case "leveltrigger": case "trigger": OverrideProperties.Add(null); LevelTriggerElements.Add(subElement); LoadElements(subElement, LevelTriggerElements.Count - 1); break; case "childobject": ChildObjects.Add(new ChildObject(subElement)); break; case "overrideproperties": var propertyOverride = new LevelObjectPrefab(subElement); OverrideProperties[OverrideProperties.Count - 1] = propertyOverride; if (propertyOverride.Sprite == null && propertyOverride.DeformableSprite == null) { propertyOverride.Sprite = Sprite; propertyOverride.DeformableSprite = DeformableSprite; } break; case "body": case "physicsbody": PhysicsBodyElement = subElement; PhysicsBodyTriggerIndex = parentTriggerIndex; break; } } } partial void InitProjSpecific(XElement element); public float GetCommonness(string levelType) { if (!OverrideCommonness.TryGetValue(levelType, out float commonness)) { return Commonness; } return commonness; } } }