diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs index 5d8a00735..e099e06a7 100644 --- a/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs +++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs @@ -109,4 +109,10 @@ public interface ITrackerGameStateService public void UpdateLastMarkedLocations(List locations); public void ClearLastViewedObject(float confidence); + + public void UpdateGanonsTowerRequirement(int crystalAmount, bool autoTracked); + + public void UpdateGanonRequirement(int crystalAmount, bool autoTracked); + + public void UpdateTourianRequirement(int bossAmount, bool autoTracked); } diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/ResponseConfig.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/ResponseConfig.cs index 30c04b703..684e788ba 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/ResponseConfig.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/ResponseConfig.cs @@ -627,6 +627,27 @@ public class ResponseConfig : IMergeable, IConfigFile public SchrodingersString? LongSpeechResponse { get; init; } + /// + /// Gets the phrases for when the player has updated the number of crystals required + /// to get into GT + /// {0} is a placeholder for the number of crystals required + /// + public SchrodingersString? UpdatedGanonsTowerCrystalRequirement { get; init; } + + /// + /// Gets the phrases for when the player has updated the number of crystals required + /// to defeat Ganon + /// {0} is a placeholder for the number of crystals required + /// + public SchrodingersString? UpdatedGanonCrystalRequirement { get; init; } + + /// + /// Gets the phrases for when the player has updated the number of bosses required + /// to enter Tourian + /// {0} is a placeholder for the number of bosses required + /// + public SchrodingersString? UpdatedTourianBossRequirement { get; init; } + /// /// Gets a dictionary that contains the phrases to respond with when no /// voice commands have been issued after a certain period of time, as diff --git a/src/TrackerCouncil.Smz3.Data/Services/TrackerStateService.cs b/src/TrackerCouncil.Smz3.Data/Services/TrackerStateService.cs index f2208b840..92795ae35 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/TrackerStateService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/TrackerStateService.cs @@ -161,6 +161,8 @@ public TrackerState CreateTrackerState(List worlds) hintStates.Add(hintState); } + var localWorld = worlds.First(x => x.IsLocalWorld); + return new TrackerState() { LocationStates = locationStates, @@ -169,10 +171,16 @@ public TrackerState CreateTrackerState(List worlds) BossStates = bossStates, RewardStates = rewardStates, PrerequisiteStates = prereqStates, - LocalWorldId = worlds.First(x => x.IsLocalWorld).Id, + LocalWorldId = localWorld.Id, Hints = hintStates, StartDateTime = DateTimeOffset.Now, UpdatedDateTime = DateTimeOffset.Now, + GanonsTowerCrystalCount = localWorld.Config.GanonsTowerCrystalCount, + MarkedGanonsTowerCrystalCount = localWorld.LegacyWorld == null ? localWorld.Config.GanonsTowerCrystalCount : null, + GanonCrystalCount = localWorld.Config.GanonCrystalCount, + MarkedGanonCrystalCount = localWorld.LegacyWorld == null ? localWorld.Config.GanonCrystalCount : null, + TourianBossCount = localWorld.Config.TourianBossCount, + MarkedTourianBossCount = localWorld.LegacyWorld == null ? localWorld.Config.TourianBossCount : null, }; } diff --git a/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj b/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj index d040d65ff..b47dfecdc 100644 --- a/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj +++ b/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Crateria/WestCrateria.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Crateria/WestCrateria.cs index 097decbc0..cacd1ebe9 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Crateria/WestCrateria.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Crateria/WestCrateria.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.Options; @@ -30,6 +32,10 @@ public WestCrateria(World world, Config config, IMetadataService? metadata, Trac public TerminatorRoom Terminator { get; } + public Accessibility MotherBrainAccessibility { get; private set; } + + public event EventHandler? UpdatedMotherBrainAccessibility; + public override bool CanEnter(Progression items, bool requireRewards) { return Logic.CanDestroyBombWalls(items) || Logic.CanParlorSpeedBoost(items); @@ -107,4 +113,48 @@ public TerminatorRoom(WestCrateria region, IMetadataService? metadata, TrackerSt }; } } + + public void UpdateMotherBrainAccessibility(Progression progression) + { + Accessibility NewAccessibility; + + var tourianBossRequirement = World.State?.TourianBossCount == null + ? Config.TourianBossCount + : World.State?.MarkedTourianBossCount ?? 4; + + if (World.Bosses.First(x => x.Type == BossType.MotherBrain).Defeated) + { + NewAccessibility = Accessibility.Cleared; + } + else if (World.LegacyWorld == null) + { + var canAccessStatueRoom = Terminator.Locations.First().IsAvailable(progression) && + (!World.Config.MetroidKeysanity || World.Config.SkipTourianBossDoor || + progression.CardCrateriaBoss); + + var canEnterTourian = World.GoldenBosses.Count(x => x.Defeated) >= tourianBossRequirement; + + NewAccessibility = canAccessStatueRoom && canEnterTourian + ? Accessibility.Available + : Accessibility.OutOfLogic; + } + else + { + var canAccessStatueRoom = World.LegacyWorld.IsLocationAccessible((int)LocationId.CrateriaTerminator, progression.LegacyProgression) && + (!World.Config.MetroidKeysanity || World.Config.SkipTourianBossDoor || + progression.CardCrateriaBoss); + + var canEnterTourian = World.GoldenBosses.Count(x => x.Defeated) >= tourianBossRequirement; + + NewAccessibility = canAccessStatueRoom && canEnterTourian + ? Accessibility.Available + : Accessibility.OutOfLogic; + } + + if (NewAccessibility != MotherBrainAccessibility) + { + MotherBrainAccessibility = NewAccessibility; + UpdatedMotherBrainAccessibility?.Invoke(this, EventArgs.Empty); + } + } } diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DarkWorld/DarkWorldNorthEast.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DarkWorld/DarkWorldNorthEast.cs index a33ebd2bb..ecb76fdbc 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DarkWorld/DarkWorldNorthEast.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DarkWorld/DarkWorldNorthEast.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.Options; @@ -49,6 +51,10 @@ public DarkWorldNorthEast(World world, Config config, IMetadataService? metadata public PyramidFairyChamber PyramidFairy { get; } + public Accessibility GanonAccessibility { get; private set; } + + public event EventHandler? UpdatedGanonAccessibility; + public override bool CanEnter(Progression items, bool requireRewards) { return Logic.CheckAgahnim(items, World, requireRewards) || @@ -60,6 +66,62 @@ public override bool CanEnter(Progression items, bool requireRewards) ); } + public void UpdateGanonAccessibility(Progression progression, Progression assumedKeyProgression) + { + Accessibility NewAccessibility; + + var ganonCrystalRequirement = World.State?.GanonCrystalCount == null + ? Config.GanonCrystalCount + : World.State?.MarkedGanonCrystalCount ?? 7; + + if (World.Bosses.First(x => x.Type == BossType.Ganon).Defeated) + { + NewAccessibility = Accessibility.Cleared; + } + else if (World.LegacyWorld == null) + { + var canAccessPyramid = Pyramid.IsAvailable(progression); + + var canClimbGt = World.GanonsTower.CanBeatBoss(progression, true) || + (!World.Config.ZeldaKeysanity && + World.GanonsTower.Locations.All(x => x.IsAvailable(assumedKeyProgression)) && + World.GanonsTower.CanBeatBoss(assumedKeyProgression, true)); + + var isPyramidOpen = World.Config.OpenPyramid || canClimbGt; + var canHurtGanon = progression.CrystalCount >= ganonCrystalRequirement && progression.MasterSword && + (progression.Lamp || progression.FireRod); + + NewAccessibility = canAccessPyramid && isPyramidOpen && canHurtGanon + ? Accessibility.Available + : Accessibility.OutOfLogic; + } + else + { + var canAccessPyramid = World.LegacyWorld.IsLocationAccessible((int)LocationId.Pyramid, progression.LegacyProgression); + + var canClimbGt = + World.LegacyWorld.IsLocationAccessible((int)LocationId.GanonsTowerMoldormChest, + progression.LegacyProgression) || (!World.Config.ZeldaKeysanity && + World.GanonsTower.Locations.All(x => + World.LegacyWorld.IsLocationAccessible((int)x.Id, + assumedKeyProgression.LegacyProgression))); + + var isPyramidOpen = World.Config.OpenPyramid || canClimbGt; + var canHurtGanon = progression.CrystalCount >= ganonCrystalRequirement && progression.MasterSword && + (progression.Lamp || progression.FireRod); + + NewAccessibility = canAccessPyramid && isPyramidOpen && canHurtGanon + ? Accessibility.Available + : Accessibility.OutOfLogic; + } + + if (NewAccessibility != GanonAccessibility) + { + GanonAccessibility = NewAccessibility; + UpdatedGanonAccessibility?.Invoke(this, EventArgs.Empty); + } + } + public class PyramidFairyChamber : Room { public PyramidFairyChamber(Region region, IMetadataService? metadata, TrackerState? trackerState) diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/GanonsTower.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/GanonsTower.cs index 7b38dcb5c..b617b2737 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/GanonsTower.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/GanonsTower.cs @@ -171,9 +171,13 @@ public GanonsTower(World world, Config config, IMetadataService? metadata, Track public override bool CanEnter(Progression items, bool requireRewards) { + var gtCrystalRequirement = World.State?.GanonsTowerCrystalCount == null + ? Config.GanonsTowerCrystalCount + : World.State?.MarkedGanonsTowerCrystalCount ?? 7; + var smBosses = new[] { BossType.Kraid, BossType.Phantoon, BossType.Draygon, BossType.Ridley }; var canEnterDDMEast = World.DarkWorldDeathMountainEast.CanEnter(items, requireRewards); - var haveEnoughCrystals = items.CrystalCount >= Config.GanonsTowerCrystalCount; + var haveEnoughCrystals = items.CrystalCount >= gtCrystalRequirement; var gtOpenBeforeGanon = Config.GanonsTowerCrystalCount < Config.GanonCrystalCount; var canBeatMetroid = World.CanDefeatBossCount(items, requireRewards, smBosses) >= Config.TourianBossCount; return World.Logic.CanNavigateDarkWorld(items) && canEnterDDMEast && haveEnoughCrystals && (gtOpenBeforeGanon || canBeatMetroid); diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/World.cs b/src/TrackerCouncil.Smz3.Data/WorldData/World.cs index a85ff2f2b..ab72969be 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/World.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/World.cs @@ -116,7 +116,7 @@ public World(Config config, string player, int id, string guid, bool isLocalWorl LegacyWorld = new LegacyWorld(legacyConfig, Player, Id, Guid); - UpdateLegacyWorld(); + UpdateLegacyWorld(trackerState); } } @@ -323,7 +323,7 @@ private void SetBottles(Random rnd) } } - public void UpdateLegacyWorld() + public void UpdateLegacyWorld(TrackerState? trackerState) { if (LegacyWorld == null) { @@ -354,9 +354,9 @@ public void UpdateLegacyWorld() GetLegacyRewardType(InnerMaridia), GetLegacyRewardType(LowerNorfairEast), ], - TowerCrystals = Config.GanonsTowerCrystalCount, - GanonCrystals = Config.GanonCrystalCount, - TourianBossTokens = Config.TourianBossCount + TowerCrystals = trackerState?.MarkedGanonsTowerCrystalCount ?? 7, + GanonCrystals = trackerState?.MarkedGanonCrystalCount ?? 7, + TourianBossTokens = trackerState?.MarkedGanonsTowerCrystalCount ?? 4 }; LegacyWorld.Setup(worldState); diff --git a/src/TrackerCouncil.Smz3.Data/maps.json b/src/TrackerCouncil.Smz3.Data/maps.json index 4fbd1f448..b8636b951 100644 --- a/src/TrackerCouncil.Smz3.Data/maps.json +++ b/src/TrackerCouncil.Smz3.Data/maps.json @@ -1733,6 +1733,8 @@ { "Name": "Dark World North East", "TypeName": "TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda.DarkWorld.DarkWorldNorthEast", + "BossX": 998, + "BossY": 815, "Rooms": [ { "Name": "Catfish", @@ -2277,6 +2279,8 @@ { "Name": "West Crateria", "TypeName": "TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid.Crateria.WestCrateria", + "BossX": 453, + "BossY": 358, "Rooms": [ { "Name": "Energy Tank, Gauntlet", diff --git a/src/TrackerCouncil.Smz3.Shared/Migrations/20250307031224_RandomGoalAmounts.Designer.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/20250307031224_RandomGoalAmounts.Designer.cs new file mode 100644 index 000000000..7801872b6 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/20250307031224_RandomGoalAmounts.Designer.cs @@ -0,0 +1,738 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TrackerCouncil.Smz3.Shared.Models; + +#nullable disable + +namespace TrackerCouncil.Smz3.Shared.Migrations +{ + [DbContext(typeof(RandomizerContext))] + [Migration("20250307031224_RandomGoalAmounts")] + partial class RandomGoalAmounts + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.10"); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.GeneratedRom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("GeneratorVersion") + .HasColumnType("INTEGER"); + + b.Property("Label") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MsuPaths") + .HasColumnType("TEXT"); + + b.Property("MsuRandomizationStyle") + .HasColumnType("INTEGER"); + + b.Property("MsuShuffleStyle") + .HasColumnType("INTEGER"); + + b.Property("MultiplayerGameDetailsId") + .HasColumnType("INTEGER"); + + b.Property("RomPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Seed") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Settings") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SpoilerPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MultiplayerGameDetailsId"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("GeneratedRoms"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.MultiplayerGameDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConnectionUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GameGuid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GameUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GeneratedRomId") + .HasColumnType("INTEGER"); + + b.Property("JoinedDate") + .HasColumnType("TEXT"); + + b.Property("PlayerGuid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PlayerKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GeneratedRomId") + .IsUnique(); + + b.ToTable("MultiplayerGames"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerBossState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoTracked") + .HasColumnType("INTEGER"); + + b.Property("BossName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Defeated") + .HasColumnType("INTEGER"); + + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerBossStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerDungeonState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoTracked") + .HasColumnType("INTEGER"); + + b.Property("Cleared") + .HasColumnType("INTEGER"); + + b.Property("HasManuallyClearedTreasure") + .HasColumnType("INTEGER"); + + b.Property("MarkedMedallion") + .HasColumnType("INTEGER"); + + b.Property("MarkedReward") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RemainingTreasure") + .HasColumnType("INTEGER"); + + b.Property("RequiredMedallion") + .HasColumnType("INTEGER"); + + b.Property("Reward") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerDungeonStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerHintState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("HintState") + .HasColumnType("INTEGER"); + + b.Property("HintTileCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LocationKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LocationString") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LocationWorldId") + .HasColumnType("INTEGER"); + + b.Property("MedallionType") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Usefulness") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerHintStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerHistoryEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("IsImportant") + .HasColumnType("INTEGER"); + + b.Property("IsUndone") + .HasColumnType("INTEGER"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("LocationName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ObjectName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Time") + .HasColumnType("REAL"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerHistoryEvents"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerItemState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ItemName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("TrackingState") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerItemStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerLocationState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Autotracked") + .HasColumnType("INTEGER"); + + b.Property("Cleared") + .HasColumnType("INTEGER"); + + b.Property("Item") + .HasColumnType("INTEGER"); + + b.Property("ItemName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ItemOwnerName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ItemWorldId") + .HasColumnType("INTEGER"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("MarkedItem") + .HasColumnType("INTEGER"); + + b.Property("MarkedUsefulness") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerLocationStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerMarkedLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ItemName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerMarkedLocations"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerPrerequisiteState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoTracked") + .HasColumnType("INTEGER"); + + b.Property("MarkedItem") + .HasColumnType("INTEGER"); + + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequiredItem") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerPrerequisiteStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerRegionState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Medallion") + .HasColumnType("INTEGER"); + + b.Property("Reward") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("TypeName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerRegionStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerRewardState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoTracked") + .HasColumnType("INTEGER"); + + b.Property("HasReceivedReward") + .HasColumnType("INTEGER"); + + b.Property("MarkedReward") + .HasColumnType("INTEGER"); + + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RewardType") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerRewardStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GanonCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("GanonsTowerCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("GiftedItemCount") + .HasColumnType("INTEGER"); + + b.Property("LocalWorldId") + .HasColumnType("INTEGER"); + + b.Property("MarkedGanonCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("MarkedGanonsTowerCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("MarkedTourianBossCount") + .HasColumnType("INTEGER"); + + b.Property("PercentageCleared") + .HasColumnType("INTEGER"); + + b.Property("SecondsElapsed") + .HasColumnType("REAL"); + + b.Property("StartDateTime") + .HasColumnType("TEXT"); + + b.Property("TourianBossCount") + .HasColumnType("INTEGER"); + + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TrackerStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerTreasureState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("HasManuallyClearedTreasure") + .HasColumnType("INTEGER"); + + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RemainingTreasure") + .HasColumnType("INTEGER"); + + b.Property("TotalTreasure") + .HasColumnType("INTEGER"); + + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TrackerStateId"); + + b.ToTable("TrackerTreasureStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.GeneratedRom", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.MultiplayerGameDetails", "MultiplayerGameDetails") + .WithMany() + .HasForeignKey("MultiplayerGameDetailsId"); + + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany() + .HasForeignKey("TrackerStateId"); + + b.Navigation("MultiplayerGameDetails"); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.MultiplayerGameDetails", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.GeneratedRom", "GeneratedRom") + .WithOne() + .HasForeignKey("TrackerCouncil.Smz3.Shared.Models.MultiplayerGameDetails", "GeneratedRomId"); + + b.Navigation("GeneratedRom"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerBossState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("BossStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerDungeonState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("DungeonStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerHintState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("Hints") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerHistoryEvent", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("History") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerItemState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("ItemStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerLocationState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("LocationStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerMarkedLocation", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("MarkedLocations") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerPrerequisiteState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("PrerequisiteStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerRegionState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("RegionStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerRewardState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("RewardStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerTreasureState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Models.TrackerState", "TrackerState") + .WithMany("TreasureStates") + .HasForeignKey("TrackerStateId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("TrackerState"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerState", b => + { + b.Navigation("BossStates"); + + b.Navigation("DungeonStates"); + + b.Navigation("Hints"); + + b.Navigation("History"); + + b.Navigation("ItemStates"); + + b.Navigation("LocationStates"); + + b.Navigation("MarkedLocations"); + + b.Navigation("PrerequisiteStates"); + + b.Navigation("RegionStates"); + + b.Navigation("RewardStates"); + + b.Navigation("TreasureStates"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Migrations/20250307031224_RandomGoalAmounts.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/20250307031224_RandomGoalAmounts.cs new file mode 100644 index 000000000..283cc4a9c --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/20250307031224_RandomGoalAmounts.cs @@ -0,0 +1,78 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TrackerCouncil.Smz3.Shared.Migrations +{ + /// + public partial class RandomGoalAmounts : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GanonCrystalCount", + table: "TrackerStates", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "GanonsTowerCrystalCount", + table: "TrackerStates", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "MarkedGanonCrystalCount", + table: "TrackerStates", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "MarkedGanonsTowerCrystalCount", + table: "TrackerStates", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "MarkedTourianBossCount", + table: "TrackerStates", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "TourianBossCount", + table: "TrackerStates", + type: "INTEGER", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "GanonCrystalCount", + table: "TrackerStates"); + + migrationBuilder.DropColumn( + name: "GanonsTowerCrystalCount", + table: "TrackerStates"); + + migrationBuilder.DropColumn( + name: "MarkedGanonCrystalCount", + table: "TrackerStates"); + + migrationBuilder.DropColumn( + name: "MarkedGanonsTowerCrystalCount", + table: "TrackerStates"); + + migrationBuilder.DropColumn( + name: "MarkedTourianBossCount", + table: "TrackerStates"); + + migrationBuilder.DropColumn( + name: "TourianBossCount", + table: "TrackerStates"); + } + } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs index 4fee9fa2d..c0351f2a4 100644 --- a/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs @@ -497,12 +497,27 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("GanonCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("GanonsTowerCrystalCount") + .HasColumnType("INTEGER"); + b.Property("GiftedItemCount") .HasColumnType("INTEGER"); b.Property("LocalWorldId") .HasColumnType("INTEGER"); + b.Property("MarkedGanonCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("MarkedGanonsTowerCrystalCount") + .HasColumnType("INTEGER"); + + b.Property("MarkedTourianBossCount") + .HasColumnType("INTEGER"); + b.Property("PercentageCleared") .HasColumnType("INTEGER"); @@ -512,6 +527,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("StartDateTime") .HasColumnType("TEXT"); + b.Property("TourianBossCount") + .HasColumnType("INTEGER"); + b.Property("UpdatedDateTime") .HasColumnType("TEXT"); diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerState.cs index 51671249a..85e4b65c9 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/TrackerState.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerState.cs @@ -17,6 +17,12 @@ public class TrackerState public int PercentageCleared { get; set; } public int LocalWorldId { get; set; } public int GiftedItemCount { get; set; } + public int? GanonsTowerCrystalCount { get; set; } + public int? MarkedGanonsTowerCrystalCount { get; set; } + public int? GanonCrystalCount { get; set; } + public int? MarkedGanonCrystalCount { get; set; } + public int? TourianBossCount { get; set; } + public int? MarkedTourianBossCount { get; set; } public ICollection ItemStates { get; set; } = new List(); public ICollection LocationStates { get; set; } = new List(); public ICollection RewardStates { get; set; } = new List(); diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/FinalBossCheck.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/FinalBossCheck.cs index 035348f07..2c65528b6 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/FinalBossCheck.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/FinalBossCheck.cs @@ -60,7 +60,12 @@ private void CheckBeatFinalBosses(SnesData data, SnesData? prevData) Logger.LogInformation("Auto tracked Mother Brain"); var boss = Tracker.World.Bosses.First(x => x.Type == BossType.MotherBrain); Tracker.BossTracker.MarkBossAsDefeated(boss, admittedGuilt: true, confidence: null, autoTracked: true); - _ = CountHyperBeamShots(); + + if (Tracker.World.Config.RomGenerator == RomGenerator.Cas) + { + _ = CountHyperBeamShots(); + } + didUpdate = true; } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/ViewedTourianBossCount.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/ViewedTourianBossCount.cs new file mode 100644 index 000000000..4930e540b --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/ViewedTourianBossCount.cs @@ -0,0 +1,26 @@ +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Tracking; + +namespace TrackerCouncil.Smz3.Tracking.AutoTracking.MetroidStateChecks; + +public class ViewedTourianBossCount : IMetroidStateCheck +{ + public bool ExecuteCheck(TrackerBase tracker, AutoTrackerMetroidState currentState, + AutoTrackerMetroidState prevState) + { + var requiredBossCount = tracker.World.State?.TourianBossCount ?? tracker.World.Config.TourianBossCount; + + if (tracker.World.State?.MarkedTourianBossCount == requiredBossCount) + { + return false; + } + + if (currentState is { CurrentRegion: 0, CurrentRoomInRegion: 48, SamusX: > 1169 and < 1250 }) + { + tracker.GameStateTracker.UpdateTourianRequirement(requiredBossCount, true); + return true; + } + + return false; + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs index 0be9caff0..1b4f7e182 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs @@ -62,6 +62,14 @@ public bool ExecuteCheck(TrackerBase tracker, AutoTrackerZeldaState currentState _lastHintTile = hintTile; tracker.AutoTracker.SetLatestViewAction("MarkHintTileAsViewed", MarkHintTileAsViewed); } + else if (currentState.OverworldScreen == 67 && currentState.IsWithinRegion(2169, 168, 2184, 169) && World.State?.GanonsTowerCrystalCount != World.State?.MarkedGanonsTowerCrystalCount) + { + tracker.GameStateTracker.UpdateGanonsTowerRequirement(World.State?.GanonsTowerCrystalCount ?? World.Config.GanonsTowerCrystalCount, true); + } + else if (currentState.OverworldScreen == 91 && currentState.IsWithinRegion(1961, 1784, 1976, 1785) && World.State?.GanonCrystalCount != World.State?.MarkedGanonCrystalCount) + { + tracker.GameStateTracker.UpdateGanonRequirement(World.State?.GanonCrystalCount ?? World.Config.GanonCrystalCount, true); + } return false; } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/PlayerProgressionService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/PlayerProgressionService.cs index abd11cb73..ebfd27e93 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/PlayerProgressionService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/PlayerProgressionService.cs @@ -77,7 +77,7 @@ public Progression GetProgression(bool assumeKeys) if (_world.World.Config.RomGenerator != RomGenerator.Cas) { progression.InitLegacyProgression(); - _world.World.UpdateLegacyWorld(); + _world.World.UpdateLegacyWorld(_world.World.State); } _progression[key] = progression; diff --git a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs index 9358cae3c..788584c77 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs @@ -643,6 +643,8 @@ public override void UpdateAllAccessibility(bool forceRefreshAll, params Item[] var actualProgression = PlayerProgressionService.GetProgression(false); var assumedKeysProgression = PlayerProgressionService.GetProgression(true); + World.DarkWorldNorthEast.UpdateGanonAccessibility(actualProgression, assumedKeysProgression); + World.WestCrateria.UpdateMotherBrainAccessibility(actualProgression); LocationTracker.UpdateAccessibility(!forceRefreshAll, actualProgression, assumedKeysProgression); RewardTracker.UpdateAccessibility(actualProgression, assumedKeysProgression); BossTracker.UpdateAccessibility(actualProgression, assumedKeysProgression); diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs index 0fbba69c9..030ec025c 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs @@ -280,4 +280,69 @@ public void ClearLastViewedObject(float confidence) Tracker.Say(x => x.NoMarkedLocations); } } + + public void UpdateGanonsTowerRequirement(int crystalAmount, bool autoTracked) + { + if (World.State == null) + { + return; + } + + var previousAmount = World.State.MarkedGanonsTowerCrystalCount; + + World.State.MarkedGanonsTowerCrystalCount = crystalAmount; + World.UpdateLegacyWorld(World.State); + UpdateAllAccessibility(true); + Tracker.Say(x => x.UpdatedGanonsTowerCrystalRequirement, args: [crystalAmount]); + + + AddUndo(autoTracked, () => + { + World.State.MarkedGanonCrystalCount = previousAmount; + World.UpdateLegacyWorld(World.State); + UpdateAllAccessibility(true); + }); + } + + public void UpdateGanonRequirement(int crystalAmount, bool autoTracked) + { + if (World.State == null) + { + return; + } + + var previousAmount = World.State.MarkedGanonCrystalCount; + + World.State.MarkedGanonCrystalCount = crystalAmount; + World.UpdateLegacyWorld(World.State); + UpdateAllAccessibility(true); + Tracker.Say(x => x.UpdatedGanonCrystalRequirement, args: [crystalAmount]); + + AddUndo(autoTracked, () => + { + World.State.MarkedGanonCrystalCount = previousAmount; + World.UpdateLegacyWorld(World.State); + }); + } + + public void UpdateTourianRequirement(int bossAmount, bool autoTracked) + { + if (World.State == null) + { + return; + } + + var previousAmount = World.State.MarkedGanonCrystalCount; + + World.State.MarkedTourianBossCount = bossAmount; + World.UpdateLegacyWorld(World.State); + UpdateAllAccessibility(true); + Tracker.Say(x => x.UpdatedTourianBossRequirement, args: [bossAmount]); + + AddUndo(autoTracked, () => + { + World.State.MarkedTourianBossCount = previousAmount; + World.UpdateLegacyWorld(World.State); + }); + } } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs new file mode 100644 index 000000000..343254eed --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoalModule.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Speech.Recognition; +using Microsoft.Extensions.Logging; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; +using TrackerCouncil.Smz3.Shared.Enums; +using TrackerCouncil.Smz3.Tracking.Services; + +namespace TrackerCouncil.Smz3.Tracking.VoiceCommands; + +/// +/// Provides voice commands for tracking goals +/// +public class GoalModule : TrackerModule +{ + private ResponseConfig _responseConfig; + private const string ItemCountKey = "ItemCount"; + + /// + /// Initializes a new instance of the class. + /// + /// The tracker instance. + /// Service to get item information + /// Service to get world information + /// Used to log information. + /// + public GoalModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, ResponseConfig responseConfig) + : base(tracker, playerProgressionService, worldQueryService, logger) + { + _responseConfig = responseConfig; + } + + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + private GrammarBuilder GetGanonsTowerCrystalCountRule() + { + return new GrammarBuilder() + .Append("Hey tracker,") + .OneOf("Ganon's Tower", "G T") + .OneOf("requires", "needs") + .Append(ItemCountKey, GetNumberChoices(0, 7)) + .OneOf("crystals", "crystal") + .Optional("to enter"); + } + + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + private GrammarBuilder GetGanonsCrystalCountRule() + { + return new GrammarBuilder() + .Append("Hey tracker,") + .OneOf("Ganon", "Ganondorf") + .OneOf("requires", "needs") + .Append(ItemCountKey, GetNumberChoices(0, 7)) + .OneOf("crystals", "crystal") + .Optional("to kill", "to defeat"); + } + + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + private GrammarBuilder GetTourianBossCountRule() + { + return new GrammarBuilder() + .Append("Hey tracker,") + .OneOf("tourian", "the statue room", "the metroid statue room", "the golden statue room") + .OneOf("requires", "needs") + .Append(ItemCountKey, GetNumberChoices(0, 4)) + .OneOf("bosses", "boss tokens"); + } + + [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] + public override void AddCommands() + { + if (TrackerBase.World.Config.RomGenerator == RomGenerator.Cas) + { + return; + } + + AddCommand("GT Crystal Count", GetGanonsTowerCrystalCountRule(), (result) => + { + var count = (int)result.Semantics[ItemCountKey].Value; + TrackerBase.GameStateTracker.UpdateGanonsTowerRequirement(count, false); + }); + + AddCommand("Ganon Crystal Count", GetGanonsCrystalCountRule(), (result) => + { + var count = (int)result.Semantics[ItemCountKey].Value; + TrackerBase.GameStateTracker.UpdateGanonRequirement(count, false); + }); + + AddCommand("Tourian Boss Count", GetTourianBossCountRule(), (result) => + { + var count = (int)result.Semantics[ItemCountKey].Value; + TrackerBase.GameStateTracker.UpdateTourianRequirement(count, false); + }); + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs index da6d814f8..292f128e2 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs @@ -601,4 +601,32 @@ protected virtual Choices GetMedallionNames() return medallions; } + + /// + /// Gets speech recognition choices ranging from min to max + /// + /// Minimum number choice + /// Maximum number choice + /// The choices for the user to choose from in speech recognition + [SupportedOSPlatform("windows")] + protected virtual Choices GetNumberChoices(int min, int max) + { + var numbers = new Choices(); + for (var i = min; i <= max; i++) + numbers.Add(new SemanticResultValue(i.ToString(), i)); + return numbers; + } + + /// + /// Gets speech recognition choices ranging from 0 to the provided number + /// + /// Maximum number choice + /// The choices for the user to choose from in speech recognition + [SupportedOSPlatform("windows")] + protected virtual Choices GetNumberChoices(int max) + + { + return GetNumberChoices(0, max); + } + } diff --git a/src/TrackerCouncil.Smz3.UI/Services/TrackerMapWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/TrackerMapWindowService.cs index ac3d9d66e..ab5cf4d44 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/TrackerMapWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/TrackerMapWindowService.cs @@ -13,6 +13,8 @@ using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid.Crateria; +using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda.DarkWorld; using TrackerCouncil.Smz3.SeedGenerator.Contracts; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Tracking.Services; @@ -203,6 +205,16 @@ private TrackerMapLocationViewModel GetBossLocationModel(TrackerMapRegion mapReg rewardRegion.Reward.UpdatedRewardState += (_, _) => UpdateBossLocationModel(model); } + if (worldRegion is DarkWorldNorthEast darkWorldNorthEast) + { + darkWorldNorthEast.UpdatedGanonAccessibility += (_, _) => UpdateBossLocationModel(model); + } + + if (worldRegion is WestCrateria westCrateria) + { + westCrateria.UpdatedMotherBrainAccessibility += (_, _) => UpdateBossLocationModel(model); + } + UpdateBossLocationModel(model); return model; } @@ -221,6 +233,16 @@ private void UpdateBossLocationModel(TrackerMapLocationViewModel location) image = "boss.png"; location.IsInLogic = true; } + else if (location.Region is DarkWorldNorthEast darkWorldNorthEast) + { + image = "boss.png"; + location.IsInLogic = darkWorldNorthEast.GanonAccessibility == Accessibility.Available; + } + else if (location.Region is WestCrateria westCrateria) + { + image = "boss.png"; + location.IsInLogic = westCrateria.MotherBrainAccessibility == Accessibility.Available; + } else { location.IsInLogic = false; diff --git a/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj b/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj index 633a89d7c..75642a0df 100644 --- a/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj +++ b/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj @@ -19,12 +19,12 @@ - - + + - + - +