Add Apr2025 Randomizer

This commit is contained in:
2025-03-06 10:29:23 -06:00
parent 83d860ef79
commit 7604c78db8
15 changed files with 422 additions and 76 deletions

View File

@@ -1,12 +1,18 @@
namespace ALttPRandomizer.Model {
using ALttPRandomizer.Randomizers;
using ALttPRandomizer.Settings;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using static ALttPRandomizer.Model.RandomizerInstance;
public class SeedSettings {
[NoSettingName]
public RandomizerInstance Randomizer { get; set; } = RandomizerInstance.Base;
[NoSettingName]
public RaceMode Race { get; set; } = RaceMode.Normal;
[ForbiddenSetting([Apr2025], Mode.Inverted)]
public Mode Mode { get; set; } = Mode.Open;
[SettingName("swords")]
@@ -19,44 +25,75 @@
[SettingName("crystals_gt")]
[JsonPropertyName("crystals_gt")]
[NoSettingName([Apr2025])]
public EntryRequirement CrystalsGT { get; set; } = EntryRequirement.Crystals7;
[SettingName("shuffle")]
[ForbiddenSetting([Apr2025], EntranceShuffle.Swapped)]
public EntranceShuffle EntranceShuffle { get; set; } = EntranceShuffle.Vanilla;
[SettingName("skullwoods")]
[RequiredSetting([Apr2025], SkullWoodsShuffle.Original)]
[NoSettingName([Apr2025])]
public SkullWoodsShuffle SkullWoods { get; set; } = SkullWoodsShuffle.Original;
[SettingName("linked_drops")]
[RequiredSetting([Apr2025], LinkedDrops.Unset)]
[NoSettingName([Apr2025])]
public LinkedDrops LinkedDrops { get; set; } = LinkedDrops.Unset;
[SettingName("shufflebosses")]
[RequiredSetting([Apr2025], BossShuffle.Vanilla)]
[NoSettingName([Apr2025])]
public BossShuffle BossShuffle { get; set; } = BossShuffle.Vanilla;
[SettingName("shuffleenemies")]
[RequiredSetting([Apr2025], EnemyShuffle.Vanilla)]
[NoSettingName([Apr2025])]
public EnemyShuffle EnemyShuffle { get; set; } = EnemyShuffle.Vanilla;
[SettingName("keyshuffle")]
public DungeonItemLocations SmallKeys { get; set; } = DungeonItemLocations.Dungeon;
[RequiredSetting([Apr2025], KeyLocations.Dungeon, KeyLocations.Wild)]
[NoSettingName([Apr2025])]
public KeyLocations SmallKeys { get; set; } = KeyLocations.Dungeon;
[SettingName("bigkeyshuffle")]
[DeniedValues(DungeonItemLocations.Universal)]
[RequiredSetting([Apr2025], DungeonItemLocations.Dungeon)]
[NoSettingName([Apr2025])]
public DungeonItemLocations BigKeys { get; set; } = DungeonItemLocations.Dungeon;
[SettingName("mapshuffle")]
[DeniedValues(DungeonItemLocations.Universal)]
[RequiredSetting([Apr2025], DungeonItemLocations.Dungeon)]
[NoSettingName([Apr2025])]
public DungeonItemLocations Maps { get; set; } = DungeonItemLocations.Dungeon;
[SettingName("compassshuffle")]
[DeniedValues(DungeonItemLocations.Universal)]
[RequiredSetting([Apr2025], DungeonItemLocations.Dungeon)]
[NoSettingName([Apr2025])]
public DungeonItemLocations Compasses { get; set; } = DungeonItemLocations.Dungeon;
[NoSettingName]
[RequiredSetting([Apr2025], ShopShuffle.Vanilla)]
public ShopShuffle ShopShuffle { get; set; } = ShopShuffle.Vanilla;
[RequiredSetting([Apr2025], DropShuffle.Vanilla)]
[NoSettingName([Apr2025])]
public DropShuffle DropShuffle { get; set; } = DropShuffle.Vanilla;
[RequiredSetting([Apr2025], Pottery.Vanilla)]
[NoSettingName([Apr2025])]
public Pottery Pottery { get; set; } = Pottery.Vanilla;
[RequiredSetting([Apr2025], PrizeShuffle.Vanilla)]
[NoSettingName([Apr2025])]
public PrizeShuffle PrizeShuffle { get; set; } = PrizeShuffle.Vanilla;
}
public enum RandomizerInstance {
[RandomizerName(BaseRandomizer.Name)] Base,
[RandomizerName(Apr2025Randomizer.Name)] Apr2025,
}
public enum RaceMode {
Normal,
[AdditionalSetting("--securerandom")] Race,
@@ -132,11 +169,17 @@
Mimics,
}
public enum KeyLocations {
[SettingName("none")] Dungeon,
[AdditionalSetting([Apr2025], "--keysanity")] Wild,
Nearby,
Universal,
}
public enum DungeonItemLocations {
[SettingName("none")] Dungeon,
Wild,
Nearby,
Universal,
}
public enum ShopShuffle {
@@ -168,5 +211,4 @@
Nearby,
Wild,
}
}

