471 lines
18 KiB
C#
471 lines
18 KiB
C#
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
partial class LevelObjectPrefab : PrefabWithUintIdentifier, ISerializableEntity
|
|
{
|
|
public readonly static PrefabCollection<LevelObjectPrefab> Prefabs = new PrefabCollection<LevelObjectPrefab>();
|
|
|
|
public class ChildObject
|
|
{
|
|
public List<string> AllowedNames;
|
|
public int MinCount, MaxCount;
|
|
|
|
public ChildObject()
|
|
{
|
|
AllowedNames = new List<string>();
|
|
MinCount = 1;
|
|
MaxCount = 1;
|
|
}
|
|
|
|
public ChildObject(XElement element)
|
|
{
|
|
AllowedNames = element.GetAttributeStringArray("names", Array.Empty<string>()).ToList();
|
|
MinCount = element.GetAttributeInt("mincount", 1);
|
|
MaxCount = Math.Max(element.GetAttributeInt("maxcount", 1), MinCount);
|
|
}
|
|
}
|
|
|
|
[Flags]
|
|
public enum SpawnPosType
|
|
{
|
|
None = 0,
|
|
MainPathWall = 1,
|
|
SidePathWall = 2,
|
|
CaveWall = 4,
|
|
NestWall = 8,
|
|
RuinWall = 16,
|
|
SeaFloor = 32,
|
|
MainPath = 64,
|
|
LevelStart = 128,
|
|
LevelEnd = 256,
|
|
OutpostWall = 512,
|
|
Wall = MainPathWall | SidePathWall | CaveWall,
|
|
}
|
|
|
|
public List<Sprite> Sprites
|
|
{
|
|
get;
|
|
private set;
|
|
} = new List<Sprite>();
|
|
|
|
public DeformableSprite DeformableSprite
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Serialize(1.0f, IsPropertySaveable.No), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
|
public float MinSize
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
[Serialize(1.0f, IsPropertySaveable.No), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
|
public float MaxSize
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Which sides of a wall the object can appear on.
|
|
/// </summary>
|
|
[Serialize((Alignment.Top | Alignment.Bottom | Alignment.Left | Alignment.Right), IsPropertySaveable.Yes, description: "Which sides of a wall the object can spawn on."), Editable]
|
|
public Alignment Alignment
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Serialize(SpawnPosType.Wall, IsPropertySaveable.No), Editable()]
|
|
public SpawnPosType SpawnPos
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public XElement Config
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public readonly List<ContentXElement> LevelTriggerElements;
|
|
|
|
/// <summary>
|
|
/// Overrides the commonness of the object in a specific level type.
|
|
/// Key = name of the level type, value = commonness in that level type.
|
|
/// </summary>
|
|
public readonly Dictionary<Identifier, float> OverrideCommonness;
|
|
|
|
public XElement PhysicsBodyElement
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
public int PhysicsBodyTriggerIndex
|
|
{
|
|
get;
|
|
private set;
|
|
} = -1;
|
|
|
|
public Dictionary<Sprite, XElement> SpriteSpecificPhysicsBodyElements
|
|
{
|
|
get;
|
|
private set;
|
|
} = new Dictionary<Sprite, XElement>();
|
|
|
|
|
|
[Serialize(10000, IsPropertySaveable.No, description: "Maximum number of this specific object per level."), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f)]
|
|
public int MaxCount
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Serialize("0.0,1.0", IsPropertySaveable.Yes, description: "The sprite depth of the object (min, max). Values of 0 or less make the object render in front of walls, values larger than 0 make it render behind walls with a parallax effect."), Editable]
|
|
public Vector2 DepthRange
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
|
|
[Serialize(3000.0f, IsPropertySaveable.Yes, description: "Objects fade out to the background color of the level the further they are from the camera. This value is the depth at which the object becomes \"maximally\" faded out."), Editable]
|
|
public float FadeOutDepth
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f),
|
|
Serialize(0.0f, IsPropertySaveable.Yes, description: "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.")]
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public float ClusteringAmount
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f),
|
|
Serialize(0.0f, IsPropertySaveable.Yes, description: "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.")]
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public float ClusteringGroup
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Editable, Serialize("0,0", IsPropertySaveable.Yes, description: "Random offset from the surface the object spawns on.")]
|
|
public Vector2 RandomOffset
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Should the object be rotated to align it with the wall surface it spawns on.")]
|
|
public bool AlignWithSurface
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the object be placed near the start of the level.")]
|
|
public bool AllowAtStart
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the object be placed near the end of the level.")]
|
|
public bool AllowAtEnd
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Minimum length of a graph edge the object can spawn on."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
|
|
/// <summary>
|
|
/// Minimum length of a graph edge the object can spawn on.
|
|
/// </summary>
|
|
public float MinSurfaceWidth
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
private Vector2 randomRotation;
|
|
[Editable, Serialize("0.0,0.0", IsPropertySaveable.Yes, description: "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, IsPropertySaveable.Yes, description: "How much the object swings (in degrees)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 360.0f)]
|
|
public float SwingAmount
|
|
{
|
|
get { return MathHelper.ToDegrees(swingAmount); }
|
|
private set
|
|
{
|
|
swingAmount = MathHelper.ToRadians(value);
|
|
}
|
|
}
|
|
|
|
public float SwingAmountRad => swingAmount;
|
|
|
|
[Serialize(0.0f, IsPropertySaveable.Yes, description: "How fast the object swings."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
|
public float SwingFrequency
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Editable, Serialize("0.0,0.0", IsPropertySaveable.Yes, description: "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, IsPropertySaveable.Yes, description: "How fast the object's scale oscillates."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
|
public float ScaleOscillationFrequency
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Editable, Serialize(1.0f, IsPropertySaveable.Yes, description: "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, IsPropertySaveable.Yes, description: "How much the object disrupts submarine's sonar."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
|
|
public float SonarDisruption
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Serialize(false, IsPropertySaveable.Yes, description: "Can the object take damage from weapons/attacks that damage level walls."), Editable]
|
|
public bool TakeLevelWallDamage
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Serialize(false, IsPropertySaveable.Yes, description: "Should the object disappear if the object is destroyed? Only relevant if TakeLevelWallDamage is true."), Editable]
|
|
public bool HideWhenBroken
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Serialize(100.0f, IsPropertySaveable.Yes, description: "Amount of health the object has. Only relevant if TakeLevelWallDamage is true."), Editable]
|
|
public float Health
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable]
|
|
public Color SpriteColor
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public string Name => Identifier.Value;
|
|
|
|
public List<ChildObject> ChildObjects
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public Dictionary<Identifier, SerializableProperty> SerializableProperties
|
|
{
|
|
get; private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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)
|
|
/// </summary>
|
|
public List<LevelObjectPrefab> OverrideProperties
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "LevelObjectPrefab (" + Identifier + ")";
|
|
}
|
|
|
|
|
|
public LevelObjectPrefab(ContentXElement element, LevelObjectPrefabsFile file, Identifier identifierOverride = default) : base(file, ParseIdentifier(identifierOverride, element))
|
|
{
|
|
ChildObjects = new List<ChildObject>();
|
|
LevelTriggerElements = new List<ContentXElement>();
|
|
OverrideProperties = new List<LevelObjectPrefab>();
|
|
OverrideCommonness = new Dictionary<Identifier, float>();
|
|
|
|
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
|
if (element != null)
|
|
{
|
|
Config = element;
|
|
|
|
LoadElements(file, element, -1);
|
|
InitProjSpecific(element);
|
|
}
|
|
|
|
//use (a bit less than) the maximum width of the sprite as the minimum surface width if no value is given
|
|
if (element != null && element.GetAttribute("minsurfacewidth") == null)
|
|
{
|
|
if (Sprites.Any()) { MinSurfaceWidth = Sprites[0].size.X * MaxSize * 0.8f; }
|
|
if (DeformableSprite != null) { MinSurfaceWidth = Math.Max(MinSurfaceWidth, DeformableSprite.Size.X * MaxSize * 0.8f); }
|
|
}
|
|
}
|
|
|
|
public static Identifier ParseIdentifier(Identifier identifierOverride, XElement element)
|
|
{
|
|
if (!identifierOverride.IsEmpty) { return identifierOverride; }
|
|
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
|
|
if (identifier.IsEmpty)
|
|
{
|
|
#if DEBUG
|
|
DebugConsole.ThrowError($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
|
|
#else
|
|
DebugConsole.AddWarning($"Level object prefab \"{element.Name}\" has no identifier! Using the name as the identifier instead...");
|
|
#endif
|
|
identifier = element.NameAsIdentifier();
|
|
}
|
|
return identifier;
|
|
}
|
|
|
|
private void LoadElements(LevelObjectPrefabsFile file, ContentXElement element, int parentTriggerIndex)
|
|
{
|
|
int propertyOverrideCount = 0;
|
|
//load sprites first, OverrideProperties may need them (defaulting to the default sprite if no override is defined)
|
|
foreach (var subElement in element.Elements())
|
|
{
|
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "sprite":
|
|
var newSprite = new Sprite(subElement, lazyLoad: true);
|
|
Sprites.Add(newSprite);
|
|
var spriteSpecificPhysicsBodyElement =
|
|
subElement.GetChildElement("PhysicsBody") ?? subElement.GetChildElement("Body") ??
|
|
subElement.GetChildElement("physicsbody") ?? subElement.GetChildElement("body");
|
|
if (spriteSpecificPhysicsBodyElement != null)
|
|
{
|
|
SpriteSpecificPhysicsBodyElements.Add(newSprite, spriteSpecificPhysicsBodyElement);
|
|
}
|
|
break;
|
|
case "deformablesprite":
|
|
DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
|
|
break;
|
|
}
|
|
}
|
|
foreach (var subElement in element.Elements())
|
|
{
|
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "overridecommonness":
|
|
Identifier levelType = subElement.GetAttributeIdentifier("leveltype", Identifier.Empty);
|
|
if (!OverrideCommonness.ContainsKey(levelType))
|
|
{
|
|
OverrideCommonness.Add(levelType, subElement.GetAttributeFloat("commonness", 1.0f));
|
|
}
|
|
break;
|
|
case "leveltrigger":
|
|
case "trigger":
|
|
OverrideProperties.Add(null);
|
|
LevelTriggerElements.Add(subElement);
|
|
LoadElements(file, subElement, LevelTriggerElements.Count - 1);
|
|
break;
|
|
case "childobject":
|
|
ChildObjects.Add(new ChildObject(subElement));
|
|
break;
|
|
case "overrideproperties":
|
|
var propertyOverride = new LevelObjectPrefab(subElement, file, identifierOverride: $"{Identifier}-{propertyOverrideCount}".ToIdentifier());
|
|
OverrideProperties[OverrideProperties.Count - 1] = propertyOverride;
|
|
if (!propertyOverride.Sprites.Any() && propertyOverride.DeformableSprite == null)
|
|
{
|
|
propertyOverride.Sprites = Sprites;
|
|
propertyOverride.DeformableSprite = DeformableSprite;
|
|
}
|
|
propertyOverrideCount++;
|
|
break;
|
|
case "body":
|
|
case "physicsbody":
|
|
PhysicsBodyElement = subElement;
|
|
PhysicsBodyTriggerIndex = parentTriggerIndex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
partial void InitProjSpecific(ContentXElement element);
|
|
|
|
|
|
public float GetCommonness(CaveGenerationParams generationParams, bool requireCaveSpecificOverride = true)
|
|
{
|
|
if (generationParams != null &&
|
|
generationParams.Identifier != Identifier.Empty &&
|
|
OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness))
|
|
{
|
|
return commonness;
|
|
}
|
|
return requireCaveSpecificOverride ? 0.0f : Commonness;
|
|
}
|
|
|
|
public float GetCommonness(LevelData levelData)
|
|
{
|
|
if (levelData.GenerationParams != null && levelData.GenerationParams.Identifier != Identifier.Empty &&
|
|
OverrideCommonness.TryGetValue(levelData.GenerationParams.Identifier, out float commonness) ||
|
|
(!levelData.GenerationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(levelData.GenerationParams.OldIdentifier, out commonness)))
|
|
{
|
|
return commonness;
|
|
}
|
|
if (levelData?.Biome != null)
|
|
{
|
|
if (OverrideCommonness.TryGetValue(levelData.Biome.Identifier, out float biomeCommonness))
|
|
{
|
|
return biomeCommonness;
|
|
}
|
|
}
|
|
return Commonness;
|
|
}
|
|
|
|
public override void Dispose() { }
|
|
}
|
|
}
|