Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs
MapleWheels 1fe68aa861 - Added float, double settingentry types. Adjusted display formatting.
- Added menu refresh on Apply button.
- Fixed package name resolution for invalid chars.
- Added helper console command to get xml-safe name for use in localization files.
- Made error messages for bad settings xml more descriptive.
- Modified GUISlider.
- Converted HarmonyEventPatchesService to a System.
- Fixed package name resolution for incompatible names in Xml and files, in many places.
- Fixed base textbox implementation for settings.
2026-03-05 15:45:00 -05:00

213 lines
8.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using Barotrauma.LuaCs.Data;
using FluentResults;
using Microsoft.Toolkit.Diagnostics;
using OneOf;
namespace Barotrauma.LuaCs;
public sealed class SettingsFileParserService :
IParserServiceOneToManyAsync<IConfigResourceInfo, IConfigInfo>,
IParserServiceOneToManyAsync<IConfigResourceInfo, IConfigProfileInfo>
{
#region DisposalControl
private AsyncReaderWriterLock _operationLock = new();
public void Dispose()
{
using var lck = _operationLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
{
return;
}
_storageService.Dispose();
_storageService = null;
}
private int _isDisposed = 0;
public bool IsDisposed
{
get => ModUtils.Threading.GetBool(ref _isDisposed);
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
}
#endregion
private IStorageService _storageService;
public SettingsFileParserService(IStorageService storageService)
{
_storageService = storageService;
}
async Task<Result<ImmutableArray<IConfigInfo>>> IParserServiceOneToManyAsync<IConfigResourceInfo, IConfigInfo>
.TryParseResourcesAsync(IConfigResourceInfo src)
{
Guard.IsNotNull(src, nameof(src));
Guard.IsNotNull(src.OwnerPackage, nameof(src.OwnerPackage));
using var lck = await _operationLock.AcquireReaderLock();
IService.CheckDisposed(this);
if (src.FilePaths.IsDefaultOrEmpty)
{
return ReturnFail($"The config file list is empty.");
}
var parsedInfo = ImmutableArray.CreateBuilder<IConfigInfo>();
foreach ((ContentPath path, Result<XDocument> docLoadResult) res in await _storageService.LoadPackageXmlFilesAsync(src.FilePaths))
{
if (res.docLoadResult.IsFailed)
{
return ReturnFail($"Failed to load document for {src.OwnerPackage.Name}").WithErrors(res.docLoadResult.Errors);
}
var settingElements = res.docLoadResult.Value.GetChildElement("Configuration")
.GetChildElements("Settings").SelectMany(e => e.GetChildElements("Setting")).ToImmutableArray();
if (settingElements.IsDefaultOrEmpty)
{
continue;
}
var packageIdent = XmlConvert.EncodeLocalName(res.path.ContentPackage!.Name);
foreach (var element in settingElements)
{
var name = element.GetAttributeString("Name", string.Empty);
if (name.IsNullOrWhiteSpace())
{
return ReturnFail(
$"The internal name for a setting in the config file '{res.path.FullPath}' is empty!");
}
var newSetting = new ConfigInfo()
{
InternalName = name,
OwnerPackage = res.path.ContentPackage,
DataType = element.GetAttributeString("Type", string.Empty),
Element = element,
EditableStates = element.GetAttributeBool("ReadOnly", false) ? RunState.Unloaded :
element.GetAttributeBool("AllowChangesWhileExecuting", true) ? RunState.Running :
RunState.LoadedNoExec,
NetSync = element.GetAttributeEnum("NetSync", NetSync.None),
#if CLIENT
DisplayName = $"{packageIdent}.{name}.DisplayName",
Description = $"{packageIdent}.{name}.Description",
DisplayCategory = $"{packageIdent}.{name}.DisplayCategory",
ShowInMenus = element.GetAttributeBool("ShowInMenus", true),
Tooltip = $"{packageIdent}.{name}.Tooltip",
ImageIconPath = element.GetAttributeString("ImageIcon", string.Empty) is {} val && !val.IsNullOrWhiteSpace() ?
ContentPath.FromRaw(res.path.ContentPackage, val) : ContentPath.Empty
#endif
};
if (!IsInfoValid(newSetting))
{
return ReturnFail($"A setting was invalid. ContentPackage: {res.path.ContentPackage.Name}. Name: {newSetting?.InternalName}");
}
parsedInfo.Add(newSetting);
}
}
return FluentResults.Result.Ok(parsedInfo.ToImmutable());
// Helpers
FluentResults.Result ReturnFail(string msg)
{
return FluentResults.Result.Fail($"{nameof(IParserServiceOneToManyAsync<IConfigResourceInfo, IConfigInfo>.TryParseResourcesAsync)}: {msg}");
}
bool IsInfoValid(ConfigInfo info)
{
return info.OwnerPackage != null
&& !info.InternalName.IsNullOrWhiteSpace()
&& !info.DataType.IsNullOrWhiteSpace()
&& info.Element != null
#if CLIENT
&& !info.DisplayName.IsNullOrWhiteSpace()
&& !info.Description.IsNullOrWhiteSpace()
&& !info.DisplayCategory.IsNullOrWhiteSpace()
&& !info.Tooltip.IsNullOrWhiteSpace()
#endif
;
}
}
async Task<Result<ImmutableArray<IConfigProfileInfo>>>
IParserServiceOneToManyAsync<IConfigResourceInfo, IConfigProfileInfo>
.TryParseResourcesAsync(IConfigResourceInfo src)
{
Guard.IsNotNull(src, nameof(src));
Guard.IsNotNull(src.OwnerPackage, nameof(src.OwnerPackage));
using var lck = await _operationLock.AcquireReaderLock();
IService.CheckDisposed(this);
if (src.FilePaths.IsDefaultOrEmpty)
{
return ReturnFail($"The config file list is empty.");
}
var parsedInfo = ImmutableArray.CreateBuilder<IConfigProfileInfo>();
foreach ((ContentPath path, Result<XDocument> docLoadResult) res in await _storageService
.LoadPackageXmlFilesAsync(src.FilePaths))
{
if (res.docLoadResult.IsFailed)
{
return ReturnFail($"Failed to load document for {src.OwnerPackage.Name}")
.WithErrors(res.docLoadResult.Errors);
}
var profileCollection = res.docLoadResult.Value.GetChildElement("Configuration")
.GetChildElement("Profiles");
if (profileCollection == null)
{
continue;
}
foreach (var profile in profileCollection.GetChildElements("Profile"))
{
var profileName = profile.GetAttributeString("Name", string.Empty);
Guard.IsNotNullOrWhiteSpace(profileName, nameof(profileName));
var settingValues = profile.GetChildElements("SettingValue").ToImmutableArray();
if (settingValues.IsDefaultOrEmpty)
{
ThrowHelper.ThrowArgumentNullException(nameof(settingValues));
}
var profileValuesBuilder = ImmutableArray.CreateBuilder<(string ConfigName, XElement Value)>();
foreach (var settingValue in settingValues)
{
var cfgName = settingValue.GetAttributeString("Name", string.Empty);
Guard.IsNotNullOrWhiteSpace(cfgName, nameof(cfgName));
profileValuesBuilder.Add((cfgName, settingValue));
}
parsedInfo.Add(new ConfigProfileInfo()
{
InternalName = profileName,
OwnerPackage = res.path.ContentPackage,
ProfileValues = profileValuesBuilder.ToImmutable()
});
}
}
return parsedInfo.ToImmutable();
FluentResults.Result ReturnFail(string msg)
{
return FluentResults.Result.Fail($"{nameof(IParserServiceOneToManyAsync<IConfigResourceInfo, IConfigProfileInfo>.TryParseResourcesAsync)}: {msg}");
}
}
}