diff --git a/.github/workflows/build-debug.yaml b/.github/workflows/build-debug.yaml index b4ded7d4e..35eb3f934 100644 --- a/.github/workflows/build-debug.yaml +++ b/.github/workflows/build-debug.yaml @@ -5,7 +5,9 @@ on: branches-ignore: - main - testing - - '5.1' + - extras + - sgl + - '5.2' pull_request: env: diff --git a/.github/workflows/build-latest.yaml b/.github/workflows/build-latest.yaml index 06f3f7c8d..54c37e8ae 100644 --- a/.github/workflows/build-latest.yaml +++ b/.github/workflows/build-latest.yaml @@ -5,7 +5,9 @@ on: branches: - main - testing - - '5.1' + - extras + - sgl + - '5.2' env: # I'm not a fan of the telemetry as-is, but this also suppresses some lines in the build log. diff --git a/CoreSourceGenerator/CoreSourceGenerator.csproj b/CoreSourceGenerator/CoreSourceGenerator.csproj index bed7384f5..b20c92c0b 100644 --- a/CoreSourceGenerator/CoreSourceGenerator.csproj +++ b/CoreSourceGenerator/CoreSourceGenerator.csproj @@ -11,10 +11,7 @@ Debug;Release;Unsafe Debug AnyCPU - - true - true - + true false diff --git a/CoreSourceGenerator/FlagsSerializeGenerator.cs b/CoreSourceGenerator/FlagsSerializeGenerator.cs index 2decebcdf..683f04bca 100644 --- a/CoreSourceGenerator/FlagsSerializeGenerator.cs +++ b/CoreSourceGenerator/FlagsSerializeGenerator.cs @@ -58,7 +58,7 @@ private static bool IsPartialClass(SyntaxNode node) .Select(f => { // Look up the source code for the declaration to find if it has a default value - var equalsSyntax = f.DeclaringSyntaxReferences[0].GetSyntax() switch + var equalsSyntax = f.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() switch { PropertyDeclarationSyntax property => property.Initializer, VariableDeclaratorSyntax variable => variable.Initializer, @@ -81,18 +81,32 @@ private static bool IsPartialClass(SyntaxNode node) var serializeFields = classSymbol.GetMembers() .OfType() .Where(f => !HasIgnoreInFlagsAttribute(f)) - .Select(f => new SerializedFieldInfo() + .Select(f => { - FieldName = f.Name, - FieldType = f.Type.ToDisplayString(), - IsDifficultyOnly = HasDifficultyOnlyAttribute(f), - IsConditionallyIncluded = HasConditionallyIncludedInFlagsAttribute(f), - DefaultValue = GetDefaultValue(f), - IsEnum = f.Type.TypeKind == TypeKind.Enum, - EnumSymbol = f.Type.TypeKind == TypeKind.Enum ? f.Type as INamedTypeSymbol : null, - Minimum = GetCustomMinimum(f), - Maximum = GetCustomMaximum(f), - CustomSerializerName = GetCustomFlagSerializer(f), + var dictionaryInterface = f.Type.AllInterfaces + .FirstOrDefault(i => i.OriginalDefinition.ToDisplayString().StartsWith("System.Collections.Generic.IDictionary")); + var innerType = dictionaryInterface != null ? dictionaryInterface.TypeArguments[0] : f.Type; + + var equalsSyntax = f.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() switch + { + PropertyDeclarationSyntax property => property.Initializer, + VariableDeclaratorSyntax variable => variable.Initializer, + _ => null + }; + + return new SerializedFieldInfo() + { + FieldName = f.Name, + FieldType = f.Type.ToDisplayString(), + IsDifficultyOnly = HasDifficultyOnlyAttribute(f), + IsConditionallyIncluded = HasConditionallyIncludedInFlagsAttribute(f), + DefaultValue = equalsSyntax?.Value.ToString(), + IsEnum = f.Type.TypeKind == TypeKind.Enum, + EnumSymbol = f.Type.TypeKind == TypeKind.Enum ? f.Type as INamedTypeSymbol : null, + Minimum = GetCustomMinimum(f), + Maximum = GetCustomMaximum(f), + CustomSerializerName = GetCustomFlagSerializer(f), + }; }).ToList(); classInfo.SerializedFields.AddRange(serializeFields); @@ -140,14 +154,6 @@ private static bool HasDifficultyOnlyAttribute(IFieldSymbol field) .Any(attr => attr.AttributeClass?.Name.StartsWith("DifficultyOnly") ?? false); } - private static string GetDefaultValue(IFieldSymbol f) - { - var defaultAttr = f.GetAttributes() - .FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "System.ComponentModel.DefaultValueAttribute"); - var arg = defaultAttr?.ConstructorArguments[0]; - return arg != null ? FormatArgument(arg.Value) : "default"; - } - private static bool HasIgnoreInFlagsAttribute(IFieldSymbol field) { return field.GetAttributes() @@ -277,6 +283,7 @@ private static string GenerateCode(ClassGenerationInfo classInfo) GenerateSerializeMethod(sb, classInfo.SerializedFields, indent); GenerateDeserializeMethod(sb, classInfo.SerializedFields, indent); + GenerateWithMethod(sb, classInfo.ClassName, classInfo.ReactiveFields, indent); // Generate helper methods for enum serialization GenerateSerializerList(sb, classInfo.SerializedFields, indent); @@ -316,7 +323,10 @@ private static void GenerateReactiveProperty(StringBuilder sb, ReactiveFieldInfo sb.AppendLine($"{indent} {{"); sb.AppendLine($"{indent} {field.FieldName} = value;"); sb.AppendLine($"{indent} OnPropertyChanged(nameof({field.PropertyName}));"); - sb.AppendLine($"{indent} OnPropertyChanged(\"Flags\");"); + sb.AppendLine($"{indent} if (!_inDeserializeFlags)"); + sb.AppendLine($"{indent} {{"); + sb.AppendLine($"{indent} OnPropertyChanged(\"Flags\");"); + sb.AppendLine($"{indent} }}"); sb.AppendLine($"{indent} }}"); sb.AppendLine($"{indent} }}"); sb.AppendLine($"{indent} }}{defaultValue}"); @@ -400,6 +410,24 @@ private static void GenerateDeserializeMethod(StringBuilder sb, List fields, string indent) + { + sb.AppendLine(); + sb.AppendLine($"{indent} public {className} With({className} other)"); + sb.AppendLine($"{indent} {{"); + sb.AppendLine($"{indent} {className} result = new();"); + sb.AppendLine(); + + foreach (var field in fields) + { + sb.AppendLine($"{indent} result.{field.PropertyName} = !Equals(other.{field.PropertyName}, this.{field.PropertyName}) ? other.{field.PropertyName} : this.{field.PropertyName};"); + } + + sb.AppendLine(); + sb.AppendLine($"{indent} return result;"); + sb.AppendLine($"{indent} }}"); + } + private static void GenerateSerializerList(StringBuilder sb, List fields, string indent) { sb.AppendLine(); diff --git a/CrossPlatformUI.Browser/CrossPlatformUI.Browser.csproj b/CrossPlatformUI.Browser/CrossPlatformUI.Browser.csproj index 3bbe79176..a63b7f6d5 100644 --- a/CrossPlatformUI.Browser/CrossPlatformUI.Browser.csproj +++ b/CrossPlatformUI.Browser/CrossPlatformUI.Browser.csproj @@ -22,8 +22,14 @@ false false + true + true + + + + true diff --git a/CrossPlatformUI.Browser/wwwroot/main.js b/CrossPlatformUI.Browser/wwwroot/main.js index 97d8e590e..8d590e303 100644 --- a/CrossPlatformUI.Browser/wwwroot/main.js +++ b/CrossPlatformUI.Browser/wwwroot/main.js @@ -1,7 +1,7 @@ import { dotnet } from './_framework/dotnet.js' import { compile } from './js65/libassembler.js' -const BUNDLE_DOWNLOAD_SIZE = 80 * 1024 * 1024; // used for progress bar - doesn't have to be exact +const BUNDLE_DOWNLOAD_SIZE = 71 * 1024 * 1024; // used for progress bar - doesn't have to be exact const is_browser = typeof window != "undefined"; if (!is_browser) throw new Error(`Expected to be running in a browser`); @@ -17,8 +17,22 @@ function showError(msg) { (() => { const origFetch = globalThis.fetch.bind(globalThis); + // This is a custom cache workaround for GitHub Pages arbitrarily + // sending different ETags for the same files. (It might depend on + // which CDN handles the request.) + // Instead rely on GitHub Pages setting Last - Modified to be the + // same for all files when the page is updated, including this script. + const deployTimestampPromise = (async () => { + try { + const r = await origFetch(import.meta.url, { cache: "default" }); + return Date.parse(r.headers.get("Last-Modified") || "") || 0; + } catch { + return 0; + } + })(); + // track cumulative progress across all boot resources - let loadedKnownBytes = 0; + let completedBytes = 0; const inFlight = new Map(); // id -> {loaded,total} let started = false; let finishTimer; @@ -40,24 +54,57 @@ function showError(msg) { }; const updateOverall = () => { - updateProgress(loadedKnownBytes / BUNDLE_DOWNLOAD_SIZE); + let loaded = completedBytes; + for (const r of inFlight.values()) { + loaded += r.loaded; + } + updateProgress(loaded / BUNDLE_DOWNLOAD_SIZE); }; - globalThis.fetch = async (input, init) => { - const res = await origFetch(input, init).catch((e) => { - showError("Network error while loading app. Please reload."); - throw e; - }); + const fetchError = (e) => { + showError("Network error while loading app. Please reload."); + throw e; + }; + globalThis.fetch = async (input, init) => { + const deployTs = await deployTimestampPromise; const url = typeof input === "string" ? input : (input && input.url) || ""; - if (!isBootResource(url, res) || !res.body || res.bodyUsed) { - return res; // leave non-boot fetches alone + const absoluteUrl = new URL(url, window.location.href).href; + + var fetchResult; + + if (deployTs !== 0) { + // check if transferSize is non-zero (there was no local cache) + const cachedFetchResult = await origFetch(input, { ...init, cache: "force-cache" }).catch(fetchError); + const perfEntries = performance?.getEntriesByName(absoluteUrl, "resource"); + const lastEntry = perfEntries?.[perfEntries.length - 1]; + const wasNetworkRequest = lastEntry?.transferSize > 0; + + var staleCache = false; + if (!wasNetworkRequest) { + // if we have a reference timestamp, ensure the cached copy isn't + // much older than the deploy timestamp. + const lm = Date.parse(cachedFetchResult.headers.get("Last-Modified")); + staleCache = !lm || lm < deployTs - 180_000; + } + + if (!staleCache) { + fetchResult = cachedFetchResult; + } + } + + if (!fetchResult) { + fetchResult = await origFetch(input, { ...init, cache: "default" }).catch(fetchError); + } + + if (!isBootResource(url, fetchResult) || !fetchResult.body || fetchResult.bodyUsed) { + return fetchResult; // leave non-boot fetches alone } started = true; - const contentLength = parseInt(res.headers.get("Content-Length") || "0", 10); + const contentLength = parseInt(fetchResult.headers.get("Content-Length") || "0", 10); const id = Math.random().toString(36).slice(2); - const reader = res.body.getReader(); + const reader = fetchResult.body.getReader(); inFlight.set(id, { loaded: 0, total: contentLength }); @@ -67,21 +114,26 @@ function showError(msg) { showError("Error reading a resource stream. Please reload."); throw e; }); + if (done) { - controller.close(); - // account for any rounding misses const r = inFlight.get(id); - if (r && r.total > 0) loadedKnownBytes += (r.total - r.loaded); - inFlight.delete(id); + + if (r) { + completedBytes += r.loaded; + inFlight.delete(id); + } + updateOverall(); + controller.close(); return; } + controller.enqueue(value); - const r = inFlight.get(id); - if (r) { - r.loaded += value.length; - if (r.total > 0) loadedKnownBytes += value.length; - inFlight.set(id, r); + + const current = inFlight.get(id); + if (current) { + current.loaded += value.length; + inFlight.set(id, current); } updateOverall(); }, @@ -89,9 +141,9 @@ function showError(msg) { }); return new Response(stream, { - headers: res.headers, - status: res.status, - statusText: res.statusText + headers: fetchResult.headers, + status: fetchResult.status, + statusText: fetchResult.statusText }); }; diff --git a/CrossPlatformUI/App.axaml b/CrossPlatformUI/App.axaml index 7f0193b75..54562a97b 100644 --- a/CrossPlatformUI/App.axaml +++ b/CrossPlatformUI/App.axaml @@ -6,6 +6,7 @@ xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:assists="clr-namespace:Material.Styles.Assists;assembly=Material.Styles" + xmlns:controls="clr-namespace:Material.Styles.Controls;assembly=Material.Styles" xmlns:behaviors="clr-namespace:CrossPlatformUI.Behaviors" x:Class="CrossPlatformUI.App"> @@ -103,6 +104,11 @@ + + diff --git a/CrossPlatformUI/App.axaml.cs b/CrossPlatformUI/App.axaml.cs index dd1061c36..2c7439c5f 100644 --- a/CrossPlatformUI/App.axaml.cs +++ b/CrossPlatformUI/App.axaml.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reactive.Linq; using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; @@ -13,6 +12,7 @@ using Avalonia.Markup.Xaml; using Material.Styles.Assists; using Microsoft.Extensions.DependencyInjection; +using ReactiveUI.Primitives; using Z2Randomizer.RandomizerCore; using CrossPlatformUI.Services; using CrossPlatformUI.ViewModels; @@ -96,10 +96,11 @@ public override void OnFrameworkInitializationCompleted() var spriteLoaderService = App.Current?.Services?.GetService(); Debug.Assert(spriteLoaderService != null); - spriteLoaderService.Sprites - .Where(sprites => sprites.Count > 0) - .Take(1) - .Subscribe(sprites => + SubscribeExtensions.Subscribe( + spriteLoaderService.Sprites + .Where(sprites => sprites.Count > 0) + .Take(1), + sprites => { var sprite = sprites.FirstOrDefault(loaded => loaded.DisplayName == main.Config.SpriteName); if (sprite != null) diff --git a/CrossPlatformUI/AppViewLocator.cs b/CrossPlatformUI/AppViewLocator.cs index 31c243b05..c876c65ec 100644 --- a/CrossPlatformUI/AppViewLocator.cs +++ b/CrossPlatformUI/AppViewLocator.cs @@ -21,8 +21,16 @@ public class AppViewLocator : IViewLocator public IViewFor? ResolveView(object? viewModel, string? contract = null) => viewModel is null ? null : CreateView(viewModel); + public IViewFor? ResolveView(object? viewModel) + => viewModel is null ? null : CreateView(viewModel); + public IViewFor? ResolveView(string? contract = null) where TViewModel : class { throw new NotImplementedException(); } + + public IViewFor? ResolveView() where TViewModel : class + { + throw new NotImplementedException(); + } } diff --git a/CrossPlatformUI/Assets/palace-blockers.png b/CrossPlatformUI/Assets/palace-blockers.png deleted file mode 100644 index 7493ce1b0..000000000 Binary files a/CrossPlatformUI/Assets/palace-blockers.png and /dev/null differ diff --git a/CrossPlatformUI/CrossPlatformUI.csproj b/CrossPlatformUI/CrossPlatformUI.csproj index 36406d1c1..f9f77db42 100644 --- a/CrossPlatformUI/CrossPlatformUI.csproj +++ b/CrossPlatformUI/CrossPlatformUI.csproj @@ -13,11 +13,6 @@ true - - - $(NoWarn);IL2026 - - true false @@ -35,17 +30,23 @@ + - + all + + + + + diff --git a/CrossPlatformUI/Lang/Resources.resx b/CrossPlatformUI/Lang/Resources.resx index 9c3c371fd..49a78cbfb 100644 --- a/CrossPlatformUI/Lang/Resources.resx +++ b/CrossPlatformUI/Lang/Resources.resx @@ -231,9 +231,20 @@ group is the same as the number in that group that steal EXP in vanilla. If selected, the amount of EXP each stealer steals is between 50% and 150% of the vanilla amount. - -Randomly reassigns which enemies are immune to stabs. The number of enemies that are -immune in each group is the same as the number in that group that are immune in vanilla. + +Vanilla: +Uses the game's original sword immunities. (Tektites and Zoras) + +Shuffle: +Randomly reassigns which enemies are immune to sword attacks and require the Fire spell +to damage. On average, the same number of enemies are immune as in the vanilla game. + +Shuffle/None if Fire unusable: +Uses Shuffle if the Fire spell is usable (not replaced by Dash nor linked with Fairy). +Otherwise, removes all sword immunities. + +None: +Removes all sword immunities. Randomizes the color palettes of enemies and NPCs. Health bar color is also randomized. @@ -701,9 +712,9 @@ linear. make room. Random Item Rooms Per Palace: + • Each palace gets 0–3 item rooms (varies by palace length/style). • Clouds in the entrance screen and Ironknuckle statues by the entrance elevator indicate how many items there are in the palace. - • Each palace gets 0–3 item rooms (varies by palace length/style). If selected, the normal zelda 2 rooms will be available for palace generation. Note that @@ -775,8 +786,10 @@ considered duplicates of each other. Has no effect on vanilla palaces. Does not prevent duplicates for Random Walk generated palaces. -Allows rooms that require an item or spell to traverse to appear in any palace. Otherwise, -palaces can only contain blocks according to the following table. +Allows rooms that cannot be passed without a specific item or spell to appear in any palace. +Otherwise, the allowed requirements for rooms in palaces are based on the items and spells +needed to pass through every room in that palace in vanilla (from either direction). +See the Wiki for the full table. Removes dead-end rooms from the room pool. This helps newer players who are not yet diff --git a/CrossPlatformUI/Presets/BeginnerPreset.cs b/CrossPlatformUI/Presets/BeginnerPreset.cs index 7a70ff6e0..e12092791 100644 --- a/CrossPlatformUI/Presets/BeginnerPreset.cs +++ b/CrossPlatformUI/Presets/BeginnerPreset.cs @@ -12,28 +12,14 @@ public static class BeginnerPreset //Start StartWithCandle = true, StartWithLife = true, - ShuffleStartingItems = false, - ShuffleStartingSpells = false, - MaxHeartContainers = MaxHeartsOption.EIGHT, - StartingHeartContainersMin = 4, - StartingHeartContainersMax = 4, StartingTechniques = StartingTechs.DOWNSTAB, StartingLives = StartingLives.Lives5, - IndeterminateOptionRate = IndeterminateOptionRate.HALF, //Overworld - PalacesCanSwapContinents = false, - ShuffleGP = false, - ShuffleEncounters = false, - AllowUnsafePathEncounters = false, - IncludeLavaInEncounterShuffle = false, EncounterRate = EncounterRate.HALF, - RiverDevilBlockerOption = RiverDevilBlockerOption.PATH, EastRocks = true, GenerateBaguWoods = false, LessImportantLocationsOption = LessImportantLocationsOption.REMOVE, - RestrictConnectionCaveShuffle = true, - AllowConnectionCavesToBeBlocked = false, GoodBoots = true, HidePalace = false, HideKasuto = false, @@ -45,41 +31,24 @@ public static class BeginnerPreset EastBiome = Biome.VANILLALIKE, DmBiome = Biome.VANILLALIKE, MazeBiome = Biome.VANILLALIKE, - WestClimate = ClimateEnum.VANILLA_WEIGHTED_WEST, - EastClimate = ClimateEnum.VANILLA_WEIGHTED_EAST, - DmClimate = ClimateEnum.CLASSIC, - ContinentConnectionType = ContinentConnectionType.NORMAL, + WestClimate = ClimateEnum.VANILLA_WEIGHTED, + EastClimate = ClimateEnum.VANILLA_WEIGHTED, //Palaces NormalPalaceStyle = PalaceStyle.VANILLA_WEIGHTED, GpStyle = PalaceStyle.TOWER, NormalPalaceLength = PalaceLengthOption.MEDIUM, GpLength = PalaceLengthOption.SHORT, - IncludeVanillaRooms = true, Includev4_0Rooms = true, Includev5_0Rooms = true, - IncludeExpertRooms = false, - TBirdRequired = true, - RemoveTBird = false, - PalacesToCompleteMin = 6, - PalacesToCompleteMax = 6, RestartAtPalacesOnGameOver = true, Global5050JarDrop = true, ReduceDripperVariance = true, ChangePalacePallettes = true, - RandomizeBossItemDrop = false, - PalaceDropStyle = PalaceDropStyle.ENTRANCE, - BossRoomsExitType = BossRoomsExitType.OVERWORLD, NoDuplicateRoomsByLayout = true, - BlockingRoomsInAnyPalace = false, - HardBosses = false, - PalaceItemRoomCount = PalaceItemRoomCount.ONE, DarkLinkMinDistance = BossRoomMinDistance.SHORT, //Levels - AttackLevelCap = 8, - MagicLevelCap = 8, - LifeLevelCap = 8, AttackEffectiveness = AttackEffectiveness.HIGH, MagicEffectiveness = MagicEffectiveness.LOW_COST, LifeEffectiveness = LifeEffectiveness.HIGH, @@ -87,16 +56,10 @@ public static class BeginnerPreset //Spells ShuffleSpellLocations = true, DisableMagicContainerRequirements = true, - RandomizeSpellSpellEnemy = false, - SwapUpAndDownStab = false, - FireOption = FireOption.NORMAL, //Enemies ShuffleOverworldEnemies = true, ShufflePalaceEnemies = true, - DripperEnemyOption = DripperEnemyOption.ONLY_BOTS, - MixLargeAndSmallEnemies = false, - GeneratorsAlwaysMatch = true, EnemyXPDrops = XPEffectiveness.RANDOM_HIGH, @@ -105,16 +68,10 @@ public static class BeginnerPreset ShuffleOverworldItems = true, MixOverworldAndPalaceItems = true, IncludePBagCavesInItemShuffle = true, - IncludeSwordTechsInShuffle = false, - IncludeQuestItemsInShuffle = false, - IncludeSpellsInShuffle = false, ShuffleSmallItems = true, - RemoveSpellItems = false, - ShufflePBagAmounts = false, PalacesContainExtraKeys = true, RandomizeNewKasutoJarRequirements = true, - AllowImportantItemDuplicates = false, PreventSpellItemChains = false, //Drops diff --git a/CrossPlatformUI/Presets/FullShufflePreset.cs b/CrossPlatformUI/Presets/FullShufflePreset.cs index 9c20c5ff7..57825a380 100644 --- a/CrossPlatformUI/Presets/FullShufflePreset.cs +++ b/CrossPlatformUI/Presets/FullShufflePreset.cs @@ -9,14 +9,6 @@ public static class FullShufflePreset { //Start StartWithCandle = true, - ShuffleStartingItems = false, - ShuffleStartingSpells = false, - MaxHeartContainers = MaxHeartsOption.EIGHT, - StartingHeartContainersMin = 4, - StartingHeartContainersMax = 4, - StartingTechniques = StartingTechs.NONE, - StartingLives = StartingLives.Lives3, - IndeterminateOptionRate = IndeterminateOptionRate.HALF, //Overworld PalacesCanSwapContinents = true, @@ -28,8 +20,6 @@ public static class FullShufflePreset RiverDevilBlockerOption = RiverDevilBlockerOption.RANDOM, EastRocks = true, GenerateBaguWoods = false, - LessImportantLocationsOption = LessImportantLocationsOption.HIDE, - RestrictConnectionCaveShuffle = true, AllowConnectionCavesToBeBlocked = true, GoodBoots = true, HidePalace = null, @@ -39,35 +29,26 @@ public static class FullShufflePreset EastBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, DmBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, MazeBiome = Biome.VANILLALIKE, - WestClimate = ClimateEnum.VANILLA_WEIGHTED_WEST, - EastClimate = ClimateEnum.VANILLA_WEIGHTED_EAST, - DmClimate = ClimateEnum.CLASSIC, + WestClimate = ClimateEnum.VANILLA_WEIGHTED, + EastClimate = ClimateEnum.VANILLA_WEIGHTED, ContinentConnectionType = ContinentConnectionType.ANYTHING_GOES, //Palaces NormalPalaceStyle = PalaceStyle.RANDOM_PER_PALACE, GpStyle = PalaceStyle.RANDOM_WALK, - NormalPalaceLength = PalaceLengthOption.FULL, GpLength = PalaceLengthOption.SHORT, - IncludeVanillaRooms = true, Includev4_0Rooms = true, Includev5_0Rooms = true, IncludeExpertRooms = true, - TBirdRequired = true, - RemoveTBird = false, - PalacesToCompleteMin = 6, - PalacesToCompleteMax = 6, RestartAtPalacesOnGameOver = true, Global5050JarDrop = true, ReduceDripperVariance = true, ChangePalacePallettes = true, RandomizeBossItemDrop = true, PalaceDropStyle = PalaceDropStyle.RANDOM, - BossRoomsExitType = BossRoomsExitType.OVERWORLD, NoDuplicateRoomsByLayout = true, BlockingRoomsInAnyPalace = true, HardBosses = true, - PalaceItemRoomCount = PalaceItemRoomCount.ONE, DarkLinkMinDistance = BossRoomMinDistance.SHORT, //Levels @@ -75,9 +56,6 @@ public static class FullShufflePreset ShuffleMagicExperience = true, ShuffleLifeExperience = true, - AttackLevelCap = 8, - MagicLevelCap = 8, - LifeLevelCap = 8, AttackEffectiveness = AttackEffectiveness.AVERAGE, MagicEffectiveness = MagicEffectiveness.AVERAGE, LifeEffectiveness = LifeEffectiveness.AVERAGE, @@ -87,7 +65,6 @@ public static class FullShufflePreset ShuffleSpellLocations = true, DisableMagicContainerRequirements = true, RandomizeSpellSpellEnemy = true, - SwapUpAndDownStab = false, FireOption = FireOption.PAIR_WITH_RANDOM, //Enemies @@ -95,13 +72,12 @@ public static class FullShufflePreset ShufflePalaceEnemies = true, DripperEnemyOption = DripperEnemyOption.ANY_GROUND_ENEMY, MixLargeAndSmallEnemies = true, - GeneratorsAlwaysMatch = true, ShuffleEnemyHP = EnemyLifeOption.MEDIUM, ShuffleBossHP = EnemyLifeOption.MEDIUM, ShuffleXPStealers = true, ShuffleXPStolenAmount = true, - ShuffleSwordImmunity = true, + SwordImmunityOption = SwordImmunityOption.SHUFFLE, EnemyXPDrops = XPEffectiveness.RANDOM, //Items @@ -114,11 +90,7 @@ public static class FullShufflePreset IncludeSpellsInShuffle = true, ShuffleSmallItems = true, - RemoveSpellItems = false, - ShufflePBagAmounts = false, - PalacesContainExtraKeys = false, RandomizeNewKasutoJarRequirements = true, - AllowImportantItemDuplicates = false, PreventSpellItemChains = false, //Drops @@ -131,6 +103,5 @@ public static class FullShufflePreset EnableSpellItemHints = true, EnableTownNameHints = true, - RevealWalkthroughWalls = false, }; } diff --git a/CrossPlatformUI/Presets/MaxRando2025Preset.cs b/CrossPlatformUI/Presets/MaxRando2025Preset.cs index 4f6a7f105..eb67c09f0 100644 --- a/CrossPlatformUI/Presets/MaxRando2025Preset.cs +++ b/CrossPlatformUI/Presets/MaxRando2025Preset.cs @@ -14,7 +14,6 @@ public static class MaxRando2025Preset StartingHeartContainersMax = 8, StartingTechniques = StartingTechs.RANDOM, StartingLives = StartingLives.LivesRandom, - IndeterminateOptionRate = IndeterminateOptionRate.HALF, //Overworld PalacesCanSwapContinents = true, @@ -23,11 +22,6 @@ public static class MaxRando2025Preset AllowUnsafePathEncounters = true, IncludeLavaInEncounterShuffle = true, EncounterRate = EncounterRate.HALF, - RiverDevilBlockerOption = RiverDevilBlockerOption.PATH, - EastRocks = false, - GenerateBaguWoods = true, - LessImportantLocationsOption = LessImportantLocationsOption.HIDE, - RestrictConnectionCaveShuffle = true, AllowConnectionCavesToBeBlocked = true, GoodBoots = true, HidePalace = null, @@ -45,39 +39,25 @@ public static class MaxRando2025Preset //Palaces NormalPalaceStyle = PalaceStyle.RECONSTRUCTED, GpStyle = PalaceStyle.RECONSTRUCTED, - RandomStylesAllowVanilla = false, - NormalPalaceLength = PalaceLengthOption.FULL, GpLength = PalaceLengthOption.RANDOM, - IncludeVanillaRooms = true, Includev4_0Rooms = true, - Includev5_0Rooms = false, IncludeExpertRooms = true, TBirdRequired = false, - RemoveTBird = false, PalacesToCompleteMin = 0, - PalacesToCompleteMax = 6, RestartAtPalacesOnGameOver = true, - Global5050JarDrop = false, - ReduceDripperVariance = false, ChangePalacePallettes = true, RandomizeBossItemDrop = true, PalaceDropStyle = PalaceDropStyle.ANY_EXIT, BossRoomsExitType = BossRoomsExitType.PALACE, - NoDuplicateRoomsByLayout = false, NoDuplicateRoomsByEnemies = true, BlockingRoomsInAnyPalace = true, HardBosses = true, - PalaceItemRoomCount = PalaceItemRoomCount.ONE, - DarkLinkMinDistance = BossRoomMinDistance.NONE, //Levels ShuffleAttackExperience = true, ShuffleMagicExperience = true, ShuffleLifeExperience = true, - AttackLevelCap = 8, - MagicLevelCap = 8, - LifeLevelCap = 8, AttackEffectiveness = AttackEffectiveness.AVERAGE, MagicEffectiveness = MagicEffectiveness.AVERAGE, LifeEffectiveness = LifeEffectiveness.AVERAGE, @@ -87,7 +67,6 @@ public static class MaxRando2025Preset ShuffleSpellLocations = true, DisableMagicContainerRequirements = true, RandomizeSpellSpellEnemy = true, - SwapUpAndDownStab = false, FireOption = FireOption.RANDOM, //Enemies @@ -95,13 +74,12 @@ public static class MaxRando2025Preset ShufflePalaceEnemies = true, DripperEnemyOption = DripperEnemyOption.ANY_GROUND_ENEMY, MixLargeAndSmallEnemies = true, - GeneratorsAlwaysMatch = true, ShuffleEnemyHP = EnemyLifeOption.MEDIUM, ShuffleBossHP = EnemyLifeOption.MEDIUM, ShuffleXPStealers = true, ShuffleXPStolenAmount = true, - ShuffleSwordImmunity = true, + SwordImmunityOption = SwordImmunityOption.SHUFFLE, EnemyXPDrops = XPEffectiveness.RANDOM, //Items @@ -114,11 +92,8 @@ public static class MaxRando2025Preset IncludeSpellsInShuffle = true, ShuffleSmallItems = true, - RemoveSpellItems = false, ShufflePBagAmounts = true, - PalacesContainExtraKeys = false, RandomizeNewKasutoJarRequirements = true, - AllowImportantItemDuplicates = false, PreventSpellItemChains = false, //Drops @@ -131,6 +106,5 @@ public static class MaxRando2025Preset EnableSpellItemHints = true, EnableTownNameHints = true, - RevealWalkthroughWalls = false, }; } diff --git a/CrossPlatformUI/Presets/MaxRandoPreset.cs b/CrossPlatformUI/Presets/MaxRandoPreset.cs index b807f7f1b..869b15b42 100644 --- a/CrossPlatformUI/Presets/MaxRandoPreset.cs +++ b/CrossPlatformUI/Presets/MaxRandoPreset.cs @@ -16,7 +16,6 @@ public static class MaxRandoPreset StartingMagicContainersMax = 8, StartingTechniques = StartingTechs.RANDOM, StartingLives = StartingLives.LivesRandom, - IndeterminateOptionRate = IndeterminateOptionRate.HALF, //Overworld PalacesCanSwapContinents = true, @@ -29,7 +28,6 @@ public static class MaxRandoPreset EastRocks = null, GenerateBaguWoods = null, LessImportantLocationsOption = LessImportantLocationsOption.RANDOM, - RestrictConnectionCaveShuffle = true, AllowConnectionCavesToBeBlocked = true, GoodBoots = true, HidePalace = null, @@ -47,17 +45,13 @@ public static class MaxRandoPreset //Palaces NormalPalaceStyle = PalaceStyle.RANDOM_PER_PALACE, GpStyle = PalaceStyle.RANDOM, - RandomStylesAllowVanilla = false, NormalPalaceLength = PalaceLengthOption.RANDOM, GpLength = PalaceLengthOption.RANDOM, - IncludeVanillaRooms = true, Includev4_0Rooms = true, Includev5_0Rooms = true, IncludeExpertRooms = true, TBirdRequired = false, - RemoveTBird = false, PalacesToCompleteMin = 0, - PalacesToCompleteMax = 6, RestartAtPalacesOnGameOver = true, Global5050JarDrop = true, ReduceDripperVariance = true, @@ -69,16 +63,12 @@ public static class MaxRandoPreset BlockingRoomsInAnyPalace = true, HardBosses = true, PalaceItemRoomCount = PalaceItemRoomCount.RANDOM_INCLUDE_ZERO, - DarkLinkMinDistance = BossRoomMinDistance.NONE, //Levels ShuffleAttackExperience = true, ShuffleMagicExperience = true, ShuffleLifeExperience = true, - AttackLevelCap = 8, - MagicLevelCap = 8, - LifeLevelCap = 8, AttackEffectiveness = AttackEffectiveness.AVERAGE, MagicEffectiveness = MagicEffectiveness.AVERAGE, LifeEffectiveness = LifeEffectiveness.AVERAGE, @@ -88,7 +78,6 @@ public static class MaxRandoPreset ShuffleSpellLocations = true, DisableMagicContainerRequirements = true, RandomizeSpellSpellEnemy = true, - SwapUpAndDownStab = false, FireOption = FireOption.RANDOM, //Enemies @@ -96,13 +85,12 @@ public static class MaxRandoPreset ShufflePalaceEnemies = true, DripperEnemyOption = DripperEnemyOption.ANY_GROUND_ENEMY, MixLargeAndSmallEnemies = true, - GeneratorsAlwaysMatch = true, ShuffleEnemyHP = EnemyLifeOption.MEDIUM, ShuffleBossHP = EnemyLifeOption.MEDIUM, ShuffleXPStealers = true, ShuffleXPStolenAmount = true, - ShuffleSwordImmunity = true, + SwordImmunityOption = SwordImmunityOption.SHUFFLE, EnemyXPDrops = XPEffectiveness.WIDE, //Items @@ -115,11 +103,8 @@ public static class MaxRandoPreset IncludeSpellsInShuffle = true, ShuffleSmallItems = true, - RemoveSpellItems = false, ShufflePBagAmounts = true, - PalacesContainExtraKeys = false, RandomizeNewKasutoJarRequirements = true, - AllowImportantItemDuplicates = false, PreventSpellItemChains = false, //Drops @@ -132,6 +117,5 @@ public static class MaxRandoPreset EnableSpellItemHints = true, EnableTownNameHints = true, - RevealWalkthroughWalls = false, }; } diff --git a/CrossPlatformUI/Presets/NormalPreset.cs b/CrossPlatformUI/Presets/NormalPreset.cs index da9c12433..8d0985e2f 100644 --- a/CrossPlatformUI/Presets/NormalPreset.cs +++ b/CrossPlatformUI/Presets/NormalPreset.cs @@ -9,40 +9,26 @@ public static class NormalPreset { //Start StartWithCandle = true, - ShuffleStartingItems = false, - ShuffleStartingSpells = false, - MaxHeartContainers = MaxHeartsOption.EIGHT, - StartingHeartContainersMin = 4, - StartingHeartContainersMax = 4, - StartingTechniques = StartingTechs.NONE, - StartingLives = StartingLives.Lives3, - IndeterminateOptionRate = IndeterminateOptionRate.HALF, //Overworld PalacesCanSwapContinents = true, ShuffleGP = true, - ShuffleEncounters = false, - AllowUnsafePathEncounters = false, - IncludeLavaInEncounterShuffle = false, EncounterRate = EncounterRate.HALF, RiverDevilBlockerOption = RiverDevilBlockerOption.RANDOM, EastRocks = true, GenerateBaguWoods = false, LessImportantLocationsOption = LessImportantLocationsOption.REMOVE, - RestrictConnectionCaveShuffle = true, AllowConnectionCavesToBeBlocked = true, GoodBoots = true, HidePalace = null, HideKasuto = null, - ShuffleWhichLocationIsHidden = false, DmSize = DmSizeOption.MEDIUM, WestBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, EastBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, MazeBiome = Biome.VANILLALIKE, DmBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, - WestClimate = ClimateEnum.VANILLA_WEIGHTED_WEST, - EastClimate = ClimateEnum.VANILLA_WEIGHTED_EAST, - DmClimate = ClimateEnum.CLASSIC, + WestClimate = ClimateEnum.VANILLA_WEIGHTED, + EastClimate = ClimateEnum.VANILLA_WEIGHTED, ContinentConnectionType = ContinentConnectionType.TRANSPORTATION_SHUFFLE, //Palaces @@ -50,25 +36,16 @@ public static class NormalPreset GpStyle = PalaceStyle.RANDOM_WALK, NormalPalaceLength = PalaceLengthOption.MEDIUM, GpLength = PalaceLengthOption.SHORT, - IncludeVanillaRooms = true, Includev4_0Rooms = true, Includev5_0Rooms = true, - IncludeExpertRooms = false, - TBirdRequired = true, - RemoveTBird = false, - PalacesToCompleteMin = 6, - PalacesToCompleteMax = 6, RestartAtPalacesOnGameOver = true, Global5050JarDrop = true, ReduceDripperVariance = true, ChangePalacePallettes = true, - RandomizeBossItemDrop = false, PalaceDropStyle = PalaceDropStyle.RANDOM, - BossRoomsExitType = BossRoomsExitType.OVERWORLD, NoDuplicateRoomsByLayout = true, BlockingRoomsInAnyPalace = true, HardBosses = true, - PalaceItemRoomCount = PalaceItemRoomCount.ONE, DarkLinkMinDistance = BossRoomMinDistance.SHORT, //Levels @@ -76,33 +53,24 @@ public static class NormalPreset ShuffleMagicExperience = true, ShuffleLifeExperience = true, - AttackLevelCap = 8, - MagicLevelCap = 8, - LifeLevelCap = 8, AttackEffectiveness = AttackEffectiveness.AVERAGE, MagicEffectiveness = MagicEffectiveness.AVERAGE, LifeEffectiveness = LifeEffectiveness.AVERAGE, //Spells - ShuffleLifeRefillAmount = false, ShuffleSpellLocations = true, DisableMagicContainerRequirements = true, - RandomizeSpellSpellEnemy = false, - SwapUpAndDownStab = false, - FireOption = FireOption.NORMAL, //Enemies ShuffleOverworldEnemies = true, ShufflePalaceEnemies = true, DripperEnemyOption = DripperEnemyOption.EASIER_GROUND_ENEMIES, - MixLargeAndSmallEnemies = false, - GeneratorsAlwaysMatch = true, ShuffleEnemyHP = EnemyLifeOption.MEDIUM, ShuffleBossHP = EnemyLifeOption.MEDIUM, ShuffleXPStealers = true, ShuffleXPStolenAmount = true, - ShuffleSwordImmunity = true, + SwordImmunityOption = SwordImmunityOption.SHUFFLE, EnemyXPDrops = XPEffectiveness.RANDOM, //Items @@ -110,16 +78,10 @@ public static class NormalPreset ShuffleOverworldItems = true, MixOverworldAndPalaceItems = true, IncludePBagCavesInItemShuffle = true, - IncludeSwordTechsInShuffle = false, IncludeQuestItemsInShuffle = true, - IncludeSpellsInShuffle = false, ShuffleSmallItems = true, - RemoveSpellItems = false, - ShufflePBagAmounts = false, - PalacesContainExtraKeys = false, RandomizeNewKasutoJarRequirements = true, - AllowImportantItemDuplicates = false, PreventSpellItemChains = false, //Drops @@ -132,6 +94,5 @@ public static class NormalPreset EnableSpellItemHints = true, EnableTownNameHints = true, - RevealWalkthroughWalls = false, }; } diff --git a/CrossPlatformUI/Presets/RandomPercentPreset.cs b/CrossPlatformUI/Presets/RandomPercentPreset.cs index c42abdacc..5984237c0 100644 --- a/CrossPlatformUI/Presets/RandomPercentPreset.cs +++ b/CrossPlatformUI/Presets/RandomPercentPreset.cs @@ -15,14 +15,11 @@ public static class RandomPercentPreset StartingHeartContainersMax = 8, StartingTechniques = StartingTechs.RANDOM, StartingLives = StartingLives.LivesRandom, - IndeterminateOptionRate = IndeterminateOptionRate.HALF, //Overworld PalacesCanSwapContinents = null, ShuffleGP = null, ShuffleEncounters = null, - AllowUnsafePathEncounters = false, - IncludeLavaInEncounterShuffle = false, EncounterRate = EncounterRate.HALF, RiverDevilBlockerOption = RiverDevilBlockerOption.RANDOM, EastRocks = null, @@ -53,30 +50,21 @@ public static class RandomPercentPreset Includev4_0Rooms = null, Includev5_0Rooms = null, TBirdRequired = null, - RemoveTBird = false, PalacesToCompleteMin = 0, - PalacesToCompleteMax = 6, RestartAtPalacesOnGameOver = true, Global5050JarDrop = null, - ReduceDripperVariance = false, ChangePalacePallettes = true, RandomizeBossItemDrop = true, PalaceDropStyle = PalaceDropStyle.ANYTHING_GOES, BossRoomsExitType = BossRoomsExitType.RANDOM_PER_PALACE, NoDuplicateRoomsByLayout = true, BlockingRoomsInAnyPalace = true, - HardBosses = false, - PalaceItemRoomCount = PalaceItemRoomCount.ONE, - DarkLinkMinDistance = BossRoomMinDistance.NONE, //Levels ShuffleAttackExperience = true, ShuffleMagicExperience = true, ShuffleLifeExperience = true, - AttackLevelCap = 8, - MagicLevelCap = 8, - LifeLevelCap = 8, AttackEffectiveness = AttackEffectiveness.AVERAGE, MagicEffectiveness = MagicEffectiveness.AVERAGE, LifeEffectiveness = LifeEffectiveness.AVERAGE, @@ -100,7 +88,7 @@ public static class RandomPercentPreset ShuffleBossHP = EnemyLifeOption.MEDIUM, ShuffleXPStealers = true, ShuffleXPStolenAmount = true, - ShuffleSwordImmunity = true, + SwordImmunityOption = SwordImmunityOption.SHUFFLE, EnemyXPDrops = XPEffectiveness.RANDOM, //Items @@ -117,7 +105,6 @@ public static class RandomPercentPreset ShufflePBagAmounts = null, PalacesContainExtraKeys = null, RandomizeNewKasutoJarRequirements = true, - AllowImportantItemDuplicates = false, PreventSpellItemChains = false, //Drops @@ -130,6 +117,5 @@ public static class RandomPercentPreset EnableSpellItemHints = null, EnableTownNameHints = null, - RevealWalkthroughWalls = false, }; } diff --git a/CrossPlatformUI/Presets/Sgl2025Preset.cs b/CrossPlatformUI/Presets/Sgl2025Preset.cs index 2bb6ea103..09ed259aa 100644 --- a/CrossPlatformUI/Presets/Sgl2025Preset.cs +++ b/CrossPlatformUI/Presets/Sgl2025Preset.cs @@ -9,40 +9,23 @@ public static class Sgl2025Preset //Start StartWithCandle = true, StartWithCross = true, - ShuffleStartingItems = false, StartWithFire = true, - ShuffleStartingSpells = false, MaxHeartContainers = MaxHeartsOption.SIX, StartingHeartContainersMin = 3, StartingHeartContainersMax = 3, - StartingTechniques = StartingTechs.NONE, - StartingLives = StartingLives.Lives3, - IndeterminateOptionRate = IndeterminateOptionRate.HALF, //Overworld PalacesCanSwapContinents = true, ShuffleGP = true, - ShuffleEncounters = false, - AllowUnsafePathEncounters = false, - IncludeLavaInEncounterShuffle = false, EncounterRate = EncounterRate.HALF, - RiverDevilBlockerOption = RiverDevilBlockerOption.PATH, - EastRocks = false, - GenerateBaguWoods = true, - LessImportantLocationsOption = LessImportantLocationsOption.HIDE, - RestrictConnectionCaveShuffle = true, AllowConnectionCavesToBeBlocked = true, GoodBoots = true, HidePalace = null, HideKasuto = null, - ShuffleWhichLocationIsHidden = false, WestBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, EastBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, MazeBiome = Biome.VANILLALIKE, DmBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, - WestClimate = ClimateEnum.CLASSIC, - EastClimate = ClimateEnum.CLASSIC, - DmClimate = ClimateEnum.CLASSIC, ContinentConnectionType = ContinentConnectionType.TRANSPORTATION_SHUFFLE, //Palaces @@ -50,27 +33,15 @@ public static class Sgl2025Preset GpStyle = PalaceStyle.RECONSTRUCTED, NormalPalaceLength = PalaceLengthOption.SHORT, GpLength = PalaceLengthOption.MEDIUM, - IncludeVanillaRooms = true, Includev4_0Rooms = true, - Includev5_0Rooms = false, IncludeExpertRooms = true, - TBirdRequired = true, - PalacesToCompleteMin = 6, - PalacesToCompleteMax = 6, RestartAtPalacesOnGameOver = true, - Global5050JarDrop = false, - ReduceDripperVariance = false, ChangePalacePallettes = true, - RandomizeBossItemDrop = false, PalaceDropStyle = PalaceDropStyle.ANY_EXIT, - BossRoomsExitType = BossRoomsExitType.OVERWORLD, - NoDuplicateRoomsByLayout = false, NoDuplicateRoomsByEnemies = true, BlockingRoomsInAnyPalace = true, HardBosses = true, AggressiveTbird = true, - PalaceItemRoomCount = PalaceItemRoomCount.ONE, - DarkLinkMinDistance = BossRoomMinDistance.NONE, //Levels ShuffleAttackExperience = true, @@ -78,55 +49,33 @@ public static class Sgl2025Preset ShuffleLifeExperience = true, AttackLevelCap = 6, - MagicLevelCap = 8, - LifeLevelCap = 8, AttackEffectiveness = AttackEffectiveness.AVERAGE_LOW, // was AttackValues = [2, 3, 4, 6, 8, 12, 14, 16]; MagicEffectiveness = MagicEffectiveness.AVERAGE, - LifeEffectiveness = LifeEffectiveness.VANILLA, //Spells - ShuffleLifeRefillAmount = false, ShuffleSpellLocations = true, DisableMagicContainerRequirements = true, - RandomizeSpellSpellEnemy = false, SwapUpAndDownStab = true, - FireOption = FireOption.NORMAL, // +"Expensive Thunder" //Enemies ShuffleOverworldEnemies = true, ShufflePalaceEnemies = true, - DripperEnemyOption = DripperEnemyOption.ONLY_BOTS, MixLargeAndSmallEnemies = true, - GeneratorsAlwaysMatch = true, ShuffleEnemyHP = EnemyLifeOption.MEDIUM, // was 100-150% - ShuffleBossHP = EnemyLifeOption.VANILLA, ShuffleXPStealers = true, ShuffleXPStolenAmount = true, - ShuffleSwordImmunity = false, EnemyXPDrops = XPEffectiveness.SLIGHTLY_HIGH, // was +0 to +2 //Items ShufflePalaceItems = true, ShuffleOverworldItems = true, MixOverworldAndPalaceItems = true, - IncludePBagCavesInItemShuffle = false, - IncludeSwordTechsInShuffle = false, - IncludeQuestItemsInShuffle = false, - IncludeSpellsInShuffle = false, - ShuffleSmallItems = false, - RemoveSpellItems = false, - ShufflePBagAmounts = false, - PalacesContainExtraKeys = false, - RandomizeNewKasutoJarRequirements = false, - AllowImportantItemDuplicates = false, PreventSpellItemChains = false, //Drops - ShuffleItemDropFrequency = false, - RandomizeDrops = false, StandardizeDrops = true, SmallEnemiesCanDropBlueJar = true, LargeEnemiesCanDropRedJar = true, @@ -137,6 +86,5 @@ public static class Sgl2025Preset EnableSpellItemHints = true, EnableTownNameHints = true, - RevealWalkthroughWalls = false, }; } diff --git a/CrossPlatformUI/Presets/StandardPreset.cs b/CrossPlatformUI/Presets/StandardPreset.cs index 4a28d6f8a..2827c986c 100644 --- a/CrossPlatformUI/Presets/StandardPreset.cs +++ b/CrossPlatformUI/Presets/StandardPreset.cs @@ -8,15 +8,6 @@ public static class StandardPreset public static readonly RandomizerConfiguration Preset = new() { //Start - StartWithCandle = false, - ShuffleStartingItems = false, - ShuffleStartingSpells = false, - MaxHeartContainers = MaxHeartsOption.EIGHT, - StartingHeartContainersMin = 4, - StartingHeartContainersMax = 4, - StartingTechniques = StartingTechs.NONE, - StartingLives = StartingLives.Lives3, - IndeterminateOptionRate = IndeterminateOptionRate.HALF, //Overworld PalacesCanSwapContinents = true, @@ -28,8 +19,6 @@ public static class StandardPreset RiverDevilBlockerOption = RiverDevilBlockerOption.RANDOM, EastRocks = true, GenerateBaguWoods = false, - LessImportantLocationsOption = LessImportantLocationsOption.HIDE, - RestrictConnectionCaveShuffle = true, AllowConnectionCavesToBeBlocked = true, GoodBoots = true, HidePalace = null, @@ -39,31 +28,21 @@ public static class StandardPreset EastBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, MazeBiome = Biome.VANILLALIKE, DmBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, - WestClimate = ClimateEnum.CLASSIC, - EastClimate = ClimateEnum.CLASSIC, - DmClimate = ClimateEnum.CLASSIC, ContinentConnectionType = ContinentConnectionType.ANYTHING_GOES, //Palaces NormalPalaceStyle = PalaceStyle.RANDOM_WALK, GpStyle = PalaceStyle.RANDOM_WALK, - NormalPalaceLength = PalaceLengthOption.FULL, GpLength = PalaceLengthOption.SHORT, - IncludeVanillaRooms = true, Includev4_0Rooms = true, Includev5_0Rooms = true, IncludeExpertRooms = true, - TBirdRequired = true, - RemoveTBird = false, - PalacesToCompleteMin = 6, - PalacesToCompleteMax = 6, RestartAtPalacesOnGameOver = true, Global5050JarDrop = true, ReduceDripperVariance = true, ChangePalacePallettes = true, RandomizeBossItemDrop = true, PalaceDropStyle = PalaceDropStyle.ANY_EXIT, - BossRoomsExitType = BossRoomsExitType.OVERWORLD, NoDuplicateRoomsByLayout = true, BlockingRoomsInAnyPalace = true, HardBosses = true, @@ -75,9 +54,6 @@ public static class StandardPreset ShuffleMagicExperience = true, ShuffleLifeExperience = true, - AttackLevelCap = 8, - MagicLevelCap = 8, - LifeLevelCap = 8, AttackEffectiveness = AttackEffectiveness.AVERAGE, MagicEffectiveness = MagicEffectiveness.AVERAGE, LifeEffectiveness = LifeEffectiveness.AVERAGE, @@ -87,21 +63,18 @@ public static class StandardPreset ShuffleSpellLocations = true, DisableMagicContainerRequirements = true, RandomizeSpellSpellEnemy = true, - SwapUpAndDownStab = false, FireOption = FireOption.PAIR_WITH_RANDOM, //Enemies ShuffleOverworldEnemies = true, ShufflePalaceEnemies = true, - DripperEnemyOption = DripperEnemyOption.ONLY_BOTS, MixLargeAndSmallEnemies = true, - GeneratorsAlwaysMatch = true, ShuffleEnemyHP = EnemyLifeOption.MEDIUM, ShuffleBossHP = EnemyLifeOption.MEDIUM, ShuffleXPStealers = true, ShuffleXPStolenAmount = true, - ShuffleSwordImmunity = true, + SwordImmunityOption = SwordImmunityOption.SHUFFLE, EnemyXPDrops = XPEffectiveness.RANDOM, //Items @@ -109,16 +82,11 @@ public static class StandardPreset ShuffleOverworldItems = true, MixOverworldAndPalaceItems = true, IncludePBagCavesInItemShuffle = true, - IncludeSwordTechsInShuffle = false, IncludeQuestItemsInShuffle = true, IncludeSpellsInShuffle = true, ShuffleSmallItems = true, - RemoveSpellItems = false, - ShufflePBagAmounts = false, - PalacesContainExtraKeys = false, RandomizeNewKasutoJarRequirements = true, - AllowImportantItemDuplicates = false, PreventSpellItemChains = false, //Drops @@ -131,7 +99,5 @@ public static class StandardPreset EnableSpellItemHints = true, EnableTownNameHints = true, - GenerateSpoiler = false, - RevealWalkthroughWalls = false, }; } diff --git a/CrossPlatformUI/Presets/StandardSwissPreset.cs b/CrossPlatformUI/Presets/StandardSwissPreset.cs index 9fbe0138f..eeb8d9a59 100644 --- a/CrossPlatformUI/Presets/StandardSwissPreset.cs +++ b/CrossPlatformUI/Presets/StandardSwissPreset.cs @@ -9,27 +9,16 @@ public static class StandardSwissPreset { //Start StartWithCandle = true, - ShuffleStartingItems = false, - ShuffleStartingSpells = false, - MaxHeartContainers = MaxHeartsOption.EIGHT, - StartingHeartContainersMin = 4, - StartingHeartContainersMax = 4, - StartingTechniques = StartingTechs.NONE, - StartingLives = StartingLives.Lives3, - IndeterminateOptionRate = IndeterminateOptionRate.HALF, //Overworld PalacesCanSwapContinents = true, ShuffleGP = true, ShuffleEncounters = true, AllowUnsafePathEncounters = true, - IncludeLavaInEncounterShuffle = false, EncounterRate = EncounterRate.HALF, RiverDevilBlockerOption = RiverDevilBlockerOption.RANDOM, EastRocks = true, GenerateBaguWoods = false, - LessImportantLocationsOption = LessImportantLocationsOption.HIDE, - RestrictConnectionCaveShuffle = true, AllowConnectionCavesToBeBlocked = true, GoodBoots = true, HidePalace = null, @@ -39,31 +28,20 @@ public static class StandardSwissPreset EastBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, MazeBiome = Biome.VANILLALIKE, DmBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, - WestClimate = ClimateEnum.CLASSIC, - EastClimate = ClimateEnum.CLASSIC, - DmClimate = ClimateEnum.CLASSIC, ContinentConnectionType = ContinentConnectionType.ANYTHING_GOES, //Palaces NormalPalaceStyle = PalaceStyle.RANDOM_WALK, GpStyle = PalaceStyle.RANDOM_WALK, - NormalPalaceLength = PalaceLengthOption.FULL, GpLength = PalaceLengthOption.SHORT, - IncludeVanillaRooms = true, Includev4_0Rooms = true, Includev5_0Rooms = true, IncludeExpertRooms = true, - TBirdRequired = true, - RemoveTBird = false, - PalacesToCompleteMin = 6, - PalacesToCompleteMax = 6, RestartAtPalacesOnGameOver = true, Global5050JarDrop = true, ReduceDripperVariance = true, ChangePalacePallettes = true, - RandomizeBossItemDrop = false, PalaceDropStyle = PalaceDropStyle.ANY_EXIT, - BossRoomsExitType = BossRoomsExitType.OVERWORLD, NoDuplicateRoomsByLayout = true, BlockingRoomsInAnyPalace = true, HardBosses = true, @@ -75,19 +53,14 @@ public static class StandardSwissPreset ShuffleMagicExperience = true, ShuffleLifeExperience = true, - AttackLevelCap = 8, - MagicLevelCap = 8, - LifeLevelCap = 8, AttackEffectiveness = AttackEffectiveness.AVERAGE, MagicEffectiveness = MagicEffectiveness.AVERAGE, LifeEffectiveness = LifeEffectiveness.AVERAGE, //Spells - ShuffleLifeRefillAmount = false, ShuffleSpellLocations = true, DisableMagicContainerRequirements = true, RandomizeSpellSpellEnemy = true, - SwapUpAndDownStab = false, FireOption = FireOption.PAIR_WITH_RANDOM, //Enemies @@ -95,13 +68,12 @@ public static class StandardSwissPreset ShufflePalaceEnemies = true, DripperEnemyOption = DripperEnemyOption.ANY_GROUND_ENEMY, MixLargeAndSmallEnemies = true, - GeneratorsAlwaysMatch = true, ShuffleEnemyHP = EnemyLifeOption.MEDIUM, ShuffleBossHP = EnemyLifeOption.MEDIUM, ShuffleXPStealers = true, ShuffleXPStolenAmount = true, - ShuffleSwordImmunity = true, + SwordImmunityOption = SwordImmunityOption.SHUFFLE, EnemyXPDrops = XPEffectiveness.RANDOM, //Items @@ -109,16 +81,11 @@ public static class StandardSwissPreset ShuffleOverworldItems = true, MixOverworldAndPalaceItems = true, IncludePBagCavesInItemShuffle = true, - IncludeSwordTechsInShuffle = false, IncludeQuestItemsInShuffle = true, IncludeSpellsInShuffle = true, ShuffleSmallItems = true, - RemoveSpellItems = false, - ShufflePBagAmounts = false, - PalacesContainExtraKeys = false, RandomizeNewKasutoJarRequirements = true, - AllowImportantItemDuplicates = false, PreventSpellItemChains = false, //Drops @@ -131,7 +98,6 @@ public static class StandardSwissPreset EnableSpellItemHints = true, EnableTownNameHints = true, - GenerateSpoiler = false, RevealWalkthroughWalls = true, }; } diff --git a/CrossPlatformUI/Presets/UpstartsTournamentPreset.cs b/CrossPlatformUI/Presets/UpstartsTournamentPreset.cs index 9ea3f0899..ab6b1d85f 100644 --- a/CrossPlatformUI/Presets/UpstartsTournamentPreset.cs +++ b/CrossPlatformUI/Presets/UpstartsTournamentPreset.cs @@ -9,14 +9,6 @@ public static class UpstartsTournamentPreset { //Start StartWithCandle = true, - ShuffleStartingItems = false, - ShuffleStartingSpells = false, - MaxHeartContainers = MaxHeartsOption.EIGHT, - StartingHeartContainersMin = 4, - StartingHeartContainersMax = 4, - StartingTechniques = StartingTechs.NONE, - StartingLives = StartingLives.Lives3, - IndeterminateOptionRate = IndeterminateOptionRate.HALF, //Overworld PalacesCanSwapContinents = true, @@ -25,10 +17,6 @@ public static class UpstartsTournamentPreset AllowUnsafePathEncounters = true, IncludeLavaInEncounterShuffle = true, EncounterRate = EncounterRate.HALF, - EastRocks = false, - GenerateBaguWoods = true, - LessImportantLocationsOption = LessImportantLocationsOption.HIDE, - RestrictConnectionCaveShuffle = true, AllowConnectionCavesToBeBlocked = true, GoodBoots = true, HidePalace = null, @@ -39,39 +27,24 @@ public static class UpstartsTournamentPreset EastBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, MazeBiome = Biome.VANILLALIKE, DmBiome = Biome.RANDOM_NO_VANILLA_OR_SHUFFLE, - WestClimate = ClimateEnum.CLASSIC, - EastClimate = ClimateEnum.CLASSIC, - DmClimate = ClimateEnum.CLASSIC, //Palaces NormalPalaceStyle = PalaceStyle.RECONSTRUCTED, GpStyle = PalaceStyle.RECONSTRUCTED, - NormalPalaceLength = PalaceLengthOption.FULL, GpLength = PalaceLengthOption.SHORT, - IncludeVanillaRooms = true, Includev4_0Rooms = true, - Includev5_0Rooms = false, IncludeExpertRooms = true, - TBirdRequired = true, - PalacesToCompleteMin = 6, - PalacesToCompleteMax = 6, RestartAtPalacesOnGameOver = true, ChangePalacePallettes = true, PalaceDropStyle = PalaceDropStyle.ANY_EXIT, - BossRoomsExitType = BossRoomsExitType.OVERWORLD, - NoDuplicateRoomsByLayout = false, NoDuplicateRoomsByEnemies = true, BlockingRoomsInAnyPalace = true, HardBosses = true, - PalaceItemRoomCount = PalaceItemRoomCount.ONE, //Levels ShuffleAttackExperience = true, ShuffleMagicExperience = true, ShuffleLifeExperience = true, - AttackLevelCap = 8, - MagicLevelCap = 8, - LifeLevelCap = 8, AttackEffectiveness = AttackEffectiveness.AVERAGE, MagicEffectiveness = MagicEffectiveness.AVERAGE, LifeEffectiveness = LifeEffectiveness.AVERAGE, @@ -80,8 +53,6 @@ public static class UpstartsTournamentPreset ShuffleLifeRefillAmount = true, ShuffleSpellLocations = true, DisableMagicContainerRequirements = true, - RandomizeSpellSpellEnemy = false, - SwapUpAndDownStab = false, FireOption = FireOption.PAIR_WITH_RANDOM, //Enemies @@ -89,13 +60,12 @@ public static class UpstartsTournamentPreset ShufflePalaceEnemies = true, DripperEnemyOption = DripperEnemyOption.ANY_GROUND_ENEMY, MixLargeAndSmallEnemies = true, - GeneratorsAlwaysMatch = true, ShuffleEnemyHP = EnemyLifeOption.MEDIUM, ShuffleBossHP = EnemyLifeOption.MEDIUM, ShuffleXPStealers = true, ShuffleXPStolenAmount = true, - ShuffleSwordImmunity = true, + SwordImmunityOption = SwordImmunityOption.SHUFFLE, EnemyXPDrops = XPEffectiveness.RANDOM, //Items @@ -103,16 +73,10 @@ public static class UpstartsTournamentPreset ShuffleOverworldItems = true, MixOverworldAndPalaceItems = true, IncludePBagCavesInItemShuffle = true, - IncludeSwordTechsInShuffle = false, - IncludeQuestItemsInShuffle = false, - IncludeSpellsInShuffle = false, ShuffleSmallItems = true, - RemoveSpellItems = false, ShufflePBagAmounts = true, - PalacesContainExtraKeys = false, RandomizeNewKasutoJarRequirements = true, - AllowImportantItemDuplicates = false, PreventSpellItemChains = false, //Drops @@ -125,6 +89,5 @@ public static class UpstartsTournamentPreset EnableSpellItemHints = true, EnableTownNameHints = true, - RevealWalkthroughWalls = false, }; } diff --git a/CrossPlatformUI/Presets/VanillaPreset.cs b/CrossPlatformUI/Presets/VanillaPreset.cs new file mode 100644 index 000000000..9755612b0 --- /dev/null +++ b/CrossPlatformUI/Presets/VanillaPreset.cs @@ -0,0 +1,11 @@ +using Z2Randomizer.RandomizerCore; + +namespace CrossPlatformUI.Presets; + +/// +/// Uses all the default values, which should always be vanilla. +/// +public static class VanillaPreset +{ + public static readonly RandomizerConfiguration Preset = new(); +} diff --git a/CrossPlatformUI/Services/SpriteLoaderService.cs b/CrossPlatformUI/Services/SpriteLoaderService.cs index 582832a2c..e95e908c8 100644 --- a/CrossPlatformUI/Services/SpriteLoaderService.cs +++ b/CrossPlatformUI/Services/SpriteLoaderService.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reactive.Linq; using System.Threading.Tasks; +using ReactiveUI.Primitives; +using ReactiveUI.Primitives.Signals; using Z2Randomizer.RandomizerCore; namespace CrossPlatformUI.Services; @@ -16,7 +17,7 @@ public class SpriteLoaderService public SpriteLoaderService(IFileSystemService fileSystemService) { _fileService = fileSystemService; - Sprites = Observable.Defer(() => Observable.FromAsync(LoadSpritesAsync)).Replay(1).AutoConnect(); + Sprites = Signal.Defer(() => Signal.FromAsync(LoadSpritesAsync)).Replay(1).AutoConnect(); } private async Task> LoadSpritesAsync() diff --git a/CrossPlatformUI/ThemeHelper.cs b/CrossPlatformUI/ThemeHelper.cs index 316769508..fbb23aa69 100644 --- a/CrossPlatformUI/ThemeHelper.cs +++ b/CrossPlatformUI/ThemeHelper.cs @@ -1,4 +1,5 @@ -using Avalonia.Media; +using Avalonia.Controls; +using Avalonia.Media; using Avalonia.Styling; using Material.Styles.Themes; @@ -14,6 +15,9 @@ public class ThemeHelper private static IBrush DarkNonStandardFlagEnabledBackground = SolidColorBrush.Parse("#80b10600"); private static IBrush LightNonStandardFlagEnabledBackground = SolidColorBrush.Parse("#d2b30000"); + private static IBrush DarkInvalidFlagUnderline = SolidColorBrush.Parse("#b10600"); + private static IBrush LightInvalidFlagUnderline = SolidColorBrush.Parse("#b30000"); + static ThemeHelper() { DarkMaterialTheme = Theme.Create(Theme.Dark, Color.Parse("#ffbc33"), Color.Parse("#969696")); DarkMaterialTheme.Paper = Color.Parse("#1a1a1a"); @@ -63,4 +67,15 @@ public static IBrush GetFlagAlertBackgroundBrush(string? theme, bool alert) return Brushes.Transparent; } } + + public static IBrush GetFlagUnderlineBrush(string? theme, bool valid) + { + // Matches the brushes the Material TextBox template uses by default (underline) and on error. + var app = App.Current; + if (app != null && app.TryFindResource(valid ? "MaterialTextBoxBorderBrush" : "MaterialValidationErrorBrush", out var brush) && brush is IBrush b) + { + return b; + } + return valid ? Brushes.Gray : (IsDark(theme) ? DarkInvalidFlagUnderline : LightInvalidFlagUnderline); + } } diff --git a/CrossPlatformUI/ViewModels/GenerateRomViewModel.cs b/CrossPlatformUI/ViewModels/GenerateRomViewModel.cs index 8bf7dcf41..49ea5c2f4 100644 --- a/CrossPlatformUI/ViewModels/GenerateRomViewModel.cs +++ b/CrossPlatformUI/ViewModels/GenerateRomViewModel.cs @@ -1,9 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Subjects; using System.IO; using System.Reflection; using System.Text.Json.Serialization; @@ -13,7 +10,8 @@ using Avalonia.Input.Platform; using Avalonia.Threading; using ReactiveUI; -using ReactiveUI.Validation.Helpers; +using ReactiveUI.Primitives.Disposables; +using RxVoid = ReactiveUI.Primitives.RxVoid; using Z2Randomizer.RandomizerCore; using Z2Randomizer.RandomizerCore.Sidescroll; using CrossPlatformUI.Services; @@ -22,7 +20,7 @@ namespace CrossPlatformUI.ViewModels; [RequiresUnreferencedCode("")] -public class GenerateRomViewModel : ReactiveValidationObject, IRoutableViewModel, IActivatableViewModel +public class GenerateRomViewModel : ReactiveObject, IRoutableViewModel, IActivatableViewModel { #pragma warning disable CS8618 @@ -63,12 +61,12 @@ await clipboard.SetTextAsync($""" this.WhenActivated(Randomize); return; - async void Randomize(CompositeDisposable disposables) + async void Randomize(MultipleDisposable disposables) { if (!Main.GenerateRomDialogOpen) return; runningMutex.Wait(); - isRunning.OnNext(true); + IsRunning = true; lastError = null; HasError = false; @@ -177,7 +175,7 @@ async void GenerateSeed() { tokenSource.Dispose(); tokenSource = null; - isRunning.OnNext(false); + IsRunning = false; runningMutex.Release(); } } @@ -210,13 +208,13 @@ public string ProgressBody { } [JsonIgnore] - public ReactiveCommand CancelGeneration { get; } + public ReactiveCommand CancelGeneration { get; } [JsonIgnore] - public ReactiveCommand CopyError { get; } + public ReactiveCommand CopyError { get; } private readonly SemaphoreSlim runningMutex = new SemaphoreSlim(1, 1); - private readonly BehaviorSubject isRunning = new BehaviorSubject(false); - public IObservable IsRunning => isRunning; + private bool isRunning; + public bool IsRunning { get => isRunning; set => this.RaiseAndSetIfChanged(ref isRunning, value); } private CancellationTokenSource? tokenSource; diff --git a/CrossPlatformUI/ViewModels/MainViewModel.cs b/CrossPlatformUI/ViewModels/MainViewModel.cs index b2e3f446b..9a27b9df1 100644 --- a/CrossPlatformUI/ViewModels/MainViewModel.cs +++ b/CrossPlatformUI/ViewModels/MainViewModel.cs @@ -1,24 +1,24 @@ using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; using System.Text.Json.Serialization; using ReactiveUI; -using ReactiveUI.Validation.Helpers; +using ReactiveUI.Primitives; +using ReactiveUI.Primitives.Disposables; +using ReactiveUI.Primitives.Signals; +using RxVoid = ReactiveUI.Primitives.RxVoid; using Z2Randomizer.RandomizerCore; namespace CrossPlatformUI.ViewModels; [RequiresUnreferencedCode("")] -public class MainViewModel : ReactiveValidationObject, IScreen, IActivatableViewModel +public class MainViewModel : ReactiveObject, IScreen, IActivatableViewModel { public string? OutputFilePath { get; set; } private RandomizerConfiguration config = new(); /// Useful inexpensive shared observable for views to attach onto /// for chaining change detection logic - public IObservable FlagsChanged { get; } + public IObservable FlagsChanged { get; } public IObservable FlagsObservable { get; } @@ -40,12 +40,12 @@ public class MainViewModel : ReactiveValidationObject, IScreen, IActivatableView public MainViewModel() { - FlagsChanged = Observable.FromEventPattern( + FlagsChanged = Signal.FromEventPattern( h => Config.PropertyChanged += h, h => Config.PropertyChanged -= h) .Where(e => e.EventArgs.PropertyName == "Flags") - .Select(_ => Unit.Default) - .StartWith(Unit.Default) + .Select(_ => default(RxVoid)) + .StartWith(default(RxVoid)) .Replay(1) .RefCount(); @@ -59,17 +59,17 @@ public MainViewModel() GenerateRomViewModel = new(this); SaveNewPresetViewModel = new(this); RandomizerViewModel = new(this); - Router.Navigate.Execute(RandomizerViewModel); + SubscribeExtensions.Subscribe(Router.Navigate.Execute(RandomizerViewModel)); GenerateRom = ReactiveCommand.CreateFromObservable( () => Router.Navigate.Execute() ); - this.WhenActivated((CompositeDisposable disposables) => + this.WhenActivated((MultipleDisposable disposables) => { if (!RomFileViewModel.HasRomData) { - Router.Navigate.Execute(RomFileViewModel); + SubscribeExtensions.Subscribe(Router.Navigate.Execute(RomFileViewModel)); } }); } @@ -109,7 +109,7 @@ public bool ShuffleAllExpState // The command that navigates a user to first view model. [JsonIgnore] - public ReactiveCommand GenerateRom { get; } + public ReactiveCommand GenerateRom { get; } [JsonIgnore] public GenerateRomViewModel GenerateRomViewModel { get; } diff --git a/CrossPlatformUI/ViewModels/RandomizerViewModel.cs b/CrossPlatformUI/ViewModels/RandomizerViewModel.cs index 8cf82394a..ab345a561 100644 --- a/CrossPlatformUI/ViewModels/RandomizerViewModel.cs +++ b/CrossPlatformUI/ViewModels/RandomizerViewModel.cs @@ -1,18 +1,15 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Disposables.Fluent; -using System.Reactive.Linq; -using System.Reactive.Subjects; using System.Text.Json.Serialization; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Avalonia.Controls; +using Avalonia.Media; using ReactiveUI; -using ReactiveUI.Validation.Extensions; -using ReactiveUI.Validation.Helpers; +using ReactiveUI.Primitives; +using ReactiveUI.Primitives.Disposables; +using RxVoid = ReactiveUI.Primitives.RxVoid; using Z2Randomizer.RandomizerCore; using CrossPlatformUI.Presets; using CrossPlatformUI.Services; @@ -21,35 +18,21 @@ namespace CrossPlatformUI.ViewModels; [RequiresUnreferencedCode("ReactiveUI uses reflection")] -public class RandomizerViewModel : ReactiveValidationObject, IRoutableViewModel, IActivatableViewModel +public class RandomizerViewModel : ReactiveObject, IRoutableViewModel, IActivatableViewModel { [JsonIgnore] public IObservable CanGenerateObservable { get; private set; } [JsonIgnore] - public BehaviorSubject FlagsValidSubject = new(true); + public IObservable FlagInputUnderlineObservable { get; } - private static bool IsFlagStringValid(string flags) => FlagPasteParser.IsValidFlagString(flags); + private bool flagsValid = true; + private bool FlagsValid { get => flagsValid; set => this.RaiseAndSetIfChanged(ref flagsValid, value); } - private string flagInput = ""; + private static bool IsFlagStringValid(string flags) => FlagPasteParser.IsValidFlagString(flags); [JsonIgnore] - public string FlagInput - { - get => flagInput; - set - { - var trimmedValue = value?.Trim() ?? ""; - var (extractedFlags, extractedSeed) = FlagPasteParser.Parse(trimmedValue); - - if (Main is not null && !string.IsNullOrEmpty(extractedSeed)) - { - Main.Config.Seed = extractedSeed; - } - - this.RaiseAndSetIfChanged(ref flagInput, extractedFlags ?? trimmedValue); - } - } + public string FlagInput { get; set { field = value.Trim(); this.RaisePropertyChanged(); } } = ""; [JsonIgnore] public string Seed @@ -62,18 +45,17 @@ public string Seed } } - [JsonIgnore] - public BehaviorSubject ThemeVariantSubject = new(""); + private string themeVariantName = ""; public string ThemeVariantName { get { - return ThemeVariantSubject.Value; + return themeVariantName; } set { ThemeHelper.SetTheme(value); - ThemeVariantSubject.OnNext(value); + this.RaiseAndSetIfChanged(ref themeVariantName, value); } } private int currentTabIndex; @@ -95,6 +77,7 @@ public RandomizerViewModel(MainViewModel main) HostScreen = Main; BiomesViewModel = new(Main); PalacesViewModel = new(Main); + SpellsViewModel = new(Main); ItemsViewModel = new(Main); HintsViewModel = new(Main, this); CustomizeViewModel = new(Main); @@ -145,9 +128,16 @@ public RandomizerViewModel(MainViewModel main) var seedValidObservable = this.WhenAnyValue(x => x.Main.Config.Seed, seed => !string.IsNullOrWhiteSpace(seed)); - CanGenerateObservable = Observable.CombineLatest( - FlagsValidSubject, seedValidObservable, Main.RomFileViewModel.HasRomDataObservable, Main.GenerateRomViewModel.IsRunning, - (flagsValid, seedValid, hasRom, isRunning) => flagsValid && seedValid && hasRom && !isRunning); + CanGenerateObservable = this.WhenAnyValue(x => x.FlagsValid).CombineLatest( + seedValidObservable, + Main.RomFileViewModel.HasRomDataObservable, + Main.GenerateRomViewModel.WhenAnyValue(x => x.IsRunning), + (flagsValid, seedValid, hasRom, isRunning) => + flagsValid && seedValid && hasRom && !isRunning); + + FlagInputUnderlineObservable = this.WhenAnyValue(x => x.ThemeVariantName) + .CombineLatest(this.WhenAnyValue(x => x.FlagsValid), (theme, valid) => + ThemeHelper.GetFlagUnderlineBrush(theme, valid)); Generate = ReactiveCommand.Create(() => { @@ -214,7 +204,7 @@ public RandomizerViewModel(MainViewModel main) return localPath; } - private void OnActivate(CompositeDisposable disposables) + private void OnActivate(MultipleDisposable disposables) { var loadedFlags = Main.Config.SerializeFlags(); // this serializes the configuration var defaultFlags = new RandomizerConfiguration().SerializeFlags(); @@ -225,17 +215,35 @@ private void OnActivate(CompositeDisposable disposables) } // flag updates from RandomizerConfiguration always overwrites our flag input - Main.FlagsObservable - .Do(x => FlagsValidSubject.OnNext(true)) - .Subscribe(flags => FlagInput = flags) + SubscribeExtensions.Subscribe(Main.FlagsObservable, flags => + { + FlagsValid = true; + FlagInput = flags; + }) .DisposeWith(disposables); - this.WhenAnyValue(x => x.FlagInput) - .WithLatestFrom(Main.FlagsObservable, - (Input, Current) => (Input, Current, IsValid: Input == Current || IsFlagStringValid(Input))) - .Do(x => FlagsValidSubject.OnNext(x.IsValid)) - .Where(x => x.IsValid && x.Input != x.Current) - .Subscribe(x => Main.Config.DeserializeFlags(x.Input)) + SubscribeExtensions.Subscribe( + this.WhenAnyValue(viewModel => viewModel.FlagInput) + .WithLatestFrom(Main.FlagsObservable, (Input, Current) => (Input, Current)), + tuple => + { + var isNew = tuple.Input != tuple.Current; + if (isNew) + { + bool isValid = IsFlagStringValid(tuple.Input); + FlagsValid = isValid; + if (isValid) + { + Main.Config.DeserializeFlags(tuple.Input); + } + } + else if (!FlagsValid) + { + // The input matches the currently loaded flags (always valid), but the flag is + // stale after a prior invalid paste left it false. + FlagsValid = true; + } + }) .DisposeWith(disposables); Main.Config.PropertyChanged += (sender, args) => @@ -248,18 +256,21 @@ private void OnActivate(CompositeDisposable disposables) } }; - this.ValidationRule(x => x.FlagInput, FlagsValidSubject, "Invalid Flags"); + // ReactiveUI.Validation (latest 7.1.0) is not updated to ReactiveUI 24 and can't be used + // this.ValidationRule(viewModel => viewModel.FlagsValid, this.WhenAnyValue(x => x.FlagsValid), "Invalid Flags"); AddValidationRules(); } private void AddValidationRules() { - Main.Config.WhenAnyValue( - x => x.ShuffleOverworldEnemies, - x => x.ShufflePalaceEnemies, - (a,b) => (a ?? true) || (b ?? true) - ).Subscribe(_ => + SubscribeExtensions.Subscribe( + Main.Config.WhenAnyValue( + x => x.ShuffleOverworldEnemies, + x => x.ShufflePalaceEnemies, + (a,b) => (a ?? true) || (b ?? true) + ), + _ => { var overworldEnemyShuffle = Main.Config.ShuffleOverworldEnemies ?? true; var palaceEnemyShuffle = Main.Config.ShufflePalaceEnemies ?? true; @@ -275,12 +286,14 @@ private void AddValidationRules() }); // When PalaceItems and OverworldItems are off, then don't allow MixingOverworldAndPalaceItems - Main.Config.WhenAnyValue( - x => x.ShufflePalaceItems, - x => x.ShuffleOverworldItems, - (palaceItems, overworldItems) => - !(palaceItems ?? true) || !(overworldItems ?? true) - ).Subscribe(_ => + SubscribeExtensions.Subscribe( + Main.Config.WhenAnyValue( + x => x.ShufflePalaceItems, + x => x.ShuffleOverworldItems, + (palaceItems, overworldItems) => + !(palaceItems ?? true) || !(overworldItems ?? true) + ), + _ => { if(!(Main.Config.ShufflePalaceItems ?? true) || !(Main.Config.ShufflePalaceItems ?? true)) { @@ -289,16 +302,14 @@ private void AddValidationRules() }); // If shuffle overworld items is off, turn off pbag cave item shuffle too - Main.Config.ObservableForProperty(x => x.ShuffleOverworldItems) - .Subscribe(x => + SubscribeExtensions.Subscribe(Main.Config.ObservableForProperty(x => x.ShuffleOverworldItems), x => { if (x.Value ?? true) return; Main.Config.IncludePBagCavesInItemShuffle = false; }); // If Palaces can't swap continents - Main.Config.ObservableForProperty(x => x.ShuffleEncounters) - .Subscribe(x => + SubscribeExtensions.Subscribe(Main.Config.ObservableForProperty(x => x.ShuffleEncounters), x => { if (x.Value ?? true) return; Main.Config.IncludeLavaInEncounterShuffle = false; @@ -306,14 +317,13 @@ private void AddValidationRules() }); // If shuffle palaces is off, then don't allow shuffling GP - Main.Config.ObservableForProperty(x => x.PalacesCanSwapContinents) - .Subscribe(x => + SubscribeExtensions.Subscribe(Main.Config.ObservableForProperty(x => x.PalacesCanSwapContinents), x => { if (x.Value ?? true) return; Main.Config.ShuffleGP = false; }); - Main.ObservableForProperty(x => x.ShuffleAllExpState).Subscribe(x => + SubscribeExtensions.Subscribe(Main.ObservableForProperty(x => x.ShuffleAllExpState), x => { if (!x.Value) return; Main.Config.ShuffleAttackExperience = true; @@ -330,34 +340,35 @@ private void AddValidationRules() [JsonIgnore] public BiomesViewModel BiomesViewModel { get; } public PalacesViewModel PalacesViewModel { get; } + public SpellsViewModel SpellsViewModel { get; } public ItemsViewModel ItemsViewModel { get; } public HintsViewModel HintsViewModel { get; } public CustomizeViewModel CustomizeViewModel { get; } [JsonIgnore] - public ReactiveCommand RerollSeed { get; } + public ReactiveCommand RerollSeed { get; } [JsonIgnore] - public ReactiveCommand Generate { get; } + public ReactiveCommand Generate { get; } [JsonIgnore] - public ReactiveCommand SaveFolder { get; } + public ReactiveCommand SaveFolder { get; } [JsonIgnore] - public ReactiveCommand CheckForUpdates { get; } + public ReactiveCommand CheckForUpdates { get; } [JsonIgnore] - public ReactiveCommand ToggleTheme { get; } + public ReactiveCommand ToggleTheme { get; } [JsonIgnore] - public ReactiveCommand VisitDiscord { get; } + public ReactiveCommand VisitDiscord { get; } [JsonIgnore] - public ReactiveCommand VisitWiki { get; } + public ReactiveCommand VisitWiki { get; } [JsonIgnore] - public ReactiveCommand SaveNewPreset { get; } + public ReactiveCommand SaveNewPreset { get; } [JsonIgnore] - public ReactiveCommand SaveAsPreset { get; } + public ReactiveCommand SaveAsPreset { get; } [JsonIgnore] - public ReactiveCommand ClearSavedPreset { get; } + public ReactiveCommand ClearSavedPreset { get; } [JsonIgnore] - public ReactiveCommand LoadPreset { get; } + public ReactiveCommand LoadPreset { get; } [JsonIgnore] - public ReactiveCommand LoadRom { get; } + public ReactiveCommand LoadRom { get; } // Unique identifier for the routable view model. [JsonIgnore] diff --git a/CrossPlatformUI/ViewModels/RomFileViewModel.cs b/CrossPlatformUI/ViewModels/RomFileViewModel.cs index dec251ba2..b3b1ab2ea 100644 --- a/CrossPlatformUI/ViewModels/RomFileViewModel.cs +++ b/CrossPlatformUI/ViewModels/RomFileViewModel.cs @@ -1,11 +1,11 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using ReactiveUI; +using RxVoid = ReactiveUI.Primitives.RxVoid; using SD.Tools.BCLExtensions.CollectionsRelated; using Z2Randomizer.RandomizerCore; using CrossPlatformUI.Services; @@ -93,7 +93,7 @@ private async Task OpenFileInternal(CancellationToken token) Main.OutputFilePath = new Uri(file.Path, ".").LocalPath; } } - HostScreen.Router.NavigateBack.Execute(); + HostScreen.Router.NavigateBack.Execute().Subscribe(); } else { @@ -102,7 +102,7 @@ private async Task OpenFileInternal(CancellationToken token) } [JsonIgnore] - public ReactiveCommand OpenFileCommand { get; } + public ReactiveCommand OpenFileCommand { get; } [JsonIgnore] public MainViewModel Main { get; } [JsonIgnore] diff --git a/CrossPlatformUI/ViewModels/SaveNewPresetViewModel.cs b/CrossPlatformUI/ViewModels/SaveNewPresetViewModel.cs index 57235a973..430420fa3 100644 --- a/CrossPlatformUI/ViewModels/SaveNewPresetViewModel.cs +++ b/CrossPlatformUI/ViewModels/SaveNewPresetViewModel.cs @@ -1,14 +1,13 @@ using System; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; using System.Text.Json; using System.Text.Json.Serialization; using DynamicData.Binding; using ReactiveUI; -using ReactiveUI.Validation.Helpers; +using ReactiveUI.Primitives; +using ReactiveUI.Primitives.Disposables; +using RxVoid = ReactiveUI.Primitives.RxVoid; using Z2Randomizer.RandomizerCore; namespace CrossPlatformUI.ViewModels; @@ -67,7 +66,7 @@ public CustomPreset(string preset, RandomizerConfiguration config) } [RequiresUnreferencedCode("ReactiveUI uses reflection")] -public class SaveNewPresetViewModel : ReactiveValidationObject, IRoutableViewModel, IActivatableViewModel +public class SaveNewPresetViewModel : ReactiveObject, IRoutableViewModel, IActivatableViewModel { #pragma warning disable CS8618 @@ -91,7 +90,7 @@ public SaveNewPresetViewModel(MainViewModel main) { Main.SaveNewPresetDialogOpen = false; }); - this.WhenActivated((CompositeDisposable disposables) => + this.WhenActivated((MultipleDisposable disposables) => { PresetName = string.Empty; }); @@ -117,9 +116,9 @@ public SaveNewPresetViewModel(MainViewModel main) [JsonIgnore] private MainViewModel Main { get; } [JsonIgnore] - public ReactiveCommand SavePreset { get; } + public ReactiveCommand SavePreset { get; } [JsonIgnore] - public ReactiveCommand CancelPreset { get; } + public ReactiveCommand CancelPreset { get; } [JsonIgnore] diff --git a/CrossPlatformUI/ViewModels/Tabs/BiomesViewModel.cs b/CrossPlatformUI/ViewModels/Tabs/BiomesViewModel.cs index 5b0e5706a..ed7faf9d8 100644 --- a/CrossPlatformUI/ViewModels/Tabs/BiomesViewModel.cs +++ b/CrossPlatformUI/ViewModels/Tabs/BiomesViewModel.cs @@ -1,8 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; -using System.Reactive.Linq; using ReactiveUI; +using ReactiveUI.Primitives; +using ReactiveUI.Primitives.Disposables; namespace CrossPlatformUI.ViewModels.Tabs; @@ -26,7 +26,7 @@ public BiomesViewModel(MainViewModel main) this.WhenActivated(OnActivate); } - internal void OnActivate(CompositeDisposable disposables) + internal void OnActivate(MultipleDisposable disposables) { } } diff --git a/CrossPlatformUI/ViewModels/Tabs/CustomizeViewModel.cs b/CrossPlatformUI/ViewModels/Tabs/CustomizeViewModel.cs index 6180bdecd..16fdaeacc 100644 --- a/CrossPlatformUI/ViewModels/Tabs/CustomizeViewModel.cs +++ b/CrossPlatformUI/ViewModels/Tabs/CustomizeViewModel.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; using System.Text.Json.Serialization; using ReactiveUI; +using ReactiveUI.Primitives; using Z2Randomizer.RandomizerCore; namespace CrossPlatformUI.ViewModels.Tabs; diff --git a/CrossPlatformUI/ViewModels/Tabs/HintsViewModel.cs b/CrossPlatformUI/ViewModels/Tabs/HintsViewModel.cs index d82396b5f..918a4b545 100644 --- a/CrossPlatformUI/ViewModels/Tabs/HintsViewModel.cs +++ b/CrossPlatformUI/ViewModels/Tabs/HintsViewModel.cs @@ -1,9 +1,9 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Media; using ReactiveUI; +using ReactiveUI.Primitives; +using ReactiveUI.Primitives.Disposables; namespace CrossPlatformUI.ViewModels.Tabs; @@ -23,13 +23,14 @@ public HintsViewModel(MainViewModel main, RandomizerViewModel randomizerViewMode .Select(_ => Main.Config.GenerateSpoiler) .DistinctUntilChanged(); - HeaderBackgroundObservable = randomizerViewModel.ThemeVariantSubject.CombineLatest(alertFlags) - .Select(pair => ThemeHelper.GetFlagAlertBackgroundBrush(pair.First, pair.Second)); + HeaderBackgroundObservable = randomizerViewModel.WhenAnyValue(x => x.ThemeVariantName) + .CombineLatest(alertFlags, (theme, alert) => + ThemeHelper.GetFlagAlertBackgroundBrush(theme, alert)); this.WhenActivated(OnActivate); } - internal void OnActivate(CompositeDisposable disposables) + internal void OnActivate(MultipleDisposable disposables) { } } diff --git a/CrossPlatformUI/ViewModels/Tabs/ItemsViewModel.cs b/CrossPlatformUI/ViewModels/Tabs/ItemsViewModel.cs index 3e1fa3d5d..ca1bdc2b1 100644 --- a/CrossPlatformUI/ViewModels/Tabs/ItemsViewModel.cs +++ b/CrossPlatformUI/ViewModels/Tabs/ItemsViewModel.cs @@ -1,8 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; -using System.Reactive.Linq; using ReactiveUI; +using ReactiveUI.Primitives; +using ReactiveUI.Primitives.Disposables; namespace CrossPlatformUI.ViewModels.Tabs; @@ -20,7 +20,7 @@ public ItemsViewModel(MainViewModel main) this.WhenActivated(OnActivate); } - internal void OnActivate(CompositeDisposable disposables) + internal void OnActivate(MultipleDisposable disposables) { } } diff --git a/CrossPlatformUI/ViewModels/Tabs/PalacesViewModel.cs b/CrossPlatformUI/ViewModels/Tabs/PalacesViewModel.cs index 37748c7da..1bfe631d5 100644 --- a/CrossPlatformUI/ViewModels/Tabs/PalacesViewModel.cs +++ b/CrossPlatformUI/ViewModels/Tabs/PalacesViewModel.cs @@ -1,8 +1,8 @@ +using ReactiveUI; +using ReactiveUI.Primitives.Disposables; using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using ReactiveUI; +using ReactiveUI.Primitives; namespace CrossPlatformUI.ViewModels.Tabs; @@ -12,15 +12,36 @@ public class PalacesViewModel : ReactiveObject, IActivatableViewModel public ViewModelActivator Activator { get; } public MainViewModel Main { get; } + public IObservable BossRoomsExitTypeIncludedObservable { get; } + public IObservable NoDuplicateRoomsByLayoutIncludedObservable { get; } + public IObservable NoDuplicateRoomsByEnemiesIncludedObservable { get; } public IObservable RandomStylesAllowVanillaIncludedObservable { get; } public IObservable RemoveLongDeadEndsIncludedObservable { get; } + public IObservable IncludeVanillaRoomsIncludedObservable { get; } + public IObservable Includev4_0RoomsIncludedObservable { get; } + public IObservable Includev5_0RoomsIncludedObservable { get; } public IObservable IncludeExpertRoomsIncludedObservable { get; } + public IObservable BlockingRoomsInAnyPalaceIncludedObservable { get; } + public IObservable RemoveTBirdIncludedObservable { get; } + public IObservable TBirdRequiredIncludedObservable { get; } public PalacesViewModel(MainViewModel main) { Main = main; Activator = new(); + BossRoomsExitTypeIncludedObservable = Main.FlagsChanged + .Select(_ => Main.Config.bossRoomsExitTypeIncluded()) + .DistinctUntilChanged(); + + NoDuplicateRoomsByLayoutIncludedObservable = Main.FlagsChanged + .Select(_ => Main.Config.noDuplicateRoomsByLayoutIncluded()) + .DistinctUntilChanged(); + + NoDuplicateRoomsByEnemiesIncludedObservable = Main.FlagsChanged + .Select(_ => Main.Config.noDuplicateRoomsByEnemiesIncluded()) + .DistinctUntilChanged(); + RandomStylesAllowVanillaIncludedObservable = Main.FlagsChanged .Select(_ => Main.Config.randomStylesAllowVanillaIncluded()) .DistinctUntilChanged(); @@ -29,14 +50,38 @@ public PalacesViewModel(MainViewModel main) .Select(_ => Main.Config.removeLongDeadEndsIncluded()) .DistinctUntilChanged(); + IncludeVanillaRoomsIncludedObservable = Main.FlagsChanged + .Select(_ => Main.Config.includeVanillaRoomsIncluded()) + .DistinctUntilChanged(); + + Includev4_0RoomsIncludedObservable = Main.FlagsChanged + .Select(_ => Main.Config.includev4_0RoomsIncluded()) + .DistinctUntilChanged(); + + Includev5_0RoomsIncludedObservable = Main.FlagsChanged + .Select(_ => Main.Config.includev5_0RoomsIncluded()) + .DistinctUntilChanged(); + IncludeExpertRoomsIncludedObservable = Main.FlagsChanged .Select(_ => Main.Config.includeExpertRoomsIncluded()) .DistinctUntilChanged(); + BlockingRoomsInAnyPalaceIncludedObservable = Main.FlagsChanged + .Select(_ => Main.Config.blockingRoomsInAnyPalaceIncluded()) + .DistinctUntilChanged(); + + RemoveTBirdIncludedObservable = Main.FlagsChanged + .Select(_ => Main.Config.removeTBirdIncluded()) + .DistinctUntilChanged(); + + TBirdRequiredIncludedObservable = Main.FlagsChanged + .Select(_ => Main.Config.tBirdRequiredIncluded()) + .DistinctUntilChanged(); + this.WhenActivated(OnActivate); } - internal void OnActivate(CompositeDisposable disposables) + internal void OnActivate(MultipleDisposable disposables) { } } diff --git a/CrossPlatformUI/ViewModels/Tabs/SpellsViewModel.cs b/CrossPlatformUI/ViewModels/Tabs/SpellsViewModel.cs new file mode 100644 index 000000000..0231451e7 --- /dev/null +++ b/CrossPlatformUI/ViewModels/Tabs/SpellsViewModel.cs @@ -0,0 +1,26 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using ReactiveUI; +using ReactiveUI.Primitives; +using ReactiveUI.Primitives.Disposables; + +namespace CrossPlatformUI.ViewModels.Tabs; + +[RequiresUnreferencedCode("ReactiveUI uses reflection")] +public class SpellsViewModel : ReactiveObject, IActivatableViewModel +{ + public ViewModelActivator Activator { get; } + public MainViewModel Main { get; } + + public SpellsViewModel(MainViewModel main) + { + Main = main; + Activator = new(); + + this.WhenActivated(OnActivate); + } + + internal void OnActivate(MultipleDisposable disposables) + { + } +} diff --git a/CrossPlatformUI/ViewModels/Tabs/SpritePreviewViewModel.cs b/CrossPlatformUI/ViewModels/Tabs/SpritePreviewViewModel.cs index ab7bdf281..fd757e5ee 100644 --- a/CrossPlatformUI/ViewModels/Tabs/SpritePreviewViewModel.cs +++ b/CrossPlatformUI/ViewModels/Tabs/SpritePreviewViewModel.cs @@ -1,24 +1,23 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; using System.Text.Json.Serialization; -using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables.Fluent; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Threading; -using CrossPlatformUI.Services; +using ReactiveUI; +using ReactiveUI.Primitives; +using ReactiveUI.Primitives.Disposables; +using ReactiveUI.Primitives.Signals; using DynamicData; using DynamicData.Binding; -using Microsoft.Extensions.DependencyInjection; -using ReactiveUI; +using CrossPlatformUI.Services; using Z2Randomizer.RandomizerCore; namespace CrossPlatformUI.ViewModels.Tabs; @@ -119,7 +118,7 @@ public LoadedCharacterSprite? Sprite [JsonIgnore] public ObservableCollectionExtended Options { get; } = new(); - private CompositeDisposable _disposables = new(); + private MultipleDisposable _disposables = new(); [JsonIgnore] public ViewModelActivator Activator { get; } @@ -135,19 +134,17 @@ public SpritePreviewViewModel(MainViewModel main) Activator = new(); this.WhenActivated(OnActivate); - options - .Connect() - .Bind(Options) - .Subscribe() + SubscribeExtensions.Subscribe(options.Connect().Bind(Options)) .DisposeWith(_disposables); var spriteLoaderService = App.Current?.Services?.GetService(); Debug.Assert(spriteLoaderService != null); - spriteLoaderService.Sprites - .Where(sprites => sprites.Count > 0) - .Take(1) - .Subscribe(sprites => + SubscribeExtensions.Subscribe( + spriteLoaderService.Sprites + .Where(sprites => sprites.Count > 0) + .Take(1), + sprites => { Dispatcher.UIThread.Post(() => LoadCharacterSprites(sprites), DispatcherPriority.Background); }) @@ -159,7 +156,7 @@ public SpritePreviewViewModel(MainViewModel main) _disposables.Dispose(); } - internal void OnActivate(CompositeDisposable disposables) + internal void OnActivate(MultipleDisposable disposables) { IObservable<(string? DisplayName, NesColor, NesColor, NesColor)> settingsObservable = this.WhenAnyValue( x => x.Main.Config.Sprite, @@ -181,21 +178,21 @@ internal void OnActivate(CompositeDisposable disposables) var hasRomObservable = Main.RomFileViewModel.ObservableForProperty(x => x.HasRomData, false, false); var optionsObservable = options.Connect().ToCollection(); - Observable.CombineLatest( - settingsObservable, - hasRomObservable, - optionsObservable, - (settings, hasRom, options) => (settings, hasRom, options) - ) - .Where(t => t.hasRom.Value && t.options.Count > 0) - .Subscribe(t => { - backgroundUpdateTask.CancelAsync().ToObservable().Subscribe(_ => + SubscribeExtensions.Subscribe( + settingsObservable + .CombineLatest(hasRomObservable, optionsObservable, (settings, hasRom, options) => (settings, hasRom, options)) + .Where(t => t.hasRom.Value && t.options.Count > 0), + t => + { + SubscribeExtensions.Subscribe( + Signal.FromAsync(async () => { await backgroundUpdateTask.CancelAsync(); return default(RxVoid); }), + _ => { backgroundUpdateTask = new CancellationTokenSource(); Dispatcher.UIThread.Post(() => UpdateCharacterSprites(Main.RomFileViewModel.RomData, backgroundUpdateTask.Token), DispatcherPriority.Background); }); }) - .DisposeWith(disposables); + .DisposeWith(_disposables); } void LoadCharacterSprites(IReadOnlyList sprites) diff --git a/CrossPlatformUI/Views/GenerateRomView.axaml.cs b/CrossPlatformUI/Views/GenerateRomView.axaml.cs index 0e680317c..8b22eb558 100644 --- a/CrossPlatformUI/Views/GenerateRomView.axaml.cs +++ b/CrossPlatformUI/Views/GenerateRomView.axaml.cs @@ -1,7 +1,8 @@ using System.Diagnostics.CodeAnalysis; -using static System.ObservableExtensions; using ReactiveUI; using ReactiveUI.Avalonia; +using ReactiveUI.Primitives; +using ReactiveUI.Primitives.Disposables; using CrossPlatformUI.ViewModels; namespace CrossPlatformUI.Views; @@ -12,22 +13,29 @@ public partial class GenerateRomView : ReactiveUserControl public GenerateRomView() { InitializeComponent(); - this.WhenActivated(disposables => + this.WhenActivated((MultipleDisposable disposables) => { - CancelGen.WhenAnyValue(x => x.IsVisible).Subscribe(_ => - { - if (CancelGen?.IsVisible ?? false) + SubscribeExtensions.Subscribe( + CancelGen.WhenAnyValue(x => x.IsVisible), + _ => { - CancelGen.Focus(); - } - }); - CloseGen.WhenAnyValue(x => x.IsVisible).Subscribe(_ => - { - if (CloseGen?.IsVisible ?? false) + if (CancelGen?.IsVisible ?? false) + { + CancelGen.Focus(); + } + }) + .DisposeWith(disposables); + + SubscribeExtensions.Subscribe( + CloseGen.WhenAnyValue(x => x.IsVisible), + _ => { - CloseGen.Focus(); - } - }); + if (CloseGen?.IsVisible ?? false) + { + CloseGen.Focus(); + } + }) + .DisposeWith(disposables); }); } } \ No newline at end of file diff --git a/CrossPlatformUI/Views/MainView.axaml b/CrossPlatformUI/Views/MainView.axaml index fc3d4029b..f4be93ee3 100644 --- a/CrossPlatformUI/Views/MainView.axaml +++ b/CrossPlatformUI/Views/MainView.axaml @@ -30,7 +30,7 @@ - + diff --git a/CrossPlatformUI/Views/MainView.axaml.cs b/CrossPlatformUI/Views/MainView.axaml.cs index f6f454afb..1f206db8e 100644 --- a/CrossPlatformUI/Views/MainView.axaml.cs +++ b/CrossPlatformUI/Views/MainView.axaml.cs @@ -2,6 +2,7 @@ using Avalonia.Markup.Xaml; using ReactiveUI; using ReactiveUI.Avalonia; +using ReactiveUI.Primitives.Disposables; using CrossPlatformUI.ViewModels; namespace CrossPlatformUI.Views; @@ -11,7 +12,7 @@ public partial class MainView : ReactiveUserControl { public MainView() { - this.WhenActivated(disposables => { }); + this.WhenActivated((MultipleDisposable disposables) => { }); AvaloniaXamlLoader.Load(this); } } \ No newline at end of file diff --git a/CrossPlatformUI/Views/MainWindow.axaml.cs b/CrossPlatformUI/Views/MainWindow.axaml.cs index 3c72d3610..d13c1360b 100644 --- a/CrossPlatformUI/Views/MainWindow.axaml.cs +++ b/CrossPlatformUI/Views/MainWindow.axaml.cs @@ -3,6 +3,7 @@ using Avalonia; using ReactiveUI; using ReactiveUI.Avalonia; +using ReactiveUI.Primitives.Disposables; using CrossPlatformUI.ViewModels; namespace CrossPlatformUI.Views; @@ -14,7 +15,7 @@ public MainWindow() { InitializeComponent(); // Prevent the previewer's DataContext from being set when the application is run. - this.WhenActivated(disposables => { + this.WhenActivated((MultipleDisposable disposables) => { var context = DataContext as MainViewModel; ClientSize = new Size(context!.WindowSize.Width,context.WindowSize.Height); PositionChanged += (_, args) => diff --git a/CrossPlatformUI/Views/NesColorDropdown.axaml b/CrossPlatformUI/Views/NesColorDropdown.axaml index ac0761779..de79d87a1 100644 --- a/CrossPlatformUI/Views/NesColorDropdown.axaml +++ b/CrossPlatformUI/Views/NesColorDropdown.axaml @@ -3,10 +3,10 @@ xmlns:assists="clr-namespace:Material.Styles.Assists;assembly=Material.Styles" x:Class="CrossPlatformUI.Views.NesColorDropdown" Width="340" Height="40"> - - + + - diff --git a/CrossPlatformUI/Views/RandomizerView.axaml b/CrossPlatformUI/Views/RandomizerView.axaml index 43dc2d822..5e8ef6e66 100644 --- a/CrossPlatformUI/Views/RandomizerView.axaml +++ b/CrossPlatformUI/Views/RandomizerView.axaml @@ -2,24 +2,25 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:viewModels="clr-namespace:CrossPlatformUI.ViewModels" xmlns:p1="clr-namespace:Material.Styles.Assists;assembly=Material.Styles" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:controls="clr-namespace:Material.Styles.Controls;assembly=Material.Styles" xmlns:tabs="clr-namespace:CrossPlatformUI.Views.Tabs" xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" + xmlns:ui="clr-namespace:CrossPlatformUI" + xmlns:vm="clr-namespace:CrossPlatformUI.ViewModels" + xmlns:views="clr-namespace:CrossPlatformUI.Views" xmlns:system="clr-namespace:System;assembly=System.Runtime" - xmlns:crossPlatformUi="clr-namespace:CrossPlatformUI" xmlns:presets="clr-namespace:CrossPlatformUI.Presets" - xmlns:views="clr-namespace:CrossPlatformUI.Views" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" Name="RandomizerViewControl" - x:DataType="viewModels:RandomizerViewModel" + x:DataType="vm:RandomizerViewModel" x:Class="CrossPlatformUI.Views.RandomizerView"> - +