Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Map/Levels/Ruins/RuinGenerationParams.cs
T
2019-03-18 20:39:27 +02:00

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;
}
}
}
}
}
}