Unstable 1.8.4.0

This commit is contained in:
Markus Isberg
2025-03-12 12:56:27 +00:00
parent a4c3e868e4
commit a4a3427e4e
627 changed files with 29860 additions and 10018 deletions
@@ -7,8 +7,13 @@ namespace Barotrauma.Extensions
public static Color Multiply(this Color color, float value, bool onlyAlpha = false)
{
return onlyAlpha ?
new Color(color.R, color.G, color.B, (byte)(color.A * value)) :
new Color((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value), (byte)(color.A * value));
new Color(color.R, color.G, color.B, MultiplyComponent(color.A, value)) :
new Color(MultiplyComponent(color.R, value), MultiplyComponent(color.G, value), MultiplyComponent(color.B, value), MultiplyComponent(color.A, value));
static byte MultiplyComponent(float colorComponent, float multiplier)
{
return (byte)MathHelper.Clamp(colorComponent * multiplier, 0.0f, 255.0f);
}
}
public static Color Multiply(this Color thisColor, Color color)
@@ -1,17 +1,27 @@
using System;
using System.Collections.Generic;
// NOTE: We should use struct in addition to Enum in all the type constraints (at the end of the method signatures), as it
// tells the compiler that we're only ever using value types, which enums always are anyway.
// This avoids a lot of allocations caused by the compiler preparing for anything, which in turn happens because despite
// how it works in practice, Enum is counted as a reference type in C#... for historical reasons.
// We use the (int)(object) cast because generic types can't be cast directly to int, so we box into object and unbox into int instead.
// It avoids some memory allocations that Convert.ToInt32() seems to do.
// NOTE: This should work fine as long as the enum values stay within - to + 2^31, so let's not use uint or long for them.
namespace Barotrauma.Extensions
{
public static class EnumExtensions
{
/// <summary>
/// Enum.HasFlag() checks if all flags matches. This method checks if any of them matches.
/// Enum.HasFlag() checks if all flags matches. This method checks if any of them matches. It also avoids boxing allocations that the built-in version might still sometimes cause.
/// E.g. when myEnum = SomeEnum.First | SomeEnum.Second, myEnum.HasFlag(SomeEnum.First | SomeEnum.Third) returns false, because not all of the flags match, but HasAnyFlag(SomeEnum.First | SomeEnum.Third) returns true, because some of the flags match.
/// </summary>
public static bool HasAnyFlag<T>(this T type, T value) where T : Enum
public static bool HasAnyFlag<T>(this T type, T value) where T : struct, Enum
{
int typeValue = Convert.ToInt32(type);
int flagValue = Convert.ToInt32(value);
int typeValue = (int)(object)type;
int flagValue = (int)(object)value;
return (typeValue & flagValue) != 0;
}
@@ -19,10 +29,10 @@ namespace Barotrauma.Extensions
/// Adds a flag value to an enum.
/// Note that enums are value types, so you need to use the value returned from this method.
/// </summary>
public static T AddFlag<T>(this T @enum, T flag) where T : Enum
public static T AddFlag<T>(this T @enum, T flag) where T : struct, Enum
{
int enumValue = Convert.ToInt32(@enum);
int flagValue = Convert.ToInt32(flag);
int enumValue = (int)(object)@enum;
int flagValue = (int)(object)flag;
return (T)(object)(enumValue | flagValue);
}
@@ -30,11 +40,19 @@ namespace Barotrauma.Extensions
/// Removes a flag value from an enum.
/// Note that enums are value types, so you need to use the value returned from this method.
/// </summary>
public static T RemoveFlag<T>(this T @enum, T flag) where T : Enum
public static T RemoveFlag<T>(this T @enum, T flag) where T : struct, Enum
{
int enumValue = Convert.ToInt32(@enum);
int flagValue = Convert.ToInt32(flag);
int enumValue = (int)(object)@enum;
int flagValue = (int)(object)flag;
return (T)(object)(enumValue & ~flagValue);
}
public static IEnumerable<T> GetIndividualFlags<T>(T flagsEnum) where T : struct, Enum
{
foreach (T value in Enum.GetValues(typeof(T)))
{
if (flagsEnum.HasAnyFlag(value)) { yield return value; }
}
}
}
}
@@ -24,6 +24,9 @@ public sealed class EpicAccountId : AccountId
public new static Option<EpicAccountId> Parse(string str)
{
if (str.IsNullOrWhiteSpace()) { return Option.None; }
//according to the documentation, this is the maximum length of the ID
const int MaxEpicAccountIDLength = 32 + 1;
if (str.Length > MaxEpicAccountIDLength + prefix.Length) { return Option.None; }
if (str.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { str = str[prefix.Length..]; }
if (!str.IsHexString()) { return Option.None; }
@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -140,7 +141,25 @@ namespace Barotrauma
}
public static Identifier[] ToIdentifiers(this string[] strings)
=> ((IEnumerable<string>)strings).ToIdentifiers().ToArray();
=> strings.AsEnumerable().ToIdentifiers().ToArray();
/// <summary>
/// Parses identifiers from a comma separated string.
/// </summary>
public static IEnumerable<Identifier> ToIdentifiers(this string tagsString, string separator = ",")
{
if (string.IsNullOrWhiteSpace(tagsString))
{
return Enumerable.Empty<Identifier>();
}
return tagsString.Split(separator, StringSplitOptions.TrimEntries).AsEnumerable().ToIdentifiers();
}
/// <summary>
/// Parses a comma separated string from a collection of identifier.
/// </summary>
public static string ConvertToString(this IEnumerable<Identifier> tags, string separator = ",")
=> string.Join(separator, tags);
public static Identifier ToIdentifier(this string? s)
{
@@ -375,7 +375,19 @@ namespace Barotrauma
}
}
/// <summary>
/// Get the point where the line segment between a1 and a2 intersects the rectangle. Note that the rectangle's y-coordinate is handled so that up is lower and bottom is higher (the way rectangles work by default in XNA).
/// </summary>
public static bool GetLineRectangleIntersection(Vector2 a1, Vector2 a2, Rectangle rect, out Vector2 intersection)
{
rect.Y += rect.Height;
return GetLineWorldRectangleIntersection(a1, a2, rect, out intersection);
}
/// <summary>
/// Get the point where the line segment between a1 and a2 intersects the rectangle. Note that the rectangle's y-coordinate is handled so that up is greater and down is lower (the way e.g. MapEntity rects are defined).
/// </summary>
public static bool GetLineWorldRectangleIntersection(Vector2 a1, Vector2 a2, Rectangle rect, out Vector2 intersection)
{
if (GetAxisAlignedLineIntersection(a1, a2,
new Vector2(rect.X, rect.Y),
@@ -678,7 +690,7 @@ namespace Barotrauma
}
/// <summary>
/// divide a convex hull into triangles
/// Divide a convex hull into triangles
/// </summary>
/// <returns>List of triangle vertices (sorted counter-clockwise)</returns>
public static List<Vector2[]> TriangulateConvexHull(List<Vector2> vertices, Vector2 center)
@@ -1100,6 +1112,52 @@ namespace Barotrauma
while (val > po2) { po2 <<= 1; }
return po2;
}
/// <summary>
/// Resizes an array of vectors to a different size. Uses bilinear interpolation to "scale" the values to the size of the new array:
/// for instance, an array such as "1, 0" would become "1, 0.5, 0" when the width is scaled from 2 to 3.
/// </summary>
public static Vector2[,] ResizeVector2Array(Vector2[,] sourceArray, int newWidth, int newHeight)
{
if (newWidth < 1)
{
throw new ArgumentException("Width must be larger than zero.", nameof(newWidth));
}
if (newHeight < 1)
{
throw new ArgumentException("Height must be larger than zero.", nameof(newHeight));
}
var destinationArray = new Vector2[newWidth, newHeight];
int oldWidth = sourceArray.GetLength(0), oldHeight = sourceArray.GetLength(1);
for (int x = 0; x < newWidth; x++)
{
for (int y = 0; y < newHeight; y++)
{
// Calculate the position in the original array
float sourceX = oldWidth == 1 ? 0 : (x / (float)(newWidth - 1)) * (oldWidth - 1);
float sourceY = oldHeight == 1 ? 0 : (y / (float)(newHeight - 1)) * (oldHeight - 1);
// Find the indices of the surrounding points
int startIndexX = (int)Math.Floor(sourceX);
int endIndexX = Math.Min(startIndexX + 1, oldWidth - 1);
int startIndexY = (int)Math.Floor(sourceY);
int endIndexY = Math.Min(startIndexY + 1, oldHeight - 1);
// Calculate interpolation weights
float tx = sourceX - startIndexX;
float ty = sourceY - startIndexY;
// Perform bilinear interpolation
Vector2 top = Vector2.Lerp(sourceArray[startIndexX, startIndexY], sourceArray[endIndexX, startIndexY], tx);
Vector2 bottom = Vector2.Lerp(sourceArray[startIndexX, endIndexY], sourceArray[endIndexX, endIndexY], tx);
destinationArray[x, y] = Vector2.Lerp(top, bottom, ty);
}
}
return destinationArray;
}
}
public class CompareCW : IComparer<Vector2>
@@ -1156,4 +1214,5 @@ namespace Barotrauma
return -CompareCW.Compare(a, b, center);
}
}
}
@@ -113,10 +113,9 @@ namespace Barotrauma
/// Gets a type by its name, with backwards compatibility for types that have been renamed.
/// <see cref="TypePreviouslyKnownAs"/>
/// </summary>
public static Type? GetTypeWithBackwardsCompatibility(string nameSpace, string typeName, bool throwOnError, bool ignoreCase)
public static Type? GetTypeWithBackwardsCompatibility(Assembly assembly, string nameSpace, string typeName, bool throwOnError, bool ignoreCase)
{
if (Assembly.GetEntryAssembly() is not { } entryAssembly) { return null; }
var types = entryAssembly
var types = assembly
.GetTypes()
.Where(t => NameMatches(t.Namespace, nameSpace, ignoreCase));