475 lines
16 KiB
C#
475 lines
16 KiB
C#
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<RuinGenerationParams> List
|
|
{
|
|
get
|
|
{
|
|
if (paramsList == null)
|
|
{
|
|
LoadAll();
|
|
}
|
|
return paramsList;
|
|
}
|
|
}
|
|
|
|
private static List<RuinGenerationParams> paramsList;
|
|
|
|
private string filePath;
|
|
|
|
private List<RuinRoom> 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<string, SerializableProperty> SerializableProperties
|
|
{
|
|
get;
|
|
private set;
|
|
} = new Dictionary<string, SerializableProperty>();
|
|
|
|
public IEnumerable<RuinRoom> RoomTypeList
|
|
{
|
|
get { return roomTypeList; }
|
|
}
|
|
|
|
private RuinGenerationParams(XElement element)
|
|
{
|
|
roomTypeList = new List<RuinRoom>();
|
|
|
|
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<RuinGenerationParams>();
|
|
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<string, SerializableProperty> SerializableProperties
|
|
{
|
|
get;
|
|
private set;
|
|
} = new Dictionary<string, SerializableProperty>();
|
|
|
|
[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<RuinEntityConfig> entityList = new List<RuinEntityConfig>();
|
|
|
|
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<RuinEntityConfig> GetPropList(RuinShape room, Rand.RandSync randSync)
|
|
{
|
|
Dictionary<int, List<RuinEntityConfig>> propGroups = new Dictionary<int, List<RuinEntityConfig>>();
|
|
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<RuinEntityConfig>();
|
|
}
|
|
propGroups[entityConfig.SingleGroupIndex].Add(entityConfig);
|
|
}
|
|
|
|
List<RuinEntityConfig> props = new List<RuinEntityConfig>();
|
|
foreach (KeyValuePair<int, List<RuinEntityConfig>> 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<string, string> 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<string, string>(
|
|
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<EntityConnection> EntityConnections { get; private set; } = new List<EntityConnection>();
|
|
|
|
|
|
public int SingleGroupIndex;
|
|
|
|
private readonly List<RuinEntityConfig> childEntities = new List<RuinEntityConfig>();
|
|
|
|
public IEnumerable<RuinEntityConfig> ChildEntities
|
|
{
|
|
get { return childEntities; }
|
|
}
|
|
|
|
public string Name => Prefab == null ? "null" : Prefab.Name;
|
|
|
|
public Dictionary<string, SerializableProperty> SerializableProperties
|
|
{
|
|
get;
|
|
private set;
|
|
} = new Dictionary<string, SerializableProperty>();
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|