using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Xml; using System.Xml.Linq; namespace Barotrauma.RuinGeneration { [Flags] enum RuinEntityType { Wall, Back, Door, Hatch, Prop } class RuinGenerationParams : ISerializableEntity { public static List List { get { if (paramsList == null) { LoadAll(); } return paramsList; } } private static List paramsList; private string filePath; private List roomTypeList; public string Name => "RuinGenerationParams"; [Serialize("5000,5000", false), Editable()] public Point SizeMin { get; set; } [Serialize("8000,8000", false), Editable()] public Point SizeMax { get; set; } [Serialize(3, false), Editable(MinValueInt = 1, MaxValueInt = 10, ToolTip = "The ruin generation algorithm \"splits\" the ruin area into two, splits these areas again, repeats this for some number of times and creates a room at each of the final split areas. This is value determines the minimum number of times the split is done.")] public int RoomDivisionIterationsMin { get; set; } [Serialize(4, false), Editable(MinValueInt = 1, MaxValueInt = 10, ToolTip = "The ruin generation algorithm \"splits\" the ruin area into two, splits these areas again, repeats this for some number of times and creates a room at each of the final split areas. This is value determines the maximum number of times the split is done.")] public int RoomDivisionIterationsMax { get; set; } [Serialize(0.5f, false), Editable(MinValueFloat = 0.1f, MaxValueFloat = 0.9f, ToolTip = "The probability for the split algorithm to split the area vertically. High values tend to create tall, vertical rooms, and low values wide, horizontal rooms.")] public float VerticalSplitProbability { get; set; } [Serialize(400, false), Editable(ToolTip = "The splitting algorithm attempts to keep the dimensions the split areas larger than this. For example, if the width of the split areas would be smaller than this after a vertical split, the algorithm will do a horizontal split.")] public int MinSplitWidth { get; set; } [Serialize("0.5,0.9", false), Editable(ToolTip = "The minimum and maximum width of a room relative to the areas created by the split algorithm.")] public Vector2 RoomWidthRange { get; set; } [Serialize("0.5,0.9", false), Editable(ToolTip = "The minimum and maximum height of a room relative to the areas created by the split algorithm.")] public Vector2 RoomHeightRange { get; set; } [Serialize("200,256", false), Editable(ToolTip = "The minimum and maximum width of the corridors between rooms.")] public Point CorridorWidthRange { get; set; } public Dictionary SerializableProperties { get; private set; } = new Dictionary(); public IEnumerable RoomTypeList { get { return roomTypeList; } } private RuinGenerationParams(XElement element) { roomTypeList = new List(); if (element != null) { foreach (XElement subElement in element.Elements()) { roomTypeList.Add(new RuinRoom(subElement)); } } SerializableProperties = SerializableProperty.DeserializeProperties(this, element); } public static RuinGenerationParams GetRandom() { if (paramsList == null) { LoadAll(); } if (paramsList.Count == 0) { DebugConsole.ThrowError("No ruin configuration files found in any content package."); return new RuinGenerationParams(null); } return paramsList[Rand.Int(paramsList.Count, Rand.RandSync.Server)]; } private static void LoadAll() { paramsList = new List(); foreach (string configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig)) { XDocument doc = XMLExtensions.TryLoadXml(configFile); if (doc?.Root == null) continue; var newParams = new RuinGenerationParams(doc.Root) { filePath = configFile }; paramsList.Add(newParams); } } public static void SaveAll() { XmlWriterSettings settings = new XmlWriterSettings { Indent = true, NewLineOnAttributes = true }; foreach (RuinGenerationParams generationParams in List) { foreach (string configFile in GameMain.Instance.GetFilesOfType(ContentType.RuinConfig)) { if (configFile != generationParams.filePath) continue; XDocument doc = XMLExtensions.TryLoadXml(configFile); if (doc?.Root == null) continue; SerializableProperty.SerializeProperties(generationParams, doc.Root); using (var writer = XmlWriter.Create(configFile, settings)) { doc.WriteTo(writer); writer.Flush(); } } } } } class RuinRoom : ISerializableEntity { public enum RoomPlacement { Any, First, Last } public string Name { get; private set; } [Serialize(1.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)] public float Commonness { get; private set; } public Dictionary SerializableProperties { get; private set; } = new Dictionary(); [Serialize(RoomPlacement.Any, false), Editable()] public RoomPlacement Placement { get; set; } [Serialize(0, false), Editable()] public int PlacementOffset { get; set; } [Serialize(false, false), Editable()] public bool IsCorridor { get; set; } [Serialize(1.0f, false), Editable()] public float MinWaterAmount { get; set; } [Serialize(1.0f, false), Editable()] public float MaxWaterAmount { get; set; } private List entityList = new List(); public RuinRoom(XElement element) { SerializableProperties = SerializableProperty.DeserializeProperties(this, element); Name = element.GetAttributeString("name", ""); if (element != null) { int groupIndex = 0; LoadEntities(element, ref groupIndex); } void LoadEntities(XElement element2, ref int groupIndex) { foreach (XElement subElement in element2.Elements()) { if (subElement.Name.ToString().ToLowerInvariant() == "chooseone") { groupIndex++; LoadEntities(subElement, ref groupIndex); } else { entityList.Add(new RuinEntityConfig(subElement) { SingleGroupIndex = groupIndex }); } } } } public RuinEntityConfig GetRandomEntity(RuinEntityType type, Alignment alignment) { var matchingEntities = entityList.FindAll(rs => rs.Type == type && rs.Alignment.HasFlag(alignment)); if (!matchingEntities.Any()) return null; return ToolBox.SelectWeightedRandom( matchingEntities, matchingEntities.Select(s => s.Commonness).ToList(), Rand.RandSync.Server); } public List GetPropList(RuinShape room, Rand.RandSync randSync) { Dictionary> propGroups = new Dictionary>(); foreach (RuinEntityConfig entityConfig in entityList) { if (entityConfig.Type != RuinEntityType.Prop) { continue; } if (room.Rect.Width < entityConfig.MinRoomSize.X || room.Rect.Height < entityConfig.MinRoomSize.Y) { continue; } if (room.Rect.Width > entityConfig.MaxRoomSize.X || room.Rect.Height > entityConfig.MaxRoomSize.Y) { continue; } if (!propGroups.ContainsKey(entityConfig.SingleGroupIndex)) { propGroups[entityConfig.SingleGroupIndex] = new List(); } propGroups[entityConfig.SingleGroupIndex].Add(entityConfig); } List props = new List(); foreach (KeyValuePair> propGroup in propGroups) { if (propGroup.Key == 0) { props.AddRange(propGroup.Value); } else { props.Add(propGroup.Value[Rand.Int(propGroup.Value.Count, randSync)]); } } return props; } } class RuinEntityConfig : ISerializableEntity { public readonly MapEntityPrefab Prefab; public enum RelativePlacement { SameRoom, NextRoom, NextCorridor, PreviousRoom, PreviousCorridor, FirstRoom, FirstCorridor, LastRoom, LastCorridor } public class EntityConnection { //which type of room to search for the item to connect to //sameroom, nextroom, previousroom, firstroom and lastroom are also valid public string RoomName { get; private set; } public string TargetEntityIdentifier { get; private set; } //Identifier of the item to run the wire from. Only needed in item assemblies to determine which item in the assembly to use. public string SourceEntityIdentifier { get; private set; } //if set, the connection is done by running a wire from //(Pair.First = the name of the connection in this item) to (Pair.Second = the name of the connection in the target item) public Pair WireConnection { get; private set; } public EntityConnection(XElement element) { RoomName = element.GetAttributeString("roomname", ""); TargetEntityIdentifier = element.GetAttributeString("targetentity", ""); SourceEntityIdentifier = element.GetAttributeString("sourceentity", ""); foreach (XElement subElement in element.Elements()) { if (subElement.Name.ToString().ToLowerInvariant() == "wire") { WireConnection = new Pair( subElement.GetAttributeString("from", ""), subElement.GetAttributeString("to", "")); } } } } [Serialize(Alignment.Bottom, false), Editable] public Alignment Alignment { get; private set; } [Serialize("0,0", false), Editable(ToolTip = "Minimum offset from the anchor position, relative to the size of the room."+ " For example, a value of { -0.5,0 } with a Bottom alignment would mean the entity can be placed anywhere between the bottom-left corner of the room and bottom-center.")] public Vector2 MinOffset { get; private set; } [Serialize("0,0", false), Editable(ToolTip = "Maximum offset from the anchor position, relative to the size of the room." + " For example, a value of { 0.5,0 } with a Bottom alignment would mean the entity can be placed anywhere between the bottom-right corner of the room and bottom-center.")] public Vector2 MaxOffset { get; private set; } [Serialize(RuinEntityType.Prop, false), Editable] public RuinEntityType Type { get; private set; } [Serialize(false, false), Editable] public bool Expand { get; private set; } [Serialize(RelativePlacement.SameRoom, false), Editable] public RelativePlacement PlacementRelativeToParent { get; private set; } [Serialize(1.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)] public float Commonness { get; private set; } [Serialize(1, false)] public int MinAmount { get; private set; } [Serialize(1, false)] public int MaxAmount { get; private set; } [Serialize("0,0", false)] public Point MinRoomSize { get; private set; } [Serialize("100000,100000", false)] public Point MaxRoomSize { get; private set; } [Serialize("", false)] public string TargetContainer { get; private set; } public List EntityConnections { get; private set; } = new List(); public int SingleGroupIndex; private readonly List childEntities = new List(); public IEnumerable ChildEntities { get { return childEntities; } } public string Name => Prefab == null ? "null" : Prefab.Name; public Dictionary SerializableProperties { get; private set; } = new Dictionary(); public RuinEntityConfig(XElement element) { string name = element.GetAttributeString("prefab", ""); Prefab = MapEntityPrefab.Find(name: null, identifier: name); if (Prefab == null) { DebugConsole.ThrowError("Loading ruin entity config failed - map entity prefab \"" + name + "\" not found."); return; } SerializableProperties = SerializableProperty.DeserializeProperties(this, element); int gIndex = 0; LoadChildren(element, ref gIndex); void LoadChildren(XElement element2, ref int groupIndex) { foreach (XElement subElement in element2.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "connection": case "entityconnection": EntityConnections.Add(new EntityConnection(subElement)); break; case "chooseone": groupIndex++; LoadChildren(subElement, ref groupIndex); break; default: childEntities.Add(new RuinEntityConfig(subElement) { SingleGroupIndex = groupIndex }); break; } } } } } }