Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Emerald.CoreX/GameOptions/IMinecraftOptionsService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Emerald.CoreX.GameOptions;

public interface IMinecraftOptionsService
{
/// <summary>
/// Parses options.txt and loads the lang file from the version JAR.
/// Returns an empty list if options.txt does not exist yet.
/// </summary>
Task<MinecraftOptionsLoadResult> LoadAsync(
Game game,
CancellationToken cancellationToken = default);

/// <summary>
/// Writes entries back to options.txt, preserving any keys that were
/// not parsed into MinecraftOptionEntry objects.
/// </summary>
Task SaveAsync(
Game game,
IEnumerable<MinecraftOptionEntry> entries,
CancellationToken cancellationToken = default);
}

public sealed class MinecraftOptionsLoadResult
{
public IReadOnlyList<MinecraftOptionEntry> Entries { get; init; } = [];
public bool OptionsFileExists { get; init; }
/// <summary>Full original key→value map, used to round-trip unknown keys on save.</summary>
public IReadOnlyDictionary<string, string> OriginalRaw { get; init; }
= new Dictionary<string, string>();
}
96 changes: 96 additions & 0 deletions Emerald.CoreX/GameOptions/MinecraftOptionEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;

namespace Emerald.CoreX.GameOptions;

public partial class MinecraftOptionEntry : ObservableObject
{
public required string Key { get; init; }
public required string DisplayName { get; init; }
public required MinecraftOptionType Type { get; init; }
public string Category { get; init; } = "General";
public double SliderMin { get; init; }
public double SliderMax { get; init; } = 1.0;
public double SliderStep { get; init; } = 0.01;
public bool SliderIsInt { get; init; }
public string? SliderSuffix { get; init; }
public IReadOnlyList<MinecraftEnumOption> EnumOptions { get; init; } = [];

[ObservableProperty]
private string _rawValue = string.Empty;

partial void OnRawValueChanged(string value)
{
OnPropertyChanged(nameof(IsBooleanTrue));
OnPropertyChanged(nameof(SliderValue));
OnPropertyChanged(nameof(EnumRawValue));
OnPropertyChanged(nameof(SelectedEnumOption));
OnPropertyChanged(nameof(DisplayValueLabel));
}

// ── Boolean ──────────────────────────────────────────────────────────────
public bool IsBooleanTrue
{
get => RawValue == "true";
set => RawValue = value ? "true" : "false";
}

// ── Slider ───────────────────────────────────────────────────────────────
public double SliderValue
{
get => double.TryParse(RawValue, NumberStyles.Float,
CultureInfo.InvariantCulture, out var d) ? d : SliderMin;
set
{
var rounded = SliderIsInt ? Math.Round(value) : value;
RawValue = SliderIsInt
? ((int)rounded).ToString(CultureInfo.InvariantCulture)
: rounded.ToString(CultureInfo.InvariantCulture);
OnPropertyChanged();
}
}

// ── Enum ─────────────────────────────────────────────────────────────────
public string? EnumRawValue
{
get => RawValue.Trim('"');
set
{
RawValue = value is null ? string.Empty : $"\"{value}\"";
OnPropertyChanged();
}
}

public MinecraftEnumOption? SelectedEnumOption
{
get => EnumOptions.FirstOrDefault(o => o.RawValue == EnumRawValue);
set
{
if (value is not null) EnumRawValue = value.RawValue;
OnPropertyChanged();
}
}

// ── Display label ─────────────────────────────────────────────────────────
public string DisplayValueLabel => Type switch
{
MinecraftOptionType.Boolean => IsBooleanTrue ? "On" : "Off",
MinecraftOptionType.SoundVolume => $"{(int)(SliderValue * 100)}%",
MinecraftOptionType.IntSlider => SliderSuffix is null
? $"{(int)SliderValue}"
: $"{(int)SliderValue}{SliderSuffix}",
MinecraftOptionType.FloatSlider => SliderSuffix is null
? $"{SliderValue:F2}"
: $"{SliderValue:F1}{SliderSuffix}",
MinecraftOptionType.Enum => EnumOptions
.FirstOrDefault(o => o.RawValue == EnumRawValue)?.DisplayLabel
?? EnumRawValue
?? RawValue,
MinecraftOptionType.KeyBind => RawValue.TrimStart("key.".ToCharArray()),
_ => RawValue
};
}

public sealed record MinecraftEnumOption(string RawValue, string DisplayLabel);
12 changes: 12 additions & 0 deletions Emerald.CoreX/GameOptions/MinecraftOptionType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Emerald.CoreX.GameOptions;

public enum MinecraftOptionType
{
Boolean,
IntSlider,
FloatSlider,
Enum,
KeyBind,
SoundVolume,
Skip
}
Loading
Loading