OBT/1.2.0(Spring Update)

Sync with Upstream
This commit is contained in:
NotAlwaysTrue
2026-04-25 13:25:41 +08:00
committed by GitHub
parent 5207b381b7
commit 59bc21973a
421 changed files with 24090 additions and 11391 deletions
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml.Linq;
using System.Xml.Serialization;
using Barotrauma.LuaCs;
using Barotrauma.Steam;
using OneOf;
namespace Barotrauma.LuaCs.Data;
#region ModConfigurationInfo
public partial record ModConfigInfo : IModConfigInfo
{
public ContentPackage Package { get; init; }
public ImmutableArray<IAssemblyResourceInfo> Assemblies { get; init; }
public ImmutableArray<ILuaScriptResourceInfo> LuaScripts { get; init; }
public ImmutableArray<IConfigResourceInfo> Configs { get; init; }
}
#endregion
#region DataContracts_Resources
public record BaseResourceInfo : IBaseResourceInfo
{
public Platform SupportedPlatforms { get; init; }
public Target SupportedTargets { get; init; }
public int LoadPriority { get; init; }
public ImmutableArray<ContentPath> FilePaths { get; init; }
public bool Optional { get; init; }
public string InternalName { get; init; }
public ContentPackage OwnerPackage { get; init; }
public ImmutableArray<Identifier> RequiredPackages { get; init; }
public ImmutableArray<Identifier> IncompatiblePackages { get; init; }
}
public record AssemblyResourceInfo : BaseResourceInfo, IAssemblyResourceInfo
{
public string FriendlyName { get; init; }
public bool IsScript { get; init; }
public bool UseInternalAccessName { get; init; }
public bool IsReferenceModeOnly { get; init; }
}
/// <summary>
/// Note: Config settings and settings-profiles are stored in the same files.
/// </summary>
public record ConfigResourceInfo : BaseResourceInfo, IConfigResourceInfo {}
public record LuaScriptsResourceInfo : BaseResourceInfo, ILuaScriptResourceInfo
{
public bool IsAutorun { get; init; }
public bool RunUnrestricted { get; init; }
}
#endregion
#region DataContracts_ParsedInfo
public record ConfigInfo : IConfigInfo
{
public string InternalName { get; init; }
public ContentPackage OwnerPackage { get; init; }
public string DataType { get; init; }
public XElement Element { get; init; }
public RunState EditableStates { get; init; }
public NetSync NetSync { get; init; }
#if CLIENT // IConfigDisplayInfo
public string DisplayName { get; init; }
public string Description { get; init; }
public string DisplayCategory { get; init; }
public bool ShowInMenus { get; init; }
public string Tooltip { get; init; }
public ContentPath ImageIconPath { get; init; }
#endif
}
public record ConfigProfileInfo : IConfigProfileInfo
{
/// <summary>
/// Profile name.
/// </summary>
public string InternalName { get; init; }
public ContentPackage OwnerPackage { get; init; }
public IReadOnlyList<(string SettingName, XElement Element)> ProfileValues { get; init; }
}
#endregion
@@ -0,0 +1,21 @@
using System;
// ReSharper disable InconsistentNaming
namespace Barotrauma.LuaCs.Data;
[Flags]
public enum Platform
{
Linux = 0x1,
OSX = 0x2,
Windows = 0x4,
Any = Linux | OSX | Windows
}
[Flags]
public enum Target
{
Client = 0x1,
Server = 0x2,
Any = Client | Server
}
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Xml.Serialization;
namespace Barotrauma.LuaCs.Data;
public interface IDependencyInfo
{
/// <summary>
/// List of dependency packages required by this resource.
/// </summary>
ImmutableArray<Identifier> RequiredPackages { get; }
/// <summary>
/// List of packages incompatible with this resource.
/// </summary>
ImmutableArray<Identifier> IncompatiblePackages { get; }
}
public interface IPlatformInfo
{
/// <summary>
/// Platforms that these localization files should be loaded for.
/// </summary>
[Required]
[XmlAttribute("Platform")]
Platform SupportedPlatforms { get; }
/// <summary>
/// Targets that these localization files should be loaded for.
/// </summary>
[Required]
[XmlAttribute("Target")]
Target SupportedTargets { get; }
}
/// <summary>
/// ResourceInfos contain metadata about a resource.
/// </summary>
public interface IResourceInfo : IPlatformInfo
{
/// <summary>
/// [Optional]
/// Specifies the loading order for all assets of the same type (ie. styles, assemblies, etc.) from
/// the same <see cref="ContentPackage"/>. Lower number is higher priority, see <see cref="System.Linq.Enumerable.OrderBy{TSource,TKey}(IEnumerable{TSource}, Func{TSource,TKey})"/>
/// </summary>
[XmlAttribute("LoadPriority")]
int LoadPriority { get; }
/// <summary>
/// Resource absolute file paths.
/// </summary>
[Required]
ImmutableArray<ContentPath> FilePaths { get; }
/// <summary>
/// Marks this resource as optional (ie. Cross-CP content). Setting this to true will allow the dependency system to
/// try and order the loading but not fail if it runs into circular dependency issues.
/// </summary>
[XmlAttribute("Optional")]
bool Optional { get; }
}
@@ -0,0 +1,35 @@
using System;
using System.Xml.Linq;
using Barotrauma.LuaCs;
using Barotrauma.Networking;
namespace Barotrauma.LuaCs.Data;
/// <summary>
/// Parsed data from a configuration xml.
/// </summary>
public partial interface IConfigInfo : IDataInfo
{
/// <summary>
/// Specifies the type initializer that will be used to instantiate the config var.
/// </summary>
string DataType { get; }
/// <summary>
/// The 'Setting' XML element.
/// </summary>
XElement Element { get; }
/// <summary>
/// In what <see cref="RunState"/>(s) is this config editable. Will be editable in the selected state, and lower value states.
/// <br/><br/>
/// <b>[Important]</b><br/> Setting this to value lower than 'Configuration` will render this config read-only.
/// <br/><br/><b>Expected Behaviour</b>:
/// <br/><b>[<see cref="RunState.Unloaded"/>|<see cref="RunState.Unloaded"/>]</b>: Read-Only.
/// <br/><b>[<see cref="RunState.LoadedNoExec"/>]</b>: Can only be changed at the Main Menu (not in a lobby).
/// <br/><b>[<see cref="RunState.Running"/>]</b>: Can be changed at the Main Menu and while a lobby is active.
/// </summary>
RunState EditableStates { get; }
/// <summary>
/// Network synchronization rules for this config.
/// </summary>
NetSync NetSync { get; }
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.LuaCs.Data;
public interface IConfigProfileInfo : IDataInfo
{
IReadOnlyList<(string SettingName, XElement Element)> ProfileValues { get; }
}
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace Barotrauma.LuaCs.Data;
/// <summary>
/// Serves as a compound-key to refer to all resources and information that comes from a specific source.
/// </summary>
public interface IDataInfo : IEqualityComparer<IDataInfo>, IEquatable<IDataInfo>
{
/// <summary>
/// Internal name unique within the resources inside a package.
/// </summary>
[XmlAttribute("Name")]
string InternalName { get; }
/// <summary>
/// The package this information belongs to.
/// </summary>
ContentPackage OwnerPackage { get; }
bool IEqualityComparer<IDataInfo>.Equals(IDataInfo x, IDataInfo y)
{
if (x is null || y is null)
return false;
if (x.OwnerPackage is null)
throw new NullReferenceException($"ContentPackage not set for resource {x}!");
if (y.OwnerPackage is null)
throw new NullReferenceException($"ContentPackage not set for resource {y}!");
if (x.InternalName.IsNullOrWhiteSpace())
throw new NullReferenceException($"InternalName not set for resource {x}!");
if (y.InternalName.IsNullOrWhiteSpace())
throw new NullReferenceException($"InternalName not set for resource {y}!");
return x.OwnerPackage == y.OwnerPackage && x.InternalName == y.InternalName;
}
bool IEquatable<IDataInfo>.Equals(IDataInfo other)
{
return Equals(this, other);
}
int IEqualityComparer<IDataInfo>.GetHashCode(IDataInfo obj)
{
if (obj.OwnerPackage is null)
throw new NullReferenceException($"ContentPackage not set for resource {obj}!");
if (obj.InternalName.IsNullOrWhiteSpace())
throw new NullReferenceException($"InternalName is null for object {obj}!");
return obj.InternalName.GetHashCode() + obj.OwnerPackage.GetHashCode();
}
}
@@ -0,0 +1,18 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Xml.Linq;
namespace Barotrauma.LuaCs.Data;
public partial interface IModConfigInfo : IAssembliesResourcesInfo,
ILuaScriptsResourcesInfo, IConfigsResourcesInfo
{
// package info
ContentPackage Package { get; }
}
public record ResourceParserInfo(
[NotNull] ContentPackage Owner,
[NotNull] XElement Element,
ImmutableArray<Identifier> Required,
ImmutableArray<Identifier> Incompatible);
@@ -0,0 +1,79 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Xml.Serialization;
namespace Barotrauma.LuaCs.Data;
public interface IBaseResourceInfo : IResourceInfo, IDataInfo, IDependencyInfo {}
public interface IConfigResourceInfo : IBaseResourceInfo {}
/// <summary>
/// Represents loadable Lua files.
/// </summary>
public interface ILuaScriptResourceInfo : IBaseResourceInfo
{
/// <summary>
/// Should this script be run automatically.
/// </summary>
[XmlAttribute("IsAutorun")]
public bool IsAutorun { get; }
/// <summary>
/// Indicates that this lua resources needs to run outside sandbox/requires unrestricted access.
/// </summary>
[XmlAttribute("RunUnrestricted")]
public bool RunUnrestricted { get; }
}
public interface IAssemblyResourceInfo : IBaseResourceInfo
{
/// <summary>
/// The friendly name of the assembly. Script files belonging to the same assembly should all have the same name.
/// Legacy scripts will all be given the sanitized name of the Content Package they belong to.
/// </summary>
[XmlAttribute("FriendlyName")]
public string FriendlyName { get; }
/// <summary>
/// Is this entry referring to a script file collection.
/// </summary>
[XmlAttribute("IsScript")]
public bool IsScript { get; }
/// <summary>
/// <b>[Required(IsScript: true)] Whether the internal compiled assembly name should be named to enabled use of the
/// <see cref="InternalsVisibleToAttribute"/> attribute.</b>
/// </summary>
[XmlAttribute("UseInternalAccessName")]
public bool UseInternalAccessName { get; }
/// <summary>
/// Should the following resources only be used for Compilation MetadataReference.
/// NOTE: Affects the entire package's assembly resources, meant for internal use only.
/// </summary>
[XmlAttribute("IsReferenceModeOnly")]
public bool IsReferenceModeOnly { get; }
}
#region Collections
public interface IAssembliesResourcesInfo
{
ImmutableArray<IAssemblyResourceInfo> Assemblies { get; }
}
public interface ILuaScriptsResourcesInfo
{
ImmutableArray<ILuaScriptResourceInfo> LuaScripts { get; }
}
public interface IConfigsResourcesInfo
{
ImmutableArray<IConfigResourceInfo> Configs { get; }
}
#endregion
@@ -0,0 +1,18 @@
namespace Barotrauma.LuaCs.Data;
/// <summary>
/// Legacy data contract for the old run configuration system. Should be deprecated
/// once no longer needed.
/// </summary>
public interface IRunConfig
{
bool UseNonPublicizedAssemblies { get; }
bool AutoGenerated { get; }
bool UseInternalAssemblyName { get; }
string Client { get; }
string Server { get; }
bool IsForced();
bool IsStandard();
bool IsForcedOrStandard();
}
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Xml.Linq;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs;
using Barotrauma.Networking;
using OneOf;
namespace Barotrauma.LuaCs.Data;
public partial interface ISettingBase : IDataInfo, IEquatable<ISettingBase>, IDisposable
{
/// <summary>
/// Settings production factory. Should be implemented by all types and registered with the Dependency Injector.
/// </summary>
/// <typeparam name="T">An interface type derived from <see cref="ISettingBase"/>.</typeparam>
public interface IFactory<out T> where T : ISettingBase
{
/// <summary>
/// Creates an instance of the given <see cref="ISettingBase"/> type.
/// </summary>
/// <param name="configInfo">Configuration information.</param>
/// <param name="valueChangePredicate">Called before a new value is assigned. Returns a boolean whether to allow
/// the value to be changed to the one given.</param>
/// <returns></returns>
T CreateInstance([NotNull]IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate);
}
IConfigInfo GetConfigInfo();
#if CLIENT
IConfigDisplayInfo GetDisplayInfo();
#endif
bool IsDisposed { get; }
Type GetValueType();
string GetStringValue();
string GetDefaultStringValue();
bool TrySetSerializedValue(OneOf<string, XElement> value);
event Action<ISettingBase> OnValueChanged;
OneOf.OneOf<string, XElement> GetSerializableValue();
}
/// <summary>
/// Creates a setting representing a value of the given <see cref="Type"/>. Must be a compatible listed type. <br/>
/// </summary>
/// <typeparam name="T">
/// <b>Compatible Types:</b><br/>
/// Any primitive type:<br/>
/// - <see cref="byte"/><br/>
/// - <see cref="sbyte"/><br/>
/// - <see cref="ushort"/><br/>
/// - <see cref="short"/><br/>
/// - <see cref="int"/><br/>
/// - <see cref="uint"/><br/>
/// - <see cref="long"/><br/>
/// - <see cref="ulong"/><br/>
/// - <see cref="float"/><br/>
/// - <see cref="double"/><br/>
/// Extension types and Enums: <br/>
/// - <see cref="string"/><br/>
/// - <see cref="Enum"/><br/>
/// </typeparam>
public interface ISettingBase<T> : ISettingBase where T : IEquatable<T>, IConvertible
{
[NotNull]
T Value { get; }
[NotNull]
T DefaultValue { get; }
bool TrySetValue(T value);
}
/// <summary>
/// Creates a setting representing a value of the given <see cref="Type"/> with a minimum and maximum value.
/// Can only be either an <see cref="int"/> or a <see cref="float"/>.
/// </summary>
/// <remarks>The type selection is limited by the Undertow implementation of the GUI Slider.</remarks>
/// <typeparam name="T">The value type, either <see cref="int"/> or <see cref="float"/></typeparam>
public interface ISettingRangeBase<T> : ISettingBase<T> where T : IEquatable<T>, IConvertible
{
T MinValue { get; }
T MaxValue { get; }
int IncrementalSteps { get; }
}
/// <summary>
/// Creates a setting representing a value of the given <see cref="Type"/> with a distinct list of selectable values.
/// Must be a type compatible with <see cref="ISettingBase{T}"/>.
/// </summary>
/// <typeparam name="T">The value type. See <see cref="ISettingBase{T}"/></typeparam>
public interface ISettingList<T> : ISettingBase<T> where T : IEquatable<T>, IConvertible
{
bool TrySetValueByIndex(int index);
IReadOnlyList<T> Options { get; }
IReadOnlyList<string> StringOptions { get; }
}
@@ -0,0 +1,113 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.AccessControl;
using Barotrauma.LuaCs;
using Barotrauma.Networking;
using FluentResults;
using OneOf.Types;
namespace Barotrauma.LuaCs.Data;
// --- Storage Service
// TODO: Configs should not be services, add new registration path for them.
public interface IStorageServiceConfig : IService
{
string LocalModsDirectory { get; }
string WorkshopModsDirectory { get; }
string GameSettingsConfigPath { get; }
#if CLIENT
string TempDownloadsDirectory { get; }
#endif
string LocalDataSavePath { get; }
string LocalDataPathRegex { get; }
string LocalPackageDataPath { get; }
}
public record StorageServiceConfig : IStorageServiceConfig
{
private static readonly string ExecutionLocation = Directory.GetCurrentDirectory().CleanUpPathCrossPlatform();
public string LocalModsDirectory { get; init; } = System.IO.Path.GetFullPath(ContentPackage.LocalModsDir).CleanUpPath();
public string WorkshopModsDirectory { get; init; } = System.IO.Path.GetFullPath(ContentPackage.WorkshopModsDir).CleanUpPath();
public string GameSettingsConfigPath { get; init; } = System.IO.Path.GetFullPath(
string.IsNullOrEmpty(GameSettings.CurrentConfig.SavePath)
? SaveUtil.DefaultSaveFolder
: GameSettings.CurrentConfig.SavePath).CleanUpPath();
#if CLIENT
public string TempDownloadsDirectory { get; init; } = System.IO.Path.GetFullPath(ModReceiver.DownloadFolder).CleanUpPath();
#endif
public string LocalDataSavePath => Path.Combine(ExecutionLocation, "Data/Mods").CleanUpPathCrossPlatform();
public string LocalDataPathRegex => "%ModDir%";
public string RunLocation => ExecutionLocation;
public string LocalPackageDataPath => Path.Combine(LocalDataSavePath, LocalDataPathRegex);
public void Dispose()
{
// cannot be disposed.
}
public bool IsDisposed => false;
}
// --- Config Service
public interface IConfigServiceConfig : IService
{
string LocalConfigPathPartial { get; }
string FileNamePattern { get; }
}
public record ConfigServiceConfig : IConfigServiceConfig
{
public string LocalConfigPathPartial => $"/Config/{FileNamePattern}.xml";
public string FileNamePattern => "<ConfigName>";
public void Dispose()
{
// ignored
}
public bool IsDisposed => false;
}
// --- Lua Scripts Service
public interface ILuaScriptServicesConfig : IService
{
bool SafeLuaIOEnabled { get; }
bool UseCaching { get; }
}
public record LuaScriptServicesConfig : ILuaScriptServicesConfig
{
public bool SafeLuaIOEnabled => true;
public bool UseCaching => true;
public void Dispose()
{
// ignored
}
public bool IsDisposed => false;
}
// --- Package Management Service
public interface IPackageManagementServiceConfig : IService
{
bool IsCsEnabled { get; }
}
public class PackageManagementServiceConfig : IPackageManagementServiceConfig
{
public void Dispose()
{
// ignored
}
public bool IsDisposed => false;
public bool IsCsEnabled => true;
}
@@ -0,0 +1,81 @@
using System;
using System.Collections.Concurrent;
using System.Xml.Linq;
using Barotrauma.LuaCs.Data;
using Microsoft.Toolkit.Diagnostics;
using Microsoft.Xna.Framework;
using OneOf;
namespace Barotrauma.LuaCs.Data;
public abstract class SettingBase : ISettingBase
{
protected SettingBase(IConfigInfo configInfo)
{
Guard.IsNotNull(configInfo, nameof(configInfo));
ConfigInfo = configInfo;
}
protected IConfigInfo ConfigInfo { get; private set; }
public string InternalName => ConfigInfo.InternalName;
public ContentPackage OwnerPackage => ConfigInfo.OwnerPackage;
public IConfigInfo GetConfigInfo() => ConfigInfo;
#if CLIENT
public IConfigDisplayInfo GetDisplayInfo() => ConfigInfo;
#endif
public virtual bool Equals(ISettingBase other)
{
return other is not null && (
ReferenceEquals(this, other) || !IsDisposed &&
OwnerPackage == other.OwnerPackage &&
InternalName.Equals(other.InternalName));
}
private int _isDisposed = 0;
public virtual bool IsDisposed
{
get => ModUtils.Threading.GetBool(ref _isDisposed);
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
}
protected abstract void OnDispose();
public virtual void Dispose()
{
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
{
return;
}
OnDispose();
ConfigInfo = null;
GC.SuppressFinalize(this);
}
// -- Must be implemented
public abstract Type GetValueType();
public abstract string GetStringValue();
public abstract string GetDefaultStringValue();
public abstract bool TrySetSerializedValue(OneOf<string, XElement> value);
public abstract event Action<ISettingBase> OnValueChanged;
public abstract OneOf<string, XElement> GetSerializableValue();
#if CLIENT
public virtual void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action<string> onSerializedValue)
{
new GUITextBox(new RectTransform(relativeSize, layoutGroup.RectTransform), font: GUIStyle.SmallFont)
{
Text = GetStringValue(),
OnTextChangedDelegate = (box, txt) =>
{
onSerializedValue?.Invoke(txt);
return true;
}
};
}
#endif
}
@@ -0,0 +1,395 @@
using System;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs;
using Barotrauma.Networking;
using Microsoft.Toolkit.Diagnostics;
using Microsoft.Xna.Framework;
using OneOf;
namespace Barotrauma.LuaCs.Data;
public partial class SettingEntry<T> : SettingBase, ISettingBase<T>, INetworkSyncVar where T : IEquatable<T>, IConvertible
{
public class Factory : ISettingBase.IFactory<ISettingBase<T>>
{
public ISettingBase<T> CreateInstance(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate)
{
Guard.IsNotNull(configInfo, nameof(configInfo));
return new SettingEntry<T>(configInfo, valueChangePredicate);
}
}
public SettingEntry(IConfigInfo configInfo,
Func<OneOf<string, XElement, object>, bool> valueChangePredicate)
: base(configInfo)
{
if (!(
typeof(T).IsEnum ||
typeof(T).IsPrimitive ||
typeof(T) == typeof(string)))
{
ThrowHelper.ThrowArgumentException($"{nameof(ISettingBase<T>)}: The type of {nameof(T)} is not an allowed type.");
}
ValueChangePredicate = valueChangePredicate;
try
{
Value = (T)Convert.ChangeType(ConfigInfo.Element.GetAttributeString("Value", null), typeof(T));
DefaultValue = Value;
}
catch (Exception e) when (e is InvalidCastException or ArgumentNullException)
{
Value = default(T);
DefaultValue = default(T);
}
}
protected Func<OneOf<string, XElement, object>, bool> ValueChangePredicate;
public T Value { get; protected set; }
public T DefaultValue { get; protected set; }
public virtual bool TrySetValue(T value)
{
if (value is null || value.Equals(Value))
{
return false;
}
#if CLIENT
if (SyncType is NetSync.ServerAuthority && NetworkingService is not null
&& GameMain.IsMultiplayer
&& GameMain.Client is not null
&& !GameMain.Client.HasPermission(this.WritePermissions))
{
return false;
}
#endif
if (!TrySetValueInternal(value))
{
return false;
}
OnValueChanged?.Invoke(this);
#if CLIENT
if (GameMain.IsMultiplayer && SyncType is NetSync.ClientOneWay or NetSync.TwoWay)
{
NetworkingService?.SendNetVar(this);
}
#elif SERVER
if (SyncType is NetSync.TwoWay or NetSync.ServerAuthority)
{
NetworkingService?.SendNetVar(this);
}
#endif
return true;
}
private bool TrySetValueInternal(T value)
{
if (value is null)
{
return false;
}
if (ValueChangePredicate != null && !ValueChangePredicate(value))
{
return false;
}
Value = value;
return true;
}
/// <summary>
/// handles internal networking rules after reading the net message (to avoid synchro issues).
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private bool TrySetValueNetwork(T value)
{
if (NetworkingService is null)
{
return false;
}
#if CLIENT
if (SyncType is NetSync.None or NetSync.ClientOneWay)
{
return false;
}
#else
if (SyncType is NetSync.None or NetSync.ServerAuthority)
{
return false;
}
#endif
if (!TrySetValueInternal(value))
{
return false;
}
#if SERVER
if (SyncType is NetSync.TwoWay)
{
NetworkingService?.SendNetVar(this);
}
#endif
OnValueChanged?.Invoke(this);
return true;
}
protected override void OnDispose()
{
ValueChangePredicate = null;
NetworkingService?.DeregisterNetVar(this);
}
public override Type GetValueType() => typeof(T);
public override string GetStringValue() => Value?.ToString() ?? string.Empty;
public override string GetDefaultStringValue() => DefaultValue?.ToString() ?? string.Empty;
public override bool TrySetSerializedValue(OneOf<string, XElement> value)
{
bool isFailed = false;
var typeConvertedValue = value.Match<T>(
(string val) =>
{
try
{
return (T)Convert.ChangeType(val, typeof(T));
}
catch (Exception e)
{
// ignored
isFailed = true;
return default(T);
}
},
(XElement val) =>
{
try
{
return (T)Convert.ChangeType(val.GetAttributeString("Value", null), typeof(T));
}
catch (Exception e)
{
isFailed = true;
return default(T);
}
});
return !isFailed && TrySetValue(typeConvertedValue);
}
public override event Action<ISettingBase> OnValueChanged;
public override OneOf<string, XElement> GetSerializableValue() => Value.ToString();
// -- Networking
protected IEntityNetworkingService NetworkingService;
public Guid InstanceId => NetworkingService?.GetNetworkIdForInstance(this) ?? Guid.Empty;
public void SetNetworkOwner(IEntityNetworkingService networkingService)
{
NetworkingService = networkingService;
}
public NetSync SyncType => ConfigInfo?.NetSync ?? NetSync.None;
// needs to be added IConfigInfo
public ClientPermissions WritePermissions => ClientPermissions.ManageSettings;
public void ReadNetMessage(IReadMessage message)
{
if (SyncType == NetSync.None || NetworkingService is null)
{
return;
}
try
{
if (typeof(T).IsEnum)
{
TrySetValueInternal((T)(object)message.ReadInt32());
}
// No...there's no better way to do this...
var typeCode = Type.GetTypeCode(typeof(T));
switch (typeCode)
{
case TypeCode.Boolean:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadBoolean(), typeCode));
return;
case TypeCode.Byte:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadByte(), typeCode));
return;
// SByte not supported by interface
case TypeCode.SByte:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadInt16(), typeCode));
return;
case TypeCode.Int16:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadInt16(), typeCode));
return;
case TypeCode.Char:
case TypeCode.UInt16:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadUInt16(), typeCode));
return;
case TypeCode.Int32:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadInt32(), typeCode));
return;
case TypeCode.UInt32:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadUInt32(), typeCode));
return;
case TypeCode.Int64:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadInt64(), typeCode));
return;
case TypeCode.UInt64:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadUInt64(), typeCode));
return;
case TypeCode.Single:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadSingle(), typeCode));
return;
case TypeCode.Double:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadDouble(), typeCode));
return;
case TypeCode.String:
TrySetValueNetwork((T)Convert.ChangeType(message.ReadString(), typeCode));
return;
case TypeCode.Decimal:
default:
ThrowHelper.ThrowNotSupportedException($"{nameof(SettingEntry<T>)}: The type {typeof(T).Name} is not supported.");
break;
}
}
catch (Exception e)
{
// Suppress unless we're testing.
#if DEBUG
throw;
#endif
}
}
public void WriteNetMessage(IWriteMessage message)
{
if (SyncType == NetSync.None || NetworkingService is null)
{
return;
}
try
{
if (typeof(T).IsEnum)
{
message.WriteInt32((int)((IConvertible)Value));
}
// No...there's no better way to do this...
var typeCode = Type.GetTypeCode(typeof(T));
switch (typeCode)
{
case TypeCode.Boolean:
message.WriteBoolean((bool)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.Byte:
message.WriteByte((byte)Convert.ChangeType(Value, typeCode)!);
return;
// SByte not supported by interface
case TypeCode.SByte:
message.WriteInt16((short)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.Int16:
message.WriteInt16((short)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.Char:
case TypeCode.UInt16:
message.WriteUInt16((ushort)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.Int32:
message.WriteInt32((int)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.UInt32:
message.WriteUInt32((uint)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.Int64:
message.WriteInt64((long)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.UInt64:
message.WriteUInt64((ulong)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.Single:
message.WriteSingle((float)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.Double:
message.WriteDouble((double)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.String:
message.WriteString((string)Convert.ChangeType(Value, typeCode)!);
return;
case TypeCode.Decimal:
default:
ThrowHelper.ThrowNotSupportedException($"{nameof(SettingEntry<T>)}: The type {typeof(T).Name} is not supported.");
break;
}
}
catch (Exception e)
{
// Suppress unless we're testing.
#if DEBUG
throw;
#endif
}
}
#if CLIENT
public override void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action<string> onSerializedValue)
{
switch (Type.GetTypeCode(typeof(T)))
{
case TypeCode.Boolean:
new GUITickBox(new RectTransform(relativeSize, layoutGroup.RectTransform), "")
{
Selected = (bool)Convert.ChangeType(this.Value, TypeCode.Boolean),
OnSelected = (box) =>
{
onSerializedValue?.Invoke(box.Selected.ToString());
return true;
}
};
break;
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Char:
case TypeCode.UInt16:
case TypeCode.Int32:
case TypeCode.UInt32:
case TypeCode.Int64:
case TypeCode.UInt64:
new GUINumberInput(new RectTransform(relativeSize, layoutGroup.RectTransform), NumberType.Int)
{
IntValue = (int)Convert.ChangeType(this.Value, TypeCode.Int32)!,
OnValueChanged = (num) =>
{
onSerializedValue?.Invoke(num.IntValue.ToString());
}
};
break;
case TypeCode.Single:
case TypeCode.Double:
new GUINumberInput(new RectTransform(relativeSize, layoutGroup.RectTransform), NumberType.Float)
{
FloatValue = (float)Convert.ChangeType(this.Value, TypeCode.Single)!,
OnValueChanged = (num) =>
{
onSerializedValue?.Invoke(num.FloatValue.ToString());
}
};
break;
case TypeCode.String:
default:
base.AddDisplayComponent(layoutGroup, relativeSize, onSerializedValue);
break;
}
}
#endif
}
@@ -0,0 +1,121 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Toolkit.Diagnostics;
using Microsoft.Xna.Framework;
namespace Barotrauma.LuaCs.Data;
public class SettingList<T> : SettingEntry<T>, ISettingList<T> where T : IEquatable<T>, IConvertible
{
public class LFactory : ISettingBase.IFactory<ISettingList<T>>
{
public ISettingList<T> CreateInstance(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate)
{
Guard.IsNotNull(configInfo, nameof(configInfo));
return new SettingList<T>(configInfo, valueChangePredicate);
}
}
public SettingList(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) : base(configInfo, valueChangePredicate)
{
if (!(
typeof(T).IsEnum ||
typeof(T).IsPrimitive ||
typeof(T) == typeof(string)))
{
ThrowHelper.ThrowArgumentException($"{nameof(ISettingBase<T>)}: The type of {nameof(T)} is not an allowed type.");
}
ValueChangePredicate = valueChangePredicate;
var valuesElements = ConfigInfo.Element.GetChildElement("Values")?.GetChildElements("Value")?.ToImmutableArray();
Guard.IsNotNull(valuesElements, this.InternalName);
if (valuesElements.Value.IsEmpty)
{
ThrowHelper.ThrowArgumentNullException($"{this.InternalName}: Could not find any values in list!");
}
foreach (var element in valuesElements.Value)
{
if (!TryConvert(element, out var v1))
{
ThrowHelper.ThrowArgumentException($"{this.InternalName}: Error while parsing list values");
}
_valuesList.Add(v1);
}
if (TryConvert(ConfigInfo.Element, out var v) && _valuesList.Contains(v))
{
Value = v;
DefaultValue = v;
}
else
{
Value = _valuesList[0];
DefaultValue = _valuesList[0];
}
bool TryConvert(XElement element, out T value)
{
try
{
value = (T)Convert.ChangeType(element.GetAttributeString("Value", null), typeof(T));
return true;
}
catch (Exception e) when (e is InvalidCastException or ArgumentNullException)
{
value = default(T);
return false;
}
}
}
private readonly List<T> _valuesList = new();
public override bool TrySetValue(T value)
{
if (!_valuesList.Contains(value))
{
return false;
}
return base.TrySetValue(value);
}
public bool TrySetValueByIndex(int index)
{
if (_valuesList.Count <= index)
{
return false;
}
return base.TrySetValue(_valuesList[index]);
}
public IReadOnlyList<T> Options => _valuesList.AsReadOnly();
public IReadOnlyList<string> StringOptions => _valuesList.Select(e => e.ToString()).ToImmutableArray();
#if CLIENT
public override void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action<string> onSerializedValue)
{
GUIUtil.Dropdown(layoutGroup, (T val) => GetLocalizedString(val.ToString(), val.ToString()), null, Options, Value, (T val) =>
{
onSerializedValue?.Invoke(val.ToString());
}, new Vector2(relativeSize.X, 1f));
string GetLocalizedString(string identifier, string defaultValue)
{
var lstr = TextManager.Get($"{XmlConvert.EncodeLocalName(OwnerPackage.Name)}.{InternalName}.{identifier}.DisplayName");
return lstr.IsNullOrWhiteSpace() ? defaultValue : lstr.Value;
}
}
#endif
}
@@ -0,0 +1,104 @@
using System;
using System.Globalization;
using System.Xml.Linq;
using Barotrauma.LuaCs.Data;
using Microsoft.Toolkit.Diagnostics;
using Microsoft.Xna.Framework;
using OneOf;
namespace Barotrauma.LuaCs.Data;
public abstract class SettingRangeBase<T> : SettingEntry<T>, ISettingRangeBase<T> where T : IEquatable<T>, IConvertible
{
public SettingRangeBase(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) : base(configInfo, valueChangePredicate)
{
}
public T MinValue { get; protected init; }
public T MaxValue { get; protected init; }
public int IncrementalSteps { get; protected init; }
}
public class SettingRangeFloat : SettingRangeBase<float>
{
public class RangeFactory : ISettingBase.IFactory<SettingRangeFloat>
{
public SettingRangeFloat CreateInstance(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate)
{
Guard.IsNotNull(configInfo, nameof(configInfo));
return new SettingRangeFloat(configInfo, valueChangePredicate);
}
}
public SettingRangeFloat(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) : base(configInfo, valueChangePredicate)
{
// funny values in case they forget to set them in the config.
MinValue = configInfo.Element.GetAttributeFloat("Min", float.MinValue);
MaxValue = configInfo.Element.GetAttributeFloat("Max", float.MaxValue);
IncrementalSteps = configInfo.Element.GetAttributeInt("Steps", 3);
}
public override bool TrySetValue(float value)
{
if (value > MaxValue || value < MinValue)
{
return false;
}
return base.TrySetValue(value);
}
#if CLIENT
public override void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action<string> onSerializedValue)
{
GUIUtil.Slider(layoutGroup, new Vector2(MinValue, MaxValue), IncrementalSteps, labelFunc: val =>
{
return val.ToString("G4", CultureInfo.InvariantCulture);
}, Value, setter: val =>
{
onSerializedValue?.Invoke(val.ToString());
}, TextManager.Get(this.GetDisplayInfo().Tooltip), relativeSize);
}
#endif
}
public class SettingRangeInt : SettingRangeBase<int>
{
public class RangeFactory : ISettingBase.IFactory<SettingRangeInt>
{
public SettingRangeInt CreateInstance(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate)
{
Guard.IsNotNull(configInfo, nameof(configInfo));
return new SettingRangeInt(configInfo, valueChangePredicate);
}
}
public SettingRangeInt(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) : base(configInfo, valueChangePredicate)
{
// funny values in case they forget to set them in the config.
MinValue = configInfo.Element.GetAttributeInt("Min", int.MinValue);
MaxValue = configInfo.Element.GetAttributeInt("Max", int.MaxValue);
IncrementalSteps = configInfo.Element.GetAttributeInt("Steps", 3);
}
public override bool TrySetValue(int value)
{
if (value > MaxValue || value < MinValue)
{
return false;
}
return base.TrySetValue(value);
}
#if CLIENT
public override void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action<string> onSerializedValue)
{
GUIUtil.Slider(layoutGroup, new Vector2(MinValue, MaxValue), IncrementalSteps, labelFunc: val =>
{
return ((int)val).ToString();
}, Value, setter: val =>
{
onSerializedValue?.Invoke(((int)val).ToString());
}, TextManager.Get(this.GetDisplayInfo().Tooltip), relativeSize);
}
#endif
}
@@ -0,0 +1,123 @@
using System;
using System.Xml.Linq;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs;
using OneOf;
namespace Barotrauma.LuaCs.Data;
public interface ISettingsRegistrationProvider : IService
{
void RegisterTypeProviders(IConfigService configService, Func<OneOf<string, XElement, object>, bool> valueChangePredicate);
}
public class SettingsEntryRegistrar : ISettingsRegistrationProvider
{
private ILuaCsInfoProvider _infoProvider;
public SettingsEntryRegistrar(ILuaCsInfoProvider infoProvider)
{
_infoProvider = infoProvider;
}
public void RegisterTypeProviders(IConfigService configService, Func<OneOf<string, XElement, object>, bool> valueChangePredicate)
{
RegisterSettingEntry<bool>(configService, "bool", valueChangePredicate);
RegisterSettingEntry<byte>(configService, "byte", valueChangePredicate);
RegisterSettingEntry<sbyte>(configService, "sbyte", valueChangePredicate);
RegisterSettingEntry<short>(configService, "short", valueChangePredicate);
RegisterSettingEntry<ushort>(configService, "ushort", valueChangePredicate);
RegisterSettingEntry<int>(configService, "int", valueChangePredicate);
RegisterSettingEntry<uint>(configService, "uint", valueChangePredicate);
RegisterSettingEntry<long>(configService, "long", valueChangePredicate);
RegisterSettingEntry<ulong>(configService, "ulong", valueChangePredicate);
RegisterSettingEntry<string>(configService, "string", valueChangePredicate);
RegisterSettingEntry<float>(configService, "float", valueChangePredicate);
RegisterSettingEntry<float>(configService, "single", valueChangePredicate);
RegisterSettingEntry<double>(configService, "double", valueChangePredicate);
// ISettingRangeBase<T>
configService.RegisterSettingTypeInitializer("rangeInt", cfgInfo =>
{
return new SettingRangeInt.RangeFactory().CreateInstance(cfgInfo.Info, (val) =>
IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate));
});
configService.RegisterSettingTypeInitializer("rangeFloat", cfgInfo =>
{
return new SettingRangeFloat.RangeFactory().CreateInstance(cfgInfo.Info, (val) =>
IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate));
});
#if CLIENT
configService.RegisterSettingTypeInitializer("control" , cfgInfo =>
{
return new SettingControl.Factory().CreateInstance(cfgInfo.Info, val =>
IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate));
});
#endif
RegisterSettingList<bool>(configService, "listBool", valueChangePredicate);
RegisterSettingList<byte>(configService, "listByte", valueChangePredicate);
RegisterSettingList<sbyte>(configService, "listSbyte", valueChangePredicate);
RegisterSettingList<short>(configService, "listShort", valueChangePredicate);
RegisterSettingList<ushort>(configService, "listUshort", valueChangePredicate);
RegisterSettingList<int>(configService, "listInt", valueChangePredicate);
RegisterSettingList<uint>(configService, "listUint", valueChangePredicate);
RegisterSettingList<long>(configService, "listLong", valueChangePredicate);
RegisterSettingList<ulong>(configService, "listUlong", valueChangePredicate);
RegisterSettingList<string>(configService, "listString", valueChangePredicate);
RegisterSettingList<float>(configService, "listFloat", valueChangePredicate);
RegisterSettingList<float>(configService, "listSingle", valueChangePredicate);
RegisterSettingList<double>(configService, "listDouble", valueChangePredicate);
}
private void RegisterSettingList<T>(IConfigService configService, string typeName, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) where T : IEquatable<T>, IConvertible
{
configService.RegisterSettingTypeInitializer(typeName, cfgInfo =>
{
return new SettingList<T>.LFactory().CreateInstance(cfgInfo.Info, (val) =>
IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate));
});
}
private void RegisterSettingEntry<T>(IConfigService configService, string typeName, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) where T : IEquatable<T>, IConvertible
{
configService.RegisterSettingTypeInitializer(typeName, cfgInfo =>
{
return new SettingEntry<T>.Factory().CreateInstance(cfgInfo.Info, (val) =>
IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate));
});
}
private bool IsValueChangeAllowed(IConfigInfo info, OneOf<string, XElement, object> newValue,
Func<OneOf<string, XElement, object>, bool> valueChangePredicate)
{
#if CLIENT
return !info.Element.GetAttributeBool("ReadOnly", false)
|| info.EditableStates < _infoProvider.CurrentRunState
|| valueChangePredicate is null
|| valueChangePredicate.Invoke(newValue);
#else
// Server has absolute authority.
return !info.Element.GetAttributeBool("ReadOnly", false);
#endif
}
public void Dispose()
{
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
{
return;
}
_infoProvider.Dispose();
_infoProvider = null;
}
private int _isDisposed;
public bool IsDisposed
{
get => ModUtils.Threading.GetBool(ref _isDisposed);
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
}
}