Unstable 0.17.0.0

This commit is contained in:
Markus Isberg
2022-02-26 02:43:01 +09:00
parent a83f375681
commit 3974067915
913 changed files with 32472 additions and 32364 deletions
@@ -0,0 +1,124 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
public class CollectionConcat<T> : ICollection<T>
{
protected readonly IEnumerable<T> enumerableA;
protected readonly IEnumerable<T> enumerableB;
public CollectionConcat(IEnumerable<T> a, IEnumerable<T> b)
{
enumerableA = a; enumerableB = b;
}
public int Count => enumerableA.Count()+enumerableB.Count();
public bool IsReadOnly => true;
public void Add(T item) => throw new InvalidOperationException();
public void Clear() => throw new InvalidOperationException();
public bool Remove(T item) => throw new InvalidOperationException();
public bool Contains(T item) => enumerableA.Contains(item) || enumerableB.Contains(item);
public void CopyTo(T[] array, int arrayIndex)
{
void performCopy(IEnumerable<T> enumerable)
{
if (enumerable is ICollection<T> collection)
{
collection.CopyTo(array, arrayIndex);
arrayIndex += collection.Count;
}
else
{
foreach (var item in enumerable)
{
array[arrayIndex] = item;
arrayIndex++;
}
}
}
performCopy(enumerableA);
performCopy(enumerableB);
}
public IEnumerator<T> GetEnumerator()
{
foreach (T item in enumerableA) { yield return item; }
foreach (T item in enumerableB) { yield return item; }
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class ListConcat<T> : CollectionConcat<T>, IList<T>, IReadOnlyList<T>
{
public ListConcat(IEnumerable<T> a, IEnumerable<T> b) : base(a, b) { }
public int IndexOf(T item)
{
int aCount = 0;
if (enumerableA is IList<T> listA)
{
int index = listA.IndexOf(item);
if (index >= 0) { return index; }
aCount = listA.Count;
}
else
{
foreach (var a in enumerableA)
{
if (object.Equals(item, a)) { return aCount; }
aCount++;
}
}
if (enumerableB is IList<T> listB)
{
int index = listB.IndexOf(item);
if (index >= 0) { return index + aCount; }
}
else
{
foreach (var b in enumerableB)
{
if (object.Equals(item, b)) { return aCount; }
aCount++;
}
}
return -1;
}
public void Insert(int index, T item)
{
throw new InvalidOperationException();
}
public void RemoveAt(int index)
{
throw new InvalidOperationException();
}
public T this[int index]
{
get
{
int aCount = enumerableA.Count();
return index < aCount ? enumerableA.ElementAt(index) : enumerableB.ElementAt(index - aCount);
}
set
{
throw new InvalidOperationException();
}
}
}
}
@@ -0,0 +1,231 @@
#if DEBUG && MODBREAKER
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
/// <summary>
/// Dictionary that's been deliberately designed to be a piece of
/// shit. Meant to be used to stress-test scenarios where we might
/// accidentally be relying on the implementation details of a
/// dictionary that shouldn't be relied on.
/// </summary>
public class CursedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey: notnull
{
private ICollection<TKey> keys;
private ICollection<TValue> values;
private readonly List<KeyValuePair<TKey, TValue>> keyValuePairs = new List<KeyValuePair<TKey, TValue>>();
private readonly Dictionary<TKey, int> keyToKvpIndex = new Dictionary<TKey, int>();
private readonly object mutex = new object();
private readonly Random rng;
public CursedDictionary()
{
rng = new Random((int)(DateTime.Now.ToBinary() % int.MaxValue));
keys = Array.Empty<TKey>();
values = Array.Empty<TValue>();
}
private void Refresh()
{
keys = keyValuePairs.Select(kvp => kvp.Key).ToArray();
values = keyValuePairs.Select(kvp => kvp.Value).ToArray();
keyToKvpIndex.Clear();
for (int i=0; i<keyValuePairs.Count; i++)
{
keyToKvpIndex[keyValuePairs[i].Key] = i;
}
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
KeyValuePair<TKey, TValue>[] clone;
lock (mutex)
{
keyValuePairs.Shuffle(rng);
Refresh();
clone = keyValuePairs.ToArray();
}
foreach (var kvp in clone)
{
yield return kvp;
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(KeyValuePair<TKey, TValue> item)
{
lock (mutex)
{
if (keyToKvpIndex.ContainsKey(item.Key))
{
throw new InvalidOperationException($"Duplicate key: {item.Key}");
}
keyValuePairs.Add(item);
keyValuePairs.Shuffle(rng);
Refresh();
}
}
public void Clear()
{
lock (mutex)
{
keyValuePairs.Clear();
Refresh();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
lock (mutex)
{
return keyValuePairs.Contains(item);
}
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
lock (mutex)
{
keyValuePairs.Shuffle(rng);
keyValuePairs.CopyTo(array, arrayIndex);
Refresh();
}
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
lock (mutex)
{
bool success = keyValuePairs.Remove(item);
keyValuePairs.Shuffle(rng);
Refresh();
return success;
}
}
public int Count
{
get
{
lock (mutex)
{
return keyValuePairs.Count;
}
}
}
public bool IsReadOnly => false;
public void Add(TKey key, TValue value) => Add(new KeyValuePair<TKey, TValue>(key, value));
public bool ContainsKey(TKey key)
{
lock (mutex)
{
return keyToKvpIndex.ContainsKey(key);
}
}
public bool TryGetValue(TKey key, out TValue value)
{
lock (mutex)
{
value = default!;
bool success = keyToKvpIndex.TryGetValue(key, out int index);
if (success)
{
value = keyValuePairs[index].Value;
}
keyValuePairs.Shuffle(rng);
Refresh();
return success;
}
}
public bool Remove(TKey key) => TryRemove(key, out _);
public bool TryRemove(TKey key, out TValue value)
{
lock (mutex)
{
value = default!;
bool success = false;
if (keyToKvpIndex.TryGetValue(key, out int index))
{
value = keyValuePairs[index].Value;
keyValuePairs.RemoveAt(index);
success = true;
}
keyValuePairs.Shuffle(rng);
Refresh();
return success;
}
}
public TValue this[TKey key]
{
get
{
lock (mutex)
{
return keyValuePairs[keyToKvpIndex[key]].Value;
}
}
set
{
lock (mutex)
{
if (!keyToKvpIndex.ContainsKey(key))
{
Add(key, value);
}
else
{
keyValuePairs[keyToKvpIndex[key]] = new KeyValuePair<TKey, TValue>(key, value);
}
keyValuePairs.Shuffle(rng);
Refresh();
}
}
}
public ICollection<TKey> Keys
{
get
{
lock (mutex)
{
return keys.ToArray();
}
}
}
public ICollection<TValue> Values
{
get
{
lock (mutex)
{
return values.ToArray();
}
}
}
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
}
}
#endif
@@ -7,7 +7,7 @@ namespace Barotrauma
class Homoglyphs
{
///List of homoglyphs taken from https://github.com/codebox/homoglyph/
private static List<uint[]> homoglyphs = new List<uint[]>(){
private readonly static uint[][] homoglyphs = {
new uint[]{0x20,0xa0,0x1680,0x2000,0x2001,0x2002,0x2003,0x2004,0x2005,0x2006,0x2007,0x2008,0x2009,0x200a,0x2028,0x2029,0x202f,0x205f},
new uint[]{0x21,0x1c3,0x2d51,0xff01},
new uint[]{0x24,0xff04},
@@ -35,9 +35,9 @@ namespace Barotrauma
{
public sealed class HttpUtility
{
public static Dictionary<string, string> ParseQueryString(string query)
public static Dictionary<Identifier, string> ParseQueryString(string query)
{
Dictionary<string, string> collection = new Dictionary<string, string>();
Dictionary<Identifier, string> collection = new Dictionary<Identifier, string>();
var splitGet = query.Split('?');
if (splitGet.Length > 1)
{
@@ -47,7 +47,7 @@ namespace Barotrauma
var splitKeyValue = kvp.Split('=');
if (splitKeyValue.Length > 1)
{
collection.Add(splitKeyValue[0], splitKeyValue[1]);
collection.Add(splitKeyValue[0].ToIdentifier(), splitKeyValue[1]);
}
}
}
@@ -22,12 +22,12 @@ namespace Barotrauma
if (parentElement is { HasElements: true })
{
srcRanges = new List<Range<int>>();
foreach (XElement subElement in parentElement.Elements())
foreach (var subElement in parentElement.Elements())
{
int id = subElement.GetAttributeInt("ID", -1);
if (id > 0) { InsertId(id); }
}
maxId = GetOffsetId(srcRanges.Last().End);
maxId = GetOffsetId(srcRanges.Any() ? srcRanges.Last().End : offset);
}
else
{
@@ -0,0 +1,79 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Barotrauma
{
public class LinkedPairSet<T1, T2> : IEnumerable<(T1, T2)>
{
private readonly Dictionary<T1, T2> t1ToT2 = new Dictionary<T1, T2>();
private readonly Dictionary<T2, T1> t2ToT1 = new Dictionary<T2, T1>();
public bool Contains(T1 t1)
{
return t1ToT2.ContainsKey(t1);
}
public bool Contains(T2 t2)
{
return t2ToT1.ContainsKey(t2);
}
public T2 this[T1 t1]
{
get { return t1ToT2[t1]; }
set
{
T2 prevT2 = t1ToT2[t1];
t2ToT1.Remove(prevT2); t2ToT1.Add(value, t1);
t1ToT2[t1] = value;
}
}
public T1 this[T2 t2]
{
get { return t2ToT1[t2]; }
set
{
T1 prevT1 = t2ToT1[t2];
t1ToT2.Remove(prevT1); t1ToT2.Add(value, t2);
t2ToT1[t2] = value;
}
}
public void Add(T1 t1, T2 t2)
{
if (Contains(t1)) { throw new ArgumentException($"{GetType().Name} already contains {t1}"); }
if (Contains(t2)) { throw new ArgumentException($"{GetType().Name} already contains {t2}"); }
t1ToT2.Add(t1, t2);
t2ToT1.Add(t2, t1);
}
public void Remove(T1 t1)
{
T2 t2 = t1ToT2[t1];
t1ToT2.Remove(t1);
t2ToT1.Remove(t2);
}
public void Remove(T2 t2)
{
T1 t1 = t2ToT1[t2];
t1ToT2.Remove(t1);
t2ToT1.Remove(t2);
}
public IEnumerator<(T1, T2)> GetEnumerator()
{
foreach (var t1 in t1ToT2.Keys)
{
yield return (t1, t1ToT2[t1]);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,55 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
{
public class ListDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{
private readonly ImmutableDictionary<TKey, int> keyToIndex;
private readonly IReadOnlyList<TValue> list;
public ListDictionary(IReadOnlyList<TValue> list, int len, Func<int, TKey> keyFunc)
{
this.list = list;
var keyToIndex = new Dictionary<TKey, int>();
for (int i = 0; i < len; i++)
{
keyToIndex.Add(keyFunc(i), i);
}
this.keyToIndex = keyToIndex.ToImmutableDictionary();
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
foreach (var kvp in keyToIndex)
{
yield return new KeyValuePair<TKey, TValue>(kvp.Key, list[kvp.Value]);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int Count => keyToIndex.Count;
public bool ContainsKey(TKey key) => keyToIndex.ContainsKey(key);
public bool TryGetValue(TKey key, out TValue value)
{
if (keyToIndex.TryGetValue(key, out int index))
{
value = list[index];
return true;
}
value = default(TValue);
return false;
}
public TValue this[TKey key] => list[keyToIndex[key]];
public IEnumerable<TKey> Keys => keyToIndex.Keys;
public IEnumerable<TValue> Values => keyToIndex.Values.Select(i => list[i]);
}
}
@@ -749,7 +749,7 @@ namespace Barotrauma
Vector2 normal = Vector2.Normalize(endSegment - startSegment);
normal = new Vector2(-normal.Y, normal.X);
midPoint += normal * Rand.Range(-offsetAmount, offsetAmount, Rand.RandSync.Server);
midPoint += normal * Rand.Range(-offsetAmount, offsetAmount, Rand.RandSync.ServerAndClient);
if (bounds.HasValue)
{
@@ -0,0 +1,9 @@
namespace Barotrauma
{
public sealed class None<T> : Option<T>
{
private None() { }
public static Option<T> Create() => new None<T>();
}
}
@@ -0,0 +1,14 @@
namespace Barotrauma
{
/// <summary>
/// Implementation of <a href="https://en.wikipedia.org/wiki/Option_type">Option type</a>.
/// </summary>
/// <remarks>
/// Credit <a href="https://github.com/Jlobblet/FunctionalStuff/tree/main/src/FunctionalStuff/Option">Jlobblet</a>
/// </remarks>
public abstract class Option<T>
{
public static Option<T> Some(T value) => Some<T>.Create(value);
public static Option<T> None() => None<T>.Create();
}
}
@@ -0,0 +1,17 @@
using System;
namespace Barotrauma
{
public sealed class Some<T> : Option<T>
{
public readonly T Value;
private Some(T value)
{
if (value is null) { throw new ArgumentNullException(nameof(value), "Some<T> cannot contain null"); }
Value = value;
}
public static Option<T> Create(T value) => new Some<T>(value);
}
}
@@ -1,26 +1,68 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Barotrauma.IO;
using Voronoi2;
namespace Barotrauma
{
public static class Rand
{
[Obsolete("TODO: remove")]
public static class Tracker
{
private readonly static List<string> logMsgs = new List<string>();
public static IReadOnlyList<string> LogMsgs => logMsgs;
public static bool Active = false;
public static void Reset()
{
logMsgs.Clear();
Active = false;
}
public static void RegisterCall(int stDepth=4)
{
if (!Active) { return; }
var st = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
var frames = st.GetFrames();
string msg = string.Join("; ",
frames.Take(stDepth).Select(f =>
$"{Path.GetFileNameWithoutExtension(f.GetFileName())}:{f.GetFileLineNumber()}"));
logMsgs.Add(msg);
}
public static void Log(string msg)
{
if (!Active) { return; }
logMsgs.Add(msg);
}
}
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)
Unsynced, //not synced, used for unimportant details like minor particle properties
ServerAndClient, //synced with the server (used for gameplay elements that the players can interact with)
#if CLIENT
ClientOnly //set to match between clients (used for misc elements that the server doesn't track, but clients want to match anyway)
#endif
}
private static Random localRandom = new Random();
private static readonly Random[] syncedRandom = new MTRandom[] {
new MTRandom(), new MTRandom()
private static readonly Dictionary<RandSync, Random> syncedRandom = new Dictionary<RandSync, Random> {
{ RandSync.ServerAndClient, new MTRandom() },
#if CLIENT
{ RandSync.ClientOnly, new MTRandom() }
#endif
};
public static Random GetRNG(RandSync randSync)
{
return randSync == RandSync.Unsynced ? localRandom : syncedRandom[(int)randSync];
return randSync == RandSync.Unsynced ? localRandom : syncedRandom[randSync];
}
public static void SetLocalRandom(int seed)
@@ -30,21 +72,32 @@ namespace Barotrauma
public static void SetSyncedSeed(int seed)
{
syncedRandom[(int)RandSync.Server] = new MTRandom(seed);
syncedRandom[(int)RandSync.ClientOnly] = new MTRandom(seed);
syncedRandom[RandSync.ServerAndClient] = new MTRandom(seed);
#if CLIENT
syncedRandom[RandSync.ClientOnly] = new MTRandom(seed);
#endif
}
public static int ThreadId = 0;
private static void CheckRandThreadSafety(RandSync sync)
{
if (ThreadId != 0 && sync == RandSync.Server)
if (sync == RandSync.ServerAndClient) { Tracker.RegisterCall(); }
if (ThreadId != 0 && sync == RandSync.Unsynced)
{
if (System.Threading.Thread.CurrentThread.ManagedThreadId != ThreadId)
{
Debug.WriteLine($"Unsynced rand used in synced thread! {Environment.StackTrace}");
}
}
if (ThreadId != 0 && sync == RandSync.ServerAndClient)
{
if (System.Threading.Thread.CurrentThread.ManagedThreadId != ThreadId)
{
#if DEBUG
throw new Exception("Unauthorized multithreaded access to RandSync.Server");
throw new Exception("Unauthorized multithreaded access to RandSync.ServerAndClient");
#else
DebugConsole.ThrowError("Unauthorized multithreaded access to RandSync.Server\n" + Environment.StackTrace.CleanupStackTrace());
DebugConsole.ThrowError("Unauthorized multithreaded access to RandSync.ServerAndClient\n" + Environment.StackTrace.CleanupStackTrace());
#endif
}
}
@@ -53,13 +106,13 @@ namespace Barotrauma
public static float Range(float minimum, float maximum, RandSync sync=RandSync.Unsynced)
{
CheckRandThreadSafety(sync);
return (float)(sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).NextDouble() * (maximum - minimum) + minimum;
return (float)(sync == RandSync.Unsynced ? localRandom : (syncedRandom[sync])).NextDouble() * (maximum - minimum) + minimum;
}
public static double Range(double minimum, double maximum, RandSync sync = RandSync.Unsynced)
{
CheckRandThreadSafety(sync);
return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).NextDouble() * (maximum - minimum) + minimum;
return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[sync])).NextDouble() * (maximum - minimum) + minimum;
}
/// <summary>
@@ -68,13 +121,13 @@ namespace Barotrauma
public static int Range(int minimum, int maximum, RandSync sync = RandSync.Unsynced)
{
CheckRandThreadSafety(sync);
return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).Next(maximum - minimum) + minimum;
return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[sync])).Next(maximum - minimum) + minimum;
}
public static int Int(int max, RandSync sync = RandSync.Unsynced)
{
CheckRandThreadSafety(sync);
return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[(int)sync])).Next(max);
return (sync == RandSync.Unsynced ? localRandom : (syncedRandom[sync])).Next(max);
}
public static Vector2 Vector(float length, RandSync sync = RandSync.Unsynced)
@@ -7,10 +7,13 @@ namespace Barotrauma
public static class ReadOnlyListExtensions
{
public static int IndexOf<T>(this IReadOnlyList<T> list, T elem)
=> list.IndexOf(input => input.Equals(elem));
public static int IndexOf<T>(this IReadOnlyList<T> list, Func<T, bool> predicate)
{
for (int i = 0; i < list.Count; i++)
{
if (list[i].Equals(elem)) { return i; }
if (predicate(list[i])) { return i; }
}
return -1;
}
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Barotrauma
{
public static class ReflectionUtils
{
public static IEnumerable<Type> GetDerivedNonAbstract<T>()
{
return Assembly.GetEntryAssembly().GetTypes().Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
}
}
@@ -0,0 +1,43 @@
#nullable enable
namespace Barotrauma
{
public abstract class Result<T, TError>
where T: notnull
where TError: notnull
{
public abstract bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public static Success<T, TError> Success(T value)
=> new Success<T, TError>(value);
public static Failure<T, TError> Failure(TError error)
=> new Failure<T, TError>(error);
}
public sealed class Success<T, TError> : Result<T, TError>
where T: notnull
where TError: notnull
{
public readonly T Value;
public override bool IsSuccess => true;
public Success(T value)
{
Value = value;
}
}
public sealed class Failure<T, TError> : Result<T, TError>
where T: notnull
where TError: notnull
{
public readonly TError Error;
public override bool IsSuccess => false;
public Failure(TError error)
{
Error = error;
}
}
}
@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Barotrauma
{
@@ -20,18 +21,17 @@ namespace Barotrauma
private const string metadataDefinition = "metadata";
private const string endDefinition = "end";
public static List<RichTextData> GetRichTextData(string text, out string sanitizedText)
public static ImmutableArray<RichTextData>? GetRichTextData(string text, out string sanitizedText)
{
List<RichTextData> textColors = null;
sanitizedText = text;
if (!string.IsNullOrEmpty(text) && text.Contains(definitionIndicator))
if (!string.IsNullOrEmpty(text) && text.Contains(definitionIndicator, System.StringComparison.Ordinal))
{
text = text.Replace("\r", "");
string[] segments = text.Split(definitionIndicator);
sanitizedText = string.Empty;
textColors = new List<RichTextData>();
List<RichTextData> textColors = new List<RichTextData>();
RichTextData tempData = null;
int prevIndex = 0;
@@ -80,9 +80,9 @@ namespace Barotrauma
}
}
}
return textColors.ToImmutableArray();
}
return textColors;
return null;
}
}
}
@@ -1,3 +1,5 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
@@ -6,7 +8,7 @@ namespace Barotrauma.IO
{
static class Validation
{
private static readonly string[] unwritableDirs = new string[] { "Content", "Data/ContentPackages" };
private static readonly string[] unwritableDirs = new string[] { "Content" };
private static readonly string[] unwritableExtensions = new string[]
{
".pdb", ".com", ".scr", ".dylib", ".so", ".a", ".app", //executables and libraries (.exe and .dll handled separately in CanWrite)
@@ -58,7 +60,11 @@ namespace Barotrauma.IO
public static class SafeXML
{
public static void SaveSafe(this System.Xml.Linq.XDocument doc, string path, bool throwExceptions = false)
public static void SaveSafe(
this System.Xml.Linq.XDocument doc,
string path,
System.Xml.Linq.SaveOptions saveOptions = System.Xml.Linq.SaveOptions.None,
bool throwExceptions = false)
{
if (!Validation.CanWrite(path, false))
{
@@ -73,7 +79,7 @@ namespace Barotrauma.IO
}
return;
}
doc.Save(path);
doc.Save(path, saveOptions);
}
public static void SaveSafe(this System.Xml.Linq.XElement element, string path, bool throwExceptions = false)
@@ -107,7 +113,7 @@ namespace Barotrauma.IO
public class XmlWriter : IDisposable
{
public readonly System.Xml.XmlWriter Writer;
public readonly System.Xml.XmlWriter? Writer;
public XmlWriter(string path, System.Xml.XmlWriterSettings settings)
{
@@ -156,65 +162,42 @@ namespace Barotrauma.IO
}
}
public static class XmlWriterExtensions
{
public static void Save(this System.Xml.Linq.XDocument doc, XmlWriter writer)
{
doc.Save(writer.Writer ?? throw new NullReferenceException("Unable to save XML document: XML writer is null."));
}
}
public static class Path
{
public static readonly char DirectorySeparatorChar = System.IO.Path.DirectorySeparatorChar;
public static readonly char AltDirectorySeparatorChar = System.IO.Path.AltDirectorySeparatorChar;
public static string GetExtension(string path)
{
return System.IO.Path.GetExtension(path);
}
public static string GetExtension(string path) => System.IO.Path.GetExtension(path);
public static string GetFileNameWithoutExtension(string path)
{
return System.IO.Path.GetFileNameWithoutExtension(path);
}
public static string GetFileNameWithoutExtension(string path) => System.IO.Path.GetFileNameWithoutExtension(path);
public static string GetPathRoot(string path)
{
return System.IO.Path.GetPathRoot(path);
}
public static string? GetPathRoot(string? path) => System.IO.Path.GetPathRoot(path);
public static string GetRelativePath(string relativeTo, string path)
{
return System.IO.Path.GetRelativePath(relativeTo, path);
}
public static string GetRelativePath(string relativeTo, string path) => System.IO.Path.GetRelativePath(relativeTo, path);
public static string GetDirectoryName(string path)
{
return System.IO.Path.GetDirectoryName(path);
}
public static string GetDirectoryName(ContentPath path) => GetDirectoryName(path.Value)!;
public static string? GetDirectoryName(string path) => System.IO.Path.GetDirectoryName(path);
public static string GetFileName(string path)
{
return System.IO.Path.GetFileName(path);
}
public static string GetFileName(string path) => System.IO.Path.GetFileName(path);
public static string GetFullPath(string path)
{
return System.IO.Path.GetFullPath(path);
}
public static string GetFullPath(string path) => System.IO.Path.GetFullPath(path);
public static string Combine(params string[] s)
{
return System.IO.Path.Combine(s);
}
public static string Combine(params string[] s) => System.IO.Path.Combine(s);
public static string GetTempFileName()
{
return System.IO.Path.GetTempFileName();
}
public static string GetTempFileName() => System.IO.Path.GetTempFileName();
public static bool IsPathRooted(string path)
{
return System.IO.Path.IsPathRooted(path);
}
public static IEnumerable<char> GetInvalidFileNameChars()
{
return System.IO.Path.GetInvalidFileNameChars();
}
public static bool IsPathRooted(string path) => System.IO.Path.IsPathRooted(path);
public static IEnumerable<char> GetInvalidFileNameChars() => System.IO.Path.GetInvalidFileNameChars();
}
public static class Directory
@@ -229,22 +212,22 @@ namespace Barotrauma.IO
System.IO.Directory.SetCurrentDirectory(path);
}
public static IEnumerable<string> GetFiles(string path)
public static string[] GetFiles(string path)
{
return System.IO.Directory.GetFiles(path);
}
public static IEnumerable<string> GetFiles(string path, string pattern, System.IO.SearchOption option = System.IO.SearchOption.AllDirectories)
public static string[] GetFiles(string path, string pattern, System.IO.SearchOption option = System.IO.SearchOption.AllDirectories)
{
return System.IO.Directory.GetFiles(path, pattern, option);
}
public static IEnumerable<string> GetDirectories(string path)
public static string[] GetDirectories(string path, string searchPattern = "*", System.IO.SearchOption searchOption = System.IO.SearchOption.TopDirectoryOnly)
{
return System.IO.Directory.GetDirectories(path);
return System.IO.Directory.GetDirectories(path, searchPattern, searchOption);
}
public static IEnumerable<string> GetFileSystemEntries(string path)
public static string[] GetFileSystemEntries(string path)
{
return System.IO.Directory.GetFileSystemEntries(path);
}
@@ -264,7 +247,7 @@ namespace Barotrauma.IO
return System.IO.Directory.Exists(path);
}
public static System.IO.DirectoryInfo CreateDirectory(string path)
public static System.IO.DirectoryInfo? CreateDirectory(string path)
{
if (!Validation.CanWrite(path, true))
{
@@ -289,10 +272,9 @@ namespace Barotrauma.IO
public static class File
{
public static bool Exists(string path)
{
return System.IO.File.Exists(path);
}
public static bool Exists(ContentPath path) => Exists(path.Value);
public static bool Exists(string path) => System.IO.File.Exists(path);
public static void Copy(string src, string dest, bool overwrite=false)
{
@@ -319,6 +301,8 @@ namespace Barotrauma.IO
System.IO.File.Move(src, dest);
}
public static void Delete(ContentPath path) => Delete(path.Value);
public static void Delete(string path)
{
if (!Validation.CanWrite(path, false))
@@ -334,7 +318,7 @@ namespace Barotrauma.IO
return System.IO.File.GetLastWriteTime(path);
}
public static FileStream Open(
public static FileStream? Open(
string path,
System.IO.FileMode mode,
System.IO.FileAccess access = System.IO.FileAccess.ReadWrite,
@@ -362,17 +346,17 @@ namespace Barotrauma.IO
return new FileStream(path, System.IO.File.Open(path, mode, access, shareVal));
}
public static FileStream OpenRead(string path)
public static FileStream? OpenRead(string path)
{
return Open(path, System.IO.FileMode.Open, System.IO.FileAccess.Read);
}
public static FileStream OpenWrite(string path)
public static FileStream? OpenWrite(string path)
{
return Open(path, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write);
}
public static FileStream Create(string path)
public static FileStream? Create(string path)
{
return Open(path, System.IO.FileMode.Create, System.IO.FileAccess.Write);
}
@@ -150,7 +150,7 @@ namespace Barotrauma
if (ownedSubsElement != null)
{
ownedSubmarines = new List<SubmarineInfo>();
foreach (XElement subElement in ownedSubsElement.Elements())
foreach (var subElement in ownedSubsElement.Elements())
{
string subName = subElement.GetAttributeString("name", "");
string ownedSubPath = Path.Combine(TempPath, subName + ".sub");
@@ -271,7 +271,7 @@ namespace Barotrauma
string folder = saveType == SaveType.Singleplayer ? SaveFolder : MultiplayerSaveFolder;
if (fileName == "Save_Default")
{
fileName = TextManager.Get("SaveFile.DefaultName", true);
fileName = TextManager.Get("SaveFile.DefaultName").Value;
if (fileName.Length == 0) fileName = "Save";
}
@@ -381,7 +381,7 @@ namespace Barotrauma
char c = BitConverter.ToChar(bytes, 0);
sb.Append(c);
}
string sFileName = sb.ToString();
string sFileName = sb.ToString().Replace('\\', '/');
fileName = sFileName;
progress?.Invoke(sFileName);
@@ -122,7 +122,7 @@ namespace Barotrauma
enumPath = string.IsNullOrWhiteSpace(startPath) ? "./" : startPath;
}
List<string> filePaths = Directory.GetFileSystemEntries(enumPath).Select(Path.GetFileName).ToList();
string[] filePaths = Directory.GetFileSystemEntries(enumPath).Select(Path.GetFileName).ToArray();
if (filePaths.Any(s => s.Equals(subDir, StringComparison.Ordinal)))
{
@@ -130,7 +130,7 @@ namespace Barotrauma
}
else
{
List<string> correctedPaths = filePaths.Where(s => s.Equals(subDir, StringComparison.OrdinalIgnoreCase)).ToList();
string[] correctedPaths = filePaths.Where(s => s.Equals(subDir, StringComparison.OrdinalIgnoreCase)).ToArray();
if (correctedPaths.Any())
{
corrected = true;
@@ -159,7 +159,7 @@ namespace Barotrauma
return fileName;
}
private static System.Text.RegularExpressions.Regex removeBBCodeRegex =
private static readonly 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)
@@ -168,15 +168,6 @@ namespace Barotrauma
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";
@@ -186,6 +177,8 @@ namespace Barotrauma
.ToArray());
}
public static int IdentifierToInt(Identifier id) => StringToInt(id.Value.ToLowerInvariant());
public static int StringToInt(string str)
{
str = str.Substring(0, Math.Min(str.Length, 32));
@@ -201,6 +194,7 @@ namespace Barotrauma
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>
@@ -264,16 +258,17 @@ namespace Barotrauma
{
string color = obj switch
{
bool b => b ? "80,250,123" : "255,85,85",
string _ => "241,250,140",
int _ => "189,147,249",
float _ => "189,147,249",
double _ => "189,147,249",
null => "255,85,85",
bool b => b ? "80,250,123" : "255,85,85",
string _ => "241,250,140",
Identifier _ => "241,250,140",
int _ => "189,147,249",
float _ => "189,147,249",
double _ => "189,147,249",
null => "255,85,85",
_ => "139,233,253"
};
return obj is string
return obj is string || obj is Identifier
? $"‖color:{color}‖\"{obj}\"‖color:end‖"
: $"‖color:{color}‖{obj ?? "null"}‖color:end‖";
}
@@ -352,7 +347,7 @@ namespace Barotrauma
return d[n, m];
}
public static string SecondsToReadableTime(float seconds)
public static LocalizedString SecondsToReadableTime(float seconds)
{
int s = (int)(seconds % 60.0f);
if (seconds < 60.0f)
@@ -363,23 +358,23 @@ namespace Barotrauma
int h = (int)(seconds / (60.0f * 60.0f));
int m = (int)((seconds / 60.0f) % 60);
string text = "";
LocalizedString 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);
LocalizedString minutesText = TextManager.GetWithVariable("timeformatminutes", "[minutes]", m.ToString());
text = text.IsNullOrEmpty() ? minutesText : LocalizedString.Join(" ", text, minutesText);
}
if (s != 0)
{
string secondsText = TextManager.GetWithVariable("timeformatseconds", "[seconds]", s.ToString());
text = string.IsNullOrEmpty(text) ? secondsText : string.Join(" ", text, secondsText);
LocalizedString secondsText = TextManager.GetWithVariable("timeformatseconds", "[seconds]", s.ToString());
text = text.IsNullOrEmpty() ? secondsText : LocalizedString.Join(" ", text, secondsText);
}
return text;
}
private static Dictionary<string, List<string>> cachedLines = new Dictionary<string, List<string>>();
public static string GetRandomLine(string filePath, Rand.RandSync randSync = Rand.RandSync.Server)
public static string GetRandomLine(string filePath, Rand.RandSync randSync = Rand.RandSync.ServerAndClient)
{
List<string> lines;
if (cachedLines.ContainsKey(filePath))
@@ -426,7 +421,20 @@ namespace Barotrauma
return buffer;
}
public static T SelectWeightedRandom<T>(IList<T> objects, IList<float> weights, Rand.RandSync randSync = Rand.RandSync.Unsynced)
public static T SelectWeightedRandom<T>(IEnumerable<T> objects, Func<T, float> weightMethod, Rand.RandSync randSync)
{
return SelectWeightedRandom(objects, weightMethod, Rand.GetRNG(randSync));
}
public static T SelectWeightedRandom<T>(IEnumerable<T> objects, Func<T, float> weightMethod, Random random)
{
List<T> objectList = objects.ToList();
List<float> weights = objectList.Select(o => weightMethod(o)).ToList();
return SelectWeightedRandom(objectList, weights, random);
}
public static T SelectWeightedRandom<T>(IList<T> objects, IList<float> weights, Rand.RandSync randSync)
{
return SelectWeightedRandom(objects, weights, Rand.GetRNG(randSync));
}
@@ -455,11 +463,14 @@ namespace Barotrauma
return default(T);
}
public static UInt32 IdentifierToUint32Hash(Identifier id, MD5 md5)
=> StringToUInt32Hash(id.Value.ToLowerInvariant(), md5);
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[] inputBytes = Encoding.UTF8.GetBytes(str);
byte[] hash = md5.ComputeHash(inputBytes);
UInt32 key = (UInt32)((str.Length & 0xff) << 24); //could use more of the hash here instead?
@@ -609,16 +620,6 @@ namespace Barotrauma
return commands.ToArray();
}
public static void OpenFileWithShell(string filename)
{
ProcessStartInfo startInfo = new ProcessStartInfo()
{
FileName = filename,
UseShellExecute = true
};
Process.Start(startInfo);
}
/// <summary>
/// Cleans up a path by replacing backslashes with forward slashes, and
/// optionally corrects the casing of the path. Recommended when serializing