Compare commits

...
14 Commits
25 changed files with 813 additions and 67 deletions
+4
View File
@@ -1,3 +1,7 @@
.env
.env.just
local.just
*.sfc *.sfc
appsettings.Development.json appsettings.Development.json
kube/ kube/
+4 -4
View File
@@ -1,15 +1,15 @@
[submodule "BaseRandomizer"] [submodule "BaseRandomizer"]
path = BaseRandomizer path = BaseRandomizer
url = https://git.gwaa.kiwi/alttpr-gwaa-kiwi/alttpr-python url = https://git.gwaa.kiwi/alttpr-gwaa-kiwi/alttpr-python.git
branch = main branch = main
[submodule "Apr2025Randomizer"] [submodule "Apr2025Randomizer"]
path = Apr2025Randomizer path = Apr2025Randomizer
url = https://git.gwaa.kiwi/alttpr-gwaa-kiwi/alttpr-python url = https://git.gwaa.kiwi/alttpr-gwaa-kiwi/alttpr-python.git
branch = apr_2025 branch = apr_2025
[submodule "BetaRandomizer"] [submodule "BetaRandomizer"]
path = BetaRandomizer path = BetaRandomizer
url = https://git.gwaa.kiwi/alttpr-gwaa-kiwi/alttpr-python url = https://git.gwaa.kiwi/alttpr-gwaa-kiwi/alttpr-python.git
branch = beta_2 branch = beta
[submodule "PikitRandomizer"] [submodule "PikitRandomizer"]
path = PikitRandomizer path = PikitRandomizer
url = https://git.gwaa.kiwi/alttpr-gwaa-kiwi/alttpr-python.git url = https://git.gwaa.kiwi/alttpr-gwaa-kiwi/alttpr-python.git
+10 -9
View File
@@ -9,15 +9,16 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.13.2" /> <PackageReference Include="Azure.Identity" Version="1.21.0" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" /> <PackageReference Include="Azure.Storage.Blobs" Version="12.28.0" />
<PackageReference Include="Microsoft.Azure.WebPubSub.AspNetCore" Version="1.4.0" /> <PackageReference Include="Microsoft.Azure.WebPubSub.AspNetCore" Version="1.5.0" />
<PackageReference Include="Serilog" Version="4.2.0" /> <PackageReference Include="Serilog" Version="4.3.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" /> <PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" /> <PackageReference Include="Serilog.Settings.Configuration" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
<PackageReference Include="System.Text.Json" Version="9.0.2" /> <PackageReference Include="System.Text.Json" Version="10.0.8" />
<PackageReference Include="YamlDotNet" Version="18.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+5 -3
View File
@@ -1,5 +1,6 @@
namespace ALttPRandomizer.Azure { namespace ALttPRandomizer.Azure {
using global::Azure.Storage.Blobs; using global::Azure.Storage.Blobs;
using global::Azure.Storage.Blobs.Models;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -23,8 +24,9 @@
await BlobClient.UploadBlobAsync(name, data); await BlobClient.UploadBlobAsync(name, data);
} }
public async Task UploadFile(string name, BinaryData data) { public async Task UploadFile(string name, BinaryData data, bool overwrite = false) {
await BlobClient.UploadBlobAsync(name, data); var client = BlobClient.GetBlobClient(name);
await client.UploadAsync(data, overwrite);
} }
public async Task UploadFileFromSource(string name, string filepath) { public async Task UploadFileFromSource(string name, string filepath) {
@@ -43,7 +45,7 @@
public async Task<Dictionary<string, BinaryData>> GetFiles(string seedId) { public async Task<Dictionary<string, BinaryData>> GetFiles(string seedId) {
var prefix = seedId + "/"; var prefix = seedId + "/";
var blobs = this.BlobClient.GetBlobsAsync(prefix: prefix); var blobs = this.BlobClient.GetBlobsAsync(new GetBlobsOptions() { Prefix = prefix });
var data = new Dictionary<string, BinaryData>(); var data = new Dictionary<string, BinaryData>();
+2
View File
@@ -1,6 +1,7 @@
namespace ALttPRandomizer { namespace ALttPRandomizer {
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using ALttPRandomizer.Serialization;
public static class JsonOptions { public static class JsonOptions {
public static readonly JsonSerializerOptions Default = new JsonSerializerOptions(JsonSerializerDefaults.Web) { public static readonly JsonSerializerOptions Default = new JsonSerializerOptions(JsonSerializerDefaults.Web) {
@@ -10,6 +11,7 @@
public static JsonSerializerOptions WithStringEnum(this JsonSerializerOptions options) { public static JsonSerializerOptions WithStringEnum(this JsonSerializerOptions options) {
options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower, false)); options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower, false));
options.Converters.Add(new RandomizableWeightsJsonConverter());
return options; return options;
} }
} }
+153
View File
@@ -0,0 +1,153 @@
namespace ALttPRandomizer.Model {
using System;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
using YamlDotNet.Serialization;
public class MysterySettings {
public RandomizableWeights<Mode> Mode { get; set; } = new();
public RandomizableWeights<Weapons> Weapons { get; set; } = new();
public RandomizableWeights<Goal> Goal { get; set; } = new();
public RandomizableWeights<EntryRequirement> CrystalsGanon { get; set; } = new();
public RandomizableWeights<BossRequirement> BossesGanon { get; set; } = new();
public RandomizableWeights<TriforceRequirement> TriforcePieces { get; set; } = new();
[YamlMember(Alias = "crystals_gt")]
public RandomizableWeights<EntryRequirement> CrystalsGT { get; set; } = new();
public RandomizableWeights<GanonItem> GanonItem { get; set; } = new();
public RandomizableWeights<EntranceShuffle> EntranceShuffle { get; set; } = new();
public RandomizableWeights<OverworldMapDungeons> OverworldMapDungeons { get; set; } = new();
public RandomizableWeights<LinksHouse> LinksHouse { get; set; } = new();
public RandomizableWeights<SkullWoodsShuffle> SkullWoods { get; set; } = new();
public RandomizableWeights<LinkedDrops> LinkedDrops { get; set; } = new();
public RandomizableWeights<BossShuffle> BossShuffle { get; set; } = new();
public RandomizableWeights<EnemyShuffle> EnemyShuffle { get; set; } = new();
public RandomizableWeights<DamageTableShuffle> DamageTableShuffle { get; set; } = new();
public RandomizableWeights<KeyLocations> SmallKeys { get; set; } = new();
public RandomizableWeights<DungeonItemLocations> BigKeys { get; set; } = new();
public RandomizableWeights<DungeonItemLocations> Maps { get; set; } = new();
public RandomizableWeights<DungeonItemLocations> Compasses { get; set; } = new();
public RandomizableWeights<ShowLootMap> ShowLootMap { get; set; } = new();
public RandomizableWeights<ShowLootHud> ShowLootHud { get; set; } = new();
public RandomizableWeights<ShowMap> ShowMap { get; set; } = new();
public RandomizableWeights<ShopShuffle> ShopShuffle { get; set; } = new();
public RandomizableWeights<DropShuffle> DropShuffle { get; set; } = new();
public RandomizableWeights<PotShuffle> PotShuffle { get; set; } = new();
public RandomizableWeights<PrizeShuffle> PrizeShuffle { get; set; } = new();
public RandomizableWeights<BootsSettings> Boots { get; set; } = new();
public RandomizableWeights<FluteSettings> Flute { get; set; } = new();
public RandomizableWeights<DarkRoomSettings> DarkRooms { get; set; } = new();
public RandomizableWeights<BombSettings> Bombs { get; set; } = new();
public RandomizableWeights<BookSettings> Book { get; set; } = new();
public RandomizableWeights<MirrorSettings> Mirror { get; set; } = new();
public RandomizableWeights<DoorShuffle> DoorShuffle { get; set; } = new();
public RandomizableWeights<DoorLobbies> Lobbies { get; set; } = new();
public RandomizableWeights<DoorTypeMode> DoorTypeMode { get; set; } = new();
public RandomizableWeights<TrapDoorMode> TrapDoorMode { get; set; } = new();
public RandomizableWeights<ExtraKeysMode> ExtraKeys { get; set; } = new();
public RandomizableWeights<FollowerShuffle> FollowerShuffle { get; set; } = new();
public RandomizableWeights<FluteShuffle> FluteShuffle { get; set; } = new();
public RandomizableWeights<OverworldLayout> OverworldLayout { get; set; } = new();
public RandomizableWeights<OverworldWorldLayouts> OverworldWorldLayouts { get; set; } = new();
public RandomizableWeights<OverworldLayoutTerrain> OverworldLayoutTerrain { get; set; } = new();
public RandomizableWeights<OverworldLayoutEdges> OverworldLayoutEdges { get; set; } = new();
public RandomizableWeights<OverworldMapFog> OverworldMapFog { get; set; } = new();
public RandomizableWeights<TileSwap> TileSwap { get; set; } = new();
public RandomizableWeights<DamageChallengeMode> DamageChallenge { get; set; } = new();
public RandomizableWeights<Hints> Hints { get; set; } = new();
}
public class DefaultMysterySettingsFilter : IOperationFilter {
private readonly MysterySettings exampleMystery;
private readonly JsonNode? exampleMysteryJson;
public DefaultMysterySettingsFilter() {
this.exampleMystery = new MysterySettings();
var mysteryFields = typeof(MysterySettings).GetProperties();
foreach (var field in mysteryFields) {
var type = field.PropertyType;
if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(RandomizableWeights<>)) {
continue;
}
Type itemType = type.GetGenericArguments()[0];
Type innerType = typeof(RandomizableWeights<>).MakeGenericType(itemType)!;
var weights = innerType.GetProperty("EqualAll")?.GetValue(null);
if (weights != null) {
field.SetValue(this.exampleMystery, weights);
}
}
this.exampleMysteryJson = JsonSerializer.SerializeToNode(this.exampleMystery, JsonOptions.Default);
}
public void Apply(OpenApiOperation operation, OperationFilterContext context) {
foreach (var description in context.ApiDescription.ParameterDescriptions) {
if (description.Type != typeof(MysterySettings)) {
continue;
}
if (description.Source.Id == "Body") {
if (operation.RequestBody != null && operation.RequestBody.Content != null) {
foreach ((_, var value) in operation.RequestBody.Content) {
value.Example = this.exampleMysteryJson;
}
}
}
}
}
}
}
@@ -0,0 +1,80 @@
namespace ALttPRandomizer.Model {
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
public interface IRandomizableWeights {
public bool IsEmpty { get; }
public object? Roll();
}
public class RandomizableWeights<T> : IRandomizableWeights where T : struct, Enum {
private static readonly Random random = new();
public ImmutableList<(T Item, int Weight)> Options { get; }
public int TotalWeights { get; }
public RandomizableWeights(IList<(T Item, int Weight)>? items = null) {
if (items == null) {
this.TotalWeights = 0;
this.Options = ImmutableList<(T, int)>.Empty;
return;
}
int totalWeight = 0;
var builder = new List<(T, int)>();
foreach (var pair in items) {
if (pair.Weight < 0) {
throw new ArgumentException("Weights must be non-negative", $"items[{pair.Item}]");
}
if (pair.Weight > 0) {
builder.Add(pair);
totalWeight += pair.Weight;
}
}
this.TotalWeights = totalWeight;
this.Options = builder.ToImmutableList();
}
public static RandomizableWeights<T> EmptyWeights => new();
public static RandomizableWeights<T> EqualAll {
get => RandomizableWeights<T>.FromDictionary(Enum.GetValues<T>().ToDictionary(v => v, v => 1));
}
public static RandomizableWeights<T> ConstantWeights(T value) {
return new(new List<(T, int)> { (value, 100) });
}
public static RandomizableWeights<T> FromDictionary(IDictionary<T, int> dict) {
return new(dict.Select(kvp => (kvp.Key, kvp.Value)).ToList());
}
public bool IsEmpty { get => this.Options.Count == 0; }
public T? Roll() {
if (this.TotalWeights <= 0) {
return default;
}
int value = random.Next(this.TotalWeights);
foreach (var (item, weight) in this.Options) {
value -= weight;
if (value < 0) {
return item;
}
}
// should never reach
return default;
}
object? IRandomizableWeights.Roll() => Roll();
}
}
+49 -11
View File
@@ -1,19 +1,24 @@
namespace ALttPRandomizer.Model { namespace ALttPRandomizer.Model {
using ALttPRandomizer.Settings; using ALttPRandomizer.Settings;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using static ALttPRandomizer.Model.RandomizerInstance; using static ALttPRandomizer.Model.RandomizerInstance;
public class SeedSettings { public class SeedSettings {
[NoSettingName] [NoSettingName]
public RandomizerInstance Randomizer { get; set; } = RandomizerInstance.Base; public RandomizerInstance Randomizer { get; set; } = RandomizerInstance.Base;
[NoSettingName] [IgnoreSetting]
public string PlayerName { get; set; } = string.Empty; public string PlayerName { get; set; } = string.Empty;
[IgnoreSetting]
public int PlayerSlot { get; set; } = 1;
[NoSettingName] [NoSettingName]
public RaceMode Race { get; set; } = RaceMode.Normal; public RaceMode Race { get; set; } = RaceMode.Normal;
[NoSettingName]
public MysteryMode Mystery { get; set; } = MysteryMode.Normal;
[ForbiddenSetting([Apr2025], Mode.Inverted)] [ForbiddenSetting([Apr2025], Mode.Inverted)]
[RequiredSetting([Pikit], Mode.Open)] [RequiredSetting([Pikit], Mode.Open)]
public Mode Mode { get; set; } = Mode.Open; public Mode Mode { get; set; } = Mode.Open;
@@ -46,6 +51,10 @@
[IgnoreSetting(Apr2025)] [IgnoreSetting(Apr2025)]
public GanonItem GanonItem { get; set; } = GanonItem.Silver; public GanonItem GanonItem { get; set; } = GanonItem.Silver;
[NoSettingName]
[IgnoreSetting(Apr2025)]
public RequireGanonItem RequireGanonItem { get; set; } = RequireGanonItem.SilverlessAllowed;
[SettingName("shuffle")] [SettingName("shuffle")]
[ForbiddenSetting([Apr2025], EntranceShuffle.Swapped)] [ForbiddenSetting([Apr2025], EntranceShuffle.Swapped)]
public EntranceShuffle EntranceShuffle { get; set; } = EntranceShuffle.Vanilla; public EntranceShuffle EntranceShuffle { get; set; } = EntranceShuffle.Vanilla;
@@ -97,7 +106,11 @@
[SettingName("showloot")] [SettingName("showloot")]
[IgnoreSetting(Apr2025)] [IgnoreSetting(Apr2025)]
public ShowLoot ShowLoot { get; set; } = ShowLoot.Never; public ShowLootMap ShowLootMap { get; set; } = ShowLootMap.Never;
[SettingName("loothud")]
[IgnoreSetting(Apr2025)]
public ShowLootHud ShowLootHud { get; set; } = ShowLootHud.Never;
[SettingName("showmap")] [SettingName("showmap")]
[IgnoreSetting(Apr2025)] [IgnoreSetting(Apr2025)]
@@ -159,7 +172,7 @@
public TrapDoorMode TrapDoorMode { get; set; } = TrapDoorMode.Some; public TrapDoorMode TrapDoorMode { get; set; } = TrapDoorMode.Some;
[SettingName("extra_keys")] [SettingName("extra_keys")]
[IgnoreSetting(Apr2025, Base)] [IgnoreSetting(Apr2025)]
public ExtraKeysMode ExtraKeys { get; set; } = ExtraKeysMode.None; public ExtraKeysMode ExtraKeys { get; set; } = ExtraKeysMode.None;
[NoSettingName] [NoSettingName]
@@ -171,23 +184,23 @@
public FluteShuffle FluteShuffle { get; set; } = FluteShuffle.Vanilla; public FluteShuffle FluteShuffle { get; set; } = FluteShuffle.Vanilla;
[SettingName("ow_layout")] [SettingName("ow_layout")]
[IgnoreSetting(Apr2025, Base)] [IgnoreSetting(Apr2025)]
public OverworldLayout OverworldLayout { get; set; } = OverworldLayout.Vanilla; public OverworldLayout OverworldLayout { get; set; } = OverworldLayout.Vanilla;
[NoSettingName] [NoSettingName]
[IgnoreSetting(Apr2025, Base)] [IgnoreSetting(Apr2025)]
public OverworldWorldLayouts OverworldWorldLayouts { get; set; } = OverworldWorldLayouts.Parallel; public OverworldWorldLayouts OverworldWorldLayouts { get; set; } = OverworldWorldLayouts.Parallel;
[NoSettingName] [NoSettingName]
[IgnoreSetting(Apr2025, Base)] [IgnoreSetting(Apr2025)]
public OverworldLayoutTerrain OverworldLayoutTerrain { get; set; } = OverworldLayoutTerrain.SameOnly; public OverworldLayoutTerrain OverworldLayoutTerrain { get; set; } = OverworldLayoutTerrain.SameOnly;
[NoSettingName] [NoSettingName]
[IgnoreSetting(Apr2025, Base)] [IgnoreSetting(Apr2025)]
public OverworldLayoutEdges OverworldLayoutEdges { get; set; } = OverworldLayoutEdges.Unrestricted; public OverworldLayoutEdges OverworldLayoutEdges { get; set; } = OverworldLayoutEdges.Unrestricted;
[NoSettingName] [NoSettingName]
[IgnoreSetting(Apr2025, Base)] [IgnoreSetting(Apr2025)]
public OverworldMapFog OverworldMapFog { get; set; } = OverworldMapFog.Fog; public OverworldMapFog OverworldMapFog { get; set; } = OverworldMapFog.Fog;
[NoSettingName] [NoSettingName]
@@ -200,6 +213,12 @@
[NoSettingName] [NoSettingName]
public Hints Hints { get; set; } = Hints.Off; public Hints Hints { get; set; } = Hints.Off;
public SeedSettings Clone() {
var newSettings = (SeedSettings) this.MemberwiseClone();
return newSettings;
}
} }
public enum RandomizerInstance { public enum RandomizerInstance {
@@ -214,6 +233,11 @@
[AdditionalSetting("--securerandom")] Race, [AdditionalSetting("--securerandom")] Race,
} }
public enum MysteryMode {
Normal,
[AdditionalSetting("--mystery")] Mystery,
}
public enum Mode { public enum Mode {
Open, Open,
Standard, Standard,
@@ -307,6 +331,11 @@
None, None,
} }
public enum RequireGanonItem {
SilverlessAllowed,
[AdditionalSetting("--require_ganon_item")] SpecialItemRequired,
}
public enum EntranceShuffle { public enum EntranceShuffle {
Vanilla, Vanilla,
Full, Full,
@@ -401,13 +430,21 @@
Wild, Wild,
} }
public enum ShowLoot { public enum ShowLootMap {
Never, Never,
Presence, Presence,
Compass, Compass,
Always, Always,
} }
public enum ShowLootHud {
Never,
Presence,
Value,
[SettingName("dungeon_value")] DungeonValue,
[SettingName("cave_value")] CaveValue,
}
public enum ShowMap { public enum ShowMap {
Visited, Visited,
Map, Map,
@@ -444,6 +481,7 @@
public enum BombSettings { public enum BombSettings {
Normal, Normal,
[AdditionalSetting("--bombbag")] BombBagRequired, [AdditionalSetting("--bombbag")] BombBagRequired,
[AddStartingItems("Bombs_(10)")] Starting,
} }
public enum BookSettings { public enum BookSettings {
@@ -530,7 +568,7 @@
public enum DamageChallengeMode { public enum DamageChallengeMode {
Normal, Normal,
OHKO, [JsonStringEnumMemberName("ohko")] OHKO,
Gloom, Gloom,
} }
+49 -11
View File
@@ -1,9 +1,12 @@
namespace ALttPRandomizer { namespace ALttPRandomizer {
using ALttPRandomizer.Azure; using ALttPRandomizer.Azure;
using ALttPRandomizer.Serialization;
using ALttPRandomizer.Model;
using ALttPRandomizer.Options; using ALttPRandomizer.Options;
using ALttPRandomizer.Randomizers; using ALttPRandomizer.Randomizers;
using ALttPRandomizer.Service; using ALttPRandomizer.Service;
using ALttPRandomizer.Settings; using ALttPRandomizer.Settings;
using global::Azure.Core;
using global::Azure.Identity; using global::Azure.Identity;
using global::Azure.Storage.Blobs; using global::Azure.Storage.Blobs;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@@ -12,11 +15,13 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Serilog; using Serilog;
using System;
using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
internal class Program internal class Program {
{
static void Main(string[] args) static void Main(string[] args)
{ {
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -41,27 +46,59 @@
var settings = provider.GetRequiredService<IOptionsMonitor<ServiceOptions>>().CurrentValue!; var settings = provider.GetRequiredService<IOptionsMonitor<ServiceOptions>>().CurrentValue!;
var logger = provider.GetRequiredService<ILogger<Program>>(); var logger = provider.GetRequiredService<ILogger<Program>>();
var allowedCors = settings.AllowedCors;
var envCors = Environment.GetEnvironmentVariable("ALLOWED_CORS");
if (envCors != null) {
allowedCors = allowedCors.Concat(envCors.Split()).ToList();
}
builder.Services.AddCors(options => { builder.Services.AddCors(options => {
options.AddPolicy("AllowDomains", policy => { options.AddPolicy("AllowDomains", policy => {
policy.WithOrigins(settings.AllowedCors.ToArray()).AllowAnyMethod().AllowAnyHeader(); policy.WithOrigins(allowedCors.ToArray()).AllowAnyMethod().AllowAnyHeader();
}); });
}); });
builder.Services.AddControllers() var yamlDeserializer =
new DeserializerBuilder()
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.WithTypeConverter(new YamlStringEnumConverter())
.WithTypeConverter(new RandomizableWeightsYamlConverters())
.Build();
var yamlFormatter =
new YamlInputFormatter(
yamlDeserializer,
provider.GetRequiredService<ILogger<YamlInputFormatter>>());
builder.Services
.AddControllers(options => {
options.InputFormatters.Add(yamlFormatter);
})
.AddJsonOptions(x => { .AddJsonOptions(x => {
x.JsonSerializerOptions.PropertyNameCaseInsensitive = true; x.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
x.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; x.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower, false)); x.JsonSerializerOptions.WithStringEnum();
}); });
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen(options => {
options.OperationFilter<DefaultMysterySettingsFilter>();
});
var options = new DefaultAzureCredentialOptions(); TokenCredential? token = null;
if (settings.AzureSettings.ClientId != null) { if (Environment.GetEnvironmentVariable("USE_SAMI") == "true") {
options.ManagedIdentityClientId = new(settings.AzureSettings.ClientId); token = new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned);
} else {
var options = new DefaultAzureCredentialOptions();
var clientId = Environment.GetEnvironmentVariable("CLIENT_ID");
if (clientId != null) {
options.ManagedIdentityClientId = new(clientId);
}
token = new DefaultAzureCredential(options);
} }
var token = new DefaultAzureCredential(options);
var seedClient = new BlobContainerClient(settings.AzureSettings.BlobstoreEndpoint, token); var seedClient = new BlobContainerClient(settings.AzureSettings.BlobstoreEndpoint, token);
builder.Services.AddSingleton(seedClient); builder.Services.AddSingleton(seedClient);
@@ -72,6 +109,7 @@
builder.Services.AddSingleton<ShutdownHandler>(); builder.Services.AddSingleton<ShutdownHandler>();
builder.Services.AddScoped<BaseRandomizer>(); builder.Services.AddScoped<BaseRandomizer>();
builder.Services.AddScoped<MysteryRandomizer>();
builder.Services.AddScoped<RandomizeService>(); builder.Services.AddScoped<RandomizeService>();
builder.Services.AddScoped<SeedService>(); builder.Services.AddScoped<SeedService>();
builder.Services.AddScoped<IdGenerator>(); builder.Services.AddScoped<IdGenerator>();
+24 -2
View File
@@ -18,6 +18,8 @@
public const int MULTI_TRIES = 100; public const int MULTI_TRIES = 100;
public const int SINGLE_TRIES = 5; public const int SINGLE_TRIES = 5;
private static readonly Random random = new();
public BaseRandomizer( public BaseRandomizer(
AzureStorage azureStorage, AzureStorage azureStorage,
CommonSettingsProcessor settingsProcessor, CommonSettingsProcessor settingsProcessor,
@@ -64,10 +66,20 @@
args.Add("--shuffletavern"); args.Add("--shuffletavern");
} }
if (settings.Randomizer == RandomizerInstance.Beta) {
if (random.NextDouble() < 0.25) {
args.Add("--orange_potion");
}
}
if (settings.DoorShuffle == DoorShuffle.Vanilla) { if (settings.DoorShuffle == DoorShuffle.Vanilla) {
settings.DoorTypeMode = DoorTypeMode.Original; settings.DoorTypeMode = DoorTypeMode.Original;
} }
if (settings.GanonItem == GanonItem.None) {
settings.RequireGanonItem = RequireGanonItem.SilverlessAllowed;
}
foreach (var arg in SettingsProcessor.GetSettings(settings.Randomizer, settings)) { foreach (var arg in SettingsProcessor.GetSettings(settings.Randomizer, settings)) {
args.Add(arg); args.Add(arg);
} }
@@ -113,7 +125,7 @@
}; };
} }
public async Task Randomize(string id, SeedSettings settings, bool uploadSettings = true) { public async Task Randomize(string id, SeedSettings settings, bool uploadSettings = true, MysterySettings? mysterySettings = null) {
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 args = this.GetArgs(settings); var args = this.GetArgs(settings);
@@ -133,16 +145,26 @@
} }
}); });
if (mysterySettings != null) {
var mysteryJson = JsonSerializer.SerializeToDocument(mysterySettings, JsonOptions.Default);
var mysteryOut = string.Format("{0}/mystery.json", id);
await AzureStorage.UploadFile(mysteryOut, new BinaryData(mysteryJson));
}
if (uploadSettings) { if (uploadSettings) {
var settingsJson = JsonSerializer.SerializeToDocument(settings, JsonOptions.Default); var settingsJson = JsonSerializer.SerializeToDocument(settings, JsonOptions.Default);
var settingsOut = string.Format("{0}/settings.json", id); var settingsOut = string.Format("{0}/settings.json", id);
await AzureStorage.UploadFile(settingsOut, new BinaryData(settingsJson)); await AzureStorage.UploadFile(settingsOut, new BinaryData(settingsJson), true);
} }
} }
public async Task RandomizeMultiworld(string id, IList<SeedSettings> settings, bool uploadSettings = true) { public async Task RandomizeMultiworld(string id, IList<SeedSettings> settings, bool uploadSettings = true) {
var generatorSettings = this.SettingsProcessor.GetGeneratorSettings(settings[0].Randomizer); var generatorSettings = this.SettingsProcessor.GetGeneratorSettings(settings[0].Randomizer);
for (var i = 0; i < settings.Count; i++) {
settings[i].PlayerSlot = i + 1;
}
Logger.LogDebug("Recieved request for id {id} to randomize multiworld settings {@settings}", id, settings); Logger.LogDebug("Recieved request for id {id} to randomize multiworld settings {@settings}", id, settings);
var names = settings.Select(s => s.PlayerName.Replace(' ', '_')).ToList(); var names = settings.Select(s => s.PlayerName.Replace(' ', '_')).ToList();
+1 -1
View File
@@ -5,6 +5,6 @@
public interface IRandomizer { public interface IRandomizer {
public void Validate(SeedSettings settings); public void Validate(SeedSettings settings);
public Task Randomize(string id, SeedSettings settings, bool uploadSettings = true); public Task Randomize(string id, SeedSettings settings, bool uploadSettings = true, MysterySettings? mysterySettings = null);
} }
} }
@@ -0,0 +1,30 @@
namespace ALttPRandomizer.Randomizers {
using System;
using System.Linq;
using ALttPRandomizer.Model;
public class MysteryRandomizer {
private static readonly Random random = new();
public SeedSettings Roll(MysterySettings mystery, SeedSettings initialSettings) {
var mysteryFields = typeof(MysterySettings).GetProperties();
var settingsFields =
typeof(SeedSettings).GetProperties()
.ToDictionary(field => field.Name, field => field);
foreach (var field in mysteryFields) {
if (!settingsFields.ContainsKey(field.Name)) {
continue;
}
var value = field.GetValue(mystery);
if (value is IRandomizableWeights weights && !weights.IsEmpty) {
settingsFields[field.Name].SetValue(initialSettings, weights.Roll());
}
}
return initialSettings;
}
}
}
+40 -8
View File
@@ -8,7 +8,10 @@
using System.Threading.Tasks; using System.Threading.Tasks;
public class SeedController : Controller { public class SeedController : Controller {
public SeedController(RandomizeService randomizeService, SeedService seedService, ILogger<SeedController> logger) { public SeedController(
RandomizeService randomizeService,
SeedService seedService,
ILogger<SeedController> logger) {
this.RandomizeService = randomizeService; this.RandomizeService = randomizeService;
this.SeedService = seedService; this.SeedService = seedService;
this.Logger = logger; this.Logger = logger;
@@ -20,10 +23,11 @@
[Route("/generate")] [Route("/generate")]
[HttpPost] [HttpPost]
public async Task<ActionResult> Generate([FromBody] SeedSettings settings) { public async Task<ObjectResult> Generate([FromBody] SeedSettings settings) {
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
return BadRequest(ModelState); return BadRequest(ModelState);
} }
try { try {
var id = await this.RandomizeService.RandomizeSeed(settings); var id = await this.RandomizeService.RandomizeSeed(settings);
var url = string.Format("/seed/{0}", id); var url = string.Format("/seed/{0}", id);
@@ -33,9 +37,31 @@
} }
} }
[Route("/mystery/{randomizer}")]
[HttpPost]
public async Task<ObjectResult> RollMystery([FromBody] MysterySettings mysterySettings, RandomizerInstance randomizer, bool race) {
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
var baseSettings = new SeedSettings() {
Randomizer = randomizer,
Race = race ? RaceMode.Race : RaceMode.Normal,
Mystery = MysteryMode.Mystery,
};
try {
var id = await this.RandomizeService.RandomizeMystery(mysterySettings, baseSettings);
var url = string.Format("/seed/{0}", id);
return Accepted(url, id);
} catch (InvalidSettingsException ex) {
return BadRequest(ex.Message);
}
}
[Route("/multiworld")] [Route("/multiworld")]
[HttpPost] [HttpPost]
public async Task<ActionResult> GenerateMultiworld([FromBody] IList<SeedSettings> settings) { public async Task<ObjectResult> GenerateMultiworld([FromBody] IList<SeedSettings> settings) {
if (!ModelState.IsValid) { if (!ModelState.IsValid) {
return BadRequest(ModelState); return BadRequest(ModelState);
} }
@@ -50,25 +76,25 @@
[Route("/seed/{id}")] [Route("/seed/{id}")]
[HttpGet] [HttpGet]
public async Task<ActionResult> GetSeed(string id) { public async Task<ObjectResult> GetSeed(string id) {
return ResolveResult(await this.SeedService.GetSeed(id)); return ResolveResult(await this.SeedService.GetSeed(id));
} }
[Route("/seed/{id}")] [Route("/seed/{id}")]
[HttpPost] [HttpPost]
public async Task<ActionResult> RetrySeed(string id) { public async Task<ObjectResult> RetrySeed(string id) {
return ResolveResult(await this.RandomizeService.RetrySeed(id)); return ResolveResult(await this.RandomizeService.RetrySeed(id));
} }
[Route("/multi/{id}")] [Route("/multi/{id}")]
[HttpGet] [HttpGet]
public async Task<ActionResult> GetMulti(string id) { public async Task<ObjectResult> GetMulti(string id) {
return ResolveResult(await this.SeedService.GetMulti(id)); return ResolveResult(await this.SeedService.GetMulti(id));
} }
[Route("/multi/{id}")] [Route("/multi/{id}")]
[HttpPost] [HttpPost]
public async Task<ActionResult> RetryMulti(string id) { public async Task<ObjectResult> RetryMulti(string id) {
return ResolveResult(await this.RandomizeService.RetryMulti(id)); return ResolveResult(await this.RandomizeService.RetryMulti(id));
} }
@@ -85,7 +111,13 @@
} }
} }
private ActionResult ResolveResult(IDictionary<string, object> result) { [Route("/healthcheck")]
[HttpGet]
public StatusCodeResult GetHealthCheck() {
return Ok();
}
private ObjectResult ResolveResult(IDictionary<string, object> result) {
if (result.TryGetValue("status", out var responseCode)) { if (result.TryGetValue("status", out var responseCode)) {
if (responseCode is int code) { if (responseCode is int code) {
return StatusCode(code, result); return StatusCode(code, result);
@@ -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;
}
}
}
}
}
@@ -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());
}
}
}
}
@@ -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();
}
}
}
}
@@ -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}");
}
}
}
@@ -2,6 +2,7 @@
using ALttPRandomizer.Azure; using ALttPRandomizer.Azure;
using ALttPRandomizer.Model; using ALttPRandomizer.Model;
using ALttPRandomizer.Randomizers; using ALttPRandomizer.Randomizers;
using ALttPRandomizer.Settings;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json; using System.Text.Json;
@@ -11,10 +12,12 @@
public RandomizeService( public RandomizeService(
IdGenerator idGenerator, IdGenerator idGenerator,
BaseRandomizer baseRandomizer, BaseRandomizer baseRandomizer,
MysteryRandomizer mysteryRandomizer,
AzureStorage azureStorage, AzureStorage azureStorage,
ILogger<RandomizeService> logger) { ILogger<RandomizeService> logger) {
this.IdGenerator = idGenerator; this.IdGenerator = idGenerator;
this.BaseRandomizer = baseRandomizer; this.BaseRandomizer = baseRandomizer;
this.MysteryRandomizer = mysteryRandomizer;
this.AzureStorage = azureStorage; this.AzureStorage = azureStorage;
this.Logger = logger; this.Logger = logger;
} }
@@ -23,6 +26,7 @@
private IdGenerator IdGenerator { get; } private IdGenerator IdGenerator { get; }
private BaseRandomizer BaseRandomizer { get; } private BaseRandomizer BaseRandomizer { get; }
private MysteryRandomizer MysteryRandomizer { get; }
private AzureStorage AzureStorage { get; } private AzureStorage AzureStorage { get; }
public async Task<string> RandomizeSeed(SeedSettings settings, string? seedId = null) { public async Task<string> RandomizeSeed(SeedSettings settings, string? seedId = null) {
@@ -35,6 +39,31 @@
return id; return id;
} }
public async Task<string> RandomizeMystery(MysterySettings mysterySettings, SeedSettings baseSettings, string? seedId = null) {
var id = seedId ?? this.IdGenerator.GenerateId();
this.Logger.LogDebug("Rolling mystery seed {seedId} with mystery {@settings}", id, mysterySettings);
SeedSettings seedSettings;
for (var i = 0; i < 10; i++) {
seedSettings = baseSettings.Clone();
this.MysteryRandomizer.Roll(mysterySettings, seedSettings);
this.Logger.LogInformation("Mystery seed {seedId} rolled settings {@settings}", id, seedSettings);
try {
this.BaseRandomizer.Validate(seedSettings);
} catch (InvalidSettingsException) {
this.Logger.LogWarning("Mystery seed {seedId} settings {@settings} failed validation; retrying", id, seedSettings);
continue;
}
await this.BaseRandomizer.Randomize(id, seedSettings, true, seedId == null ? mysterySettings : null);
return id;
}
throw new InvalidSettingsException("Could not roll settings that pass validation");
}
public async Task<string> RandomizeMultiworld(IList<SeedSettings> settings, string? multiId = null) { public async Task<string> RandomizeMultiworld(IList<SeedSettings> settings, string? multiId = null) {
var id = multiId ?? this.IdGenerator.GenerateId(); var id = multiId ?? this.IdGenerator.GenerateId();
this.Logger.LogInformation("Generating multiworld {seedId} with settings {@settings}", id, settings); this.Logger.LogInformation("Generating multiworld {seedId} with settings {@settings}", id, settings);
+1
View File
@@ -59,6 +59,7 @@
} }
internal class IgnoreSettingAttribute : RandomizerSpecificAttribute { internal class IgnoreSettingAttribute : RandomizerSpecificAttribute {
public IgnoreSettingAttribute() : base(null) { }
public IgnoreSettingAttribute(params RandomizerInstance[] randomizers) : base(randomizers) { } public IgnoreSettingAttribute(params RandomizerInstance[] randomizers) : base(randomizers) { }
} }
@@ -10,7 +10,7 @@
var props = typeof(SeedSettings).GetProperties(BindingFlags.Instance | BindingFlags.Public); var props = typeof(SeedSettings).GetProperties(BindingFlags.Instance | BindingFlags.Public);
var starting = new List<string>(); var starting = new List<string>();
foreach (var prop in props) { foreach (var prop in props) {
if (prop.Name == nameof(SeedSettings.PlayerName)) { if (prop.GetCustomAttributes<IgnoreSettingAttribute>().Any(att => att.HasRandomizer(randomizer))) {
continue; continue;
} }
@@ -19,10 +19,6 @@
var fi = prop.PropertyType.GetField(valueFieldName, BindingFlags.Static | BindingFlags.Public) var fi = prop.PropertyType.GetField(valueFieldName, BindingFlags.Static | BindingFlags.Public)
?? throw new SettingsLookupException("Could not get field info for value {0}.{1}", prop.PropertyType, valueFieldName); ?? throw new SettingsLookupException("Could not get field info for value {0}.{1}", prop.PropertyType, valueFieldName);
if (prop.GetCustomAttributes<IgnoreSettingAttribute>().Any(att => att.HasRandomizer(randomizer))) {
continue;
}
if (!prop.GetCustomAttributes<NoSettingNameAttribute>().Any(att => att.HasRandomizer(randomizer))) { if (!prop.GetCustomAttributes<NoSettingNameAttribute>().Any(att => att.HasRandomizer(randomizer))) {
var settingName = var settingName =
prop.GetCustomAttributes<SettingNameAttribute>() prop.GetCustomAttributes<SettingNameAttribute>()
@@ -72,17 +68,12 @@
} }
} }
internal GeneratorSettingsAttribute GetGeneratorSettings(RandomizerInstance randomizer) internal GeneratorSettingsAttribute GetGeneratorSettings(RandomizerInstance randomizer) {
{
var fi = typeof(RandomizerInstance).GetField(randomizer.ToString(), BindingFlags.Static | BindingFlags.Public); var fi = typeof(RandomizerInstance).GetField(randomizer.ToString(), BindingFlags.Static | BindingFlags.Public);
var settings = fi?.GetCustomAttribute<GeneratorSettingsAttribute>(); var settings = fi?.GetCustomAttribute<GeneratorSettingsAttribute>();
if (settings == null) { return settings ?? throw new InvalidSettingsException("Invalid randomizer: {0}", randomizer);
throw new InvalidSettingsException("Invalid randomizer: {0}", randomizer);
}
return settings;
} }
} }
+1 -2
View File
@@ -5,10 +5,9 @@
"allowedCors": [ "allowedCors": [
"https://new.alttpr.gwaa.kiwi", "https://new.alttpr.gwaa.kiwi",
"https://api.alttpr.gwaa.kiwi", "https://api.alttpr.gwaa.kiwi",
"http://localhost:8082" "http://localhost:8082",
], ],
"azureSettings": { "azureSettings": {
"clientId": "a48d5ae1-fa0a-4e33-9586-18c0eca0a28c",
"blobstoreEndpoint": "https://alttprstorage.blob.core.windows.net/seeds" "blobstoreEndpoint": "https://alttprstorage.blob.core.windows.net/seeds"
}, },
"generators": { "generators": {
+25
View File
@@ -0,0 +1,25 @@
set dotenv-filename := ".env.just"
import? "local.just"
[private]
@list:
just --list --list-submodules --unsorted
# Build docker image
[group("docker")]
[arg("TAG", long="tag", short="t")]
build VERSION="latest" TAG=env("DOCKER_TAG"):
DOCKER_BUILDKIT=1 docker build . --progress=plain -t {{TAG}}:{{VERSION}}
# Run docker image
[group("docker")]
[arg("TAG", long="tag", short="t")]
run VERSION="latest" TAG=env("DOCKER_TAG"): (build VERSION TAG)
docker run -p 5000:8080 {{TAG}}:{{VERSION}}
# Push docker image to registry
[group("docker")]
[arg("TAG", long="tag", short="t")]
push VERSION="latest" TAG=env("DOCKER_TAG"): (build VERSION TAG)
docker push {{TAG}}:{{VERSION}}