Preparation fo Mystery rolling

This commit is contained in:
2026-05-24 02:55:03 -05:00
parent 47bd6110cf
commit bceff701bf
15 changed files with 646 additions and 32 deletions

View File

@@ -0,0 +1,82 @@
namespace ALttPRandomizer.Serialization {
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using ALttPRandomizer.Model;
public class RandomizableWeightsJsonConverter : JsonConverterFactory {
public override bool CanConvert(Type typeToConvert) {
if (!typeToConvert.IsGenericType) {
return false;
}
if (typeToConvert.GetGenericTypeDefinition() != typeof(RandomizableWeights<>)) {
return false;
}
return true;
}
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) {
Type itemType = typeToConvert.GetGenericArguments()[0];
Type innerType = typeof(RandomizableWeightsConverterInner<>).MakeGenericType(itemType);
return (JsonConverter) Activator.CreateInstance(innerType, args: options)!;
}
private class RandomizableWeightsConverterInner<T> : JsonConverter<RandomizableWeights<T>> where T : struct, Enum {
private readonly JsonConverter<IDictionary<T, int>> dictionaryConverter;
private readonly JsonConverter<T> valueConverter;
private readonly Type valueType;
private readonly Type dictionaryType;
public RandomizableWeightsConverterInner(JsonSerializerOptions options) {
dictionaryConverter = (JsonConverter<IDictionary<T, int>>) options.GetConverter(typeof(IDictionary<T, int>));
valueConverter = (JsonConverter<T>) options.GetConverter(typeof(T));
this.valueType = typeof(T);
this.dictionaryType = typeof(IDictionary<T, int>);
}
public override RandomizableWeights<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
switch (reader.TokenType) {
case JsonTokenType.Null:
return RandomizableWeights<T>.EmptyWeights;
case JsonTokenType.String:
case JsonTokenType.Number:
var value = valueConverter.Read(ref reader, this.valueType, options);
return RandomizableWeights<T>.ConstantWeights(value);
case JsonTokenType.StartObject:
var dict = dictionaryConverter.Read(ref reader, this.dictionaryType, options);
if (dict == null) {
return RandomizableWeights<T>.EmptyWeights;
} else {
return RandomizableWeights<T>.FromDictionary(dict);
}
default:
throw new JsonException();
}
}
public override void Write(Utf8JsonWriter writer, RandomizableWeights<T> value, JsonSerializerOptions options) {
switch (value.Options.Count) {
case 0:
writer.WriteNullValue();
break;
case 1:
this.valueConverter.Write(writer, value.Options[0].Item, options);
break;
default:
writer.WriteStartObject();
foreach (var (item, weight) in value.Options) {
this.valueConverter.WriteAsPropertyName(writer, item, options);
writer.WriteNumberValue(weight);
}
writer.WriteEndObject();
break;
}
}
}
}
}

View File

@@ -0,0 +1,105 @@
namespace ALttPRandomizer.Serialization {
using System;
using System.Collections.Generic;
using ALttPRandomizer.Model;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
public class RandomizableWeightsYamlConverters : IYamlTypeConverter {
private readonly Dictionary<Type, IYamlTypeConverter> converters = new();
public bool Accepts(Type type) {
if (this.converters.ContainsKey(type)) {
return true;
}
if (!type.IsGenericType) {
return false;
}
if (type.GetGenericTypeDefinition() != typeof(RandomizableWeights<>)) {
return false;
}
Type itemType = type.GetGenericArguments()[0];
Type innerType = typeof(RandomizableWeightsYamlConverter<>).MakeGenericType(itemType)!;
this.converters[type] = (IYamlTypeConverter) Activator.CreateInstance(innerType)!;
return true;
}
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) {
return this.converters[type].ReadYaml(parser, type, rootDeserializer);
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) {
this.converters[type].WriteYaml(emitter, value, type, serializer);
}
}
internal class RandomizableWeightsYamlConverter<T> : IYamlTypeConverter where T : struct, Enum {
private readonly Type valueType;
private readonly Type dictionaryType;
public RandomizableWeightsYamlConverter() {
this.valueType = typeof(T);
this.dictionaryType = typeof(IDictionary<T, int>);
}
public bool Accepts(Type type) {
return type == typeof(RandomizableWeights<T>);
}
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) {
if (parser.Accept<Scalar>(out var evt)) {
if (evt.Value == "null") {
parser.Consume<Scalar>();
return RandomizableWeights<T>.EmptyWeights;
}
var value = rootDeserializer.Invoke(this.valueType);
if (value is T t) {
return RandomizableWeights<T>.ConstantWeights(t);
} else {
return RandomizableWeights<T>.EmptyWeights;
}
}
if (parser.Accept<MappingStart>(out _)) {
var value = rootDeserializer.Invoke(this.dictionaryType);
if (value is IDictionary<T, int> dict) {
return RandomizableWeights<T>.FromDictionary(dict);
} else {
return RandomizableWeights<T>.EmptyWeights;
}
}
throw new YamlException($"Error reading type RandomizableWeights<{typeof(T).Name}>.");
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) {
if (value is RandomizableWeights<T> weights) {
switch (weights.Options.Count) {
case 0:
emitter.Emit(new Scalar("null"));
break;
case 1:
serializer.Invoke(weights.Options[0].Item, valueType);
break;
default:
emitter.Emit(new MappingStart());
foreach (var (item, weight) in weights.Options) {
serializer.Invoke(item, valueType);
emitter.Emit(new Scalar(weight.ToString()));
}
emitter.Emit(new MappingEnd());
break;
}
} else {
emitter.Emit(new MappingStart());
emitter.Emit(new MappingEnd());
}
}
}
}

