Add Apr2025 Randomizer
This commit is contained in:
@@ -11,4 +11,4 @@ ALttPRandomizer/[Bb]in
|
||||
*/DR_*
|
||||
*/ER_*
|
||||
*/OR_*
|
||||
*/data/base2current.json
|
||||
BaseRandomizer/data/base2current.json
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -4,3 +4,6 @@
|
||||
[submodule "BaseRandomizer"]
|
||||
path = BaseRandomizer
|
||||
url = https://github.com/ardnaxelarak/ALttPDoorRandomizer
|
||||
[submodule "Apr2025Randomizer"]
|
||||
path = Apr2025Randomizer
|
||||
url = https://github.com/ardnaxelarak/ALttPDoorRandomizer
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>();
|
||||
|
||||
159
ALttPRandomizer/Randomizers/Apr2025Randomizer.cs
Normal file
159
ALttPRandomizer/Randomizers/Apr2025Randomizer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
10
ALttPRandomizer/Randomizers/IRandomizer.cs
Normal file
10
ALttPRandomizer/Randomizers/IRandomizer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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}")]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
1
Apr2025Randomizer
Submodule
1
Apr2025Randomizer
Submodule
Submodule Apr2025Randomizer added at 1d84a6b348
@@ -12,7 +12,10 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0 AS final
|
||||
EXPOSE 8080
|
||||
EXPOSE 8081
|
||||
|
||||
RUN tdnf install -y python3
|
||||
RUN tdnf install -y python3 wget unzip
|
||||
RUN wget https://github.com/Alcaro/Flips/releases/download/v198/flips-linux.zip
|
||||
RUN unzip flips-linux.zip -d /flips
|
||||
RUN rm flips-linux.zip
|
||||
|
||||
RUN mkdir -p /randomizer/data
|
||||
RUN touch /randomizer/data/base2current.json
|
||||
@@ -30,6 +33,9 @@ RUN python3 -m pip install -r requirements.txt
|
||||
|
||||
COPY BaseRandomizer/ .
|
||||
|
||||
WORKDIR /apr2025_randomizer
|
||||
COPY Apr2025Randomizer/ .
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
COPY ALttPRandomizer/appsettings.Docker.json appsettings.json
|
||||
|
||||
Reference in New Issue
Block a user