(61d00a474) v0.9.7.1
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
public static class AssemblyInfo
|
||||
{
|
||||
/// <summary> Gets the git hash value from the assembly
|
||||
/// or null if it cannot be found. </summary>
|
||||
public static string GetGitRevision()
|
||||
{
|
||||
var asm = typeof(AssemblyInfo).Assembly;
|
||||
var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
return attrs.FirstOrDefault(a => a.Key == "GitRevision")?.Value;
|
||||
}
|
||||
|
||||
/// <summary> Gets the git branch name from the assembly
|
||||
/// or null if it cannot be found. </summary>
|
||||
public static string GetGitBranch()
|
||||
{
|
||||
var asm = typeof(AssemblyInfo).Assembly;
|
||||
var attrs = asm.GetCustomAttributes<AssemblyMetadataAttribute>();
|
||||
return attrs.FirstOrDefault(a => a.Key == "GitBranch")?.Value;
|
||||
}
|
||||
|
||||
/// <summary> Gets the build platform and configuration </summary>
|
||||
public static string GetBuildString()
|
||||
{
|
||||
string retVal = "Unknown";
|
||||
#if WINDOWS
|
||||
retVal = "Windows";
|
||||
#elif OSX
|
||||
retVal = "Mac";
|
||||
#elif LINUX
|
||||
retVal = "Linux";
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
retVal = "Debug" + retVal;
|
||||
#elif UNSTABLE
|
||||
retVal = "Unstable" + retVal;
|
||||
#else
|
||||
retVal = "Release" + retVal;
|
||||
#endif
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class IPExtensions
|
||||
{
|
||||
//workaround for .NET Framework 4.5 bug; presumably fixed in later versions
|
||||
//see https://stackoverflow.com/questions/23608829/why-does-ipaddress-maptoipv4-throw-argumentoutofrangeexception
|
||||
public static IPAddress MapToIPv4NoThrow(this IPAddress address)
|
||||
{
|
||||
byte[] addressBytes = address.GetAddressBytes();
|
||||
|
||||
return new IPAddress(addressBytes.Skip(addressBytes.Length - 4).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Mersenne Twister based random
|
||||
/// </summary>
|
||||
public sealed class MTRandom : Random
|
||||
{
|
||||
private const int N = 624;
|
||||
private const int M = 397;
|
||||
private const uint MATRIX_A = 0x9908b0dfU;
|
||||
private const uint UPPER_MASK = 0x80000000U;
|
||||
private const uint LOWER_MASK = 0x7fffffffU;
|
||||
private const uint TEMPER1 = 0x9d2c5680U;
|
||||
private const uint TEMPER2 = 0xefc60000U;
|
||||
private const int TEMPER3 = 11;
|
||||
private const int TEMPER4 = 7;
|
||||
private const int TEMPER5 = 15;
|
||||
private const int TEMPER6 = 18;
|
||||
|
||||
private UInt32[] mt;
|
||||
private int mti;
|
||||
private UInt32[] mag01;
|
||||
|
||||
private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0);
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with randomized seed
|
||||
/// </summary>
|
||||
public MTRandom()
|
||||
{
|
||||
Initialize((uint)Environment.TickCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with provided 32 bit seed
|
||||
/// </summary>
|
||||
public MTRandom(int seed)
|
||||
{
|
||||
Initialize((uint)Math.Abs(seed));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (Re)initialize this instance with provided 32 bit seed
|
||||
/// </summary>
|
||||
private void Initialize(uint seed)
|
||||
{
|
||||
mt = new UInt32[N];
|
||||
mti = N + 1;
|
||||
mag01 = new UInt32[] { 0x0U, MATRIX_A };
|
||||
mt[0] = seed;
|
||||
for (int i = 1; i < N; i++)
|
||||
mt[i] = (UInt32)(1812433253 * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
|
||||
/// </summary>
|
||||
private uint NextUInt32()
|
||||
{
|
||||
UInt32 y;
|
||||
if (mti >= N)
|
||||
{
|
||||
GenRandAll();
|
||||
mti = 0;
|
||||
}
|
||||
y = mt[mti++];
|
||||
y ^= (y >> TEMPER3);
|
||||
y ^= (y << TEMPER4) & TEMPER1;
|
||||
y ^= (y << TEMPER5) & TEMPER2;
|
||||
y ^= (y >> TEMPER6);
|
||||
return y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value that is greater or equal than 0 and less than Int32.MaxValue
|
||||
/// </summary>
|
||||
public override int Next()
|
||||
{
|
||||
var retval = (int)(0x7FFFFFFF & NextUInt32());
|
||||
if (retval == 0x7FFFFFFF)
|
||||
return NextInt32();
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random value is greater or equal than 0 and less than maxValue
|
||||
/// </summary>
|
||||
public override int Next(int maxValue)
|
||||
{
|
||||
return (int)(NextDouble() * maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value greater or equal than 0 and less or equal than Int32.MaxValue (inclusively)
|
||||
/// </summary>
|
||||
public int NextInt32()
|
||||
{
|
||||
return (int)(0x7FFFFFFF & NextUInt32());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns random value larger or equal to 0.0 and less than 1.0
|
||||
/// </summary>
|
||||
public override double NextDouble()
|
||||
{
|
||||
return c_realUnitInt * NextInt32();
|
||||
}
|
||||
|
||||
private void GenRandAll()
|
||||
{
|
||||
int kk = 1;
|
||||
UInt32 y;
|
||||
UInt32 p;
|
||||
y = mt[0] & UPPER_MASK;
|
||||
do
|
||||
{
|
||||
p = mt[kk];
|
||||
mt[kk - 1] = mt[kk + (M - 1)] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1];
|
||||
y = p & UPPER_MASK;
|
||||
} while (++kk < N - M + 1);
|
||||
do
|
||||
{
|
||||
p = mt[kk];
|
||||
mt[kk - 1] = mt[kk + (M - N - 1)] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1];
|
||||
y = p & UPPER_MASK;
|
||||
} while (++kk < N);
|
||||
p = mt[0];
|
||||
mt[N - 1] = mt[M - 1] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,992 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
//TODO: Currently this is only used for text positioning? -> move there?
|
||||
[Flags]
|
||||
public enum Alignment
|
||||
{
|
||||
CenterX = 1, Left = 2, Right = 4, CenterY = 8, Top = 16, Bottom = 32,
|
||||
TopLeft = (Top | Left), TopCenter = (CenterX | Top), TopRight = (Top | Right),
|
||||
CenterLeft = (Left | CenterY), Center = (CenterX | CenterY), CenterRight = (Right | CenterY),
|
||||
BottomLeft = (Bottom | Left), BottomCenter = (CenterX | Bottom), BottomRight = (Bottom | Right),
|
||||
}
|
||||
|
||||
static class MathUtils
|
||||
{
|
||||
public static float Percentage(float portion, float total)
|
||||
{
|
||||
return portion / total * 100;
|
||||
}
|
||||
|
||||
public static int PositiveModulo(int i, int n)
|
||||
{
|
||||
return (i % n + n) % n;
|
||||
}
|
||||
|
||||
public static double Distance(double x1, double y1, double x2, double y2)
|
||||
{
|
||||
double dX = x1 - x2;
|
||||
double dY = y1 - y2;
|
||||
return Math.Sqrt(dX * dX + dY * dY);
|
||||
}
|
||||
|
||||
public static double DistanceSquared(double x1, double y1, double x2, double y2)
|
||||
{
|
||||
double dX = x1 - x2;
|
||||
double dY = y1 - y2;
|
||||
return dX * dX + dY * dY;
|
||||
}
|
||||
|
||||
public static int DistanceSquared(int x1, int y1, int x2, int y2)
|
||||
{
|
||||
int dX = x1 - x2;
|
||||
int dY = y1 - y2;
|
||||
return dX * dX + dY * dY;
|
||||
}
|
||||
|
||||
public static Vector2 SmoothStep(Vector2 v1, Vector2 v2, float amount)
|
||||
{
|
||||
return new Vector2(
|
||||
MathHelper.SmoothStep(v1.X, v2.X, amount),
|
||||
MathHelper.SmoothStep(v1.Y, v2.Y, amount));
|
||||
}
|
||||
|
||||
public static float SmoothStep(float t)
|
||||
{
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
|
||||
public static float SmootherStep(float t)
|
||||
{
|
||||
return t * t * t * (t * (6f * t - 15f) + 10f);
|
||||
}
|
||||
|
||||
public static float EaseIn(float t)
|
||||
{
|
||||
return 1f - (float)Math.Cos(t * MathHelper.PiOver2);
|
||||
}
|
||||
|
||||
public static float EaseOut(float t)
|
||||
{
|
||||
return (float)Math.Sin(t * MathHelper.PiOver2);
|
||||
}
|
||||
|
||||
public static Vector2 ClampLength(this Vector2 v, float length)
|
||||
{
|
||||
float currLength = v.Length();
|
||||
if (currLength > length)
|
||||
{
|
||||
return v / currLength * length;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public static bool Contains(this Rectangle rect, double x, double y)
|
||||
{
|
||||
return x > rect.X && x < rect.Right && y > rect.Y && y < rect.Bottom;
|
||||
}
|
||||
|
||||
public static float Round(float value, float div)
|
||||
{
|
||||
return (value < 0.0f) ?
|
||||
(float)Math.Ceiling(value / div) * div :
|
||||
(float)Math.Floor(value / div) * div;
|
||||
}
|
||||
|
||||
public static float RoundTowardsClosest(float value, float div)
|
||||
{
|
||||
return (float)Math.Round(value / div) * div;
|
||||
}
|
||||
|
||||
public static float VectorToAngle(Vector2 vector)
|
||||
{
|
||||
return (float)Math.Atan2(vector.Y, vector.X);
|
||||
}
|
||||
|
||||
public static Point ToPoint(Vector2 vector)
|
||||
{
|
||||
return new Point((int)vector.X,(int)vector.Y);
|
||||
}
|
||||
|
||||
public static bool IsValid(float value)
|
||||
{
|
||||
return (!float.IsInfinity(value) && !float.IsNaN(value));
|
||||
}
|
||||
|
||||
public static bool IsValid(Vector2 vector)
|
||||
{
|
||||
return (IsValid(vector.X) && IsValid(vector.Y));
|
||||
}
|
||||
|
||||
public static Rectangle ExpandRect(Rectangle rect, int amount)
|
||||
{
|
||||
return new Rectangle(rect.X - amount, rect.Y + amount, rect.Width + amount * 2, rect.Height + amount * 2);
|
||||
}
|
||||
|
||||
public static int VectorOrientation(Vector2 p1, Vector2 p2, Vector2 p)
|
||||
{
|
||||
// Determinant
|
||||
float Orin = (p2.X - p1.X) * (p.Y - p1.Y) - (p.X - p1.X) * (p2.Y - p1.Y);
|
||||
|
||||
if (Orin > 0)
|
||||
return -1; // (* Orientation is to the left-hand side *)
|
||||
if (Orin < 0)
|
||||
return 1; // (* Orientation is to the right-hand side *)
|
||||
|
||||
return 0; // (* Orientation is neutral aka collinear *)
|
||||
}
|
||||
|
||||
|
||||
public static float CurveAngle(float from, float to, float step)
|
||||
{
|
||||
|
||||
from = WrapAngleTwoPi(from);
|
||||
to = WrapAngleTwoPi(to);
|
||||
|
||||
if (Math.Abs(from - to) < MathHelper.Pi)
|
||||
{
|
||||
// The simple case - a straight lerp will do.
|
||||
return MathHelper.Lerp(from, to, step);
|
||||
}
|
||||
|
||||
// If we get here we have the more complex case.
|
||||
// First, increment the lesser value to be greater.
|
||||
if (from < to)
|
||||
from += MathHelper.TwoPi;
|
||||
else
|
||||
to += MathHelper.TwoPi;
|
||||
|
||||
float retVal = MathHelper.Lerp(from, to, step);
|
||||
|
||||
// Now ensure the return value is between 0 and 2pi
|
||||
if (retVal >= MathHelper.TwoPi)
|
||||
retVal -= MathHelper.TwoPi;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// wrap the angle between 0.0f and 2pi
|
||||
/// </summary>
|
||||
public static float WrapAngleTwoPi(float angle)
|
||||
{
|
||||
if (float.IsInfinity(angle) || float.IsNegativeInfinity(angle) || float.IsNaN(angle))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
while (angle < 0)
|
||||
angle += MathHelper.TwoPi;
|
||||
while (angle >= MathHelper.TwoPi)
|
||||
angle -= MathHelper.TwoPi;
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// wrap the angle between -pi and pi
|
||||
/// </summary>
|
||||
public static float WrapAnglePi(float angle)
|
||||
{
|
||||
if (float.IsInfinity(angle) || float.IsNegativeInfinity(angle) || float.IsNaN(angle))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
// Ensure that -pi <= angle < pi for both "from" and "to"
|
||||
while (angle < -MathHelper.Pi)
|
||||
angle += MathHelper.TwoPi;
|
||||
while (angle >= MathHelper.Pi)
|
||||
angle -= MathHelper.TwoPi;
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
public static float GetShortestAngle(float from, float to)
|
||||
{
|
||||
// Ensure that 0 <= angle < 2pi for both "from" and "to"
|
||||
from = WrapAngleTwoPi(from);
|
||||
to = WrapAngleTwoPi(to);
|
||||
|
||||
if (Math.Abs(from - to) < MathHelper.Pi)
|
||||
{
|
||||
return to - from;
|
||||
}
|
||||
|
||||
// If we get here we have the more complex case.
|
||||
// First, increment the lesser value to be greater.
|
||||
if (from < to)
|
||||
from += MathHelper.TwoPi;
|
||||
else
|
||||
to += MathHelper.TwoPi;
|
||||
|
||||
return to - from;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle between the two angles, where the direction matters.
|
||||
/// </summary>
|
||||
public static float GetMidAngle(float from, float to)
|
||||
{
|
||||
float max = Math.Max(from, to);
|
||||
float min = Math.Min(from, to);
|
||||
float diff = max - min;
|
||||
if (from < to)
|
||||
{
|
||||
// Clockwise
|
||||
return from + diff / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// CCW
|
||||
return from - diff / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// solves the angle opposite to side a (parameters: lengths of each side)
|
||||
/// </summary>
|
||||
public static float SolveTriangleSSS(float a, float b, float c)
|
||||
{
|
||||
float A = (float)Math.Acos((b * b + c * c - a * a) / (2 * b * c));
|
||||
|
||||
if (float.IsNaN(A)) A = 1.0f;
|
||||
|
||||
return A;
|
||||
}
|
||||
|
||||
public static byte AngleToByte(float angle)
|
||||
{
|
||||
angle = WrapAngleTwoPi(angle);
|
||||
angle = angle * (255.0f / MathHelper.TwoPi);
|
||||
return Convert.ToByte(angle);
|
||||
}
|
||||
|
||||
public static float ByteToAngle(byte b)
|
||||
{
|
||||
float angle = (float)b;
|
||||
angle = angle * (MathHelper.TwoPi / 255.0f);
|
||||
return angle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// check whether line from a to b is intersecting with line from c to b
|
||||
/// </summary>
|
||||
public static bool LinesIntersect(Vector2 a, Vector2 b, Vector2 c, Vector2 d)
|
||||
{
|
||||
float denominator = ((b.X - a.X) * (d.Y - c.Y)) - ((b.Y - a.Y) * (d.X - c.X));
|
||||
float numerator1 = ((a.Y - c.Y) * (d.X - c.X)) - ((a.X - c.X) * (d.Y - c.Y));
|
||||
float numerator2 = ((a.Y - c.Y) * (b.X - a.X)) - ((a.X - c.X) * (b.Y - a.Y));
|
||||
|
||||
if (denominator == 0) return numerator1 == 0 && numerator2 == 0;
|
||||
|
||||
float r = numerator1 / denominator;
|
||||
float s = numerator2 / denominator;
|
||||
|
||||
return (r >= 0 && r <= 1) && (s >= 0 && s <= 1);
|
||||
}
|
||||
|
||||
// a1 is line1 start, a2 is line1 end, b1 is line2 start, b2 is line2 end
|
||||
public static bool GetLineIntersection(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2, out Vector2 intersection)
|
||||
{
|
||||
intersection = Vector2.Zero;
|
||||
|
||||
Vector2 b = a2 - a1;
|
||||
Vector2 d = b2 - b1;
|
||||
float bDotDPerp = b.X * d.Y - b.Y * d.X;
|
||||
|
||||
// if b dot d == 0, it means the lines are parallel so have infinite intersection points
|
||||
if (bDotDPerp == 0) return false;
|
||||
|
||||
Vector2 c = b1 - a1;
|
||||
float t = (c.X * d.Y - c.Y * d.X) / bDotDPerp;
|
||||
if (t < 0 || t > 1) return false;
|
||||
|
||||
float u = (c.X * b.Y - c.Y * b.X) / bDotDPerp;
|
||||
if (u < 0 || u > 1) return false;
|
||||
|
||||
intersection = a1 + t * b;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool GetAxisAlignedLineIntersection(Vector2 a1, Vector2 a2, Vector2 axisAligned1, Vector2 axisAligned2, bool isHorizontal, out Vector2 intersection)
|
||||
{
|
||||
intersection = Vector2.Zero;
|
||||
|
||||
if (!isHorizontal)
|
||||
{
|
||||
float xDiff = axisAligned1.X - a1.X;
|
||||
if (Math.Sign(xDiff) == Math.Sign(axisAligned1.X - a2.X)) { return false; }
|
||||
|
||||
float s = (a2.Y - a1.Y) / (a2.X - a1.X);
|
||||
float y = a1.Y + xDiff * s;
|
||||
|
||||
if (axisAligned1.Y < axisAligned2.Y)
|
||||
{
|
||||
if (y < axisAligned1.Y) return false;
|
||||
if (y > axisAligned2.Y) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (y > axisAligned1.Y) return false;
|
||||
if (y < axisAligned2.Y) return false;
|
||||
}
|
||||
|
||||
intersection = new Vector2(axisAligned1.X, y);
|
||||
return true;
|
||||
}
|
||||
else //horizontal line
|
||||
{
|
||||
float yDiff = axisAligned1.Y - a1.Y;
|
||||
if (Math.Sign(yDiff) == Math.Sign(axisAligned1.Y - a2.Y)) { return false; }
|
||||
|
||||
float s = (a2.X - a1.X) / (a2.Y - a1.Y);
|
||||
float x = a1.X + yDiff * s;
|
||||
|
||||
if (axisAligned1.X < axisAligned2.X)
|
||||
{
|
||||
if (x < axisAligned1.X) return false;
|
||||
if (x > axisAligned2.X) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x > axisAligned1.X) return false;
|
||||
if (x < axisAligned2.X) return false;
|
||||
}
|
||||
|
||||
intersection = new Vector2(x, axisAligned1.Y);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool GetLineRectangleIntersection(Vector2 a1, Vector2 a2, Rectangle rect, out Vector2 intersection)
|
||||
{
|
||||
if (GetAxisAlignedLineIntersection(a1, a2,
|
||||
new Vector2(rect.X, rect.Y),
|
||||
new Vector2(rect.Right, rect.Y),
|
||||
true, out intersection))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GetAxisAlignedLineIntersection(a1, a2,
|
||||
new Vector2(rect.X, rect.Y-rect.Height),
|
||||
new Vector2(rect.Right, rect.Y-rect.Height),
|
||||
true, out intersection))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(GetAxisAlignedLineIntersection(a1, a2,
|
||||
new Vector2(rect.X, rect.Y),
|
||||
new Vector2(rect.X, rect.Y - rect.Height),
|
||||
false, out intersection))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GetAxisAlignedLineIntersection(a1, a2,
|
||||
new Vector2(rect.Right, rect.Y),
|
||||
new Vector2(rect.Right, rect.Y - rect.Height),
|
||||
false, out intersection))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*public static List<Vector2> GetLineRectangleIntersections(Vector2 a1, Vector2 a2, Rectangle rect)
|
||||
{
|
||||
List<Vector2> intersections = new List<Vector2>();
|
||||
|
||||
Vector2? intersection = GetAxisAlignedLineIntersection(a1, a2,
|
||||
new Vector2(rect.X, rect.Y),
|
||||
new Vector2(rect.Right, rect.Y),
|
||||
true);
|
||||
|
||||
if (intersection != null) intersections.Add((Vector2)intersection);
|
||||
|
||||
intersection = GetAxisAlignedLineIntersection(a1, a2,
|
||||
new Vector2(rect.X, rect.Y - rect.Height),
|
||||
new Vector2(rect.Right, rect.Y - rect.Height),
|
||||
true);
|
||||
|
||||
if (intersection != null) intersections.Add((Vector2)intersection);
|
||||
|
||||
intersection = GetAxisAlignedLineIntersection(a1, a2,
|
||||
new Vector2(rect.X, rect.Y),
|
||||
new Vector2(rect.X, rect.Y - rect.Height),
|
||||
false);
|
||||
|
||||
if (intersection != null) intersections.Add((Vector2)intersection);
|
||||
|
||||
intersection = GetAxisAlignedLineIntersection(a1, a2,
|
||||
new Vector2(rect.Right, rect.Y),
|
||||
new Vector2(rect.Right, rect.Y - rect.Height),
|
||||
false);
|
||||
|
||||
if (intersection != null) intersections.Add((Vector2)intersection);
|
||||
|
||||
return intersections;
|
||||
}*/
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the intersections between a line (either infinite or a line segment) and a circle
|
||||
/// </summary>
|
||||
/// <param name="circlePos">Center of the circle</param>
|
||||
/// <param name="radius">Radius of the circle</param>
|
||||
/// <param name="point1">1st point on the line</param>
|
||||
/// <param name="point2">2nd point on the line</param>
|
||||
/// <param name="isLineSegment">Is the line a segment or infinite</param>
|
||||
/// <returns>The number of intersections</returns>
|
||||
public static int GetLineCircleIntersections(Vector2 circlePos, float radius,
|
||||
Vector2 point1, Vector2 point2, bool isLineSegment, out Vector2? intersection1, out Vector2? intersection2)
|
||||
{
|
||||
float dx, dy, A, B, C, det;
|
||||
|
||||
dx = point2.X - point1.X;
|
||||
dy = point2.Y - point1.Y;
|
||||
|
||||
A = dx * dx + dy * dy;
|
||||
B = 2 * (dx * (point1.X - circlePos.X) + dy * (point1.Y - circlePos.Y));
|
||||
C = (point1.X - circlePos.X) * (point1.X - circlePos.X) + (point1.Y - circlePos.Y) * (point1.Y - circlePos.Y) - radius * radius;
|
||||
|
||||
det = B * B - 4 * A * C;
|
||||
if ((A <= 0.0000001) || (det < 0))
|
||||
{
|
||||
// No real solutions.
|
||||
intersection1 = null;
|
||||
intersection2 = null;
|
||||
return 0;
|
||||
}
|
||||
else if (det == 0)
|
||||
{
|
||||
// One solution.
|
||||
float t = -B / (2 * A);
|
||||
intersection1 = new Vector2(point1.X + t * dx, point1.Y + t * dy);
|
||||
intersection2 = null;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Two solutions.
|
||||
float t1 = (float)((-B + Math.Sqrt(det)) / (2 * A));
|
||||
float t2 = (float)((-B - Math.Sqrt(det)) / (2 * A));
|
||||
|
||||
//if the line is not infinite, we need to check if the intersections are on the segment
|
||||
if (isLineSegment)
|
||||
{
|
||||
if (t1 >= 0 && t1 <= 1.0f)
|
||||
{
|
||||
intersection1 = point1 + new Vector2(dx, dy) * t1;
|
||||
if (t2 >= 0 && t2 <= 1.0f)
|
||||
{
|
||||
//both intersections on the segment
|
||||
intersection2 = point1 + new Vector2(dx, dy) * t2;
|
||||
return 2;
|
||||
|
||||
}
|
||||
//only the first intersection is on the segment
|
||||
intersection2 = null;
|
||||
return 1;
|
||||
}
|
||||
else if (t2 >= 0 && t2 <= 1.0f)
|
||||
{
|
||||
//only the second intersection is on the segment
|
||||
intersection1 = point1 + new Vector2(dx, dy) * t2;
|
||||
intersection2 = null;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
//neither is on the segment
|
||||
intersection1 = null;
|
||||
intersection2 = null;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
intersection1 = point1 + new Vector2(dx, dy) * t1;
|
||||
intersection2 = point1 + new Vector2(dx, dy) * t2;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static float LineToPointDistance(Vector2 lineA, Vector2 lineB, Vector2 point)
|
||||
{
|
||||
float xDiff = lineB.X - lineA.X;
|
||||
float yDiff = lineB.Y - lineA.Y;
|
||||
|
||||
if (xDiff == 0 && yDiff == 0)
|
||||
{
|
||||
return Vector2.Distance(lineA, point);
|
||||
}
|
||||
|
||||
return (float)(Math.Abs(xDiff * (lineA.Y - point.Y) - yDiff * (lineA.X - point.X)) /
|
||||
Math.Sqrt(xDiff * xDiff + yDiff * yDiff));
|
||||
}
|
||||
|
||||
public static bool CircleIntersectsRectangle(Vector2 circlePos, float radius, Rectangle rect)
|
||||
{
|
||||
int halfWidth = rect.Width / 2;
|
||||
float xDist = Math.Abs(circlePos.X - (rect.X + halfWidth));
|
||||
if (xDist > halfWidth + radius) { return false; }
|
||||
|
||||
int halfHeight = rect.Height / 2;
|
||||
float yDist = Math.Abs(circlePos.Y - (rect.Y + halfHeight));
|
||||
if (yDist > halfHeight + radius) { return false; }
|
||||
|
||||
if (xDist <= halfWidth || yDist <= halfHeight) { return true; }
|
||||
|
||||
float distSqX = xDist - halfWidth;
|
||||
float distSqY = yDist - halfHeight;
|
||||
|
||||
return distSqX * distSqX + distSqY * distSqY <= radius * radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a point on a circle's circumference
|
||||
/// </summary>
|
||||
/// <param name="center">Center of the circle</param>
|
||||
/// <param name="radius">Radius of the circle</param>
|
||||
/// <param name="angle">Angle (in radians) from the center</param>
|
||||
/// <returns></returns>
|
||||
public static Vector2 GetPointOnCircumference(Vector2 center, float radius, float angle)
|
||||
{
|
||||
return new Vector2(
|
||||
center.X + radius * (float)Math.Cos(angle),
|
||||
center.Y + radius * (float)Math.Sin(angle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific number of evenly distributed points on a circle's circumference
|
||||
/// </summary>
|
||||
/// <param name="center">Center of the circle</param>
|
||||
/// <param name="radius">Radius of the circle</param>
|
||||
/// <param name="points">Number of points to calculate</param>
|
||||
/// <param name="firstAngle">Angle (in radians) of the first point from the center</param>
|
||||
/// <returns></returns>
|
||||
public static Vector2[] GetPointsOnCircumference(Vector2 center, float radius, int points, float firstAngle = 0.0f)
|
||||
{
|
||||
var maxAngle = (float)(2 * Math.PI);
|
||||
var angleStep = maxAngle / points;
|
||||
var coordinates = new Vector2[points];
|
||||
for (int i = 0; i < points; i++)
|
||||
{
|
||||
var angle = firstAngle + (i * angleStep);
|
||||
if (angle > maxAngle) { angle -= maxAngle; }
|
||||
coordinates[i] = GetPointOnCircumference(center, radius, angle);
|
||||
}
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
List<Vector2[]> triangles = new List<Vector2[]>();
|
||||
|
||||
int triangleCount = vertices.Count - 2;
|
||||
|
||||
vertices.Sort(new CompareCCW(center));
|
||||
|
||||
int lastIndex = 1;
|
||||
for (int i = 0; i < triangleCount; i++)
|
||||
{
|
||||
Vector2[] triangleVertices = new Vector2[3];
|
||||
triangleVertices[0] = vertices[0];
|
||||
int k = 1;
|
||||
for (int j = lastIndex; j <= lastIndex + 1; j++)
|
||||
{
|
||||
triangleVertices[k] = vertices[j];
|
||||
k++;
|
||||
}
|
||||
lastIndex += 1;
|
||||
|
||||
triangles.Add(triangleVertices);
|
||||
}
|
||||
|
||||
return triangles;
|
||||
}
|
||||
|
||||
public static List<Vector2> GiftWrap(List<Vector2> points)
|
||||
{
|
||||
if (points.Count == 0) return points;
|
||||
|
||||
Vector2 leftMost = points[0];
|
||||
foreach (Vector2 point in points)
|
||||
{
|
||||
if (point.X < leftMost.X) leftMost = point;
|
||||
}
|
||||
|
||||
List<Vector2> wrappedPoints = new List<Vector2>();
|
||||
|
||||
Vector2 currPoint = leftMost;
|
||||
Vector2 endPoint;
|
||||
do
|
||||
{
|
||||
wrappedPoints.Add(currPoint);
|
||||
endPoint = points[0];
|
||||
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
if (points[i] == currPoint) continue;
|
||||
if (currPoint == endPoint ||
|
||||
MathUtils.VectorOrientation(currPoint, endPoint, points[i]) == -1)
|
||||
{
|
||||
endPoint = points[i];
|
||||
}
|
||||
}
|
||||
|
||||
currPoint = endPoint;
|
||||
|
||||
}
|
||||
while (endPoint != leftMost);
|
||||
|
||||
return wrappedPoints;
|
||||
}
|
||||
|
||||
public static List<Vector2[]> GenerateJaggedLine(Vector2 start, Vector2 end, int iterations, float offsetAmount)
|
||||
{
|
||||
List<Vector2[]> segments = new List<Vector2[]>();
|
||||
|
||||
segments.Add(new Vector2[] { start, end });
|
||||
|
||||
for (int n = 0; n < iterations; n++)
|
||||
{
|
||||
for (int i = 0; i < segments.Count; i++)
|
||||
{
|
||||
Vector2 startSegment = segments[i][0];
|
||||
Vector2 endSegment = segments[i][1];
|
||||
|
||||
segments.RemoveAt(i);
|
||||
|
||||
Vector2 midPoint = (startSegment + endSegment) / 2.0f;
|
||||
|
||||
Vector2 normal = Vector2.Normalize(endSegment - startSegment);
|
||||
normal = new Vector2(-normal.Y, normal.X);
|
||||
midPoint += normal * Rand.Range(-offsetAmount, offsetAmount, Rand.RandSync.Server);
|
||||
|
||||
segments.Insert(i, new Vector2[] { startSegment, midPoint });
|
||||
segments.Insert(i + 1, new Vector2[] { midPoint, endSegment });
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
offsetAmount *= 0.5f;
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
// Returns the human-readable file size for an arbitrary, 64-bit file size
|
||||
// The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB"
|
||||
public static string GetBytesReadable(long i)
|
||||
{
|
||||
// Get absolute value
|
||||
long absolute_i = (i < 0 ? -i : i);
|
||||
// Determine the suffix and readable value
|
||||
string suffix;
|
||||
double readable;
|
||||
if (absolute_i >= 0x1000000000000000) // Exabyte
|
||||
{
|
||||
suffix = "EB";
|
||||
readable = (i >> 50);
|
||||
}
|
||||
else if (absolute_i >= 0x4000000000000) // Petabyte
|
||||
{
|
||||
suffix = "PB";
|
||||
readable = (i >> 40);
|
||||
}
|
||||
else if (absolute_i >= 0x10000000000) // Terabyte
|
||||
{
|
||||
suffix = "TB";
|
||||
readable = (i >> 30);
|
||||
}
|
||||
else if (absolute_i >= 0x40000000) // Gigabyte
|
||||
{
|
||||
suffix = "GB";
|
||||
readable = (i >> 20);
|
||||
}
|
||||
else if (absolute_i >= 0x100000) // Megabyte
|
||||
{
|
||||
suffix = "MB";
|
||||
readable = (i >> 10);
|
||||
}
|
||||
else if (absolute_i >= 0x400) // Kilobyte
|
||||
{
|
||||
suffix = "KB";
|
||||
readable = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
return i.ToString("0 B"); // Byte
|
||||
}
|
||||
// Divide by 1024 to get fractional value
|
||||
readable = (readable / 1024);
|
||||
// Return formatted number with suffix
|
||||
return readable.ToString("0.# ") + suffix;
|
||||
}
|
||||
|
||||
public static void SplitRectanglesHorizontal(List<Rectangle> rects, Vector2 point)
|
||||
{
|
||||
for (int i = 0; i < rects.Count; i++)
|
||||
{
|
||||
if (point.Y > rects[i].Y && point.Y < rects[i].Y + rects[i].Height)
|
||||
{
|
||||
Rectangle rect1 = rects[i];
|
||||
Rectangle rect2 = rects[i];
|
||||
|
||||
rect1.Height = (int)(point.Y - rects[i].Y);
|
||||
|
||||
rect2.Height = rects[i].Height - rect1.Height;
|
||||
rect2.Y = rect1.Y + rect1.Height;
|
||||
rects[i] = rect1;
|
||||
rects.Insert(i + 1, rect2); i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SplitRectanglesVertical(List<Rectangle> rects, Vector2 point)
|
||||
{
|
||||
for (int i = 0; i < rects.Count; i++)
|
||||
{
|
||||
if (point.X>rects[i].X && point.X<rects[i].X+rects[i].Width)
|
||||
{
|
||||
Rectangle rect1 = rects[i];
|
||||
Rectangle rect2 = rects[i];
|
||||
|
||||
rect1.Width = (int)(point.X-rects[i].X);
|
||||
|
||||
rect2.Width = rects[i].Width - rect1.Width;
|
||||
rect2.X = rect1.X + rect1.Width;
|
||||
rects[i] = rect1;
|
||||
rects.Insert(i + 1, rect2); i++;
|
||||
}
|
||||
}
|
||||
|
||||
/*for (int i = 0; i < rects.Count; i++)
|
||||
{
|
||||
if (rects[i].Width <= 0 || rects[i].Height <= 0)
|
||||
{
|
||||
rects.RemoveAt(i); i--;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Float comparison. Note that may still fail in some cases.
|
||||
/// </summary>
|
||||
// References:
|
||||
// http://floating-point-gui.de/errors/comparison/
|
||||
// https://stackoverflow.com/questions/3874627/floating-point-comparison-functions-for-c-sharp
|
||||
public static bool NearlyEqual(float a, float b, float epsilon = 0.0001f)
|
||||
{
|
||||
float diff = Math.Abs(a - b);
|
||||
if (a == b)
|
||||
{
|
||||
// shortcut, handles infinities
|
||||
return true;
|
||||
}
|
||||
else if (a == 0 || b == 0 || diff < float.Epsilon)
|
||||
{
|
||||
// a or b is zero or both are extremely close to it
|
||||
// relative error is less meaningful here
|
||||
return diff < epsilon;
|
||||
}
|
||||
else
|
||||
{
|
||||
// use relative error
|
||||
return diff / (Math.Abs(a) + Math.Abs(b)) < epsilon;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Float comparison. Note that may still fail in some cases.
|
||||
/// </summary>
|
||||
public static bool NearlyEqual(Vector2 a, Vector2 b, float epsilon = 0.0001f)
|
||||
{
|
||||
return NearlyEqual(a.X, b.X, epsilon) && NearlyEqual(a.Y, b.Y, epsilon);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a position in a curve.
|
||||
/// </summary>
|
||||
public static Vector2 Bezier(Vector2 start, Vector2 control, Vector2 end, float t)
|
||||
{
|
||||
return Pow(1 - t, 2) * start + 2 * t * (1 - t) * control + Pow(t, 2) * end;
|
||||
}
|
||||
|
||||
public static float Pow(float f, float p)
|
||||
{
|
||||
return (float)Math.Pow(f, p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates a point in 2d space around another point.
|
||||
/// Modified from:
|
||||
/// http://www.gamefromscratch.com/post/2012/11/24/GameDev-math-recipes-Rotating-one-point-around-another-point.aspx
|
||||
/// </summary>
|
||||
public static Vector2 RotatePointAroundTarget(Vector2 point, Vector2 target, float degrees, bool clockWise = true)
|
||||
{
|
||||
// (Math.PI / 180) * degrees
|
||||
var angle = MathHelper.ToRadians(degrees);
|
||||
var sin = Math.Sin(angle);
|
||||
var cos = Math.Cos(angle);
|
||||
if (!clockWise)
|
||||
{
|
||||
sin = -sin;
|
||||
}
|
||||
Vector2 dir = point - target;
|
||||
var x = (cos * dir.X) - (sin * dir.Y) + target.X;
|
||||
var y = (sin * dir.X) + (cos * dir.Y) + target.Y;
|
||||
return new Vector2((float)x, (float)y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates a point in 2d space around the origin
|
||||
/// </summary>
|
||||
public static Vector2 RotatePoint(Vector2 point, float radians)
|
||||
{
|
||||
var sin = Math.Sin(radians);
|
||||
var cos = Math.Cos(radians);
|
||||
var x = (cos * point.X) - (sin * point.Y);
|
||||
var y = (sin * point.X) + (cos * point.Y);
|
||||
return new Vector2((float)x, (float)y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the corners of an imaginary rectangle.
|
||||
/// Unlike the XNA rectangle, this can be rotated with the up parameter.
|
||||
/// </summary>
|
||||
public static Vector2[] GetImaginaryRect(Vector2 up, Vector2 center, Vector2 size)
|
||||
{
|
||||
return GetImaginaryRect(new Vector2[4], up, center, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the corners of an imaginary rectangle.
|
||||
/// Unlike the XNA Rectangle, this can be rotated with the up parameter.
|
||||
/// </summary>
|
||||
public static Vector2[] GetImaginaryRect(Vector2[] corners, Vector2 up, Vector2 center, Vector2 size)
|
||||
{
|
||||
if (corners.Length != 4)
|
||||
{
|
||||
throw new Exception("Invalid length for the corners array. Must be 4.");
|
||||
}
|
||||
Vector2 halfSize = size / 2;
|
||||
Vector2 left = up.Right();
|
||||
corners[0] = center + up * halfSize.Y + left * halfSize.X;
|
||||
corners[1] = center + up * halfSize.Y - left * halfSize.X;
|
||||
corners[2] = center - up * halfSize.Y - left * halfSize.X;
|
||||
corners[3] = center - up * halfSize.Y + left * halfSize.X;
|
||||
return corners;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a point is inside a rectangle.
|
||||
/// Unlike the XNA Rectangle, this rectangle might have been rotated.
|
||||
/// For XNA Rectangles, use the Contains instance method.
|
||||
/// </summary>
|
||||
public static bool RectangleContainsPoint(Vector2[] corners, Vector2 point)
|
||||
{
|
||||
if (corners.Length != 4)
|
||||
{
|
||||
throw new Exception("Invalid length of the corners array! Must be 4");
|
||||
}
|
||||
return RectangleContainsPoint(corners[0], corners[1], corners[2], corners[3], point);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a point is inside a rectangle.
|
||||
/// Unlike the XNA Rectangle, this rectangle might have been rotated.
|
||||
/// For XNA Rectangles, use the Contains instance method.
|
||||
/// </summary>
|
||||
public static bool RectangleContainsPoint(Vector2 c1, Vector2 c2, Vector2 c3, Vector2 c4, Vector2 point)
|
||||
{
|
||||
return TriangleContainsPoint(c1, c2, c3, point) || TriangleContainsPoint(c1, c3, c4, point);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slightly modified from https://gamedev.stackexchange.com/questions/110229/how-do-i-efficiently-check-if-a-point-is-inside-a-rotated-rectangle
|
||||
/// </summary>
|
||||
public static bool TriangleContainsPoint(Vector2 c1, Vector2 c2, Vector2 c3, Vector2 point)
|
||||
{
|
||||
// Compute vectors
|
||||
Vector2 v0 = c3 - c1;
|
||||
Vector2 v1 = c2 - c1;
|
||||
Vector2 v2 = point - c1;
|
||||
|
||||
// Compute dot products
|
||||
float dot00 = Vector2.Dot(v0, v0);
|
||||
float dot01 = Vector2.Dot(v0, v1);
|
||||
float dot02 = Vector2.Dot(v0, v2);
|
||||
float dot11 = Vector2.Dot(v1, v1);
|
||||
float dot12 = Vector2.Dot(v1, v2);
|
||||
|
||||
// Compute barycentric coordinates
|
||||
float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
|
||||
float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
|
||||
float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
|
||||
|
||||
// Check if the point is in triangle
|
||||
return u >= 0 && v >= 0 && (u + v) < 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a scalar t from a value v between a range from min to max. Clamped between 0 and 1.
|
||||
/// </summary>
|
||||
public static float InverseLerp(float min, float max, float v)
|
||||
{
|
||||
float diff = max - min;
|
||||
// Ensure that we don't get division by zero exceptions.
|
||||
if (diff == 0) { return v >= max ? 1f : 0f; }
|
||||
return MathHelper.Clamp((v - min) / diff, 0f, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
class CompareCCW : IComparer<Vector2>
|
||||
{
|
||||
private Vector2 center;
|
||||
|
||||
public CompareCCW(Vector2 center)
|
||||
{
|
||||
this.center = center;
|
||||
}
|
||||
public int Compare(Vector2 a, Vector2 b)
|
||||
{
|
||||
return Compare(a, b, center);
|
||||
}
|
||||
|
||||
public static int Compare(Vector2 a, Vector2 b, Vector2 center)
|
||||
{
|
||||
if (a == b) return 0;
|
||||
if (a.X - center.X >= 0 && b.X - center.X < 0) return -1;
|
||||
if (a.X - center.X < 0 && b.X - center.X >= 0) return 1;
|
||||
if (a.X - center.X == 0 && b.X - center.X == 0)
|
||||
{
|
||||
if (a.Y - center.Y >= 0 || b.Y - center.Y >= 0) return Math.Sign(b.Y - a.Y);
|
||||
return Math.Sign(a.Y - b.Y);
|
||||
}
|
||||
|
||||
// compute the cross product of vectors (center -> a) x (center -> b)
|
||||
float det = (a.X - center.X) * (b.Y - center.Y) - (b.X - center.X) * (a.Y - center.Y);
|
||||
if (det < 0) return -1;
|
||||
if (det > 0) return 1;
|
||||
|
||||
// points a and b are on the same line from the center
|
||||
// check which point is closer to the center
|
||||
float d1 = (a.X - center.X) * (a.X - center.X) + (a.Y - center.Y) * (a.Y - center.Y);
|
||||
float d2 = (b.X - center.X) * (b.X - center.X) + (b.Y - center.Y) * (b.Y - center.Y);
|
||||
return Math.Sign(d2 - d1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class Rand
|
||||
{
|
||||
public enum RandSync
|
||||
{
|
||||
Unsynced = -1, //not synced, used for unimportant details like minor particle properties
|
||||
Server = 0, //synced with the server (used for gameplay elements that the players can interact with)
|
||||
ClientOnly = 1 //set to match between clients (used for misc elements that the server doesn't track, but clients want to match anyway)
|
||||
}
|
||||
|
||||
private static Random localRandom = new Random();
|
||||
private static readonly Random[] syncedRandom = new MTRandom[] {
|
||||
new MTRandom(), new MTRandom()
|
||||
};
|
||||
|
||||
public static Random GetRNG(RandSync randSync)
|
||||
{
|
||||
return randSync == RandSync.Unsynced ? localRandom : syncedRandom[(int)randSync];
|
||||
}
|
||||
|
||||
public static void SetLocalRandom(int seed)
|
||||
{
|
||||
localRandom = new Random(seed);
|
||||
}
|
||||
|
||||
public static void SetSyncedSeed(int seed)
|
||||
{
|
||||
syncedRandom[(int)RandSync.Server] = new MTRandom(seed);
|
||||
syncedRandom[(int)RandSync.ClientOnly] = new MTRandom(seed);
|
||||
}
|
||||
|
||||
public static float Range(float minimum, float maximum, RandSync sync=RandSync.Unsynced)
|
||||
{
|
||||
return (float)(sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).NextDouble() * (maximum - minimum) + minimum;
|
||||
}
|
||||
|
||||
public static double Range(double minimum, double maximum, RandSync sync = RandSync.Unsynced)
|
||||
{
|
||||
return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).NextDouble() * (maximum - minimum) + minimum;
|
||||
}
|
||||
|
||||
public static int Range(int minimum, int maximum, RandSync sync = RandSync.Unsynced)
|
||||
{
|
||||
return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).Next(maximum - minimum) + minimum;
|
||||
}
|
||||
|
||||
public static int Int(int max, RandSync sync = RandSync.Unsynced)
|
||||
{
|
||||
return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).Next(max);
|
||||
}
|
||||
|
||||
public static Vector2 Vector(float length, RandSync sync = RandSync.Unsynced)
|
||||
{
|
||||
Vector2 randomVector = new Vector2(Range(-1.0f, 1.0f, sync), Range(-1.0f, 1.0f, sync));
|
||||
|
||||
if (randomVector.LengthSquared() < 0.001f) return new Vector2(0.0f, length);
|
||||
|
||||
return Vector2.Normalize(randomVector) * length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Random float between 0 and 1.
|
||||
/// </summary>
|
||||
public static float Value(RandSync sync = RandSync.Unsynced)
|
||||
{
|
||||
return Range(0f, 1f, sync);
|
||||
}
|
||||
|
||||
public static Color Color(bool randomAlpha = false, RandSync sync = RandSync.Unsynced)
|
||||
{
|
||||
if (randomAlpha)
|
||||
{
|
||||
return new Color(Value(sync), Value(sync), Value(sync), Value(sync));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Color(Value(sync), Value(sync), Value(sync));
|
||||
}
|
||||
}
|
||||
|
||||
public static DoubleVector2 Vector(double length, RandSync sync = RandSync.Unsynced)
|
||||
{
|
||||
double x = Range(-1.0, 1.0, sync);
|
||||
double y = Range(-1.0, 1.0, sync);
|
||||
|
||||
double len = Math.Sqrt(x * x + y * y);
|
||||
if (len < 0.00001) return new DoubleVector2(0.0, length);
|
||||
|
||||
return new DoubleVector2(x / len * length, y / len * length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class SaveUtil
|
||||
{
|
||||
private static readonly string LegacySaveFolder = Path.Combine("Data", "Saves");
|
||||
private static readonly string LegacyMultiplayerSaveFolder = Path.Combine(LegacySaveFolder, "Multiplayer");
|
||||
|
||||
#if OSX
|
||||
//"/*user*/Library/Application Support/Daedalic Entertainment GmbH/" on Mac
|
||||
public static string SaveFolder = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
|
||||
"Library",
|
||||
"Application Support",
|
||||
"Daedalic Entertainment GmbH",
|
||||
"Barotrauma");
|
||||
#else
|
||||
//"C:/Users/*user*/AppData/Local/Daedalic Entertainment GmbH/" on Windows
|
||||
//"/home/*user*/.local/share/Daedalic Entertainment GmbH/" on Linux
|
||||
public static string SaveFolder = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Daedalic Entertainment GmbH",
|
||||
"Barotrauma");
|
||||
#endif
|
||||
|
||||
public static string MultiplayerSaveFolder = Path.Combine(SaveFolder, "Multiplayer");
|
||||
|
||||
public static readonly string SubmarineDownloadFolder = Path.Combine("Submarines", "Downloaded");
|
||||
public static readonly string CampaignDownloadFolder = Path.Combine("Data", "Saves", "Multiplayer");
|
||||
|
||||
public delegate void ProgressDelegate(string sMessage);
|
||||
|
||||
public static string TempPath
|
||||
{
|
||||
#if SERVER
|
||||
get { return Path.Combine(SaveFolder, "temp_server"); }
|
||||
#else
|
||||
get { return Path.Combine(SaveFolder, "temp"); }
|
||||
#endif
|
||||
}
|
||||
|
||||
public enum SaveType
|
||||
{
|
||||
Singleplayer,
|
||||
Multiplayer
|
||||
}
|
||||
|
||||
public static void SaveGame(string filePath)
|
||||
{
|
||||
DebugConsole.Log("Saving the game to: " + filePath);
|
||||
Directory.CreateDirectory(TempPath);
|
||||
try
|
||||
{
|
||||
ClearFolder(TempPath, new string[] { GameMain.GameSession.Submarine.FilePath });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to clear folder", e);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
string subPath = Path.Combine(TempPath, Submarine.MainSub.Name + ".sub");
|
||||
if (Submarine.Loaded.Contains(Submarine.MainSub))
|
||||
{
|
||||
Submarine.MainSub.FilePath = subPath;
|
||||
Submarine.MainSub.SaveAs(Submarine.MainSub.FilePath);
|
||||
}
|
||||
else if (Submarine.MainSub.FilePath != subPath)
|
||||
{
|
||||
if (File.Exists(subPath))
|
||||
{
|
||||
File.Delete(subPath);
|
||||
}
|
||||
File.Copy(Submarine.MainSub.FilePath, subPath);
|
||||
Submarine.MainSub.FilePath = subPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error saving submarine", e);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
GameMain.GameSession.Save(Path.Combine(TempPath, "gamesession.xml"));
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error saving gamesession", e);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
CompressDirectory(TempPath, filePath, null);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error compressing save file", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadGame(string filePath)
|
||||
{
|
||||
DebugConsole.Log("Loading save file: " + filePath);
|
||||
DecompressToDirectory(filePath, TempPath, null);
|
||||
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
|
||||
if (doc == null) { return; }
|
||||
|
||||
string subPath = Path.Combine(TempPath, doc.Root.GetAttributeString("submarine", "")) + ".sub";
|
||||
Submarine selectedSub = new Submarine(subPath, "");
|
||||
GameMain.GameSession = new GameSession(selectedSub, filePath, doc);
|
||||
}
|
||||
|
||||
public static void LoadGame(string filePath, GameSession gameSession)
|
||||
{
|
||||
DebugConsole.Log("Loading save file for an existing game session (" + filePath + ")");
|
||||
DecompressToDirectory(filePath, TempPath, null);
|
||||
XDocument doc = XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
|
||||
if (doc == null) { return; }
|
||||
gameSession.Load(doc.Root);
|
||||
}
|
||||
|
||||
public static XDocument LoadGameSessionDoc(string filePath)
|
||||
{
|
||||
DebugConsole.Log("Loading game session doc: " + filePath);
|
||||
try
|
||||
{
|
||||
DecompressToDirectory(filePath, TempPath, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error decompressing " + filePath, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return XMLExtensions.TryLoadXml(Path.Combine(TempPath, "gamesession.xml"));
|
||||
}
|
||||
|
||||
public static void DeleteSave(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("ERROR: deleting save file \"" + filePath + "\" failed.", e);
|
||||
}
|
||||
|
||||
//deleting a multiplayer save file -> also delete character data
|
||||
if (Path.GetFullPath(Path.GetDirectoryName(filePath)).Equals(Path.GetFullPath(MultiplayerSaveFolder)))
|
||||
{
|
||||
string characterDataSavePath = MultiPlayerCampaign.GetCharacterDataSavePath(filePath);
|
||||
if (File.Exists(characterDataSavePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(characterDataSavePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("ERROR: deleting character data file \"" + characterDataSavePath + "\" failed.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetSavePath(SaveType saveType, string saveName)
|
||||
{
|
||||
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
|
||||
return Path.Combine(folder, saveName);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetSaveFiles(SaveType saveType)
|
||||
{
|
||||
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
DebugConsole.Log("Save folder \"" + folder + " not found! Attempting to create a new folder...");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to create the folder \"" + folder + "\"!", e);
|
||||
}
|
||||
}
|
||||
|
||||
List<string> files = Directory.GetFiles(folder, "*.save").ToList();
|
||||
string legacyFolder = saveType == SaveType.Singleplayer ? LegacySaveFolder : LegacyMultiplayerSaveFolder;
|
||||
if (Directory.Exists(legacyFolder))
|
||||
{
|
||||
files.AddRange(Directory.GetFiles(legacyFolder, "*.save"));
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public static string CreateSavePath(SaveType saveType, string fileName = "Save_Default")
|
||||
{
|
||||
fileName = ToolBox.RemoveInvalidFileNameChars(fileName);
|
||||
|
||||
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
|
||||
if (fileName == "Save_Default")
|
||||
{
|
||||
fileName = TextManager.Get("SaveFile.DefaultName", true);
|
||||
if (fileName.Length == 0) fileName = "Save";
|
||||
}
|
||||
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
DebugConsole.Log("Save folder \"" + folder + "\" not found. Created new folder");
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
|
||||
string extension = ".save";
|
||||
string pathWithoutExtension = Path.Combine(folder, fileName);
|
||||
|
||||
if (!File.Exists(pathWithoutExtension + extension))
|
||||
{
|
||||
return pathWithoutExtension + extension;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (File.Exists(pathWithoutExtension + " " + i + extension))
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
||||
return pathWithoutExtension + " " + i + extension;
|
||||
}
|
||||
|
||||
public static void CompressStringToFile(string fileName, string value)
|
||||
{
|
||||
// A.
|
||||
// Write string to temporary file.
|
||||
string temp = Path.GetTempFileName();
|
||||
File.WriteAllText(temp, value);
|
||||
|
||||
// B.
|
||||
// Read file into byte array buffer.
|
||||
byte[] b;
|
||||
using (FileStream f = new FileStream(temp, FileMode.Open))
|
||||
{
|
||||
b = new byte[f.Length];
|
||||
f.Read(b, 0, (int)f.Length);
|
||||
}
|
||||
|
||||
// C.
|
||||
// Use GZipStream to write compressed bytes to target file.
|
||||
using (FileStream f2 = new FileStream(fileName, FileMode.Create))
|
||||
using (GZipStream gz = new GZipStream(f2, CompressionMode.Compress, false))
|
||||
{
|
||||
gz.Write(b, 0, b.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CompressFile(string sDir, string sRelativePath, GZipStream zipStream)
|
||||
{
|
||||
//Compress file name
|
||||
char[] chars = sRelativePath.ToCharArray();
|
||||
zipStream.Write(BitConverter.GetBytes(chars.Length), 0, sizeof(int));
|
||||
foreach (char c in chars)
|
||||
zipStream.Write(BitConverter.GetBytes(c), 0, sizeof(char));
|
||||
|
||||
//Compress file content
|
||||
byte[] bytes = File.ReadAllBytes(Path.Combine(sDir, sRelativePath));
|
||||
zipStream.Write(BitConverter.GetBytes(bytes.Length), 0, sizeof(int));
|
||||
zipStream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
public static void CompressDirectory(string sInDir, string sOutFile, ProgressDelegate progress)
|
||||
{
|
||||
string[] sFiles = Directory.GetFiles(sInDir, "*.*", SearchOption.AllDirectories);
|
||||
int iDirLen = sInDir[sInDir.Length - 1] == Path.DirectorySeparatorChar ? sInDir.Length : sInDir.Length + 1;
|
||||
|
||||
using (FileStream outFile = new FileStream(sOutFile, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
using (GZipStream str = new GZipStream(outFile, CompressionMode.Compress))
|
||||
foreach (string sFilePath in sFiles)
|
||||
{
|
||||
string sRelativePath = sFilePath.Substring(iDirLen);
|
||||
progress?.Invoke(sRelativePath);
|
||||
CompressFile(sInDir, sRelativePath, str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Stream DecompressFiletoStream(string fileName)
|
||||
{
|
||||
using (FileStream originalFileStream = new FileStream(fileName, FileMode.Open))
|
||||
{
|
||||
MemoryStream decompressedFileStream = new MemoryStream();
|
||||
|
||||
using (GZipStream decompressionStream = new GZipStream(originalFileStream, CompressionMode.Decompress))
|
||||
{
|
||||
decompressionStream.CopyTo(decompressedFileStream);
|
||||
return decompressedFileStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DecompressFile(string sDir, GZipStream zipStream, ProgressDelegate progress)
|
||||
{
|
||||
//Decompress file name
|
||||
byte[] bytes = new byte[sizeof(int)];
|
||||
int Readed = zipStream.Read(bytes, 0, sizeof(int));
|
||||
if (Readed < sizeof(int))
|
||||
return false;
|
||||
|
||||
int iNameLen = BitConverter.ToInt32(bytes, 0);
|
||||
if (iNameLen > 255)
|
||||
{
|
||||
throw new Exception("Failed to decompress \"" + sDir + "\" (file name length > 255). The file may be corrupted.");
|
||||
}
|
||||
|
||||
bytes = new byte[sizeof(char)];
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < iNameLen; i++)
|
||||
{
|
||||
zipStream.Read(bytes, 0, sizeof(char));
|
||||
char c = BitConverter.ToChar(bytes, 0);
|
||||
sb.Append(c);
|
||||
}
|
||||
string sFileName = sb.ToString();
|
||||
progress?.Invoke(sFileName);
|
||||
|
||||
//Decompress file content
|
||||
bytes = new byte[sizeof(int)];
|
||||
zipStream.Read(bytes, 0, sizeof(int));
|
||||
int iFileLen = BitConverter.ToInt32(bytes, 0);
|
||||
|
||||
bytes = new byte[iFileLen];
|
||||
zipStream.Read(bytes, 0, bytes.Length);
|
||||
|
||||
string sFilePath = Path.Combine(sDir, sFileName);
|
||||
string sFinalDir = Path.GetDirectoryName(sFilePath);
|
||||
if (!Directory.Exists(sFinalDir))
|
||||
Directory.CreateDirectory(sFinalDir);
|
||||
|
||||
int maxRetries = 4;
|
||||
for (int i = 0; i <= maxRetries; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (FileStream outFile = new FileStream(sFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
outFile.Write(bytes, 0, iFileLen);
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (i >= maxRetries || !File.Exists(sFilePath)) { throw; }
|
||||
DebugConsole.NewMessage("Failed decompress file \"" + sFilePath + "\" {" + e.Message + "}, retrying in 250 ms...", Color.Red);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void DecompressToDirectory(string sCompressedFile, string sDir, ProgressDelegate progress)
|
||||
{
|
||||
DebugConsole.Log("Decompressing " + sCompressedFile + " to " + sDir + "...");
|
||||
int maxRetries = 4;
|
||||
for (int i = 0; i <= maxRetries; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (FileStream inFile = new FileStream(sCompressedFile, FileMode.Open, FileAccess.Read, FileShare.None))
|
||||
using (GZipStream zipStream = new GZipStream(inFile, CompressionMode.Decompress, true))
|
||||
while (DecompressFile(sDir, zipStream, progress)) { };
|
||||
|
||||
break;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (i >= maxRetries || !File.Exists(sCompressedFile)) { throw; }
|
||||
DebugConsole.NewMessage("Failed decompress file \"" + sCompressedFile + "\" {" + e.Message + "}, retrying in 250 ms...", Color.Red);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CopyFolder(string sourceDirName, string destDirName, bool copySubDirs, bool overwriteExisting = false)
|
||||
{
|
||||
// Get the subdirectories for the specified directory.
|
||||
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
|
||||
|
||||
if (!dir.Exists)
|
||||
{
|
||||
throw new DirectoryNotFoundException(
|
||||
"Source directory does not exist or could not be found: "
|
||||
+ sourceDirName);
|
||||
}
|
||||
|
||||
DirectoryInfo[] dirs = dir.GetDirectories();
|
||||
// If the destination directory doesn't exist, create it.
|
||||
if (!Directory.Exists(destDirName))
|
||||
{
|
||||
Directory.CreateDirectory(destDirName);
|
||||
}
|
||||
|
||||
// Get the files in the directory and copy them to the new location.
|
||||
FileInfo[] files = dir.GetFiles();
|
||||
foreach (FileInfo file in files)
|
||||
{
|
||||
string tempPath = Path.Combine(destDirName, file.Name);
|
||||
file.CopyTo(tempPath, overwriteExisting);
|
||||
}
|
||||
|
||||
// If copying subdirectories, copy them and their contents to new location.
|
||||
if (copySubDirs)
|
||||
{
|
||||
foreach (DirectoryInfo subdir in dirs)
|
||||
{
|
||||
string tempPath = Path.Combine(destDirName, subdir.Name);
|
||||
CopyFolder(subdir.FullName, tempPath, copySubDirs, overwriteExisting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CleanUnnecessarySaveFiles()
|
||||
{
|
||||
if (Directory.Exists(CampaignDownloadFolder))
|
||||
{
|
||||
ClearFolder(CampaignDownloadFolder);
|
||||
Directory.Delete(CampaignDownloadFolder);
|
||||
}
|
||||
if (Directory.Exists(TempPath))
|
||||
{
|
||||
ClearFolder(TempPath);
|
||||
Directory.Delete(TempPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearFolder(string folderName, string[] ignoredFileNames = null)
|
||||
{
|
||||
DirectoryInfo dir = new DirectoryInfo(folderName);
|
||||
|
||||
foreach (FileInfo fi in dir.GetFiles())
|
||||
{
|
||||
if (ignoredFileNames != null)
|
||||
{
|
||||
bool ignore = false;
|
||||
foreach (string ignoredFile in ignoredFileNames)
|
||||
{
|
||||
if (Path.GetFileName(fi.FullName).Equals(Path.GetFileName(ignoredFile)))
|
||||
{
|
||||
ignore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ignore) continue;
|
||||
}
|
||||
fi.IsReadOnly = false;
|
||||
fi.Delete();
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo di in dir.GetDirectories())
|
||||
{
|
||||
ClearFolder(di.FullName, ignoredFileNames);
|
||||
int maxRetries = 4;
|
||||
for (int i = 0; i <= maxRetries; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
di.Delete();
|
||||
break;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
if (i >= maxRetries) { throw; }
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class TaskPool
|
||||
{
|
||||
private struct TaskAction
|
||||
{
|
||||
public Task Task;
|
||||
public Action<Task, object> OnCompletion;
|
||||
public object UserData;
|
||||
}
|
||||
|
||||
private static List<TaskAction> taskActions = new List<TaskAction>();
|
||||
|
||||
private static void AddInternal(Task task, Action<Task, object> onCompletion, object userdata)
|
||||
{
|
||||
lock (taskActions)
|
||||
{
|
||||
taskActions.Add(new TaskAction() { Task = task, OnCompletion = onCompletion, UserData = userdata });
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(Task task, Action<Task> onCompletion)
|
||||
{
|
||||
AddInternal(task, (Task t, object obj) => { onCompletion(t); }, null);
|
||||
}
|
||||
|
||||
public static void Add<U>(Task task, U userdata, Action<Task, U> onCompletion) where U : class
|
||||
{
|
||||
AddInternal(task, (Task t, object obj) => { onCompletion(t, (U)obj); }, userdata);
|
||||
}
|
||||
|
||||
public static void Add<T>(Task<T> task, Action<Task<T>> onCompletion)
|
||||
{
|
||||
AddInternal(task, (Task t, object obj) => { onCompletion((Task<T>)t); }, null);
|
||||
}
|
||||
|
||||
public static void Add<T,U>(Task<T> task, U userdata, Action<Task<T>, U> onCompletion) where U : class
|
||||
{
|
||||
AddInternal(task, (Task t, object obj) => { onCompletion((Task<T>)t, (U)obj); }, userdata);
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
lock (taskActions)
|
||||
{
|
||||
for (int i = 0; i < taskActions.Count; i++)
|
||||
{
|
||||
if (taskActions[i].Task.IsCompleted)
|
||||
{
|
||||
taskActions[i].OnCompletion?.Invoke(taskActions[i].Task, taskActions[i].UserData);
|
||||
taskActions.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void PrintTaskExceptions(Task task, string msg)
|
||||
{
|
||||
DebugConsole.ThrowError(msg);
|
||||
foreach (Exception e in task.Exception.InnerExceptions)
|
||||
{
|
||||
DebugConsole.ThrowError(e.Message + "\n" + e.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Networking;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class Pair<T1, T2>
|
||||
{
|
||||
public T1 First { get; set; }
|
||||
public T2 Second { get; set; }
|
||||
|
||||
public Pair(T1 first, T2 second)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
}
|
||||
}
|
||||
|
||||
public class Triplet<T1, T2, T3>
|
||||
{
|
||||
public T1 First { get; set; }
|
||||
public T2 Second { get; set; }
|
||||
public T3 Third { get; set; }
|
||||
|
||||
public Triplet(T1 first, T2 second, T3 third)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
Third = third;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class ToolBox
|
||||
{
|
||||
static internal class Epoch
|
||||
{
|
||||
private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current Unix Epoch (Coordinated Universal Time )
|
||||
/// </summary>
|
||||
public static int NowUTC
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)(DateTime.UtcNow.Subtract(epoch).TotalSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current Unix Epoch (user's current time)
|
||||
/// </summary>
|
||||
public static int NowLocal
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)(DateTime.Now.Subtract(epoch).TotalSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert an epoch to a datetime
|
||||
/// </summary>
|
||||
public static DateTime ToDateTime(decimal unixTime)
|
||||
{
|
||||
return epoch.AddSeconds((long)unixTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a DateTime to a unix time
|
||||
/// </summary>
|
||||
public static uint FromDateTime(DateTime dt)
|
||||
{
|
||||
return (uint)(dt.Subtract(epoch).TotalSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsProperFilenameCase(string filename)
|
||||
{
|
||||
//File case only matters on Linux where the filesystem is case-sensitive, so we don't need these errors in release builds.
|
||||
//It also seems Path.GetFullPath may return a path with an incorrect case on Windows when the case of any of the game's
|
||||
//parent folders have been changed.
|
||||
#if !DEBUG && !LINUX
|
||||
return true;
|
||||
#endif
|
||||
|
||||
CorrectFilenameCase(filename, out bool corrected);
|
||||
|
||||
return !corrected;
|
||||
}
|
||||
|
||||
public static string CorrectFilenameCase(string filename, out bool corrected)
|
||||
{
|
||||
char[] delimiters = { '/', '\\' };
|
||||
string[] subDirs = filename.Split(delimiters);
|
||||
string originalFilename = filename;
|
||||
filename = "";
|
||||
corrected = false;
|
||||
|
||||
#if !WINDOWS
|
||||
if (File.Exists(originalFilename))
|
||||
{
|
||||
return originalFilename;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Path.IsPathRooted(originalFilename))
|
||||
{
|
||||
return originalFilename; //assume that rooted paths have correct case since these are generated by the game
|
||||
}
|
||||
|
||||
for (int i = 0; i < subDirs.Length; i++)
|
||||
{
|
||||
if (i == subDirs.Length - 1 && string.IsNullOrEmpty(subDirs[i]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
string enumPath = string.IsNullOrEmpty(filename) ? "./" : filename;
|
||||
List<string> filePaths = Directory.GetFileSystemEntries(enumPath).Select(s => Path.GetFileName(s)).ToList();
|
||||
if (filePaths.Any(s => s.Equals(subDirs[i], StringComparison.Ordinal)))
|
||||
{
|
||||
filename += subDirs[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<string> correctedPaths = filePaths.Where(s => s.Equals(subDirs[i], StringComparison.OrdinalIgnoreCase));
|
||||
if (correctedPaths.Any())
|
||||
{
|
||||
corrected = true;
|
||||
filename += correctedPaths.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
//DebugConsole.ThrowError($"File \"{originalFilename}\" not found!");
|
||||
corrected = false;
|
||||
return originalFilename;
|
||||
}
|
||||
}
|
||||
if (i < subDirs.Length - 1) { filename += "/"; }
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
public static string RemoveInvalidFileNameChars(string fileName)
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars().Concat(new char[] {':', ';'});
|
||||
foreach (char invalidChar in invalidChars)
|
||||
{
|
||||
fileName = fileName.Replace(invalidChar.ToString(), "");
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private static System.Text.RegularExpressions.Regex removeBBCodeRegex =
|
||||
new System.Text.RegularExpressions.Regex(@"\[\/?(?:b|i|u|url|quote|code|img|color|size)*?.*?\]");
|
||||
|
||||
public static string RemoveBBCodeTags(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str)) { return str; }
|
||||
return removeBBCodeRegex.Replace(str, "");
|
||||
}
|
||||
|
||||
public static string LimitString(string str, int maxCharacters)
|
||||
{
|
||||
if (str == null || maxCharacters < 0) return null;
|
||||
|
||||
if (maxCharacters < 4 || str.Length <= maxCharacters) return str;
|
||||
|
||||
return str.Substring(0, maxCharacters - 3) + "...";
|
||||
}
|
||||
|
||||
public static string RandomSeed(int length)
|
||||
{
|
||||
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
return new string(
|
||||
Enumerable.Repeat(chars, length)
|
||||
.Select(s => s[Rand.Int(s.Length)])
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
public static int StringToInt(string str)
|
||||
{
|
||||
str = str.Substring(0, Math.Min(str.Length, 32));
|
||||
|
||||
str = str.PadLeft(4, 'a');
|
||||
|
||||
byte[] asciiBytes = Encoding.ASCII.GetBytes(str);
|
||||
|
||||
for (int i = 4; i < asciiBytes.Length; i++)
|
||||
{
|
||||
asciiBytes[i % 4] ^= asciiBytes[i];
|
||||
}
|
||||
|
||||
return BitConverter.ToInt32(asciiBytes, 0);
|
||||
}
|
||||
/// <summary>
|
||||
/// a method for changing inputtypes with old names to the new ones to ensure backwards compatibility with older subs
|
||||
/// </summary>
|
||||
public static string ConvertInputType(string inputType)
|
||||
{
|
||||
if (inputType == "ActionHit" || inputType == "Action") return "Use";
|
||||
if (inputType == "SecondaryHit" || inputType == "Secondary") return "Aim";
|
||||
|
||||
return inputType;
|
||||
}
|
||||
|
||||
// Convert an RGB value into an HLS value.
|
||||
public static Vector3 RgbToHLS(Vector3 color)
|
||||
{
|
||||
double h, l, s;
|
||||
|
||||
double double_r = color.X;
|
||||
double double_g = color.Y;
|
||||
double double_b = color.Z;
|
||||
|
||||
// Get the maximum and minimum RGB components.
|
||||
double max = double_r;
|
||||
if (max < double_g) max = double_g;
|
||||
if (max < double_b) max = double_b;
|
||||
|
||||
double min = double_r;
|
||||
if (min > double_g) min = double_g;
|
||||
if (min > double_b) min = double_b;
|
||||
|
||||
double diff = max - min;
|
||||
l = (max + min) / 2;
|
||||
if (Math.Abs(diff) < 0.00001)
|
||||
{
|
||||
s = 0;
|
||||
h = 0; // H is really undefined.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (l <= 0.5) s = diff / (max + min);
|
||||
else s = diff / (2 - max - min);
|
||||
|
||||
double r_dist = (max - double_r) / diff;
|
||||
double g_dist = (max - double_g) / diff;
|
||||
double b_dist = (max - double_b) / diff;
|
||||
|
||||
if (double_r == max) h = b_dist - g_dist;
|
||||
else if (double_g == max) h = 2 + r_dist - b_dist;
|
||||
else h = 4 + g_dist - r_dist;
|
||||
|
||||
h = h * 60;
|
||||
if (h < 0) h += 360;
|
||||
}
|
||||
|
||||
return new Vector3((float)h, (float)l, (float)s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the minimum number of single-character edits (i.e. insertions, deletions or substitutions) required to change one string into the other
|
||||
/// </summary>
|
||||
public static int LevenshteinDistance(string s, string t)
|
||||
{
|
||||
int n = s.Length;
|
||||
int m = t.Length;
|
||||
int[,] d = new int[n + 1, m + 1];
|
||||
|
||||
if (n == 0 || m == 0) return 0;
|
||||
|
||||
for (int i = 0; i <= n; d[i, 0] = i++) ;
|
||||
for (int j = 0; j <= m; d[0, j] = j++) ;
|
||||
|
||||
for (int i = 1; i <= n; i++)
|
||||
{
|
||||
for (int j = 1; j <= m; j++)
|
||||
{
|
||||
int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
|
||||
|
||||
d[i, j] = Math.Min(
|
||||
Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
|
||||
d[i - 1, j - 1] + cost);
|
||||
}
|
||||
}
|
||||
|
||||
return d[n, m];
|
||||
}
|
||||
|
||||
public static string SecondsToReadableTime(float seconds)
|
||||
{
|
||||
int s = (int)(seconds % 60.0f);
|
||||
if (seconds < 60.0f)
|
||||
{
|
||||
return TextManager.GetWithVariable("timeformatseconds", "[seconds]", s.ToString());
|
||||
}
|
||||
|
||||
int h = (int)(seconds / (60.0f * 60.0f));
|
||||
int m = (int)((seconds / 60.0f) % 60);
|
||||
|
||||
string text = "";
|
||||
if (h != 0) { text = TextManager.GetWithVariable("timeformathours", "[hours]", h.ToString()); }
|
||||
if (m != 0)
|
||||
{
|
||||
string minutesText = TextManager.GetWithVariable("timeformatminutes", "[minutes]", m.ToString());
|
||||
text = string.IsNullOrEmpty(text) ? minutesText : string.Join(" ", text, minutesText);
|
||||
}
|
||||
if (s != 0)
|
||||
{
|
||||
string secondsText = TextManager.GetWithVariable("timeformatseconds", "[seconds]", s.ToString());
|
||||
text = string.IsNullOrEmpty(text) ? secondsText : string.Join(" ", text, secondsText);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private static Dictionary<string, List<string>> cachedLines = new Dictionary<string, List<string>>();
|
||||
public static string GetRandomLine(string filePath)
|
||||
{
|
||||
List<string> lines;
|
||||
if (cachedLines.ContainsKey(filePath))
|
||||
{
|
||||
lines = cachedLines[filePath];
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
using (StreamReader file = new StreamReader(filePath))
|
||||
{
|
||||
lines = File.ReadLines(filePath).ToList();
|
||||
cachedLines.Add(filePath, lines);
|
||||
if (lines.Count == 0)
|
||||
{
|
||||
DebugConsole.ThrowError("File \"" + filePath + "\" is empty!");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't open file \"" + filePath + "\"!", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
if (lines.Count == 0) return "";
|
||||
return lines[Rand.Range(0, lines.Count, Rand.RandSync.Server)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a number of bits from the buffer and inserts them to a new NetBuffer instance
|
||||
/// </summary>
|
||||
public static IReadMessage ExtractBits(this IReadMessage originalBuffer, int numberOfBits)
|
||||
{
|
||||
var buffer = new ReadWriteMessage();
|
||||
|
||||
for (int i = 0; i < numberOfBits; i++)
|
||||
{
|
||||
bool bit = originalBuffer.ReadBoolean();
|
||||
buffer.Write(bit);
|
||||
}
|
||||
buffer.BitPosition = 0;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static T SelectWeightedRandom<T>(IList<T> objects, IList<float> weights, Rand.RandSync randSync)
|
||||
{
|
||||
return SelectWeightedRandom(objects, weights, Rand.GetRNG(randSync));
|
||||
}
|
||||
|
||||
public static T SelectWeightedRandom<T>(IList<T> objects, IList<float> weights, Random random)
|
||||
{
|
||||
if (objects.Count == 0) return default(T);
|
||||
|
||||
if (objects.Count != weights.Count)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SelectWeightedRandom, number of objects does not match the number of weights.\n" + Environment.StackTrace);
|
||||
return objects[0];
|
||||
}
|
||||
|
||||
float totalWeight = weights.Sum();
|
||||
|
||||
float randomNum = (float)(random.NextDouble() * totalWeight);
|
||||
for (int i = 0; i < objects.Count; i++)
|
||||
{
|
||||
if (randomNum <= weights[i])
|
||||
{
|
||||
return objects[i];
|
||||
}
|
||||
randomNum -= weights[i];
|
||||
}
|
||||
return default(T);
|
||||
}
|
||||
|
||||
public static UInt32 StringToUInt32Hash(string str, MD5 md5)
|
||||
{
|
||||
//calculate key based on MD5 hash instead of string.GetHashCode
|
||||
//to ensure consistent results across platforms
|
||||
byte[] inputBytes = Encoding.ASCII.GetBytes(str);
|
||||
byte[] hash = md5.ComputeHash(inputBytes);
|
||||
|
||||
UInt32 key = (UInt32)((str.Length & 0xff) << 24); //could use more of the hash here instead?
|
||||
key |= (UInt32)(hash[hash.Length - 3] << 16);
|
||||
key |= (UInt32)(hash[hash.Length - 2] << 8);
|
||||
key |= (UInt32)(hash[hash.Length - 1]);
|
||||
|
||||
return key;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a new instance of the class with all properties and fields copied.
|
||||
/// </summary>
|
||||
public static T CreateCopy<T>(this T source, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public) where T : new() => CopyValues(source, new T(), flags);
|
||||
public static T CopyValuesTo<T>(this T source, T target, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public) => CopyValues(source, target, flags);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the values of the source to the destination. May not work, if the source is of higher inheritance class than the destination. Does not work with virtual properties.
|
||||
/// </summary>
|
||||
public static T CopyValues<T>(T source, T destination, BindingFlags flags = BindingFlags.Instance | BindingFlags.Public)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new Exception("Failed to copy object. Source is null.");
|
||||
}
|
||||
if (destination == null)
|
||||
{
|
||||
throw new Exception("Failed to copy object. Destination is null.");
|
||||
}
|
||||
Type type = source.GetType();
|
||||
var properties = type.GetProperties(flags);
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (property.CanWrite)
|
||||
{
|
||||
property.SetValue(destination, property.GetValue(source, null), null);
|
||||
}
|
||||
}
|
||||
var fields = type.GetFields(flags);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
field.SetValue(destination, field.GetValue(source));
|
||||
}
|
||||
// Check that the fields match.Uncomment to apply the test, if in doubt.
|
||||
//if (fields.Any(f => { var value = f.GetValue(destination); return value == null || !value.Equals(f.GetValue(source)); }))
|
||||
//{
|
||||
// throw new Exception("Failed to copy some of the fields.");
|
||||
//}
|
||||
return destination;
|
||||
}
|
||||
|
||||
public static string ByteArrayToString(byte[] ba)
|
||||
{
|
||||
StringBuilder hex = new StringBuilder(ba.Length * 2);
|
||||
foreach (byte b in ba)
|
||||
hex.AppendFormat("{0:x2}", b);
|
||||
return hex.ToString();
|
||||
}
|
||||
|
||||
public static string ConvertAbsoluteToRelativePath(string path)
|
||||
{
|
||||
string[] splitted = path.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
|
||||
string currentFolder = Environment.CurrentDirectory.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Last();
|
||||
// Filter out the current folder -> result is "Content/blaahblaah" or "Mods/blaahblaah" etc.
|
||||
IEnumerable<string> filtered = splitted.SkipWhile(part => part != currentFolder).Skip(1);
|
||||
return string.Join("/", filtered);
|
||||
}
|
||||
|
||||
public static string EscapeCharacters(string str)
|
||||
{
|
||||
return str.Replace("\\", "\\\\").Replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
public static string UnescapeCharacters(string str)
|
||||
{
|
||||
string retVal = "";
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
if (str[i] != '\\')
|
||||
{
|
||||
retVal += str[i];
|
||||
}
|
||||
else if (i+1<str.Length)
|
||||
{
|
||||
if (str[i+1] == '\\')
|
||||
{
|
||||
retVal += "\\";
|
||||
}
|
||||
else if (str[i+1] == '\"')
|
||||
{
|
||||
retVal += "\"";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static string[] SplitCommand(string command)
|
||||
{
|
||||
command = command.Trim();
|
||||
|
||||
List<string> commands = new List<string>();
|
||||
int escape = 0;
|
||||
bool inQuotes = false;
|
||||
string piece = "";
|
||||
|
||||
for (int i = 0; i < command.Length; i++)
|
||||
{
|
||||
if (command[i] == '\\')
|
||||
{
|
||||
if (escape == 0) escape = 2;
|
||||
else piece += '\\';
|
||||
}
|
||||
else if (command[i] == '"')
|
||||
{
|
||||
if (escape == 0) inQuotes = !inQuotes;
|
||||
else piece += '"';
|
||||
}
|
||||
else if (command[i] == ' ' && !inQuotes)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece);
|
||||
piece = "";
|
||||
}
|
||||
else if (escape == 0) piece += command[i];
|
||||
|
||||
if (escape > 0) escape--;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(piece)) commands.Add(piece); //add final piece
|
||||
|
||||
return commands.ToArray();
|
||||
}
|
||||
|
||||
public static void OpenFileWithShell(string filename)
|
||||
{
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = filename,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
|
||||
public static string CleanUpPathCrossPlatform(this string path, bool correctFilenameCase = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path)) { return ""; }
|
||||
|
||||
path = path.Replace('\\', '/');
|
||||
while (path.IndexOf("//") >= 0)
|
||||
{
|
||||
path = path.Replace("//", "/");
|
||||
}
|
||||
|
||||
if (correctFilenameCase)
|
||||
{
|
||||
string correctedPath = CorrectFilenameCase(path, out _);
|
||||
if (!string.IsNullOrEmpty(correctedPath)) { path = correctedPath; }
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string CleanUpPath(this string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path)) { return ""; }
|
||||
|
||||
path = path.Replace('\\', '/');
|
||||
while (path.IndexOf("//") >= 0)
|
||||
{
|
||||
path = path.Replace("//", "/");
|
||||
}
|
||||
#if LINUX || OSX
|
||||
//required on *nix platforms to load in mods made on Windows
|
||||
string correctedPath = CorrectFilenameCase(path, out _);
|
||||
if (!string.IsNullOrEmpty(correctedPath)) { path = correctedPath; }
|
||||
#endif
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public static class UpdaterUtil
|
||||
{
|
||||
public const string Version = "1.1";
|
||||
|
||||
public static void SaveFileList(string filePath)
|
||||
{
|
||||
XDocument doc = new XDocument(CreateFileList());
|
||||
|
||||
doc.Save(filePath);
|
||||
}
|
||||
|
||||
public static XElement CreateFileList()
|
||||
{
|
||||
XElement root = new XElement("filelist");
|
||||
string currentDir = Directory.GetCurrentDirectory();
|
||||
|
||||
string[] files = Directory.GetFiles(currentDir, "*", SearchOption.AllDirectories);
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
XElement fileElement = new XElement("file");
|
||||
fileElement.Add(new XAttribute("path", GetRelativePath(file, currentDir)));
|
||||
fileElement.Add(new XAttribute("md5", GetFileMd5Hash(file)));
|
||||
|
||||
root.Add(fileElement);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public static List<string> GetFileList(XDocument fileListDoc)
|
||||
{
|
||||
List<string> fileList = new List<string>();
|
||||
|
||||
XElement fileListElement = fileListDoc.Root;
|
||||
|
||||
if (fileListElement == null)
|
||||
{
|
||||
throw new Exception("Received list of new files was corrupted");
|
||||
}
|
||||
|
||||
foreach (XElement file in fileListElement.Elements())
|
||||
{
|
||||
string filePath = file.GetAttributeString("path", "");
|
||||
|
||||
fileList.Add(filePath);
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
public static List<string> GetRequiredFiles(XDocument fileListDoc)
|
||||
{
|
||||
List<string> requiredFiles = new List<string>();
|
||||
|
||||
XElement fileList = fileListDoc.Root;
|
||||
|
||||
if (fileList==null)
|
||||
{
|
||||
throw new Exception("Received list of new files was corrupted");
|
||||
}
|
||||
|
||||
foreach (XElement file in fileList.Elements())
|
||||
{
|
||||
string filePath = file.GetAttributeString("path", "");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
requiredFiles.Add(filePath);
|
||||
continue;
|
||||
}
|
||||
|
||||
string md5 = file.GetAttributeString("md5", "");
|
||||
|
||||
if (GetFileMd5Hash(filePath) != md5)
|
||||
{
|
||||
requiredFiles.Add(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
return requiredFiles;
|
||||
}
|
||||
|
||||
private static string GetFileMd5Hash(string filePath)
|
||||
{
|
||||
Md5Hash md5Hash = null;
|
||||
var md5 = MD5.Create();
|
||||
using (var stream = File.OpenRead(filePath))
|
||||
{
|
||||
md5Hash = new Md5Hash(md5.ComputeHash(stream));
|
||||
}
|
||||
|
||||
return md5Hash.Hash;
|
||||
}
|
||||
|
||||
public static string GetRelativePath(string filespec, string folder)
|
||||
{
|
||||
Uri pathUri = new Uri(filespec);
|
||||
// Folders must end in a slash
|
||||
if (!folder.EndsWith(Path.DirectorySeparatorChar.ToString()))
|
||||
{
|
||||
folder += Path.DirectorySeparatorChar;
|
||||
}
|
||||
Uri folderUri = new Uri(folder);
|
||||
return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// moves the files in the updatefolder to the install folder
|
||||
/// if there's an existing file with the same name in the install folder and it can't be removed,
|
||||
/// it will be renamed as "OLD_[filename]"
|
||||
/// </summary>
|
||||
/// <param name="updateFileFolder"></param>
|
||||
public static void InstallUpdatedFiles(string updateFileFolder)
|
||||
{
|
||||
string[] files = Directory.GetFiles(updateFileFolder, "*", SearchOption.AllDirectories);
|
||||
|
||||
string currentDir = Directory.GetCurrentDirectory();
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
string fileRelPath = GetRelativePath(file, updateFileFolder);
|
||||
|
||||
if (File.Exists(fileRelPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(fileRelPath);
|
||||
}
|
||||
|
||||
//couldn't delete file, probably because it's already in use
|
||||
catch
|
||||
{
|
||||
string oldFileName = Path.Combine(currentDir, Path.GetDirectoryName(fileRelPath), "OLD_"+Path.GetFileName(fileRelPath));
|
||||
|
||||
if (File.Exists(oldFileName)) File.Delete(oldFileName);
|
||||
|
||||
File.Move(fileRelPath, oldFileName);
|
||||
}
|
||||
}
|
||||
|
||||
string directoryName = Path.GetDirectoryName(fileRelPath);
|
||||
if (!string.IsNullOrWhiteSpace(directoryName))
|
||||
{
|
||||
Directory.CreateDirectory(directoryName);
|
||||
}
|
||||
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("moving: "+file+" -> "+fileRelPath);
|
||||
File.Move(file, fileRelPath);
|
||||
}
|
||||
|
||||
Directory.Delete(updateFileFolder, true);
|
||||
}
|
||||
|
||||
public static void CleanUnnecessaryFiles(List<string> filesToKeep)
|
||||
{
|
||||
string currentDir = Directory.GetCurrentDirectory();
|
||||
|
||||
string[] files = Directory.GetFiles(currentDir, "*", SearchOption.AllDirectories);
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
string relativePath = GetRelativePath(file, currentDir);
|
||||
|
||||
string dirRoot = relativePath.Split(Path.DirectorySeparatorChar).First();
|
||||
if (dirRoot != "Content") continue;
|
||||
|
||||
if (filesToKeep.Contains(relativePath)) continue;
|
||||
|
||||
if (Path.GetFileName(file).Split('_').First() == "OLD") continue;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("deleting file "+file);
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Could not delete file \"" + file + "\" (" + e.Message + ")");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void CleanOldFiles()
|
||||
{
|
||||
string currentDir = Directory.GetCurrentDirectory();
|
||||
|
||||
string[] files = Directory.GetFiles(currentDir, "*", SearchOption.AllDirectories);
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
if (Path.GetFileName(file).Split('_').First() != "OLD") continue;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("deleting file " + file);
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Could not delete file \"" + file + "\" (" + e.Message + ")");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user