View File

@@ -0,0 +1,40 @@
namespace ALttPRandomizer.Serialization {
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using YamlDotNet.Serialization;
public class YamlInputFormatter : TextInputFormatter {
private IDeserializer Deserializer { get; }
private ILogger<YamlInputFormatter> Logger { get; }
public YamlInputFormatter(IDeserializer deserializer, ILogger<YamlInputFormatter> logger) {
this.Deserializer = deserializer;
this.Logger = logger;
this.SupportedEncodings.Add(Encoding.UTF8);
this.SupportedEncodings.Add(Encoding.Unicode);
this.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/yaml"));
this.SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/yaml"));
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) {
var request = context.HttpContext.Request;
using var reader = context.ReaderFactory(request.Body, encoding);
var type = context.ModelType;
try {
var content = await reader.ReadToEndAsync();
var model = this.Deserializer.Deserialize(content, type);
return await InputFormatterResult.SuccessAsync(model);
} catch (Exception ex) {
this.Logger.LogInformation(ex, "Error parsing YAML");
return await InputFormatterResult.FailureAsync();
}
}
}
}

View File

@@ -0,0 +1,72 @@
namespace ALttPRandomizer.Serialization {
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json.Serialization;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
public class YamlStringEnumConverter : IYamlTypeConverter {
private readonly Dictionary<Type, Dictionary<string, object>> deserializationMap;
private readonly Dictionary<Type, Dictionary<object, string>> serializationMap;
public YamlStringEnumConverter() {
this.deserializationMap = new();
this.serializationMap = new();
}
public bool Accepts(Type type) => type.IsEnum;
private void RegisterType(Type type) {
if (this.serializationMap.ContainsKey(type) && this.deserializationMap.ContainsKey(type)) {
return;
}
this.deserializationMap[type] = new();
this.serializationMap[type] = new();
var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static);
foreach (var field in fields) {
var alias = UnderscoredNamingConvention.Instance.Apply(field.Name);
var value = Enum.Parse(type, field.Name);
var att = field.GetCustomAttribute<JsonStringEnumMemberNameAttribute>();
if (att != null) {
alias = att.Name;
}
this.deserializationMap[type][alias] = value;
this.serializationMap[type][value] = alias;
}
}
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) {
this.RegisterType(type);
var stringValue = parser.Consume<Scalar>().Value;
if (this.deserializationMap[type].TryGetValue(stringValue, out var value)) {
return value;
}
throw new YamlException($"Invalid value \"{stringValue}\" for type {type}");
}
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) {
this.RegisterType(type);
if (value is null) {
throw new YamlException($"Unexpected null value of type {type}");
}
if (this.serializationMap[type].TryGetValue(value, out var stringValue)) {
emitter.Emit(new Scalar(stringValue));
return;
}
throw new YamlException($"Invalid value \"{value}\" for type {type}");
}
}
}