- Fixed network synchro of vars, needs synctype testing.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public sealed partial class ConfigService
|
||||
{
|
||||
public ImmutableArray<ISettingBase> GetDisplayableConfigs()
|
||||
{
|
||||
using var _ = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
return _settingsInstances.Values
|
||||
.Where(s => !s.IsDisposed)
|
||||
.Where(s => s.GetDisplayInfo().ShowInMenus)
|
||||
.Where(s => !GameMain.IsMultiplayer || s.GetConfigInfo().NetSync != NetSync.ServerAuthority)
|
||||
.Where(s => s.GetConfigInfo().EditableStates >= _infoProvider.CurrentRunState)
|
||||
.ToImmutableArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public partial class LoggerService : ILoggerService, IClientLoggerService
|
||||
{
|
||||
private GUIFrame _overlayFrame;
|
||||
private GUITextBlock _textBlock;
|
||||
private double _showTimer = 0;
|
||||
|
||||
|
||||
private void CreateOverlay(string message)
|
||||
{
|
||||
_overlayFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.03f), null), null, new Color(50, 50, 50, 100))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
GUILayoutGroup layout =
|
||||
new GUILayoutGroup(
|
||||
new RectTransform(new Vector2(0.8f, 0.8f), _overlayFrame.RectTransform, Anchor.CenterLeft), false,
|
||||
Anchor.Center);
|
||||
|
||||
_textBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), layout.RectTransform), message);
|
||||
_overlayFrame.RectTransform.MinSize = new Point((int)(_textBlock.TextSize.X * 1.2), 0);
|
||||
|
||||
layout.Recalculate();
|
||||
}
|
||||
|
||||
public void AddToGUIUpdateList()
|
||||
{
|
||||
if (_overlayFrame != null && Timing.TotalTime <= _showTimer)
|
||||
{
|
||||
_overlayFrame.AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowErrorOverlay(string message, float time = 5f, float duration = 1.5f)
|
||||
{
|
||||
if (Timing.TotalTime <= _showTimer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CreateOverlay(message);
|
||||
|
||||
_overlayFrame.Flash(Color.Red, duration, true);
|
||||
_showTimer = Timing.TotalTime + time;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public sealed partial class ModConfigFileParserService :
|
||||
IParserServiceAsync<ResourceParserInfo, IStylesResourceInfo>
|
||||
{
|
||||
async Task<Result<IStylesResourceInfo>> IParserServiceAsync<ResourceParserInfo, IStylesResourceInfo>.TryParseResourceAsync(ResourceParserInfo src)
|
||||
{
|
||||
using var lck = await _operationsLock.AcquireReaderLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (CheckThrowNullRefs(src, "Style") is { IsFailed: true } fail)
|
||||
return fail;
|
||||
|
||||
var runtimeEnv = GetRuntimeEnvironment(src.Element);
|
||||
var fileResults = await UnsafeGetCheckedFiles(src.Element, src.Owner, ".xml");
|
||||
|
||||
if (fileResults.IsFailed)
|
||||
return FluentResults.Result.Fail(fileResults.Errors);
|
||||
|
||||
return new StylesResourceInfo()
|
||||
{
|
||||
SupportedPlatforms = runtimeEnv.Platform,
|
||||
SupportedTargets = Target.Client, // clientside only
|
||||
LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0),
|
||||
FilePaths = fileResults.Value,
|
||||
Optional = src.Element.GetAttributeBool("Optional", false),
|
||||
InternalName = src.Element.GetAttributeString("Name", string.Empty),
|
||||
OwnerPackage = src.Owner,
|
||||
RequiredPackages = src.Required,
|
||||
IncompatiblePackages = src.Incompatible
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<ImmutableArray<Result<IStylesResourceInfo>>> TryParseResourcesAsync(IEnumerable<ResourceParserInfo> sources)
|
||||
{
|
||||
return await this.TryParseGenericResourcesAsync<IStylesResourceInfo>(sources);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using Barotrauma.LuaCs;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
partial class NetworkingService : INetworkingService, IEventServerConnected, IEventServerRawNetMessageReceived
|
||||
{
|
||||
private ConcurrentDictionary<ushort, ConcurrentQueue<IReadMessage>> receiveQueue = new();
|
||||
|
||||
public void OnServerConnected()
|
||||
{
|
||||
ActivateNetVars();
|
||||
SendSyncMessage();
|
||||
}
|
||||
|
||||
private void ActivateNetVars()
|
||||
{
|
||||
if (GameMain.Client == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// re-activate net vars
|
||||
// todo: unregister net vars on client disconnect, currently handled by unloading the state machine.
|
||||
foreach (var networkSyncVar in netVars.Keys)
|
||||
{
|
||||
networkSyncVar.SetNetworkOwner(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader)
|
||||
{
|
||||
if (serverPacketHeader != ServerHeader)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ServerToClient luaCsHeader = (ServerToClient)netMessage.ReadByte();
|
||||
|
||||
switch (luaCsHeader)
|
||||
{
|
||||
case ServerToClient.NetMessageNetId:
|
||||
HandleNetMessageString(netMessage);
|
||||
break;
|
||||
|
||||
case ServerToClient.NetMessageInternalId:
|
||||
HandleNetMessageId(netMessage);
|
||||
break;
|
||||
|
||||
case ServerToClient.ReceiveNetIds:
|
||||
ReadIds(netMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SendSyncMessage()
|
||||
{
|
||||
if (GameMain.Client == null) { return; }
|
||||
|
||||
WriteOnlyMessage message = new WriteOnlyMessage();
|
||||
message.WriteByte((byte)ClientHeader);
|
||||
message.WriteByte((byte)ClientToServer.RequestSync);
|
||||
GameMain.Client.ClientPeer.Send(message, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
public IWriteMessage Start(NetId netId)
|
||||
{
|
||||
var message = new WriteOnlyMessage();
|
||||
|
||||
message.WriteByte((byte)ClientHeader);
|
||||
|
||||
if (idToPacket.ContainsKey(netId))
|
||||
{
|
||||
message.WriteByte((byte)ClientToServer.NetMessageInternalId);
|
||||
message.WriteUInt16(idToPacket[netId]);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.WriteByte((byte)ClientToServer.NetMessageNetId);
|
||||
NetId.Write(message, netId);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public void SendToServer(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable)
|
||||
{
|
||||
GameMain.Client.ClientPeer.Send(netMessage, deliveryMethod);
|
||||
}
|
||||
|
||||
public void Send(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable)
|
||||
=> SendToServer(netMessage, deliveryMethod);
|
||||
|
||||
private void RequestId(NetId netId)
|
||||
{
|
||||
if (idToPacket.ContainsKey(netId)) { return; }
|
||||
|
||||
if (GameMain.Client == null) { return; }
|
||||
|
||||
WriteOnlyMessage message = new WriteOnlyMessage();
|
||||
message.WriteByte((byte)ClientHeader);
|
||||
message.WriteByte((byte)ClientToServer.RequestSingleNetId);
|
||||
|
||||
NetId.Write(message, netId);
|
||||
|
||||
SendToServer(message, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
private void HandleNetMessageId(IReadMessage netMessage, Client client = null)
|
||||
{
|
||||
ushort id = netMessage.ReadUInt16();
|
||||
|
||||
if (packetToId.ContainsKey(id))
|
||||
{
|
||||
HandleNetMessage(netMessage, packetToId[id], client);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!receiveQueue.ContainsKey(id)) { receiveQueue[id] = new ConcurrentQueue<IReadMessage>(); }
|
||||
receiveQueue[id].Enqueue(netMessage);
|
||||
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
_loggerService.LogMessage($"Received NetMessage with unknown id {id} from server, storing in queue in case we receive the id later.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadIds(IReadMessage netMessage)
|
||||
{
|
||||
ushort size = netMessage.ReadUInt16();
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
ushort packetId = netMessage.ReadUInt16();
|
||||
NetId netId = NetId.Read(netMessage);
|
||||
|
||||
packetToId[packetId] = netId;
|
||||
idToPacket[netId] = packetId;
|
||||
|
||||
if (!receiveQueue.ContainsKey(packetId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We could have received messages before receiving the sync message, so we need to process them now
|
||||
|
||||
while (receiveQueue[packetId].TryDequeue(out var queueMessage))
|
||||
{
|
||||
if (netReceives.ContainsKey(netId))
|
||||
{
|
||||
netReceives[netId](queueMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
using Microsoft.Toolkit.Diagnostics;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public class UIStylesCollection : HashlessFile, IUIStylesCollection
|
||||
{
|
||||
public class Factory : IUIStylesCollection.IFactory
|
||||
{
|
||||
public IEnumerable<IUIStylesCollection> CreateInstance(IStylesResourceInfo info, IStorageService storageService)
|
||||
{
|
||||
Guard.IsNotNull(info, nameof(info));
|
||||
Guard.IsNotNull(info.OwnerPackage, nameof(info.OwnerPackage));
|
||||
if (info.FilePaths.IsDefaultOrEmpty)
|
||||
{
|
||||
return ImmutableArray<IUIStylesCollection>.Empty;
|
||||
}
|
||||
|
||||
var builder = ImmutableArray.CreateBuilder<IUIStylesCollection>();
|
||||
foreach (var contentPath in info.FilePaths)
|
||||
{
|
||||
builder.Add(new UIStylesCollection(contentPath, storageService));
|
||||
}
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
//ignore, stateless service
|
||||
}
|
||||
|
||||
public bool IsDisposed => false;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, GUIFont> _fonts = new();
|
||||
private readonly ConcurrentDictionary<string, GUISprite> _sprites = new();
|
||||
private readonly ConcurrentDictionary<string, GUISpriteSheet> _spriteSheets = new();
|
||||
private readonly ConcurrentDictionary<string, GUICursor> _cursors = new();
|
||||
private readonly ConcurrentDictionary<string, GUIColor> _colors = new();
|
||||
|
||||
/// <summary>
|
||||
/// Only for internal reference.
|
||||
/// </summary>
|
||||
private UIStyleFile _fakeFile;
|
||||
|
||||
private IStorageService _storageService;
|
||||
|
||||
public UIStylesCollection(ContentPath path, IStorageService storageService) : base(path.ContentPackage, path)
|
||||
{
|
||||
Guard.IsNotNull(path, nameof(path));
|
||||
Guard.IsNotNull(path.ContentPackage, nameof(path.ContentPackage));
|
||||
_storageService = storageService;
|
||||
_fakeFile = new UIStyleFile(path.ContentPackage, path);
|
||||
}
|
||||
|
||||
public new ContentPath Path => base.Path;
|
||||
|
||||
public Result<GUIFont> GetFont(string name)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
if (_fonts.TryGetValue(name, out var asset))
|
||||
{
|
||||
return asset;
|
||||
}
|
||||
|
||||
return FluentResults.Result.Fail($"{nameof(GetFont)}: Failed to find the font with the name '{name}'");
|
||||
}
|
||||
|
||||
public Result<GUISprite> GetSprite(string name)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
if (_sprites.TryGetValue(name, out var asset))
|
||||
{
|
||||
return asset;
|
||||
}
|
||||
|
||||
return FluentResults.Result.Fail($"{nameof(GetSprite)}: Failed to find the sprite with the name '{name}'");
|
||||
}
|
||||
|
||||
public Result<GUISpriteSheet> GetSpriteSheet(string name)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
if (_spriteSheets.TryGetValue(name, out var asset))
|
||||
{
|
||||
return asset;
|
||||
}
|
||||
|
||||
return FluentResults.Result.Fail($"{nameof(GetSpriteSheet)}: Failed to find the spritesheet with the name '{name}'");
|
||||
}
|
||||
|
||||
public Result<GUICursor> GetCursor(string name)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
if (_cursors.TryGetValue(name, out var asset))
|
||||
{
|
||||
return asset;
|
||||
}
|
||||
|
||||
return FluentResults.Result.Fail($"{nameof(GetCursor)}: Failed to find the cursor with the name '{name}'");
|
||||
}
|
||||
|
||||
public Result<GUIColor> GetColor(string name)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
if (_colors.TryGetValue(name, out var asset))
|
||||
{
|
||||
return asset;
|
||||
}
|
||||
|
||||
return FluentResults.Result.Fail($"{nameof(GetColor)}: Failed to find the color with the name '{name}'");
|
||||
}
|
||||
|
||||
public override void LoadFile()
|
||||
{
|
||||
using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (_storageService.LoadPackageXml(Path) is not { IsSuccess: true } result)
|
||||
{
|
||||
DebugConsole.LogError($"Failed to load xml from {Path.FullPath}.");
|
||||
ThrowHelper.ThrowArgumentException($"Failed to load xml from {Path.FullPath}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var root = result.Value.Root?.FromPackage(Path.ContentPackage);
|
||||
if (root is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var styleElement = root.Name.LocalName.ToLowerInvariant() == "style" ? root : root.GetChildElement("style");
|
||||
if (styleElement is null)
|
||||
return;
|
||||
|
||||
var childElements = styleElement.GetChildElements("Font");
|
||||
if (childElements is not null)
|
||||
AddToList<GUIFont, GUIFontPrefab>(_fonts, childElements, _fakeFile);
|
||||
|
||||
childElements = styleElement.GetChildElements("Sprite");
|
||||
if (childElements is not null)
|
||||
AddToList<GUISprite, GUISpritePrefab>(_sprites, childElements, _fakeFile);
|
||||
|
||||
childElements = styleElement.GetChildElements("Spritesheet");
|
||||
if (childElements is not null)
|
||||
AddToList<GUISpriteSheet, GUISpriteSheetPrefab>(_spriteSheets, childElements, _fakeFile);
|
||||
|
||||
childElements = styleElement.GetChildElements("Cursor");
|
||||
if (childElements is not null)
|
||||
AddToList<GUICursor, GUICursorPrefab>(_cursors, childElements, _fakeFile);
|
||||
|
||||
childElements = styleElement.GetChildElements("Color");
|
||||
if (childElements is not null)
|
||||
AddToList<GUIColor, GUIColorPrefab>(_colors, childElements, _fakeFile);
|
||||
|
||||
void AddToList<T1, T2>(ConcurrentDictionary<string, T1> dict, IEnumerable<ContentXElement> elem, UIStyleFile file) where T1 : GUISelector<T2> where T2 : GUIPrefab
|
||||
{
|
||||
foreach (ContentXElement prefabElement in elem)
|
||||
{
|
||||
string name = prefabElement.GetAttributeString("name", string.Empty);
|
||||
if (name != string.Empty)
|
||||
{
|
||||
var prefab = (T2)Activator.CreateInstance(typeof(T2), new object[]{ prefabElement, file })!;
|
||||
if (!dict.ContainsKey(name))
|
||||
dict[name] = (T1)Activator.CreateInstance(typeof(T1), new object[] { name })!;
|
||||
dict[name].Prefabs.Add(prefab, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UnloadFile()
|
||||
{
|
||||
using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
_fonts.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile));
|
||||
_sprites.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile));
|
||||
_spriteSheets.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile));
|
||||
_cursors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile));
|
||||
_colors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile));
|
||||
}
|
||||
|
||||
public override void Sort()
|
||||
{
|
||||
using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
_fonts.Values.ForEach(p => p.Prefabs.Sort());
|
||||
_sprites.Values.ForEach(p => p.Prefabs.Sort());
|
||||
_spriteSheets.Values.ForEach(p => p.Prefabs.Sort());
|
||||
_cursors.Values.ForEach(p => p.Prefabs.Sort());
|
||||
_colors.Values.ForEach(p => p.Prefabs.Sort());
|
||||
}
|
||||
|
||||
#region INTERNAL_DISPOSE
|
||||
|
||||
private readonly AsyncReaderWriterLock _lock = new();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_fonts.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile));
|
||||
_sprites.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile));
|
||||
_spriteSheets.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile));
|
||||
_cursors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile));
|
||||
_colors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile));
|
||||
|
||||
_fonts.Clear();
|
||||
_sprites.Clear();
|
||||
_spriteSheets.Clear();
|
||||
_cursors.Clear();
|
||||
_colors.Clear();
|
||||
}
|
||||
|
||||
private int _isDisposed;
|
||||
public bool IsDisposed
|
||||
{
|
||||
get => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
using Microsoft.Toolkit.Diagnostics;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public class UIStylesService : IUIStylesService
|
||||
{
|
||||
#region DISPOSAL
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var collection in _stylesCollections.Values.SelectMany(c => c))
|
||||
{
|
||||
try
|
||||
{
|
||||
collection.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
|
||||
_stylesCollections.Clear();
|
||||
_storageService.Dispose();
|
||||
_stylesCollectionFactory.Dispose();
|
||||
|
||||
_storageService = null;
|
||||
_stylesCollectionFactory = null;
|
||||
}
|
||||
|
||||
private int _isDisposed = 0;
|
||||
public bool IsDisposed
|
||||
{
|
||||
get => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
|
||||
}
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
var result = FluentResults.Result.Ok();
|
||||
|
||||
foreach (var collection in _stylesCollections.Values.SelectMany(c => c))
|
||||
{
|
||||
try
|
||||
{
|
||||
collection.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
result.WithError(new ExceptionalError(e));
|
||||
}
|
||||
}
|
||||
|
||||
_stylesCollections.Clear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private readonly AsyncReaderWriterLock _lock = new();
|
||||
|
||||
#endregion
|
||||
|
||||
private IStorageService _storageService;
|
||||
private IUIStylesCollection.IFactory _stylesCollectionFactory;
|
||||
|
||||
private ConcurrentDictionary<(ContentPackage Package, string InternalName), ImmutableArray<IUIStylesCollection>>
|
||||
_stylesCollections = new();
|
||||
|
||||
public UIStylesService(IUIStylesCollection.IFactory stylesCollectionFactory, IStorageService storageService)
|
||||
{
|
||||
_stylesCollectionFactory = stylesCollectionFactory;
|
||||
_storageService = storageService;
|
||||
}
|
||||
|
||||
public Result<GUIColor> GetColor(ContentPackage package, string internalName, string assetName)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName));
|
||||
Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName));
|
||||
|
||||
if (!_stylesCollections.TryGetValue((package, internalName), out var collection)
|
||||
|| collection.IsDefaultOrEmpty)
|
||||
{
|
||||
return FluentResults.Result.Fail(
|
||||
$"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]");
|
||||
}
|
||||
|
||||
var failedResult = new FluentResults.Result();
|
||||
|
||||
foreach (var stylesCollection in collection)
|
||||
{
|
||||
var res = stylesCollection.GetColor(assetName);
|
||||
if (res.IsSuccess)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
failedResult.WithErrors(res.Errors);
|
||||
}
|
||||
|
||||
return failedResult;
|
||||
}
|
||||
|
||||
public Result<GUICursor> GetCursor(ContentPackage package, string internalName, string assetName)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName));
|
||||
Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName));
|
||||
|
||||
if (!_stylesCollections.TryGetValue((package, internalName), out var collection)
|
||||
|| collection.IsDefaultOrEmpty)
|
||||
{
|
||||
return FluentResults.Result.Fail(
|
||||
$"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]");
|
||||
}
|
||||
|
||||
var failedResult = new FluentResults.Result();
|
||||
|
||||
foreach (var stylesCollection in collection)
|
||||
{
|
||||
var res = stylesCollection.GetCursor(assetName);
|
||||
if (res.IsSuccess)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
failedResult.WithErrors(res.Errors);
|
||||
}
|
||||
|
||||
return failedResult;
|
||||
}
|
||||
|
||||
public Result<GUIFont> GetFont(ContentPackage package, string internalName, string assetName)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName));
|
||||
Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName));
|
||||
|
||||
if (!_stylesCollections.TryGetValue((package, internalName), out var collection)
|
||||
|| collection.IsDefaultOrEmpty)
|
||||
{
|
||||
return FluentResults.Result.Fail(
|
||||
$"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]");
|
||||
}
|
||||
|
||||
var failedResult = new FluentResults.Result();
|
||||
|
||||
foreach (var stylesCollection in collection)
|
||||
{
|
||||
var res = stylesCollection.GetFont(assetName);
|
||||
if (res.IsSuccess)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
failedResult.WithErrors(res.Errors);
|
||||
}
|
||||
|
||||
return failedResult;
|
||||
}
|
||||
|
||||
public Result<GUISprite> GetSprite(ContentPackage package, string internalName, string assetName)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName));
|
||||
Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName));
|
||||
|
||||
if (!_stylesCollections.TryGetValue((package, internalName), out var collection)
|
||||
|| collection.IsDefaultOrEmpty)
|
||||
{
|
||||
return FluentResults.Result.Fail(
|
||||
$"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]");
|
||||
}
|
||||
|
||||
var failedResult = new FluentResults.Result();
|
||||
|
||||
foreach (var stylesCollection in collection)
|
||||
{
|
||||
var res = stylesCollection.GetSprite(assetName);
|
||||
if (res.IsSuccess)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
failedResult.WithErrors(res.Errors);
|
||||
}
|
||||
|
||||
return failedResult;
|
||||
}
|
||||
|
||||
public Result<GUISpriteSheet> GetSpriteSheet(ContentPackage package, string internalName, string assetName)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName));
|
||||
Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName));
|
||||
|
||||
if (!_stylesCollections.TryGetValue((package, internalName), out var collection)
|
||||
|| collection.IsDefaultOrEmpty)
|
||||
{
|
||||
return FluentResults.Result.Fail(
|
||||
$"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]");
|
||||
}
|
||||
|
||||
var failedResult = new FluentResults.Result();
|
||||
|
||||
foreach (var stylesCollection in collection)
|
||||
{
|
||||
var res = stylesCollection.GetSpriteSheet(assetName);
|
||||
if (res.IsSuccess)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
failedResult.WithErrors(res.Errors);
|
||||
}
|
||||
|
||||
return failedResult;
|
||||
}
|
||||
|
||||
public FluentResults.Result LoadAssets(ImmutableArray<IStylesResourceInfo> resources)
|
||||
{
|
||||
using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
if (resources.IsDefaultOrEmpty)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentNullException(nameof(resources));
|
||||
}
|
||||
|
||||
var operationSuccess = FluentResults.Result.Ok();
|
||||
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
var builder = ImmutableArray.CreateBuilder<IUIStylesCollection>();
|
||||
if (_stylesCollections.TryGetValue((resource.OwnerPackage, resource.InternalName), out var collection))
|
||||
{
|
||||
builder.AddRange(collection);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var newCollections = _stylesCollectionFactory.CreateInstance(resource, _storageService).ToImmutableArray();
|
||||
foreach (var stylesCollection in newCollections)
|
||||
{
|
||||
stylesCollection.LoadFile();
|
||||
}
|
||||
builder.AddRange(newCollections);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
operationSuccess.WithError(new ExceptionalError(e));
|
||||
continue;
|
||||
}
|
||||
|
||||
_stylesCollections[(resource.OwnerPackage, resource.InternalName)] = builder.ToImmutable();
|
||||
}
|
||||
|
||||
return operationSuccess;
|
||||
}
|
||||
|
||||
public FluentResults.Result UnloadPackages(ImmutableArray<ContentPackage> packages)
|
||||
{
|
||||
using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
var toRemove = _stylesCollections
|
||||
.Select(c => c.Key)
|
||||
.Where(c => packages.Contains(c.Package))
|
||||
.ToImmutableArray();
|
||||
|
||||
var result = FluentResults.Result.Ok();
|
||||
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
if (_stylesCollections.TryRemove(key, out var collection) && !collection.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var stylesCollection in collection)
|
||||
{
|
||||
try
|
||||
{
|
||||
stylesCollection.UnloadFile();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
result.WithError(new ExceptionalError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public FluentResults.Result UnloadPackage(ContentPackage package)
|
||||
{
|
||||
// Yes, this is very cursed/inefficient. We don't care.
|
||||
return UnloadPackages(new [] { package }.ToImmutableArray());
|
||||
}
|
||||
|
||||
public FluentResults.Result UnloadAllPackages()
|
||||
{
|
||||
using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
var result = FluentResults.Result.Ok();
|
||||
|
||||
foreach (var key in _stylesCollections.Keys.ToImmutableArray())
|
||||
{
|
||||
if (_stylesCollections.TryRemove(key, out var collection) && !collection.IsDefaultOrEmpty)
|
||||
{
|
||||
foreach (var stylesCollection in collection)
|
||||
{
|
||||
try
|
||||
{
|
||||
stylesCollection.UnloadFile();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
result.WithError(new ExceptionalError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface IClientLoggerService : IReusableService
|
||||
{
|
||||
void AddToGUIUpdateList();
|
||||
void ShowErrorOverlay(string message, float time = 5f, float duration = 1.5f);
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.LuaCs;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public partial interface IConfigService
|
||||
{
|
||||
ImmutableArray<ISettingBase> GetDisplayableConfigs();
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ISettingsMenuSystem : ISystem
|
||||
{
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface IUIStylesCollection : IService
|
||||
{
|
||||
public interface IFactory : IService
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a new <see cref="IUIStylesCollection"/> for-each <see cref="ContentPath"/> in the given
|
||||
/// <see cref="IStylesResourceInfo.FilePaths"/> or empty is none.
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <param name="storageService"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IUIStylesCollection> CreateInstance(IStylesResourceInfo info, IStorageService storageService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The assigned/target <see cref="ContentPath"/> for this collection.
|
||||
/// </summary>
|
||||
public ContentPath Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GUIFont"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public Result<GUIFont> GetFont(string name);
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GUISprite"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public Result<GUISprite> GetSprite(string name);
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GUISpriteSheet"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public Result<GUISpriteSheet> GetSpriteSheet(string name);
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GUICursor"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public Result<GUICursor> GetCursor(string name);
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GUIColor"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public Result<GUIColor> GetColor(string name);
|
||||
|
||||
#region BAROTRAUMA.UISTYLEFILE
|
||||
|
||||
/// <summary>
|
||||
/// Definition of <see cref="HashlessFile.LoadFile"/>
|
||||
/// </summary>
|
||||
internal void LoadFile();
|
||||
/// <summary>
|
||||
/// Definition of <see cref="HashlessFile.UnloadFile"/>
|
||||
/// </summary>
|
||||
internal void UnloadFile();
|
||||
/// <summary>
|
||||
/// Definition of <see cref="HashlessFile.Sort"/>
|
||||
/// </summary>
|
||||
internal void Sort();
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface IUIStylesService : IReusableService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the first loaded <see cref="GUIColor"/>.
|
||||
/// </summary>
|
||||
/// <param name="package">The target <see cref="ContentPackage"/></param>
|
||||
/// <param name="internalName">The targets <see cref="IDataInfo.InternalName"/> as specified in the ModConfig.xml.</param>
|
||||
/// <param name="assetName">The asset's name as specified in the styles XML file.</param>
|
||||
/// <returns>A <see cref="FluentResults.Result"/> indicating success, and the target if succeeded.</returns>
|
||||
public Result<GUIColor> GetColor(ContentPackage package, string internalName, string assetName);
|
||||
/// <summary>
|
||||
/// Gets the loaded <see cref="GUICursor"/>.
|
||||
/// </summary>
|
||||
/// <param name="package">The target <see cref="ContentPackage"/></param>
|
||||
/// <param name="internalName">The targets <see cref="IDataInfo.InternalName"/> as specified in the ModConfig.xml.</param>
|
||||
/// <param name="assetName">The asset's name as specified in the styles XML file.</param>
|
||||
/// <returns>A <see cref="FluentResults.Result"/> indicating success, and the target if succeeded.</returns>
|
||||
public Result<GUICursor> GetCursor(ContentPackage package, string internalName, string assetName);
|
||||
/// <summary>
|
||||
/// Gets the loaded <see cref="GUIFont"/>.
|
||||
/// </summary>
|
||||
/// <param name="package">The target <see cref="ContentPackage"/></param>
|
||||
/// <param name="internalName">The targets <see cref="IDataInfo.InternalName"/> as specified in the ModConfig.xml.</param>
|
||||
/// <param name="assetName">The asset's name as specified in the styles XML file.</param>
|
||||
/// <returns>A <see cref="FluentResults.Result"/> indicating success, and the target if succeeded.</returns>
|
||||
public Result<GUIFont> GetFont(ContentPackage package, string internalName, string assetName);
|
||||
/// <summary>
|
||||
/// Gets the loaded <see cref="GUISprite"/>.
|
||||
/// </summary>
|
||||
/// <param name="package">The target <see cref="ContentPackage"/></param>
|
||||
/// <param name="internalName">The targets <see cref="IDataInfo.InternalName"/> as specified in the ModConfig.xml.</param>
|
||||
/// <param name="assetName">The asset's name as specified in the styles XML file.</param>
|
||||
/// <returns>A <see cref="FluentResults.Result"/> indicating success, and the target if succeeded.</returns>
|
||||
public Result<GUISprite> GetSprite(ContentPackage package, string internalName, string assetName);
|
||||
/// <summary>
|
||||
/// Gets the loaded <see cref="GUISpriteSheet"/>.
|
||||
/// </summary>
|
||||
/// <param name="package">The target <see cref="ContentPackage"/></param>
|
||||
/// <param name="internalName">The targets <see cref="IDataInfo.InternalName"/> as specified in the ModConfig.xml.</param>
|
||||
/// <param name="assetName">The asset's name as specified in the styles XML file.</param>
|
||||
/// <returns>A <see cref="FluentResults.Result"/> indicating success, and the target if succeeded.</returns>
|
||||
public Result<GUISpriteSheet> GetSpriteSheet(ContentPackage package, string internalName, string assetName);
|
||||
|
||||
public FluentResults.Result LoadAssets(ImmutableArray<IStylesResourceInfo> resources);
|
||||
|
||||
public FluentResults.Result UnloadPackages(ImmutableArray<ContentPackage> packages);
|
||||
|
||||
public FluentResults.Result UnloadPackage(ContentPackage package);
|
||||
|
||||
public FluentResults.Result UnloadAllPackages();
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
internal sealed class ModsControlsSettingsMenu : ModsSettingsMenuBase
|
||||
{
|
||||
public ModsControlsSettingsMenu(GUIFrame contentFrame,
|
||||
IPackageManagementService packageManagementService,
|
||||
IConfigService configService,
|
||||
SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void DisposeInternal()
|
||||
{
|
||||
// TODO: Finish this later.
|
||||
}
|
||||
|
||||
public override void ApplyInstalledModChanges()
|
||||
{
|
||||
// TODO: Finish this later.
|
||||
}
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Vector2 = Microsoft.Xna.Framework.Vector2;
|
||||
using Vector4 = Microsoft.Xna.Framework.Vector4;
|
||||
|
||||
// ReSharper disable ObjectCreationAsStatement
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
||||
{
|
||||
private ImmutableArray<ISettingBase> _settingsInstancesGameplay;
|
||||
// menu vars
|
||||
private GUILayoutGroup _modCategoryDisplayGroup, _settingsDisplayGroup;
|
||||
private string _selectedSearchQuery = string.Empty;
|
||||
private ContentPackage _selectedContentPackage;
|
||||
private string _selectedCategory = string.Empty;
|
||||
|
||||
private event Action OnApplyInstalledModsChanges;
|
||||
|
||||
public ModsGameplaySettingsMenu(GUIFrame contentFrame,
|
||||
IPackageManagementService packageManagementService,
|
||||
IConfigService configService,
|
||||
SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance)
|
||||
{
|
||||
_settingsInstancesGameplay = configService.GetDisplayableConfigs()
|
||||
.ToImmutableArray();
|
||||
|
||||
|
||||
var mainLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), contentFrame.RectTransform, Anchor.Center), false, Anchor.TopLeft);
|
||||
// page title
|
||||
var menuTitleLayoutGroup = new GUILayoutGroup(
|
||||
new RectTransform(new Vector2(1f, 0.06f), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft);
|
||||
GUIUtil.Label(menuTitleLayoutGroup, "Mods Gameplay Settings", GUIStyle.LargeFont, new Vector2(1f, 1f));
|
||||
|
||||
// page contents
|
||||
var contentAreaLayoutGroup = new GUILayoutGroup(
|
||||
new RectTransform(new Vector2(1f, 0.94f), mainLayoutGroup.RectTransform, Anchor.BottomLeft), false,
|
||||
Anchor.TopLeft);
|
||||
|
||||
var searchBarLayoutGroup = new GUILayoutGroup(
|
||||
new RectTransform(new Vector2(1f, 0.06f), contentAreaLayoutGroup.RectTransform, Anchor.TopCenter), true, Anchor.CenterLeft);
|
||||
GUIUtil.Label(searchBarLayoutGroup, "Search: ", GUIStyle.SubHeadingFont, new Vector2(0.1f, 1f));
|
||||
var searchBar = new GUITextBox(
|
||||
new RectTransform(new Vector2(0.85f, 0.1f), searchBarLayoutGroup.RectTransform, Anchor.TopLeft),
|
||||
createClearButton: true)
|
||||
{
|
||||
OnTextChangedDelegate = (btn, txt) =>
|
||||
{
|
||||
GenerateDisplayFromFilter(txt);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
// main display area
|
||||
var settingsContentAreaGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.90f), contentAreaLayoutGroup.RectTransform, Anchor.BottomCenter));
|
||||
GUIUtil.Spacer(settingsContentAreaGroup, Vector2.One);
|
||||
(_modCategoryDisplayGroup, _settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true);
|
||||
_modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f);
|
||||
_settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f);
|
||||
|
||||
// default category
|
||||
_selectedCategory = "All";
|
||||
|
||||
OnApplyInstalledModsChanges = () =>
|
||||
{
|
||||
_settingsInstancesGameplay = configService.GetDisplayableConfigs()
|
||||
.ToImmutableArray();
|
||||
if (_selectedContentPackage is not null && !GetTargetPackagesList().Contains(_selectedContentPackage))
|
||||
{
|
||||
_selectedContentPackage = null;
|
||||
_selectedCategory = string.Empty;
|
||||
}
|
||||
|
||||
GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList());
|
||||
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
||||
};
|
||||
|
||||
GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList());
|
||||
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
||||
|
||||
void GenerateDisplayFromFilter(string text)
|
||||
{
|
||||
_selectedSearchQuery = text;
|
||||
GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList());
|
||||
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
||||
}
|
||||
|
||||
string GetLocalizedString(string identifier, string defaultValue)
|
||||
{
|
||||
var lstr = TextManager.Get(identifier);
|
||||
return lstr.IsNullOrWhiteSpace() ? defaultValue : lstr.Value;
|
||||
}
|
||||
|
||||
// Filters by selected package and query text
|
||||
ImmutableArray<string> GetDisplayCategoriesList()
|
||||
{
|
||||
return GetFilteredSettingsList()
|
||||
.Select(s => GetLocalizedString(s.GetDisplayInfo().DisplayCategory, "General"))
|
||||
.Concat(new []{ "All" })
|
||||
.Distinct()
|
||||
.OrderBy(s => s)
|
||||
.ToImmutableArray();
|
||||
}
|
||||
|
||||
// Filters by query text
|
||||
ImmutableArray<ContentPackage> GetTargetPackagesList()
|
||||
{
|
||||
return _settingsInstancesGameplay
|
||||
.Where(s => SettingMatchesQuery(s, _selectedSearchQuery))
|
||||
.Select(s => s.OwnerPackage)
|
||||
.Concat(new[] { ContentPackageManager.VanillaCorePackage })
|
||||
.Distinct()
|
||||
.OrderByDescending(p => p == ContentPackageManager.VanillaCorePackage ? 0 : 1)
|
||||
.ThenBy(p => p.Name)
|
||||
.ToImmutableArray();
|
||||
}
|
||||
|
||||
// Filters by selected package, query text, and selected category.
|
||||
ImmutableArray<ISettingBase> GetDisplaySettingsList()
|
||||
{
|
||||
return GetFilteredSettingsList()
|
||||
.Where(s => _selectedCategory.IsNullOrWhiteSpace()
|
||||
|| _selectedCategory == "All"
|
||||
|| GetLocalizedString(s.GetDisplayInfo().DisplayCategory, "General") == _selectedCategory)
|
||||
.OrderBy(s => GetLocalizedString(s.GetDisplayInfo().DisplayName, s.InternalName))
|
||||
.ToImmutableArray();
|
||||
}
|
||||
|
||||
// Filters by selected package and by query text.
|
||||
ImmutableArray<ISettingBase> GetFilteredSettingsList()
|
||||
{
|
||||
return _settingsInstancesGameplay
|
||||
.Where(s => SettingMatchesQuery(s, _selectedSearchQuery))
|
||||
.Where(s => _selectedContentPackage is null
|
||||
|| _selectedContentPackage == ContentPackageManager.VanillaCorePackage // vanilla is treated as all packages
|
||||
|| s.OwnerPackage == _selectedContentPackage)
|
||||
.OrderBy(s => GetLocalizedString(s.GetDisplayInfo().DisplayName, s.InternalName))
|
||||
.ToImmutableArray();
|
||||
}
|
||||
|
||||
|
||||
bool SettingMatchesQuery(ISettingBase setting, string queryText)
|
||||
{
|
||||
if (queryText.IsNullOrWhiteSpace())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
queryText = queryText.ToLowerInvariant().Trim();
|
||||
|
||||
if (setting.InternalName.ToLowerInvariant().Trim().Contains(queryText) || setting.OwnerPackage.Name.ToLowerInvariant().Trim().Contains(queryText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var displayInfo = setting.GetDisplayInfo();
|
||||
return TextManager.Get(displayInfo.DisplayName).Value.ToLowerInvariant().Trim().Contains(queryText)
|
||||
|| TextManager.Get(displayInfo.DisplayCategory).Value.ToLowerInvariant().Trim().Contains(queryText)
|
||||
|| TextManager.Get(displayInfo.Description).Value.ToLowerInvariant().Trim().Contains(queryText)
|
||||
|| TextManager.Get(displayInfo.Tooltip).Value.ToLowerInvariant().Trim().Contains(queryText);
|
||||
}
|
||||
|
||||
string GetPackageName(ContentPackage package)
|
||||
{
|
||||
return package is null || package == ContentPackageManager.VanillaCorePackage ? "All" : package.Name;
|
||||
}
|
||||
|
||||
ContentPackage GetCurrentSelectedPackage(ImmutableArray<ContentPackage> packages)
|
||||
{
|
||||
if (_selectedContentPackage is null)
|
||||
{
|
||||
return ContentPackageManager.VanillaCorePackage;
|
||||
}
|
||||
|
||||
if (packages.Contains(_selectedContentPackage))
|
||||
{
|
||||
return _selectedContentPackage;
|
||||
}
|
||||
|
||||
if (packages.Length > 0)
|
||||
{
|
||||
_selectedContentPackage = packages[0];
|
||||
return packages[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup, ImmutableArray<ContentPackage> packagesList,
|
||||
ImmutableArray<string> categories)
|
||||
{
|
||||
layoutGroup.ClearChildren();
|
||||
var packageSelectionList = GUIUtil.Dropdown<ContentPackage>(layoutGroup, cp => GetPackageName(cp), null,
|
||||
packagesList, GetCurrentSelectedPackage(packagesList), cp =>
|
||||
{
|
||||
_selectedContentPackage = cp;
|
||||
_selectedCategory = string.Empty;
|
||||
GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList());
|
||||
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
||||
}, new Vector2(1f, 0.07f));
|
||||
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.945f), layoutGroup.RectTransform));
|
||||
const float entryHeight = 0.122f;
|
||||
float sizeY = MathF.Max(categories.Length * entryHeight, 1f);
|
||||
var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, sizeY), containerBox.Content.RectTransform), style: null, color: Color.Black)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
var displayCategoriesLayout = new GUILayoutGroup(new RectTransform(Vector2.One, displayedCategoriesFrame.RectTransform));
|
||||
|
||||
foreach (var category in categories)
|
||||
{
|
||||
var btn = new GUIButton(new RectTransform(new Vector2(1f, entryHeight), displayCategoriesLayout.RectTransform),
|
||||
text: category, color: Color.TransparentBlack)
|
||||
{
|
||||
CanBeFocused = true,
|
||||
CanBeSelected = true,
|
||||
TextColor = Color.PeachPuff,
|
||||
HoverColor = new Color(50, 50, 50, 255),
|
||||
HoverTextColor = Color.White,
|
||||
SelectedColor = new Color(50, 50, 50, 255),
|
||||
SelectedTextColor = Color.White,
|
||||
OnPressed = () =>
|
||||
{
|
||||
_selectedCategory = category;
|
||||
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray<ISettingBase> settings)
|
||||
{
|
||||
layoutGroup.ClearChildren();
|
||||
const float settingHeight = 0.0625f;
|
||||
|
||||
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), layoutGroup.RectTransform));
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
var entry = AddSettingToDisplay(
|
||||
setting,
|
||||
containerBox.Content.RectTransform,
|
||||
settingHeight: settingHeight,
|
||||
labelSize: new Vector2(0.6f, 1f),
|
||||
controlSize: new Vector2(0.4f, 1f));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
(GUIFrame entryFrame, GUILayoutGroup entryLayoutGroup) AddSettingToDisplay(ISettingBase setting,
|
||||
RectTransform parent, float settingHeight, Vector2 labelSize, Vector2 controlSize)
|
||||
{
|
||||
GUIFrame entryFrame = new GUIFrame(new RectTransform(new Vector2(1f, settingHeight), parent))
|
||||
{
|
||||
Color = Color.DarkGray
|
||||
};
|
||||
GUILayoutGroup entryLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, entryFrame.RectTransform), isHorizontal: true);
|
||||
|
||||
// padding
|
||||
new GUIFrame(new RectTransform(new Vector2(0.02f, 1f), entryLayoutGroup.RectTransform),
|
||||
color: Color.TransparentBlack);
|
||||
|
||||
new GUITextBlock(new RectTransform(labelSize - new Vector2(0.05f, 0f), entryLayoutGroup.RectTransform),
|
||||
GetLocalizedString(setting.GetDisplayInfo().DisplayName, setting.GetDisplayInfo().DisplayName),
|
||||
textColor: Color.PeachPuff,
|
||||
font: GUIStyle.SmallFont,
|
||||
textAlignment: Alignment.Left)
|
||||
{
|
||||
ToolTip = GetLocalizedString(setting.GetDisplayInfo().Tooltip, string.Empty)
|
||||
};
|
||||
|
||||
setting.AddDisplayComponent(entryLayoutGroup, controlSize, newValue =>
|
||||
{
|
||||
NewValuesCache[setting] = newValue;
|
||||
});
|
||||
return (entryFrame, entryLayoutGroup);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void DisposeInternal()
|
||||
{
|
||||
NewValuesCache.Clear();
|
||||
_modCategoryDisplayGroup?.Parent.RemoveChild(_modCategoryDisplayGroup);
|
||||
_settingsDisplayGroup?.Parent.RemoveChild(_settingsDisplayGroup);
|
||||
_modCategoryDisplayGroup = null;
|
||||
_settingsDisplayGroup = null;
|
||||
|
||||
}
|
||||
|
||||
public override void ApplyInstalledModChanges()
|
||||
{
|
||||
foreach (var kvp in NewValuesCache)
|
||||
{
|
||||
if (kvp.Key.IsDisposed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
kvp.Key.TrySetValue(kvp.Value);
|
||||
ConfigService.SaveConfigValue(kvp.Key);
|
||||
}
|
||||
NewValuesCache.Clear();
|
||||
OnApplyInstalledModsChanges?.Invoke();
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
internal abstract class ModsSettingsMenuBase : IDisposable
|
||||
{
|
||||
public GUIFrame ContentFrame { get; private set; }
|
||||
protected IPackageManagementService PackageManagementService { get; private set; }
|
||||
protected IConfigService ConfigService { get; private set; }
|
||||
protected SettingsMenu SettingsMenuInstance { get; private set; }
|
||||
protected readonly ConcurrentDictionary<ISettingBase, string> NewValuesCache = new();
|
||||
|
||||
protected ModsSettingsMenuBase(GUIFrame contentFrame,
|
||||
IPackageManagementService packageManagementService,
|
||||
IConfigService configService, SettingsMenu settingsMenuInstance)
|
||||
{
|
||||
ContentFrame = contentFrame;
|
||||
PackageManagementService = packageManagementService;
|
||||
ConfigService = configService;
|
||||
SettingsMenuInstance = settingsMenuInstance;
|
||||
}
|
||||
|
||||
protected abstract void DisposeInternal();
|
||||
public abstract void ApplyInstalledModChanges();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeInternal();
|
||||
ContentFrame?.Parent.RemoveChild(ContentFrame);
|
||||
SettingsMenuInstance = null;
|
||||
ContentFrame = null;
|
||||
PackageManagementService = null;
|
||||
ConfigService = null;
|
||||
NewValuesCache.Clear();
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public class SettingsMenuSystem : ISettingsMenuSystem
|
||||
{
|
||||
|
||||
private ModsControlsSettingsMenu _controlsMenuInstance;
|
||||
private ModsGameplaySettingsMenu _gameplayMenuInstance;
|
||||
private GUIFrame _gameplayContentFrame;
|
||||
private GUIFrame _controlsContentFrame;
|
||||
private SettingsMenu _settingsMenuInstance;
|
||||
|
||||
private readonly Harmony _harmony;
|
||||
private readonly IPackageManagementService _packageManagementService;
|
||||
private readonly IConfigService _configService;
|
||||
private static SettingsMenuSystem SystemInstance;
|
||||
|
||||
public SettingsMenuSystem(IPackageManagementService packageManagementService, IConfigService configService)
|
||||
{
|
||||
_packageManagementService = packageManagementService;
|
||||
_configService = configService;
|
||||
SystemInstance = this;
|
||||
_harmony = Harmony.CreateAndPatchAll(typeof(SettingsMenuSystem));
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(SettingsMenu), "CreateModsTab"), HarmonyPostfix]
|
||||
private static void SettingsMenu_CreateModsTab_Post(SettingsMenu __instance)
|
||||
{
|
||||
SystemInstance._settingsMenuInstance = __instance;
|
||||
SystemInstance.CreateSettingsMenu(__instance);
|
||||
}
|
||||
|
||||
private void CreateSettingsMenu(SettingsMenu __instance)
|
||||
{
|
||||
DisposeMenuFrames();
|
||||
|
||||
var tabCount = Enum.GetValues<SettingsMenu.Tab>().Length;
|
||||
var tabGameplayIndex = (SettingsMenu.Tab)tabCount;
|
||||
var tabControlsIndex = (SettingsMenu.Tab)tabCount+1;
|
||||
|
||||
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
|
||||
"SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton");
|
||||
/*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance,
|
||||
"SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton");
|
||||
*/
|
||||
|
||||
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, __instance);
|
||||
//_controlsMenuInstance = new ModsControlsSettingsMenu(_controlsContentFrame, _packageManagementService, _configService, __instance);
|
||||
}
|
||||
|
||||
private GUIFrame CreateNewContentTab(SettingsMenu.Tab tab, SettingsMenu settingsMenuInstance, string settingsMenuTabName, string settingMenuHoverTextIdent)
|
||||
{
|
||||
if (settingsMenuInstance.tabContents.TryGetValue(tab, out (GUIButton Button, GUIFrame Content) tabContent))
|
||||
{
|
||||
return tabContent.Content;
|
||||
}
|
||||
|
||||
var contentFr = new GUIFrame(new RectTransform(Vector2.One * 0.95f, settingsMenuInstance.contentFrame.RectTransform, Anchor.Center, Pivot.Center), style: null);
|
||||
|
||||
var button = new GUIButton(new RectTransform(Vector2.One, settingsMenuInstance.tabber.RectTransform,
|
||||
Anchor.TopLeft, Pivot.TopLeft, scaleBasis: ScaleBasis.Smallest), "", style: settingsMenuTabName)
|
||||
{
|
||||
ToolTip = TextManager.Get(settingMenuHoverTextIdent),
|
||||
OnClicked = (b, _) =>
|
||||
{
|
||||
settingsMenuInstance.SelectTab(tab);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
button.RectTransform.MaxSize = RectTransform.MaxPoint;
|
||||
button.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint);
|
||||
|
||||
settingsMenuInstance.tabContents.Add(tab, (button, contentFr));
|
||||
|
||||
return contentFr;
|
||||
}
|
||||
|
||||
|
||||
[HarmonyPatch(typeof(SettingsMenu), nameof(SettingsMenu.ApplyInstalledModChanges)), HarmonyPostfix]
|
||||
private static void SettingsMenu_ApplyInstalledModChanges_Post()
|
||||
{
|
||||
SystemInstance._gameplayMenuInstance?.ApplyInstalledModChanges();
|
||||
SystemInstance._controlsMenuInstance?.ApplyInstalledModChanges();
|
||||
}
|
||||
|
||||
private void DisposeMenuFrames()
|
||||
{
|
||||
_controlsMenuInstance?.Dispose();
|
||||
_gameplayMenuInstance?.Dispose();
|
||||
_controlsMenuInstance = null;
|
||||
_gameplayMenuInstance = null;
|
||||
}
|
||||
|
||||
#region DISPOSAL
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
DisposeMenuFrames();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
private int _isDisposed = 0;
|
||||
public bool IsDisposed
|
||||
{
|
||||
get => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
|
||||
}
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user