View File

@@ -5,9 +5,10 @@
public class ServiceOptions {
public string Baserom { get; set; } = null!;
public string PythonPath { get; set; } = null!;
public string RandomizerPath { get; set; } = null!;
public string FlipsPath { get; set; } = null!;
public IList<string> AllowedCors { get; set; } = new List<string>();
public AzureSettings AzureSettings { get; set; } = new AzureSettings();
public IDictionary<string, string> RandomizerPaths { get; set; } = new Dictionary<string, string>();
}
public class AzureSettings {

View File

@@ -1,7 +1,7 @@
namespace ALttPRandomizer
{
namespace ALttPRandomizer {
using ALttPRandomizer.Azure;
using ALttPRandomizer.Options;
using ALttPRandomizer.Randomizers;
using ALttPRandomizer.Service;
using ALttPRandomizer.Settings;
using global::Azure.Identity;
@@ -12,6 +12,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Serilog;
using System;
internal class Program
{
@@ -60,9 +61,13 @@
var seedClient = new BlobContainerClient(settings.AzureSettings.BlobstoreEndpoint, token);
builder.Services.AddSingleton(seedClient);
builder.Services.AddSingleton(sp => sp);
builder.Services.AddSingleton<AzureStorage>();
builder.Services.AddSingleton<CommonSettingsProcessor>();
builder.Services.AddScoped<Randomizer>();
builder.Services.AddKeyedScoped<IRandomizer, BaseRandomizer>(BaseRandomizer.Name);
builder.Services.AddKeyedScoped<IRandomizer, Apr2025Randomizer>(Apr2025Randomizer.Name);
builder.Services.AddScoped<RandomizeService>();
builder.Services.AddScoped<SeedService>();
builder.Services.AddScoped<IdGenerator>();

View File

@@ -0,0 +1,159 @@
namespace ALttPRandomizer.Randomizers {
using ALttPRandomizer;
using ALttPRandomizer.Azure;
using ALttPRandomizer.Model;
using ALttPRandomizer.Options;
using ALttPRandomizer.Settings;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Diagnostics;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
public class Apr2025Randomizer : IRandomizer {
public const string Name = "apr2025";
public const RandomizerInstance Instance = RandomizerInstance.Apr2025;
public Apr2025Randomizer(
AzureStorage azureStorage,
CommonSettingsProcessor settingsProcessor,
IOptionsMonitor<ServiceOptions> optionsMonitor,
ILogger<BaseRandomizer> logger) {
AzureStorage = azureStorage;
SettingsProcessor = settingsProcessor;
OptionsMonitor = optionsMonitor;
Logger = logger;
}
private CommonSettingsProcessor SettingsProcessor { get; }
private AzureStorage AzureStorage { get; }
private IOptionsMonitor<ServiceOptions> OptionsMonitor { get; }
private ILogger<BaseRandomizer> Logger { get; }
private ServiceOptions Configuration => OptionsMonitor.CurrentValue;
public void Validate(SeedSettings settings) {
this.SettingsProcessor.ValidateSettings(Instance, settings);
}
public async Task Randomize(string id, SeedSettings settings) {
Logger.LogDebug("Recieved request for id {id} to randomize settings {@settings}", id, settings);
var start = new ProcessStartInfo() {
FileName = Configuration.PythonPath,
WorkingDirectory = Configuration.RandomizerPaths[Name],
RedirectStandardOutput = true,
RedirectStandardError = true,
};
var args = start.ArgumentList;
args.Add("EntranceRandomizer.py");
args.Add("--rom");
args.Add(Configuration.Baserom);
args.Add("--outputpath");
args.Add(Path.GetTempPath());
args.Add("--outputname");
args.Add(id);
args.Add("--json_spoiler");
args.Add("--quickswap");
foreach (var arg in SettingsProcessor.GetSettings(Instance, settings)) {
args.Add(arg);
}
Logger.LogInformation("Randomizing with args: {args}", string.Join(" ", args));
var generating = string.Format("{0}/generating", id);
await AzureStorage.UploadFile(generating, BinaryData.Empty);
var process = Process.Start(start) ?? throw new GenerationFailedException("Process failed to start.");
process.EnableRaisingEvents = true;
process.OutputDataReceived += (_, args) => Logger.LogInformation("Randomizer STDOUT: {output}", args.Data);
process.ErrorDataReceived += (_, args) => Logger.LogInformation("Randomizer STDERR: {output}", args.Data);
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.Exited += async (sender, args) => {
var exitcode = process.ExitCode;
if (exitcode != 0) {
await GenerationFailed(id, exitcode);
} else {
await GenerationSucceeded(id, settings);
}
};
var settingsJson = JsonSerializer.SerializeToDocument(settings, JsonOptions.Default);
var settingsOut = string.Format("{0}/settings.json", id);
await AzureStorage.UploadFile(settingsOut, new BinaryData(settingsJson));
}
private async Task GenerationSucceeded(string id, SeedSettings settings) {
var rom = Path.Join(Path.GetTempPath(), string.Format("ER_{0}.sfc", id));
var spoilerIn = Path.Join(Path.GetTempPath(), string.Format("ER_{0}_Spoiler.json", id));
var spoilerOut = string.Format("{0}/spoiler.json", id);
var uploadSpoiler = AzureStorage.UploadFileAndDelete(spoilerOut, spoilerIn);
var metaIn = Path.Join(Path.GetTempPath(), string.Format("ER_{0}_Meta.json", id));
var metaOut = string.Format("{0}/meta.json", id);
var uploadMeta = AzureStorage.UploadFileAndDelete(metaOut, metaIn);
var bpsIn = Path.Join(Path.GetTempPath(), string.Format("ER_{0}.bps", id));
var flips = new ProcessStartInfo() {
FileName = Configuration.FlipsPath,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
var args = flips.ArgumentList;
args.Add("--create");
args.Add(Configuration.Baserom);
args.Add(rom);
args.Add(bpsIn);
var process = Process.Start(flips) ?? throw new GenerationFailedException("Process failed to start.");
process.EnableRaisingEvents = true;
process.OutputDataReceived += (_, args) => Logger.LogInformation("flips STDOUT: {output}", args.Data);
process.ErrorDataReceived += (_, args) => Logger.LogInformation("flips STDERR: {output}", args.Data);
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync();
if (process.ExitCode != 0) {
await this.GenerationFailed(id, process.ExitCode);
return;
}
var bpsOut = string.Format("{0}/patch.bps", id);
var uploadPatch = AzureStorage.UploadFileAndDelete(bpsOut, bpsIn);
var generating = string.Format("{0}/generating", id);
var deleteGenerating = AzureStorage.DeleteFile(generating);
await Task.WhenAll(uploadPatch, uploadSpoiler, uploadMeta, deleteGenerating);
Logger.LogDebug("Deleting file {filepath}", rom);
File.Delete(rom);
Logger.LogInformation("Finished uploading seed id {id}", id);
}
private async Task GenerationFailed(string id, int exitcode) {
var generating = string.Format("{0}/generating", id);
var deleteGenerating = AzureStorage.DeleteFile(generating);
await Task.WhenAll(deleteGenerating);
}
}
}

View File

@@ -1,4 +1,5 @@
namespace ALttPRandomizer {
namespace ALttPRandomizer.Randomizers {
using ALttPRandomizer;
using ALttPRandomizer.Azure;
using ALttPRandomizer.Model;
using ALttPRandomizer.Options;
@@ -12,30 +13,37 @@
using System.Text.Json;
using System.Threading.Tasks;
public class Randomizer {
public Randomizer(
public class BaseRandomizer : IRandomizer {
public const string Name = "base";
public const RandomizerInstance Instance = RandomizerInstance.Base;
public BaseRandomizer(
AzureStorage azureStorage,
CommonSettingsProcessor settingsProcessor,
IOptionsMonitor<ServiceOptions> optionsMonitor,
ILogger<Randomizer> logger) {
this.AzureStorage = azureStorage;
this.SettingsProcessor = settingsProcessor;
this.OptionsMonitor = optionsMonitor;
this.Logger = logger;
ILogger<BaseRandomizer> logger) {
AzureStorage = azureStorage;
SettingsProcessor = settingsProcessor;
OptionsMonitor = optionsMonitor;
Logger = logger;
}
private CommonSettingsProcessor SettingsProcessor { get; }
private AzureStorage AzureStorage { get; }
private IOptionsMonitor<ServiceOptions> OptionsMonitor { get; }
private ILogger<Randomizer> Logger { get; }
private ServiceOptions Configuration => this.OptionsMonitor.CurrentValue;
private ILogger<BaseRandomizer> Logger { get; }
private ServiceOptions Configuration => OptionsMonitor.CurrentValue;
public void Validate(SeedSettings settings) {
this.SettingsProcessor.ValidateSettings(Instance, settings);
}
public async Task Randomize(string id, SeedSettings settings) {
this.Logger.LogDebug("Recieved request for id {id} to randomize settings {@settings}", id, settings);
Logger.LogDebug("Recieved request for id {id} to randomize settings {@settings}", id, settings);
var start = new ProcessStartInfo() {
FileName = Configuration.PythonPath,
WorkingDirectory = Configuration.RandomizerPath,
WorkingDirectory = Configuration.RandomizerPaths[Name],
RedirectStandardOutput = true,
RedirectStandardError = true,
};
@@ -43,7 +51,7 @@
var args = start.ArgumentList;
args.Add("DungeonRandomizer.py");
args.Add("--rom");
args.Add(this.Configuration.Baserom);
args.Add(Configuration.Baserom);
args.Add("--bps");
args.Add("--outputpath");
@@ -60,17 +68,17 @@
args.Add("--shufflelinks");
args.Add("--shuffletavern");
foreach (var arg in this.SettingsProcessor.GetSettings(settings)) {
foreach (var arg in SettingsProcessor.GetSettings(Instance, settings)) {
args.Add(arg);
}
this.Logger.LogInformation("Randomizing with args: {args}", string.Join(" ", args));
Logger.LogInformation("Randomizing with args: {args}", string.Join(" ", args));
var process = Process.Start(start) ?? throw new GenerationFailedException("Process failed to start.");
process.EnableRaisingEvents = true;
process.OutputDataReceived += (_, args) => this.Logger.LogInformation("Randomizer STDOUT: {output}", args.Data);
process.ErrorDataReceived += (_, args) => this.Logger.LogInformation("Randomizer STDERR: {output}", args.Data);
process.OutputDataReceived += (_, args) => Logger.LogInformation("Randomizer STDOUT: {output}", args.Data);
process.ErrorDataReceived += (_, args) => Logger.LogInformation("Randomizer STDERR: {output}", args.Data);
process.BeginOutputReadLine();
process.BeginErrorReadLine();
@@ -79,18 +87,18 @@
var exitcode = process.ExitCode;
if (exitcode != 0) {
await this.GenerationFailed(id, exitcode);
await GenerationFailed(id, exitcode);
} else {
await this.GenerationSucceeded(id, settings);
await GenerationSucceeded(id, settings);
}
};
var settingsJson = JsonSerializer.SerializeToDocument(settings, JsonOptions.Default);
var settingsOut = string.Format("{0}/settings.json", id);
var uploadSettings = this.AzureStorage.UploadFile(settingsOut, new BinaryData(settingsJson));
var uploadSettings = AzureStorage.UploadFile(settingsOut, new BinaryData(settingsJson));
var generating = string.Format("{0}/generating", id);
var uploadGenerating = this.AzureStorage.UploadFile(generating, BinaryData.Empty);
var uploadGenerating = AzureStorage.UploadFile(generating, BinaryData.Empty);
await Task.WhenAll(uploadSettings, uploadGenerating);
}
@@ -100,29 +108,29 @@
var bpsIn = Path.Join(Path.GetTempPath(), string.Format("OR_{0}.bps", id));
var bpsOut = string.Format("{0}/patch.bps", id);
var uploadPatch = this.AzureStorage.UploadFileAndDelete(bpsOut, bpsIn);
var uploadPatch = AzureStorage.UploadFileAndDelete(bpsOut, bpsIn);
var spoilerIn = Path.Join(Path.GetTempPath(), string.Format("OR_{0}_Spoiler.json", id));
var spoilerOut = string.Format("{0}/spoiler.json", id);
var uploadSpoiler = this.AzureStorage.UploadFileAndDelete(spoilerOut, spoilerIn);
var uploadSpoiler = AzureStorage.UploadFileAndDelete(spoilerOut, spoilerIn);
var metaIn = Path.Join(Path.GetTempPath(), string.Format("OR_{0}_Meta.json", id));
var metaOut = string.Format("{0}/meta.json", id);
var meta = this.ProcessMetadata(metaIn);
var uploadMeta = this.AzureStorage.UploadFile(metaOut, new BinaryData(meta));
var meta = ProcessMetadata(metaIn);
var uploadMeta = AzureStorage.UploadFile(metaOut, new BinaryData(meta));
var generating = string.Format("{0}/generating", id);
var deleteGenerating = this.AzureStorage.DeleteFile(generating);
var deleteGenerating = AzureStorage.DeleteFile(generating);
await Task.WhenAll(uploadPatch, uploadSpoiler, uploadMeta, deleteGenerating);
this.Logger.LogDebug("Deleting file {filepath}", metaIn);
Logger.LogDebug("Deleting file {filepath}", metaIn);
File.Delete(metaIn);
this.Logger.LogDebug("Deleting file {filepath}", rom);
Logger.LogDebug("Deleting file {filepath}", rom);
File.Delete(rom);
this.Logger.LogInformation("Finished uploading seed id {id}", id);
Logger.LogInformation("Finished uploading seed id {id}", id);
}
private JsonDocument ProcessMetadata(string path) {
@@ -146,7 +154,7 @@
private async Task GenerationFailed(string id, int exitcode) {
var generating = string.Format("{0}/generating", id);
var deleteGenerating = this.AzureStorage.DeleteFile(generating);
var deleteGenerating = AzureStorage.DeleteFile(generating);
await Task.WhenAll(deleteGenerating);
}

View File

@@ -0,0 +1,10 @@
namespace ALttPRandomizer.Randomizers {
using ALttPRandomizer.Model;
using System.Threading.Tasks;
public interface IRandomizer {
public void Validate(SeedSettings settings);
public Task Randomize(string id, SeedSettings settings);
}
}

View File

@@ -1,6 +1,7 @@
namespace ALttPRandomizer {
using ALttPRandomizer.Model;
using ALttPRandomizer.Service;
using ALttPRandomizer.Settings;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
@@ -19,9 +20,13 @@
[Route("/generate")]
[HttpPost]
public async Task<ActionResult> Generate(SeedSettings settings) {
var id = await this.RandomizeService.RandomizeSeed(settings);
var url = string.Format("/seed/{0}", id);
return Accepted(url, id);
try {
var id = await this.RandomizeService.RandomizeSeed(settings);
var url = string.Format("/seed/{0}", id);
return Accepted(url, id);
} catch (InvalidSettingsException ex) {
return BadRequest(ex.Message);
}
}
[Route("/seed/{id}")]

View File

@@ -1,24 +1,41 @@
using ALttPRandomizer.Model;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
namespace ALttPRandomizer.Service {
using ALttPRandomizer.Model;
using ALttPRandomizer.Randomizers;
using ALttPRandomizer.Settings;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace ALttPRandomizer.Service {
public class RandomizeService {
public RandomizeService(IdGenerator idGenerator, Randomizer randomizer, ILogger<RandomizeService> logger) {
public RandomizeService(IdGenerator idGenerator, IServiceProvider serviceProvider, ILogger<RandomizeService> logger) {
this.IdGenerator = idGenerator;
this.Randomizer = randomizer;
this.ServiceProvider = serviceProvider;
this.Logger = logger;
}
private ILogger<RandomizeService> Logger { get; }
private IdGenerator IdGenerator { get; }
private Randomizer Randomizer { get; }
private IServiceProvider ServiceProvider { get; }
public async Task<string> RandomizeSeed(SeedSettings settings) {
var id = this.IdGenerator.GenerateId();
this.Logger.LogInformation("Generating seed {seedId} with settings {@settings}", id, settings);
await this.Randomizer.Randomize(id, settings);
var fi = typeof(RandomizerInstance).GetField(settings.Randomizer.ToString(), BindingFlags.Static | BindingFlags.Public);
var randomizerKey = fi?.GetCustomAttribute<RandomizerNameAttribute>()?.Name;
if (randomizerKey == null) {
throw new InvalidSettingsException("Invalid randomizer: {0}", settings.Randomizer);
}
var randomizer = this.ServiceProvider.GetRequiredKeyedService<IRandomizer>(randomizerKey);
randomizer.Validate(settings);
await randomizer.Randomize(id, settings);
return id;
}
}

View File

@@ -1,21 +1,83 @@
namespace ALttPRandomizer.Settings {
using ALttPRandomizer.Model;
using System;
using System.Linq;
internal class SettingNameAttribute : Attribute {
public SettingNameAttribute(string name) {
internal class RandomizerNameAttribute : Attribute {
public RandomizerNameAttribute(string name) {
this.Name = name;
}
public string Name { get; }
}
internal class NoSettingNameAttribute : Attribute { }
internal abstract class RandomizerSpecificAttribute : Attribute {
public RandomizerSpecificAttribute(RandomizerInstance[]? randomizers) {
this.Randomizers = randomizers;
}
internal class AdditionalSettingAttribute : Attribute {
public AdditionalSettingAttribute(string setting) {
protected RandomizerInstance[]? Randomizers { get; }
public bool HasRandomizer(RandomizerInstance name) {
if (this.Randomizers == null) {
return true;
}
return this.Randomizers.Contains(name);
}
}
internal class SettingNameAttribute : RandomizerSpecificAttribute {
public SettingNameAttribute(string name) : base(null) {
this.Name = name;
}
public SettingNameAttribute(RandomizerInstance[] randomizers, string name) : base(randomizers) {
this.Name = name;
}
public string Name { get; }
}
internal class NoSettingNameAttribute : RandomizerSpecificAttribute {
public NoSettingNameAttribute() : base(null) { }
public NoSettingNameAttribute(RandomizerInstance[] randomizers) : base(randomizers) { }
}
internal class AdditionalSettingAttribute : RandomizerSpecificAttribute {
public AdditionalSettingAttribute(string setting) : base(null) {
this.Setting = setting;
}
public AdditionalSettingAttribute(RandomizerInstance[] randomizers, string setting) : base(randomizers) {
this.Setting = setting;
}
public string Setting { get; }
}
internal class RequiredSettingAttribute : RandomizerSpecificAttribute {
public RequiredSettingAttribute(params object[] values) : base(null) {
this.Values = values;
}
public RequiredSettingAttribute(RandomizerInstance[] randomizers, params object[] values) : base(randomizers) {
this.Values = values;
}
public object[] Values { get; }
}
internal class ForbiddenSettingAttribute : RandomizerSpecificAttribute {
public ForbiddenSettingAttribute(params object[] values) : base(null) {
this.Values = values;
}
public ForbiddenSettingAttribute(RandomizerInstance[] randomizers, params object[] values) : base(randomizers) {
this.Values = values;
}
public object[] Values { get; }
}
}

View File

@@ -1,38 +1,61 @@
using System;
namespace ALttPRandomizer.Settings {
namespace ALttPRandomizer.Settings {
using ALttPRandomizer.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
public class CommonSettingsProcessor {
public IList<string> GetSettings(SeedSettings settings) {
var args = new List<string>();
public IEnumerable<string> GetSettings(RandomizerInstance randomizer, SeedSettings settings) {
var props = typeof(SeedSettings).GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var prop in props) {
var value = prop.GetValue(settings) ?? throw new SettingsLookupException("settings.{} not found", prop.Name);
var valueFieldName = value.ToString() ?? throw new SettingsLookupException("settings.{}.ToString() returned null", prop.Name);
var value = prop.GetValue(settings) ?? throw new SettingsLookupException("settings.{0} not found", prop.Name);
var valueFieldName = value.ToString() ?? throw new SettingsLookupException("settings.{0}.ToString() returned null", prop.Name);
var fi = prop.PropertyType.GetField(valueFieldName, BindingFlags.Static | BindingFlags.Public)
?? throw new SettingsLookupException("Could not get field info for value {}.{}", prop.PropertyType, valueFieldName);
?? throw new SettingsLookupException("Could not get field info for value {0}.{1}", prop.PropertyType, valueFieldName);
if (prop.GetCustomAttribute<NoSettingNameAttribute>() == null) {
var settingName = prop.GetCustomAttribute<SettingNameAttribute>()?.Name ?? prop.Name.ToLower();
var valueName = fi.GetCustomAttribute<SettingNameAttribute>()?.Name ?? valueFieldName.ToLower();
if (!prop.GetCustomAttributes<NoSettingNameAttribute>().Any(att => att.HasRandomizer(randomizer))) {
var settingName =
prop.GetCustomAttributes<SettingNameAttribute>()
.FirstOrDefault(att => att.HasRandomizer(randomizer))?.Name ?? prop.Name.ToLower();
var valueName =
fi.GetCustomAttributes<SettingNameAttribute>()
.FirstOrDefault(att => att.HasRandomizer(randomizer))?.Name ?? valueFieldName.ToLower();
args.Add(string.Format("--{0}={1}", settingName, valueName));
yield return string.Format("--{0}={1}", settingName, valueName);
}
foreach (var att in fi.GetCustomAttributes<AdditionalSettingAttribute>()) {
args.Add(att.Setting);
foreach (var att in fi.GetCustomAttributes<AdditionalSettingAttribute>().Where(att => att.HasRandomizer(randomizer))) {
yield return att.Setting;
}
}
}
return args;
public void ValidateSettings(RandomizerInstance randomizer, SeedSettings settings) {
var props = typeof(SeedSettings).GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var prop in props) {
var value = prop.GetValue(settings) ?? throw new SettingsLookupException("settings.{0} not found", prop.Name);
foreach (var att in prop.GetCustomAttributes<RequiredSettingAttribute>().Where(att => att.HasRandomizer(randomizer))) {
if (!att.Values.Contains(value)) {
throw new InvalidSettingsException("{0} contains value {1} not in required set [{2}]", prop.Name, value, string.Join(", ", att.Values));
}
}
foreach (var att in prop.GetCustomAttributes<ForbiddenSettingAttribute>().Where(att => att.HasRandomizer(randomizer))) {
if (att.Values.Contains(value)) {
throw new InvalidSettingsException("{0} contains forbidden value {1}", prop.Name, value);
}
}
}
}
}
public class SettingsLookupException : Exception {
public SettingsLookupException(string message, params object?[] args) : base(string.Format(message, args)) { }
}
public class InvalidSettingsException : Exception {
public InvalidSettingsException(string message, params object?[] args) : base(string.Format(message, args)) { }
}
}

View File

@@ -2,7 +2,7 @@
"ALttPRandomizer": {
"baserom": "/randomizer/alttp.sfc",
"pythonPath": "/usr/bin/python3",
"randomizerPath": "/randomizer",
"flipsPath": "/flips/flips",
"allowedCors": [
"https://new.alttpr.gwaa.kiwi",
"http://localhost:8082"
@@ -10,6 +10,10 @@
"azureSettings": {
"clientId": "a48d5ae1-fa0a-4e33-9586-18c0eca0a28c",
"blobstoreEndpoint": "https://alttprstorage.blob.core.windows.net/seeds"
},
"randomizerPaths": {
"base": "/randomizer",
"apr2025": "/apr2025_randomizer"
}
},
"Serilog": {