diff --git a/.gitignore b/.gitignore index 45e8f02a5..5ce039cab 100644 --- a/.gitignore +++ b/.gitignore @@ -347,4 +347,7 @@ healthchecksdb /asar/ setup/Output/ patch-config* -**/.DS_Store \ No newline at end of file +**/.DS_Store +**/*.db +**/*.db-shm +**/*.db-wal diff --git a/TrackerCouncil.Smz3.sln b/TrackerCouncil.Smz3.sln index b98cee2dd..b9543006e 100644 --- a/TrackerCouncil.Smz3.sln +++ b/TrackerCouncil.Smz3.sln @@ -11,11 +11,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrackerCouncil.Smz3.Shared" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrackerCouncil.Smz3.Tools", "src\TrackerCouncil.Smz3.Tools\TrackerCouncil.Smz3.Tools.csproj", "{B594A95C-7B87-447A-8A80-EBF6991EB04C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrackerCouncil.Smz3.UI.Legacy", "src\TrackerCouncil.Smz3.UI.Legacy\TrackerCouncil.Smz3.UI.Legacy.csproj", "{CCEDE012-894B-48F6-811E-B2324E74C604}" - ProjectSection(ProjectDependencies) = postProject - {A9465041-3416-4C60-A160-1454A045B90F} = {A9465041-3416-4C60-A160-1454A045B90F} - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F11C9119-7B5E-46E4-9A58-E157B80C7E9F}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig @@ -69,10 +64,6 @@ Global {B594A95C-7B87-447A-8A80-EBF6991EB04C}.Debug|Any CPU.Build.0 = Debug|Any CPU {B594A95C-7B87-447A-8A80-EBF6991EB04C}.Release|Any CPU.ActiveCfg = Release|Any CPU {B594A95C-7B87-447A-8A80-EBF6991EB04C}.Release|Any CPU.Build.0 = Release|Any CPU - {CCEDE012-894B-48F6-811E-B2324E74C604}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CCEDE012-894B-48F6-811E-B2324E74C604}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CCEDE012-894B-48F6-811E-B2324E74C604}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CCEDE012-894B-48F6-811E-B2324E74C604}.Release|Any CPU.Build.0 = Release|Any CPU {286CCBD5-FB69-4120-A0DB-FBDAD5C43072}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {286CCBD5-FB69-4120-A0DB-FBDAD5C43072}.Debug|Any CPU.Build.0 = Debug|Any CPU {286CCBD5-FB69-4120-A0DB-FBDAD5C43072}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/TrackerCouncil.Smz3.Abstractions/IItemService.cs b/src/TrackerCouncil.Smz3.Abstractions/IItemService.cs deleted file mode 100644 index ad716c186..000000000 --- a/src/TrackerCouncil.Smz3.Abstractions/IItemService.cs +++ /dev/null @@ -1,168 +0,0 @@ -using TrackerCouncil.Smz3.Shared; -using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; -using TrackerCouncil.Smz3.Data.WorldData; -using TrackerCouncil.Smz3.Data.WorldData.Regions; -using TrackerCouncil.Smz3.Shared.Enums; - -namespace TrackerCouncil.Smz3.Abstractions; - -/// -/// Defines methods for managing items and their tracking state. -/// -public interface IItemService -{ - /// - /// Enumerates all items that can be tracked for all players. - /// - /// A collection of items. - IEnumerable AllItems(); - - /// - /// Enumerates all items that can be tracked for the local player. - /// - /// A collection of items. - IEnumerable LocalPlayersItems(); - - /// - /// Enumarates all currently tracked items for the local player. - /// - /// - /// A collection of items that have been tracked at least once. - /// - IEnumerable TrackedItems(); - - /// - /// Finds the item with the specified name for the local player. - /// - /// - /// The name of the item or item stage to find. - /// - /// - /// An representing the item with the specified - /// name, or if there is no item that has the - /// specified name. - /// - Item? FirstOrDefault(string name); - - /// - /// Finds an item with the specified item type for the local player. - /// - /// The type of item to find. - /// - /// An representing the item. If there are - /// multiple configured items with the same type, this method returns - /// one at random. If there no configured items with the specified type, - /// this method returns . - /// - Item? FirstOrDefault(ItemType itemType); - - /// - /// Returns a random name for the specified item including article, e.g. - /// "an E-Tank" or "the Book of Mudora". - /// - /// The type of item whose name to get. - /// - /// The name of the type of item, including "a", "an" or "the" if - /// applicable. - /// - string GetName(ItemType itemType); - - /// - /// Indicates whether an item of the specified type has been tracked - /// for the local player. - /// - /// The type of item to check. - /// - /// if an item with the specified type has been - /// tracked at least once; otherwise, . - /// - bool IsTracked(ItemType itemType); - - /// - /// Finds an reward with the specified item type. - /// - /// The type of reward to find. - /// - /// An representing the reward. If there are - /// multiple configured rewards with the same type, this method returns - /// one at random. If there no configured rewards with the specified type, - /// this method returns . - /// - Reward? FirstOrDefault(RewardType rewardType); - - /// - /// Returns a random name for the specified item including article, e.g. - /// "a blue crystal" or "the green pendant". - /// - /// The reward of item whose name to get. - /// - /// The name of the reward of item, including "a", "an" or "the" if - /// applicable. - /// - string GetName(RewardType rewardType); - - /// - /// Enumerates all rewards that can be tracked for the local player. - /// - /// A collection of rewards. - - IEnumerable AllRewards(); - - /// - /// Enumerates all rewards that can be tracked for the local player. - /// - /// A collection of rewards. - - IEnumerable LocalPlayersRewards(); - - /// - /// Enumarates all currently tracked rewards for the local player. - /// - /// - /// A collection of reward that have been tracked. - /// - IEnumerable TrackedRewards(); - - /// - /// Enumerates all bosses that can be tracked for all players. - /// - /// A collection of bosses. - - IEnumerable AllBosses(); - - /// - /// Enumerates all bosses that can be tracked for the local player. - /// - /// A collection of bosses. - - IEnumerable LocalPlayersBosses(); - - /// - /// Enumarates all currently tracked bosses for the local player. - /// - /// - /// A collection of bosses that have been tracked. - /// - IEnumerable TrackedBosses(); - - /// - /// Retrieves the progression containing all of the tracked items, rewards, and bosses - /// for determining in logic locations - /// - /// If it should be assumed that the player has all keys and keycards - /// - Progression GetProgression(bool assumeKeys); - - /// - /// Retrieves the progression containing all of the tracked items, rewards, and bosses - /// for determining in logic locations - /// - /// The area being looked at to see if keys/keycards should be assumed or not - /// - Progression GetProgression(IHasLocations area); - - /// - /// Clears cached progression - /// - void ResetProgression(); -} diff --git a/src/TrackerCouncil.Smz3.Abstractions/IPlayerProgressionService.cs b/src/TrackerCouncil.Smz3.Abstractions/IPlayerProgressionService.cs new file mode 100644 index 000000000..2105572a5 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Abstractions/IPlayerProgressionService.cs @@ -0,0 +1,75 @@ +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Abstractions; + +/// +/// Defines methods for retrieving the progression and individual tracking statuses +/// +public interface IPlayerProgressionService +{ + /// + /// Enumarates all currently tracked items for the local player. + /// + /// + /// A collection of items that have been tracked at least once. + /// + IEnumerable TrackedItems(); + + /// + /// Indicates whether an item of the specified type has been tracked + /// for the local player. + /// + /// The type of item to check. + /// + /// if an item with the specified type has been + /// tracked at least once; otherwise, . + /// + bool IsTracked(ItemType itemType); + + /// + /// Enumarates all currently tracked rewards for the local player. + /// + /// + /// A collection of reward that have been tracked. + /// + IEnumerable TrackedRewards(); + + /// + /// Enumarates all currently tracked bosses for the local player. + /// + /// + /// A collection of bosses that have been tracked. + /// + IEnumerable TrackedBosses(); + + /// + /// Retrieves the progression containing all of the tracked items, rewards, and bosses + /// for determining in logic locations + /// + /// If it should be assumed that the player has all keys and keycards + /// + Progression GetProgression(bool assumeKeys); + + /// + /// Retrieves the progression containing all of the tracked items, rewards, and bosses + /// for determining in logic locations + /// + /// The area being looked at to see if keys/keycards should be assumed or not + /// + Progression GetProgression(IHasLocations area); + + /// + /// Retrieves the progression containing all of the tracked items, rewards, and bosses + /// for determining in logic locations + /// + /// The location being looked at to see if keys/keycards should be assumed or not + /// + Progression GetProgression(Location location); + + /// + /// Clears cached progression + /// + void ResetProgression(); +} diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerBossService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerBossService.cs new file mode 100644 index 000000000..0e4ed9ee7 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerBossService.cs @@ -0,0 +1,58 @@ +using TrackerCouncil.Smz3.Data.Tracking; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; + +namespace TrackerCouncil.Smz3.Abstractions; + +public interface ITrackerBossService +{ + public event EventHandler? BossUpdated; + + /// + /// Marks a dungeon as cleared and, if possible, tracks the boss reward. + /// + /// The dungeon that was cleared. + /// The speech recognition confidence. + /// If this was cleared by the auto tracker + /// + /// if the command implies the boss was killed; + /// if the boss was simply "tracked". + /// + public void MarkBossAsDefeated(IHasBoss region, float? confidence = null, bool autoTracked = false, bool admittedGuilt = false); + + /// + /// Marks a boss as defeated. + /// + /// The boss that was defeated. + /// + /// if the command implies the boss was killed; + /// if the boss was simply "tracked". + /// + /// The speech recognition confidence. + /// If this was tracked by the auto tracker + public void MarkBossAsDefeated(Boss boss, bool admittedGuilt = true, float? confidence = null, + bool autoTracked = false); + + /// + /// Un-marks a boss as defeated. + /// + /// The boss that should be 'revived'. + /// The speech recognition confidence. + public void MarkBossAsNotDefeated(Boss boss, float? confidence = null); + + /// + /// Un-marks a dungeon as cleared and, if possible, untracks the boss + /// reward. + /// + /// The dungeon that should be un-cleared. + /// The speech recognition confidence. + public void MarkBossAsNotDefeated(IHasBoss region, float? confidence = null); + + public void UpdateAccessibility(Progression? actualProgression = null, Progression? withKeysProgression = null); + + public void UpdateAccessibility(Boss boss, Progression? actualProgression = null, Progression? withKeysProgression = null); + + public void UpdateAccessibility(IHasBoss region, Progression? actualProgression = null, Progression? withKeysProgression = null); + + +} diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs new file mode 100644 index 000000000..b80350a4d --- /dev/null +++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerGameStateService.cs @@ -0,0 +1,111 @@ +using MSURandomizerLibrary.Configs; +using TrackerCouncil.Smz3.Data.Tracking; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared.Models; + +namespace TrackerCouncil.Smz3.Abstractions; + +public interface ITrackerGameStateService +{ + /// + /// Gets if the local player has beaten the game or not + /// + public bool HasBeatenGame { get; } + + /// + /// The last viewed hint tile or set of locations + /// + public ViewedObject? LastViewedObject { get; } + + /// + /// The region the player is currently in according to the Auto Tracker + /// + public Region? CurrentRegion { get; } + + /// + /// The map to display for the player + /// + public string CurrentMap { get; } + + /// + /// The current track number being played + /// + public int CurrentTrackNumber { get; } + + /// + /// Occurs when the map has been updated + /// + public event EventHandler? MapUpdated; + + /// + /// Occurs when the map has been updated + /// + public event EventHandler? BeatGame; + + /// + /// Occurs when the map has died + /// + public event EventHandler? PlayerDied; + + /// + /// Occurs when a hint tile is viewed that is for a region, dungeon, or group of locations + /// + public event EventHandler? HintTileUpdated; + + /// + /// Occurs when the current played track number is updated + /// + public event EventHandler? TrackNumberUpdated; + + /// + /// Occurs when the current track has changed + /// + public event EventHandler? TrackChanged; + + /// + /// Updates the region that the player is in + /// + /// The region the player is in + /// Set to true to update the map for the player to match the region + /// If the time should be reset if this is the first region update + public void UpdateRegion(Region region, bool updateMap = false, bool resetTime = false); + + /// + /// Updates the map to display for the user + /// + /// The name of the map + public void UpdateMap(string map); + + /// + /// Called when the game is beaten by entering triforce room + /// or entering the ship after beating both bosses + /// + /// If this was triggered by the auto tracker + public void GameBeaten(bool autoTracked); + + /// + /// Called when the player has died + /// + public void TrackDeath(bool autoTracked); + + /// + /// Updates the current track number being played + /// + /// The number of the track + public void UpdateTrackNumber(int number); + + /// + /// Updates the current track being played + /// + /// The current MSU pack + /// The current track + /// Formatted output text matching the requested style + public void UpdateTrack(Msu msu, Track track, string outputText); + + public void UpdateHintTile(PlayerHintTile hintTile); + + public void UpdateLastMarkedLocations(List locations); + + public void ClearLastViewedObject(float confidence); +} diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerItemService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerItemService.cs new file mode 100644 index 000000000..35d1c754f --- /dev/null +++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerItemService.cs @@ -0,0 +1,81 @@ +using TrackerCouncil.Smz3.Data.Tracking; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; + +namespace TrackerCouncil.Smz3.Abstractions; + +public interface ITrackerItemService +{ + public event EventHandler? ItemTracked; + + /// + /// Tracks the specifies item. + /// + /// The item data to track. + /// + /// The text that was tracked, when triggered by voice command. + /// + /// The speech recognition confidence. + /// + /// to attempt to clear a location for the + /// tracked item; if that is done by the caller. + /// + /// If this was tracked by the auto tracker + /// The location an item was tracked from + /// If the item was gifted to the player by tracker or another player + /// If tracker should not say anything + /// + /// if the item was actually tracked; if the item could not be tracked, e.g. when + /// tracking Bow twice. + /// + bool TrackItem(Item item, string? trackedAs = null, float? confidence = null, bool tryClear = true, + bool autoTracked = false, Location? location = null, bool giftedItem = false, bool silent = false); + + /// + /// Tracks the specifies item and clears it from the specified dungeon. + /// + /// The item data to track. + /// + /// The text that was tracked, when triggered by voice command. + /// + /// The dungeon the item was tracked in. + /// The speech recognition confidence. + void TrackItemFrom(Item item, IHasTreasure hasTreasure, string? trackedAs = null, float? confidence = null); + + /// + /// Tracks the specified item and clears it from the specified room. + /// + /// The item data to track. + /// + /// The text that was tracked, when triggered by voice command. + /// + /// The area the item was found in. + /// The speech recognition confidence. + void TrackItemFrom(Item item, IHasLocations area, string? trackedAs = null, float? confidence = null); + + /// + /// Sets the item count for the specified item. + /// + /// The item to track. + /// + /// The amount of the item that is in the player's inventory now. + /// + /// The speech recognition confidence. + void TrackItemAmount(Item item, int count, float confidence); + + /// + /// Tracks multiple items at the same time + /// + /// The items to track + /// If the items were tracked via auto tracker + /// If the items were gifted to the player + void TrackItems(List items, bool autoTracked, bool giftedItem); + + /// + /// Removes an item from the tracker. + /// + /// The item to untrack. + /// The speech recognition confidence. + void UntrackItem(Item item, float? confidence = null); +} diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerLocationService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerLocationService.cs new file mode 100644 index 000000000..116f033cc --- /dev/null +++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerLocationService.cs @@ -0,0 +1,88 @@ +using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; +using TrackerCouncil.Smz3.Data.Tracking; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Abstractions; + +public interface ITrackerLocationService +{ + public event EventHandler LocationCleared; + + public event EventHandler LocationMarked; + + /// + /// Clears an item from the specified location. + /// + /// The location to clear. + /// The speech recognition confidence. + /// If this was tracked by the auto tracker + void Clear(Location location, float? confidence = null, bool autoTracked = false, bool stateResponse = true, bool allowLocationComments = false, bool updateTreasureCount = true); + + /// + /// Unclears an item from the specified location. + /// + /// The location to clear. + /// The speech recognition confidence. + /// If this was tracked by the auto tracker + void Unclear(Location location, bool updateTreasureCount = true); + + /// + /// Clears an item from the specified locations. + /// + /// The locations to clear. + /// The speech recognition confidence + void Clear(List locations, float? confidence = null); + + /// + /// Marks an item at the specified location. + /// + /// The location to mark. + /// + /// The item that is found at . + /// + /// The speech recognition confidence. + /// If the marked location was auto tracked + public void MarkLocation(Location location, Item item, float? confidence = null, bool autoTracked = false); + + /// + /// Marks an item at the specified location. + /// + /// The location to mark. + /// + /// The item that is found at . + /// + /// The speech recognition confidence. + /// If the marked location was auto tracked + /// The metadata of the item + public void MarkLocation(Location location, ItemType item, float? confidence = null, bool autoTracked = false, + ItemData? metadata = null); + + /// + /// Clears every item in the specified area, optionally tracking the + /// cleared items. + /// + /// The area whose items to clear. + /// + /// true to track any items found; false to only clear the + /// affected locations. + /// + /// + /// true to include every item in , even + /// those that are not in logic. false to only include chests + /// available with current items. + /// + /// The speech recognition confidence. + /// + /// Set to true to ignore keys when clearing the location. + /// + public void ClearArea(IHasLocations area, bool trackItems, bool includeUnavailable = false, + float? confidence = null, bool assumeKeys = false); + + public void UpdateAccessibility(bool unclearedOnly = true, Progression? actualProgression = null, Progression? withKeysProgression = null); + + public void UpdateAccessibility(IEnumerable locations, Progression? actualProgression = null, Progression? withKeysProgression = null); + + public void UpdateAccessibility(Location location, Progression? actualProgression = null, Progression? withKeysProgression = null); +} diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerModeService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerModeService.cs new file mode 100644 index 000000000..83eb542bb --- /dev/null +++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerModeService.cs @@ -0,0 +1,89 @@ +using TrackerCouncil.Smz3.Data.Tracking; + +namespace TrackerCouncil.Smz3.Abstractions; + +public interface ITrackerModeService +{ + /// + /// Indicates whether Tracker is in Go Mode. + /// + public bool GoMode { get; protected set; } + + /// + /// Indicates whether Tracker is in Peg World mode. + /// + public bool PegWorldMode { get; protected set; } + + /// + /// Indicates whether Tracker is in Shaktool mode. + /// + public bool ShaktoolMode { get; protected set; } + + /// + /// The number of pegs that have been pegged for Peg World mode + /// + public int PegsPegged { get; protected set; } + + // + /// Occurs when Peg World mode has been toggled on. + /// + public event EventHandler? ToggledPegWorldModeOn; + + /// + /// Occurs when going to Shaktool + /// + public event EventHandler? ToggledShaktoolMode; + + /// + /// Occurs when a Peg World peg has been pegged. + /// + public event EventHandler? PegPegged; + + /// + /// Occurs when Go mode has been turned on. + /// + public event EventHandler? GoModeToggledOn; + + /// + /// Occurs when Go mode has been turned off. + /// + public event EventHandler? GoModeToggledOff; + + /// + /// Toggles Go Mode on. + /// + /// The speech recognition confidence. + public void ToggleGoMode(float? confidence = null); + + /// + /// Pegs a Peg World peg. + /// + /// The speech recognition confidence. + public void Peg(float? confidence = null); + + public void SetPegs(int count); + + /// + /// Starts Peg World mode. + /// + /// The speech recognition confidence. + public void StartPegWorldMode(float? confidence = null); + + /// + /// Turns Peg World mode off. + /// + /// The speech recognition confidence. + public void StopPegWorldMode(float? confidence = null); + + /// + /// Starts Peg World mode. + /// + /// The speech recognition confidence. + public void StartShaktoolMode(float? confidence = null); + + /// + /// Turns Peg World mode off. + /// + /// The speech recognition confidence. + public void StopShaktoolMode(float? confidence = null); +} diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerPrerequisiteService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerPrerequisiteService.cs new file mode 100644 index 000000000..8807ad064 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerPrerequisiteService.cs @@ -0,0 +1,17 @@ +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Abstractions; + +public interface ITrackerPrerequisiteService +{ + /// + /// Sets the dungeon's medallion requirement to the specified item. + /// + /// The dungeon to mark. + /// The medallion that is required. + /// The speech recognition confidence. + /// If the marked dungeon requirement was autotracked + public void SetDungeonRequirement(IHasPrerequisite region, ItemType? medallion = null, float? confidence = null, + bool autoTracked = false); +} diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerRewardService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerRewardService.cs new file mode 100644 index 000000000..165c575bc --- /dev/null +++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerRewardService.cs @@ -0,0 +1,44 @@ +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Abstractions; + +public interface ITrackerRewardService +{ + /// + /// Sets the dungeon's reward to the specific pendant or crystal. + /// + /// The dungeon to mark. + /// + /// The type of pendant or crystal, or null to cycle through the + /// possible rewards. + /// + /// The speech recognition confidence. + /// If this was called by the auto tracker + public void SetAreaReward(IHasReward rewardRegion, RewardType? reward = null, float? confidence = null, + bool autoTracked = false); + + /// + /// Gives the area's reward to the player + /// + /// The region to give the reward for + /// If this is from auto tracking + /// If the response should be stated + public void GiveAreaReward(IHasReward rewardRegion, bool isAutoTracked, bool stateResponse); + + public void RemoveAreaReward(IHasReward rewardRegion, bool stateResponse); + + /// + /// Sets the reward of all unmarked dungeons. + /// + /// The reward to set. + /// The speech recognition confidence. + public void SetUnmarkedRewards(RewardType reward, float? confidence = null); + + public void UpdateAccessibility(Progression? actualProgression = null, Progression? withKeysProgression = null); + + public void UpdateAccessibility(Reward reward, Progression? actualProgression = null, Progression? withKeysProgression = null); + + public void UpdateAccessibility(IHasReward region, Progression? actualProgression = null, Progression? withKeysProgression = null); +} diff --git a/src/TrackerCouncil.Smz3.Abstractions/ITrackerTreasureService.cs b/src/TrackerCouncil.Smz3.Abstractions/ITrackerTreasureService.cs new file mode 100644 index 000000000..c28f22acf --- /dev/null +++ b/src/TrackerCouncil.Smz3.Abstractions/ITrackerTreasureService.cs @@ -0,0 +1,43 @@ +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; + +namespace TrackerCouncil.Smz3.Abstractions; + +public interface ITrackerTreasureService +{ + /// + /// Removes one or more items from the available treasure in the specified region. + /// + /// The dungeon. + /// The number of treasures to track. + /// The speech recognition confidence. + /// If this was called by the auto tracker + /// If tracker should state the treasure amount + /// + /// true if treasure was tracked; false if there is no + /// treasure left to track. + /// + /// + /// This method adds to the undo history if the return value is + /// true. + /// + /// + /// is less than 1. + /// + bool TrackDungeonTreasure(IHasTreasure region, float? confidence = null, int amount = 1, + bool autoTracked = false, bool stateResponse = true); + + public bool UntrackDungeonTreasure(IHasTreasure region, int amount = 1); + + Action? TryTrackDungeonTreasure(Location location, float? confidence, bool autoTracked = false, + bool stateResponse = true); + + public Action? TryUntrackDungeonTreasure(Location location); + + /// + /// Marks all locations and treasure within a dungeon as cleared. + /// + /// The dungeon to clear. + /// The speech recognition confidence. + public void ClearDungeon(IHasTreasure treasureRegion, float? confidence = null); +} diff --git a/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs b/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs index 723ca45c9..c6b273b14 100644 --- a/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs +++ b/src/TrackerCouncil.Smz3.Abstractions/TrackerBase.cs @@ -1,12 +1,9 @@ -using MSURandomizerLibrary.Configs; using TrackerCouncil.Smz3.Data.Configuration; using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.Tracking; using TrackerCouncil.Smz3.Data.WorldData; -using TrackerCouncil.Smz3.Data.WorldData.Regions; -using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Shared.Models; namespace TrackerCouncil.Smz3.Abstractions; @@ -19,56 +16,6 @@ public abstract class TrackerBase /// public event EventHandler? SpeechRecognized; - /// - /// Occurs when one or more items have been tracked. - /// - public event EventHandler? ItemTracked; - - /// - /// Occurs when a location has been cleared. - /// - public event EventHandler? LocationCleared; - - /// - /// Occurs when Peg World mode has been toggled on. - /// - public event EventHandler? ToggledPegWorldModeOn; - - /// - /// Occurs when going to Shaktool - /// - public event EventHandler? ToggledShaktoolMode; - - /// - /// Occurs when a Peg World peg has been pegged. - /// - public event EventHandler? PegPegged; - - /// - /// Occurs when the properties of a dungeon have changed. - /// - public event EventHandler? DungeonUpdated; - - /// - /// Occurs when the properties of a boss have changed. - /// - public event EventHandler? BossUpdated; - - /// - /// Occurs when the marked locations have changed - /// - public event EventHandler? MarkedLocationsUpdated; - - /// - /// Occurs when Go mode has been turned on. - /// - public event EventHandler? GoModeToggledOn; - - /// - /// Occurs when Go mode has been turned off. - /// - public event EventHandler? GoModeToggledOff; - /// /// Occurs when the last action was undone. /// @@ -80,70 +27,36 @@ public abstract class TrackerBase public event EventHandler? StateLoaded; /// - /// Occurs when the map has been updated + /// Occurs when the voice recognition has been enabled or disabled /// - public event EventHandler? MapUpdated; + public event EventHandler? VoiceRecognitionEnabledChanged; /// - /// Occurs when the map has been updated + /// Gets a reference to the . /// - public event EventHandler? BeatGame; + public IPlayerProgressionService PlayerProgressionService { get; protected init; } = null!; - /// - /// Occurs when the map has died - /// - public event EventHandler? PlayerDied; + public ITrackerTreasureService TreasureTracker { get; protected set; } = null!; - /// - /// Occurs when the current played track number is updated - /// - public event EventHandler? TrackNumberUpdated; + public ITrackerItemService ItemTracker { get; protected set; } = null!; - /// - /// Occurs when the current track has changed - /// - public event EventHandler? TrackChanged; + public ITrackerLocationService LocationTracker { get; protected set; } = null!; - /// - /// Occurs when a hint tile is viewed that is for a region, dungeon, or group of locations - /// - public event EventHandler? HintTileUpdated; + public ITrackerRewardService RewardTracker { get; protected set; } = null!; - /// - /// Occurs when the voice recognition has been enabled or disabled - /// - public event EventHandler? VoiceRecognitionEnabledChanged; + public ITrackerBossService BossTracker { get; protected set; } = null!; - /// - /// Gets a reference to the . - /// - public IItemService ItemService { get; protected init; } = null!; + public ITrackerPrerequisiteService PrerequisiteTracker { get; protected set; } = null!; - /// - /// The number of pegs that have been pegged for Peg World mode - /// - public int PegsPegged { get; protected set; } + public ITrackerModeService ModeTracker { get; protected set; } = null!; + + public ITrackerGameStateService GameStateTracker { get; protected set; } = null!; /// /// Gets the world for the currently tracked playthrough. /// public World World { get; protected init; } = null!; - /// - /// Indicates whether Tracker is in Go Mode. - /// - public bool GoMode { get; protected set; } - - /// - /// Indicates whether Tracker is in Peg World mode. - /// - public bool PegWorldMode { get; protected set; } - - /// - /// Indicates whether Tracker is in Shaktool mode. - /// - public bool ShaktoolMode { get; protected set; } - /// /// If the speech recognition engine was fully initialized /// @@ -195,21 +108,6 @@ public abstract class TrackerBase /// public string? RomPath { get; protected set; } - /// - /// The region the player is currently in according to the Auto Tracker - /// - public Region? CurrentRegion { get; protected set; } - - /// - /// The map to display for the player - /// - public string CurrentMap { get; protected set; } = ""; - - /// - /// The current track number being played - /// - public int CurrentTrackNumber { get; protected set; } - /// /// Gets a string describing tracker's mood. /// @@ -238,7 +136,7 @@ public abstract class TrackerBase /// /// Module that houses the history /// - protected IHistoryService History { get; init; } = null!; + public IHistoryService History { get; init; } = null!; /// /// Gets or sets a value indicating whether Tracker may give hints when @@ -252,16 +150,6 @@ public abstract class TrackerBase /// public bool SpoilersEnabled { get; set; } - /// - /// Gets if the local player has beaten the game or not - /// - public bool HasBeatenGame { get; protected set; } - - /// - /// The last viewed hint tile or set of locations - /// - public ViewedObject? LastViewedObject { get; set; } - /// /// Attempts to replace a user name with a pronunciation-corrected /// version of it. @@ -294,68 +182,14 @@ public abstract class TrackerBase /// public abstract Task SaveAsync(); + public abstract void MarkAsDirty(bool isDirty = true); + /// /// Undoes the last operation. /// /// The speech recognition confidence. public abstract void Undo(float confidence); - /// - /// Toggles Go Mode on. - /// - /// The speech recognition confidence. - public abstract void ToggleGoMode(float? confidence = null); - - /// - /// Removes one or more items from the available treasure in the - /// specified dungeon. - /// - /// The dungeon. - /// The number of treasures to track. - /// The speech recognition confidence. - /// If this was called by the auto tracker - /// If tracker should state the treasure ammount - /// - /// true if treasure was tracked; false if there is no - /// treasure left to track. - /// - /// - /// This method adds to the undo history if the return value is - /// true. - /// - /// - /// is less than 1. - /// - public abstract bool TrackDungeonTreasure(IDungeon dungeon, float? confidence = null, int amount = 1, bool autoTracked = false, bool stateResponse = true); - - /// - /// Sets the dungeon's reward to the specific pendant or crystal. - /// - /// The dungeon to mark. - /// - /// The type of pendant or crystal, or null to cycle through the - /// possible rewards. - /// - /// The speech recognition confidence. - /// If this was called by the auto tracker - public abstract void SetDungeonReward(IDungeon dungeon, RewardType? reward = null, float? confidence = null, bool autoTracked = false); - - /// - /// Sets the reward of all unmarked dungeons. - /// - /// The reward to set. - /// The speech recognition confidence. - public abstract void SetUnmarkedDungeonReward(RewardType reward, float? confidence = null); - - /// - /// Sets the dungeon's medallion requirement to the specified item. - /// - /// The dungeon to mark. - /// The medallion that is required. - /// The speech recognition confidence. - /// If the marked dungeon requirement was auto tracked - public abstract void SetDungeonRequirement(IDungeon dungeon, ItemType? medallion = null, float? confidence = null, bool autoTracked = false); - /// /// Starts voice recognition. /// @@ -476,274 +310,6 @@ public abstract bool Say( /// public abstract void Error(); - /// - /// Tracks the specifies item. - /// - /// The item data to track. - /// - /// The text that was tracked, when triggered by voice command. - /// - /// The speech recognition confidence. - /// - /// to attempt to clear a location for the - /// tracked item; if that is done by the caller. - /// - /// If this was tracked by the auto tracker - /// The location an item was tracked from - /// If the item was gifted to the player by tracker or another player - /// If tracker should not say anything - /// - /// if the item was actually tracked; if the item could not be tracked, e.g. when - /// tracking Bow twice. - /// - public abstract bool TrackItem(Item item, string? trackedAs = null, float? confidence = null, bool tryClear = true, bool autoTracked = false, Location? location = null, bool giftedItem = false, bool silent = false); - - /// - /// Tracks multiple items at the same time - /// - /// The items to track - /// If the items were tracked via auto tracker - /// If the items were gifted to the player - public abstract void TrackItems(List items, bool autoTracked, bool giftedItem); - - /// - /// Removes an item from the tracker. - /// - /// The item to untrack. - /// The speech recognition confidence. - public abstract void UntrackItem(Item item, float? confidence = null); - - /// - /// Tracks the specifies item and clears it from the specified dungeon. - /// - /// The item data to track. - /// - /// The text that was tracked, when triggered by voice command. - /// - /// The dungeon the item was tracked in. - /// The speech recognition confidence. - public abstract void TrackItem(Item item, IDungeon dungeon, string? trackedAs = null, float? confidence = null); - - /// - /// Tracks the specified item and clears it from the specified room. - /// - /// The item data to track. - /// - /// The text that was tracked, when triggered by voice command. - /// - /// The area the item was found in. - /// The speech recognition confidence. - public abstract void TrackItem(Item item, IHasLocations area, string? trackedAs = null, float? confidence = null); - - /// - /// Sets the item count for the specified item. - /// - /// The item to track. - /// - /// The amount of the item that is in the player's inventory now. - /// - /// The speech recognition confidence. - public abstract void TrackItemAmount(Item item, int count, float confidence); - - /// - /// Clears every item in the specified area, optionally tracking the - /// cleared items. - /// - /// The area whose items to clear. - /// - /// true to track any items found; false to only clear the - /// affected locations. - /// - /// - /// true to include every item in , even - /// those that are not in logic. false to only include chests - /// available with current items. - /// - /// The speech recognition confidence. - /// - /// Set to true to ignore keys when clearing the location. - /// - public abstract void ClearArea(IHasLocations area, bool trackItems, bool includeUnavailable = false, float? confidence = null, bool assumeKeys = false); - - /// - /// Marks all locations and treasure within a dungeon as cleared. - /// - /// The dungeon to clear. - /// The speech recognition confidence. - public abstract void ClearDungeon(IDungeon dungeon, float? confidence = null); - - /// - /// Clears an item from the specified location. - /// - /// The location to clear. - /// The speech recognition confidence. - /// If this was tracked by the auto tracker - public abstract void Clear(Location location, float? confidence = null, bool autoTracked = false); - - /// - /// Clears an item from the specified locations. - /// - /// The locations to clear. - /// The speech recognition confidence. - public abstract void Clear(List locations, float? confidence = null); - - /// - /// Marks a dungeon as cleared and, if possible, tracks the boss reward. - /// - /// The dungeon that was cleared. - /// The speech recognition confidence. - /// If this was cleared by the auto tracker - public abstract void MarkDungeonAsCleared(IDungeon dungeon, float? confidence = null, bool autoTracked = false); - - /// - /// Marks a boss as defeated. - /// - /// The boss that was defeated. - /// - /// if the command implies the boss was killed; - /// if the boss was simply "tracked". - /// - /// The speech recognition confidence. - /// If this was tracked by the auto tracker - public abstract void MarkBossAsDefeated(Boss boss, bool admittedGuilt = true, float? confidence = null, bool autoTracked = false); - - /// - /// Un-marks a boss as defeated. - /// - /// The boss that should be 'revived'. - /// The speech recognition confidence. - public abstract void MarkBossAsNotDefeated(Boss boss, float? confidence = null); - - /// - /// Un-marks a dungeon as cleared and, if possible, untracks the boss - /// reward. - /// - /// The dungeon that should be un-cleared. - /// The speech recognition confidence. - public abstract void MarkDungeonAsIncomplete(IDungeon dungeon, float? confidence = null); - - /// - /// Marks an item at the specified location. - /// - /// The location to mark. - /// - /// The item that is found at . - /// - /// The speech recognition confidence. - /// If the marked location was auto tracked - public abstract void MarkLocation(Location location, Item item, float? confidence = null, bool autoTracked = false); - - /// - /// Marks an item at the specified location. - /// - /// The location to mark. - /// The item that is found at . - /// The speech recognition confidence. - /// If the marked location was auto tracked - /// The metadata of the item - public abstract void MarkLocation(Location location, ItemType item, float? confidence = null, - bool autoTracked = false, ItemData? metadata = null); - - /// - /// Pegs a Peg World peg, incrementing the count by one. - /// - /// The speech recognition confidence. - public abstract void Peg(float? confidence = null); - - /// - /// Sets the Peg World peg count to the given value. - /// - /// The new count of hammered pegs. - public abstract void SetPegs(int count); - - /// - /// Starts Peg World mode. - /// - /// The speech recognition confidence. - public abstract void StartPegWorldMode(float? confidence = null); - - /// - /// Turns Peg World mode off. - /// - /// The speech recognition confidence. - public abstract void StopPegWorldMode(float? confidence = null); - - /// - /// Starts Peg World mode. - /// - /// The speech recognition confidence. - public abstract void StartShaktoolMode(float? confidence = null); - - /// - /// Turns Peg World mode off. - /// - /// The speech recognition confidence. - public abstract void StopShaktoolMode(float? confidence = null); - - /// - /// Updates the region that the player is in - /// - /// The region the player is in - /// Set to true to update the map for the player to match the region - /// If the time should be reset if this is the first region update - public abstract void UpdateRegion(Region region, bool updateMap = false, bool resetTime = false); - - /// - /// Updates the map to display for the user - /// - /// The name of the map - public abstract void UpdateMap(string map); - - /// - /// Called when the game is beaten by entering triforce room - /// or entering the ship after beating both bosses - /// - /// If this was triggered by the auto tracker - public abstract void GameBeaten(bool autoTracked); - - /// - /// Called when the player has died - /// - public abstract void TrackDeath(bool autoTracked); - - /// - /// Updates the current track number being played - /// - /// The number of the track - public abstract void UpdateTrackNumber(int number); - - /// - /// Updates the current track being played - /// - /// The current MSU pack - /// The current track - /// Formatted output text matching the requested style - public abstract void UpdateTrack(Msu msu, Track track, string outputText); - - /// - /// Marks a hint tile as viewed or cleared - /// - /// Details about the hint for the player - public abstract void UpdateHintTile(PlayerHintTile playerHintTile); - - /// - /// Updates the most recently marked locations to be able to clear later - /// - /// List of locations that were just marked - public abstract void UpdateLastMarkedLocations(List locations); - - /// - /// Clears the most recently marked locations - /// - /// Voice recognition confidence - public abstract void ClearLastViewedObject(float confidence); - - /// - /// Reports how many Hyper Beam shots were needed to defeat Mother Brain - /// - public abstract void CountHyperBeamShots(int count); - /// /// Resets the idle timers when tracker will comment on nothing happening /// @@ -757,15 +323,9 @@ public abstract void MarkLocation(Location location, ItemType item, float? confi /// public abstract void AddUndo(Action undo); - /// - /// Determines whether or not the specified reward is worth getting. - /// - /// The dungeon reward. - /// - /// if the reward leads to something good; - /// otherwise, . - /// - public abstract bool IsWorth(RewardType reward); + public abstract (Action Action, DateTime UndoTime) PopUndo(); + + public abstract void UpdateAllAccessibility(bool forceRefreshAll, params Item[] items); /// /// Formats a string so that it will be pronounced correctly by the @@ -776,17 +336,6 @@ public abstract void MarkLocation(Location location, ItemType item, float? confi public static string CorrectPronunciation(string name) => name.Replace("Samus", "Sammus"); - /// - /// Determines whether or not the specified item is worth getting. - /// - /// The item whose worth to consider. - /// - /// is the item is worth getting or leads to - /// another item that is worth getting; otherwise, . - /// - public abstract bool IsWorth(Item item); - /// /// Invokes the SpeechRecognized event /// @@ -796,96 +345,6 @@ protected virtual void OnSpeechRecognized(TrackerEventArgs args) SpeechRecognized?.Invoke(this, args); } - /// - /// Invokes the ItemTracked event - /// - /// - protected virtual void OnItemTracked(ItemTrackedEventArgs args) - { - ItemTracked?.Invoke(this, args); - } - - /// - /// Invokes the LocationCleared event - /// - /// - protected virtual void OnLocationCleared(LocationClearedEventArgs args) - { - LocationCleared?.Invoke(this, args); - } - - /// - /// Invokes the ToggledPegWorldModeOn event - /// - /// - protected virtual void OnToggledPegWorldModeOn(TrackerEventArgs args) - { - ToggledPegWorldModeOn?.Invoke(this, args); - } - - /// - /// Invokes the ToggledShaktoolMode event - /// - /// - protected virtual void OnToggledShaktoolMode(TrackerEventArgs args) - { - ToggledShaktoolMode?.Invoke(this, args); - } - - /// - /// Invokes the PegPegged event - /// - /// - protected virtual void OnPegPegged(TrackerEventArgs args) - { - PegPegged?.Invoke(this, args); - } - - /// - /// Invokes the DungeonUpdated event - /// - /// - protected virtual void OnDungeonUpdated(DungeonTrackedEventArgs args) - { - DungeonUpdated?.Invoke(this, args); - } - - /// - /// Invokes the BossUpdated event - /// - /// - protected virtual void OnBossUpdated(BossTrackedEventArgs args) - { - BossUpdated?.Invoke(this, args); - } - - /// - /// Invokes the MarkedLocationsUpdated event - /// - /// - protected virtual void OnMarkedLocationsUpdated(TrackerEventArgs args) - { - MarkedLocationsUpdated?.Invoke(this, args); - } - - /// - /// Invokes the GoModeToggledOn event - /// - /// - protected virtual void OnGoModeToggledOn(TrackerEventArgs args) - { - GoModeToggledOn?.Invoke(this, args); - } - - /// - /// Invokes the GoModeToggledOff event - /// - /// - protected virtual void OnGoModeToggledOff(TrackerEventArgs args) - { - GoModeToggledOff?.Invoke(this, args); - } - /// /// Invokes the ActionUndone event /// @@ -903,59 +362,6 @@ protected virtual void OnStateLoaded() StateLoaded?.Invoke(this, EventArgs.Empty); } - /// - /// Invokes the MapUpdated event - /// - protected virtual void OnMapUpdated() - { - MapUpdated?.Invoke(this, EventArgs.Empty); - } - - /// - /// Invokes the BeatGame event - /// - /// - protected virtual void OnBeatGame(TrackerEventArgs args) - { - BeatGame?.Invoke(this, args); - } - - /// - /// Invokes the PlayerDied event - /// - /// - protected virtual void OnPlayerDied(TrackerEventArgs args) - { - PlayerDied?.Invoke(this, args); - } - - /// - /// Invokes the TrackNumberUpdated event - /// - /// - protected virtual void OnTrackNumberUpdated(TrackNumberEventArgs args) - { - TrackNumberUpdated?.Invoke(this, args); - } - - /// - /// Invokes the TrackChanged event - /// - /// - protected virtual void OnTrackChanged(TrackChangedEventArgs args) - { - TrackChanged?.Invoke(this, args); - } - - /// - /// Invokes the HintTileUpdated event - /// - /// - protected virtual void OnHintTileUpdated(HintTileUpdatedEventArgs args) - { - HintTileUpdated?.Invoke(this, args); - } - /// /// Invokes the VoiceRecognitionEnabledChanged event /// diff --git a/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs b/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs index 9b2db1df3..b6fd3bc34 100644 --- a/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs +++ b/src/TrackerCouncil.Smz3.Data.SchemaGenerator/Program.cs @@ -100,7 +100,7 @@ private static void CreateTemplates(string outputPath) // Boss Template var bossConfig = configProvider.GetBossConfig(new List(), null); var templateBossConfig = new BossConfig(); - templateBossConfig.AddRange(bossConfig.Select(boss => new BossInfo { Boss = boss.Boss })); + templateBossConfig.AddRange(bossConfig.Select(boss => new BossInfo(boss.Boss))); var exampleBossConfig = BossConfig.Example(); WriteTemplate(templatePath, "bosses", templateBossConfig, exampleBossConfig); diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/BossConfig.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/BossConfig.cs index e5c7b3ba0..2851185f6 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/BossConfig.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigFiles/BossConfig.cs @@ -25,84 +25,29 @@ public BossConfig() : base() /// public static BossConfig Default() { - return new BossConfig - { - new() - { - Boss = "Spore Spawn", - MemoryAddress = 1, - MemoryFlag = 0x2, - }, - new() - { - Boss = "Botwoon", - MemoryAddress = 4, - MemoryFlag = 0x2, - }, - new() - { - Boss = "Kraid", - Type = BossType.Kraid, - MemoryAddress = 1, - MemoryFlag = 0x1, - }, - new() - { - Boss = "Crocomire", - MemoryAddress = 2, - MemoryFlag = 0x2, - }, - new() - { - Boss = "Phantoon", - Type = BossType.Phantoon, - MemoryAddress = 3, - MemoryFlag = 0x1, - }, - new() - { - Boss = "Shaktool", - }, - new() - { - Boss = "Draygon", - Type = BossType.Draygon, - MemoryAddress = 4, - MemoryFlag = 0x1, - }, - new() - { - Boss = "Ridley", - Type = BossType.Ridley, - MemoryAddress = 2, - MemoryFlag = 0x1, - }, - new() - { - Boss = "Mother Brain", - }, - new() - { - Boss = "Bomb Torizo", - MemoryAddress = 0, - MemoryFlag = 0x4, - }, - new() - { - Boss = "Golden Torizo", - MemoryAddress = 2, - MemoryFlag = 0x4, - }, - }; + return + [ + new BossInfo("Spore Spawn") { MemoryAddress = 1, MemoryFlag = 0x2, }, + new BossInfo("Botwoon") { MemoryAddress = 4, MemoryFlag = 0x2, }, + new BossInfo("Kraid") { Type = BossType.Kraid, MemoryAddress = 1, MemoryFlag = 0x1, }, + new BossInfo("Crocomire") { MemoryAddress = 2, MemoryFlag = 0x2, }, + new BossInfo("Phantoon") { Type = BossType.Phantoon, MemoryAddress = 3, MemoryFlag = 0x1, }, + new BossInfo("Shaktool"), + new BossInfo("Draygon") { Type = BossType.Draygon, MemoryAddress = 4, MemoryFlag = 0x1, }, + new BossInfo("Ridley") { Type = BossType.Ridley, MemoryAddress = 2, MemoryFlag = 0x1, }, + new BossInfo("Mother Brain") { Type = BossType.MotherBrain }, + new BossInfo("Bomb Torizo") { MemoryAddress = 0, MemoryFlag = 0x4, }, + new BossInfo("Golden Torizo") { MemoryAddress = 2, MemoryFlag = 0x4, } + + ]; } public static object Example() { return new BossConfig { - new() + new BossInfo("Bomb Torizo") { - Boss = "Bomb Torizo", Name = new("Bomb Torizo", "Bomb Chozo", new Possibility("Bozo", 0.1)), WhenTracked = new SchrodingersString("Message when clearing the boss", new Possibility("Another message when clearing the boss", 0.1)), WhenDefeated = new SchrodingersString("Message when defeating the boss", new Possibility("Another message when defeating the boss", 0.1)), diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/BossInfo.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/BossInfo.cs index f16d1d731..02788021a 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/BossInfo.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/BossInfo.cs @@ -13,10 +13,10 @@ namespace TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; /// public class BossInfo : IMergeable { - /// - /// Constructor - /// - public BossInfo() { } + public BossInfo() + { + Name = []; + } /// /// Initializes a new instance of the class. @@ -33,6 +33,7 @@ public BossInfo(SchrodingersString name) /// The name of the boss. public BossInfo(string name) { + Boss = name; Name = new SchrodingersString(name); } @@ -45,7 +46,7 @@ public BossInfo(string name) /// /// Gets the name of the boss. /// - public SchrodingersString? Name { get; set; } + public SchrodingersString Name { get; set; } /// /// Gets the phrases to respond with when the boss has been tracked (but diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/SchrodingersStringExtensions.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/SchrodingersStringExtensions.cs index 440540e9c..b5a566a78 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/SchrodingersStringExtensions.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/SchrodingersStringExtensions.cs @@ -18,11 +18,7 @@ public static class SchrodingersStringExtensions /// public static SchrodingersString GetName(this IHasLocations area) { - if (area is IDungeon dungeon) - { - return dungeon.DungeonMetadata.Name ?? new SchrodingersString(dungeon.DungeonName); - } - else if (area is Region region) + if (area is Region region) { return region.Metadata.Name ?? new SchrodingersString(region.Name); } diff --git a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/TrackerMapLocation.cs b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/TrackerMapLocation.cs index 3edc51fbf..0651d7708 100644 --- a/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/TrackerMapLocation.cs +++ b/src/TrackerCouncil.Smz3.Data/Configuration/ConfigTypes/TrackerMapLocation.cs @@ -88,9 +88,9 @@ public string GetName(World world) if (room?.Metadata != null) return room.Metadata.Name?[0] ?? Name; - var dungeon = world.Dungeons.SingleOrDefault(x => x.DungeonName.Equals(Name, StringComparison.OrdinalIgnoreCase)); - if (dungeon?.DungeonMetadata != null) - return dungeon.DungeonMetadata.Name?[0] ?? Name; + var dungeon = world.Regions.SingleOrDefault(x => x.Name.Equals(Name, StringComparison.OrdinalIgnoreCase)); + if (dungeon?.Metadata != null) + return dungeon.Metadata.Name?[0] ?? Name; var location = world.Locations.SingleOrDefault(x => x.Name.Equals(Name, StringComparison.OrdinalIgnoreCase)); if (location?.Metadata != null) diff --git a/src/TrackerCouncil.Smz3.Data/GeneratedData/WorldGenerationData.cs b/src/TrackerCouncil.Smz3.Data/GeneratedData/WorldGenerationData.cs index a75e78827..09739f8af 100644 --- a/src/TrackerCouncil.Smz3.Data/GeneratedData/WorldGenerationData.cs +++ b/src/TrackerCouncil.Smz3.Data/GeneratedData/WorldGenerationData.cs @@ -4,6 +4,8 @@ using System.Text.Json; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Shared.Multiplayer; namespace TrackerCouncil.Smz3.Data.GeneratedData; @@ -31,12 +33,11 @@ public WorldGenerationData(World world, Dictionary? patches = null) public MultiplayerPlayerGenerationData GetPlayerGenerationData() { - var locationItems = World.Locations - .Select(x => new PlayerGenerationLocationData(x.Id, x.Item.World.Id, x.Item.Type)).ToList(); - var dungeonData = World.Dungeons - .Select(x => new PlayerGenerationDungeonData(x.DungeonName, x.DungeonRewardType, x.Medallion)).ToList(); - return new MultiplayerPlayerGenerationData(World.Guid, World.Id, locationItems, dungeonData, World.HintTiles.ToList()); + var locations = World.Locations.Select(x => new PlayerGenerationLocationData(x.Id, x.Item.World.Id, x.Item.Type)).ToList(); + var bosses = World.BossRegions.ToDictionary(x => x.GetType().Name, x => x.BossType); + var rewards = World.RewardRegions.ToDictionary(x => x.GetType().Name, x => x.RewardType); + var prerequisites = World.PrerequisiteRegions.ToDictionary(x => x.GetType().Name, x => x.RequiredItem); + return new MultiplayerPlayerGenerationData(World.Guid, World.Id, locations, bosses, rewards, prerequisites, World.HintTiles.ToList()); } - } diff --git a/src/TrackerCouncil.Smz3.Data/Interfaces/IRomGenerationService.cs b/src/TrackerCouncil.Smz3.Data/Interfaces/IRomGenerationService.cs index 5ca69404a..51afa2e95 100644 --- a/src/TrackerCouncil.Smz3.Data/Interfaces/IRomGenerationService.cs +++ b/src/TrackerCouncil.Smz3.Data/Interfaces/IRomGenerationService.cs @@ -15,4 +15,6 @@ public interface IRomGenerationService public Task GeneratePreSeededRomAsync(RandomizerOptions options, SeedData seed, MultiplayerGameDetails multiplayerGameDetails); + + public void ApplyCasPatches(byte[] rom, PatchOptions options); } diff --git a/src/TrackerCouncil.Smz3.Data/Options/PlandoConfig.cs b/src/TrackerCouncil.Smz3.Data/Options/PlandoConfig.cs index cf0d9f31f..07f830fc9 100644 --- a/src/TrackerCouncil.Smz3.Data/Options/PlandoConfig.cs +++ b/src/TrackerCouncil.Smz3.Data/Options/PlandoConfig.cs @@ -39,8 +39,8 @@ public PlandoConfig(World world) .ToDictionary(x => x.ToString(), x => x.Item.Type); Rewards = world.Regions.Where(x => x is IHasReward) .ToDictionary(x => x.ToString(), x => ((IHasReward)x).RewardType); - Medallions = world.Regions.Where(x => x is INeedsMedallion) - .ToDictionary(x => x.ToString(), x => ((INeedsMedallion)x).Medallion); + Medallions = world.Regions.Where(x => x is IHasPrerequisite) + .ToDictionary(x => x.ToString(), x => ((IHasPrerequisite)x).RequiredItem); Logic = world.Config.LogicConfig.Clone(); StartingInventory = world.Config.ItemOptions; var prizes = DropPrizes.GetPool(world.Config.CasPatches.ZeldaDrops); diff --git a/src/TrackerCouncil.Smz3.Data/Options/RandomizerOptions.cs b/src/TrackerCouncil.Smz3.Data/Options/RandomizerOptions.cs index e1908eada..e042d02bf 100644 --- a/src/TrackerCouncil.Smz3.Data/Options/RandomizerOptions.cs +++ b/src/TrackerCouncil.Smz3.Data/Options/RandomizerOptions.cs @@ -36,10 +36,10 @@ public RandomizerOptions(GeneralOptions generalOptions, PatchOptions patchOptions, LogicConfig logicConfig) { - GeneralOptions = generalOptions ?? new(); - SeedOptions = seedOptions ?? new(); - PatchOptions = patchOptions ?? new(); - LogicConfig = logicConfig ?? new(); + GeneralOptions = generalOptions; + SeedOptions = seedOptions; + PatchOptions = patchOptions; + LogicConfig = logicConfig; } public event PropertyChangedEventHandler? PropertyChanged; @@ -59,12 +59,13 @@ public RandomizerOptions(GeneralOptions generalOptions, [JsonIgnore, YamlIgnore] public string? FilePath { get; set; } + public string? ApplicationVersion { get; set; } + public bool IsAdvancedMode { get; set; } public double WindowWidth { get; set; } = 500d; public double WindowHeight { get; set; } = 600d; - public string MultiplayerUrl { get; set; } = ""; [YamlIgnore] diff --git a/src/TrackerCouncil.Smz3.Data/RandomizerVersion.cs b/src/TrackerCouncil.Smz3.Data/RandomizerVersion.cs index b92af6715..c44c8614d 100644 --- a/src/TrackerCouncil.Smz3.Data/RandomizerVersion.cs +++ b/src/TrackerCouncil.Smz3.Data/RandomizerVersion.cs @@ -11,4 +11,8 @@ public class RandomizerVersion public static string VersionString => Version.ToString(); public static int MajorVersion => Version.Major; + + public static Version PreReplaceDungeonStateVersion => new Version(6, 0); + + public static bool IsVersionPreReplaceDungeonState(int version) => version <= PreReplaceDungeonStateVersion.Major; } diff --git a/src/TrackerCouncil.Smz3.Data/Services/GameDbService.cs b/src/TrackerCouncil.Smz3.Data/Services/GameDbService.cs index 12964d954..a4f70e6ad 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/GameDbService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/GameDbService.cs @@ -93,11 +93,18 @@ public bool DeleteGeneratedRom(GeneratedRom rom, out string error) { context.Entry(rom.TrackerState).Collection(x => x.ItemStates).Load(); context.Entry(rom.TrackerState).Collection(x => x.LocationStates).Load(); + context.Entry(rom.TrackerState).Collection(x => x.BossStates).Load(); + context.Entry(rom.TrackerState).Collection(x => x.RewardStates).Load(); + context.Entry(rom.TrackerState).Collection(x => x.PrerequisiteStates).Load(); + context.Entry(rom.TrackerState).Collection(x => x.TreasureStates).Load(); + context.Entry(rom.TrackerState).Collection(x => x.Hints).Load(); + context.Entry(rom.TrackerState).Collection(x => x.History).Load(); + +#pragma warning disable CS0618 // Type or member is obsolete context.Entry(rom.TrackerState).Collection(x => x.RegionStates).Load(); context.Entry(rom.TrackerState).Collection(x => x.DungeonStates).Load(); context.Entry(rom.TrackerState).Collection(x => x.MarkedLocations).Load(); - context.Entry(rom.TrackerState).Collection(x => x.BossStates).Load(); - context.Entry(rom.TrackerState).Collection(x => x.History).Load(); +#pragma warning restore CS0618 // Type or member is obsolete context.TrackerStates.Remove(rom.TrackerState); } diff --git a/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs b/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs index 5828b6490..979797ac5 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/IMetadataService.cs @@ -141,13 +141,13 @@ public interface IMetadataService /// /// Returns extra information for the specified dungeon. /// - /// + /// /// The dungeon to get extra information for. /// /// /// A new for the specified dungeon region. /// - public DungeonInfo Dungeon(IDungeon dungeon); + public DungeonInfo Dungeon(IHasTreasure hasTreasure); /// /// Returns extra information for the specified room. @@ -245,4 +245,28 @@ public interface IMetadataService /// The type of the reward /// public RewardInfo? Reward(RewardType type); + + /// + /// Returns a random name for the specified item including article, e.g. + /// "an E-Tank" or "the Book of Mudora". + /// + /// The type of item whose name to get. + /// + /// The name of the type of item, including "a", "an" or "the" if + /// applicable. + /// + string GetName(ItemType itemType); + + + /// + /// Returns a random name for the specified item including article, e.g. + /// "a blue crystal" or "the green pendant". + /// + /// The reward of item whose name to get. + /// + /// The name of the reward of item, including "a", "an" or "the" if + /// applicable. + /// + string GetName(RewardType rewardType); + } diff --git a/src/TrackerCouncil.Smz3.Data/Services/ITrackerStateService.cs b/src/TrackerCouncil.Smz3.Data/Services/ITrackerStateService.cs index 9541c427c..616fa751f 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/ITrackerStateService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/ITrackerStateService.cs @@ -9,7 +9,7 @@ public interface ITrackerStateService { public Task CreateStateAsync(IEnumerable world, GeneratedRom generatedRom); - public TrackerState CreateTrackerState(IEnumerable worlds); + public TrackerState CreateTrackerState(List worlds); public Task SaveStateAsync(IEnumerable worlds, GeneratedRom generatedRom, double secondsElapsed); diff --git a/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs b/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs index e6c5cc720..137bfe47d 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/MetadataService.cs @@ -171,14 +171,14 @@ public DungeonInfo Dungeon() where TRegion : Region /// /// Returns extra information for the specified dungeon. /// - /// + /// /// The dungeon to get extra information for. /// /// /// A new for the specified dungeon region. /// - public DungeonInfo Dungeon(IDungeon dungeon) - => Dungeon(dungeon.GetType()); + public DungeonInfo Dungeon(IHasTreasure hasTreasure) + => Dungeon(hasTreasure.GetType()); /// /// Returns extra information for the specified room. @@ -287,4 +287,9 @@ public LocationInfo Location(Location location) /// public RewardInfo? Reward(RewardType type) => Rewards.FirstOrDefault(x => x.RewardType == type); + + public string GetName(ItemType itemType) => Item(itemType)?.NameWithArticle ?? itemType.GetDescription(); + + public string GetName(RewardType rewardType) => Reward(rewardType)?.NameWithArticle ?? rewardType.GetDescription(); + } diff --git a/src/TrackerCouncil.Smz3.Data/Services/TrackerStateService.cs b/src/TrackerCouncil.Smz3.Data/Services/TrackerStateService.cs index 98e1f7627..58024d55b 100644 --- a/src/TrackerCouncil.Smz3.Data/Services/TrackerStateService.cs +++ b/src/TrackerCouncil.Smz3.Data/Services/TrackerStateService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -8,7 +9,6 @@ using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Data.WorldData.Regions; -using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Shared.Models; @@ -29,23 +29,22 @@ public TrackerStateService(RandomizerContext dbContext, ILogger worlds, GeneratedRom generatedRom) { - var state = CreateTrackerState(worlds); + var worldList = worlds.ToList(); - foreach (var world in worlds) + var state = CreateTrackerState(worldList); + + foreach (var world in worldList) { world.State = state; } generatedRom.TrackerState = state; await _randomizerContext.SaveChangesAsync(); - } - public TrackerState CreateTrackerState(IEnumerable worlds) + public TrackerState CreateTrackerState(List worlds) { - var worldList = worlds.ToList(); - - var locationStates = worldList + var locationStates = worlds .SelectMany(x => x.Locations) .Select(x => new TrackerLocationState { @@ -60,7 +59,7 @@ public TrackerState CreateTrackerState(IEnumerable worlds) var addedItems = new List<(World, ItemType)>(); var itemStates = new List(); - foreach (var item in worldList.SelectMany(x => x.AllItems)) + foreach (var item in worlds.SelectMany(x => x.AllItems)) { if (addedItems.Contains((item.World, item.Type))) continue; itemStates.Add(new TrackerItemState @@ -72,31 +71,52 @@ public TrackerState CreateTrackerState(IEnumerable worlds) addedItems.Add((item.World, item.Type)); } - var dungeonStates = worldList - .SelectMany(x => x.Dungeons) - .Select(x => new TrackerDungeonState + var rewardStates = worlds + .SelectMany(x => x.RewardRegions) + .Select(x => new TrackerRewardState { - Name = x.GetType().Name, - RemainingTreasure = x.GetTreasureCount(), - Reward = x is IHasReward rewardRegion ? rewardRegion.RewardType : null, - RequiredMedallion = x is INeedsMedallion medallionRegion ? medallionRegion.Medallion : null, - MarkedReward = x is CastleTower ? RewardType.Agahnim : null, - WorldId = ((Region)x).World.Id + RegionName = x.GetType().Name, + RewardType = x.RewardType, + MarkedReward = x.MarkedReward, + WorldId = x.World.Id }) .ToList(); - var bossStates = worldList + var treasureStates = worlds + .SelectMany(x => x.TreasureRegions) + .Select(region => new TrackerTreasureState + { + RegionName = region.GetType().Name, + RemainingTreasure = region.GetTreasureCount(), + TotalTreasure = region.GetTreasureCount(), + WorldId = ((Region)region).World.Id + }) + .ToList(); + + var bossStates = worlds .SelectMany(x => x.AllBosses) - .Select(boss => new TrackerBossState() + .Select(boss => new TrackerBossState { BossName = boss.Name, + RegionName = boss.Region?.GetType().Name ?? string.Empty, Type = boss.Type, WorldId = boss.World.Id, }) .ToList(); + var prereqStates = worlds + .SelectMany(x => x.PrerequisiteRegions) + .Select(region => new TrackerPrerequisiteState + { + RequiredItem = region.RequiredItem, + RegionName = region.GetType().Name, + WorldId = region.World.Id + }) + .ToList(); + + var hintStates = new List(); - foreach (var hint in worldList.SelectMany(x => x.HintTiles)) + foreach (var hint in worlds.SelectMany(x => x.HintTiles)) { var hintState = new TrackerHintState() { @@ -115,7 +135,7 @@ public TrackerState CreateTrackerState(IEnumerable worlds) } // Add starting equipment, including items that may not be in the world anymore - foreach (var world in worldList) + foreach (var world in worlds) { var startingInventory = ItemSettingOptions.GetStartingItemTypes(world.Config).ToList(); foreach (var itemType in startingInventory.Distinct()) @@ -140,7 +160,7 @@ public TrackerState CreateTrackerState(IEnumerable worlds) } // Add items from metadata that may be missing - foreach (var world in worldList) + foreach (var world in worlds) { foreach (var itemMetadata in _configs.Items.Where(m => !itemStates.Any(s => m.Is(s) && s.WorldId == world.Id))) { @@ -159,9 +179,11 @@ public TrackerState CreateTrackerState(IEnumerable worlds) { LocationStates = locationStates, ItemStates = itemStates, - DungeonStates = dungeonStates, + TreasureStates = treasureStates, BossStates = bossStates, - LocalWorldId = worldList.First(x => x.IsLocalWorld).Id, + RewardStates = rewardStates, + PrerequisiteStates = prereqStates, + LocalWorldId = worlds.First(x => x.IsLocalWorld).Id, Hints = hintStates, StartDateTime = DateTimeOffset.Now, UpdatedDateTime = DateTimeOffset.Now @@ -191,7 +213,7 @@ public TrackerState CreateTrackerState(IEnumerable worlds) MedallionType = hint.MedallionType, HintTileCode = hint.HintTileCode, State = hint - }); + }).ToImmutableList(); world.State = trackerState; } @@ -201,6 +223,7 @@ public TrackerState CreateTrackerState(IEnumerable worlds) public async Task SaveStateAsync(IEnumerable worlds, GeneratedRom generatedRom, double secondsElapsed) { + var worldList = worlds.ToList(); var trackerState = generatedRom.TrackerState; if (trackerState == null) @@ -208,43 +231,42 @@ public async Task SaveStateAsync(IEnumerable worlds, GeneratedRom generat return; } - foreach (var world in worlds) + foreach (var world in worldList) { world.State = trackerState; } - SaveLocationStates(worlds, trackerState); - SaveItemStates(worlds, trackerState); - SaveBossStates(worlds, trackerState); + SaveLocationStates(worldList, trackerState); + SaveItemStates(worldList, trackerState); + SaveBossStates(worldList, trackerState); trackerState.UpdatedDateTime = DateTimeOffset.Now; trackerState.SecondsElapsed = secondsElapsed; - await _randomizerContext.SaveChangesAsync(); } - private void SaveLocationStates(IEnumerable worlds, TrackerState trackerState) + private void SaveLocationStates(List worlds, TrackerState trackerState) { var totalLocations = worlds.SelectMany(x => x.Locations).Count(); - var clearedLocations = worlds.SelectMany(x => x.Locations).Count(x => x.State.Cleared == true); + var clearedLocations = worlds.SelectMany(x => x.Locations).Count(x => x.State.Cleared); var percCleared = (int)Math.Floor((double)clearedLocations / totalLocations * 100); trackerState.PercentageCleared = percCleared; } - private void SaveItemStates(IEnumerable worlds, TrackerState trackerState) + private void SaveItemStates(List worlds, TrackerState trackerState) { // Add any new item states var itemStates = worlds .SelectMany(x => x.AllItems) .Select(x => x.State).Distinct() - .Where(x => x != null && !trackerState.ItemStates.Contains(x)) + .Where(x => !trackerState.ItemStates.Contains(x)) .NonNull() .ToList(); itemStates.ForEach(x => trackerState.ItemStates.Add(x) ); } - private void SaveBossStates(IEnumerable worlds, TrackerState trackerState) + private void SaveBossStates(List worlds, TrackerState trackerState) { // Add any new item states var bossStates = worlds diff --git a/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj b/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj index 0c44b97ab..4fe79a71e 100644 --- a/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj +++ b/src/TrackerCouncil.Smz3.Data/TrackerCouncil.Smz3.Data.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.Data/Tracking/AutoTrackerMetroidState.cs b/src/TrackerCouncil.Smz3.Data/Tracking/AutoTrackerMetroidState.cs index d948f49c3..0016b6f8a 100644 --- a/src/TrackerCouncil.Smz3.Data/Tracking/AutoTrackerMetroidState.cs +++ b/src/TrackerCouncil.Smz3.Data/Tracking/AutoTrackerMetroidState.cs @@ -105,8 +105,9 @@ public bool IsSamusInArea(int minX, int maxX, int minY, int maxY) /// Checks to make sure that the state is valid and fully loaded. There's a period upon first booting up that /// all of these are 0s, but some of the memory in the location data can be screwy. /// - public bool IsValid => CurrentRoom != 0 || CurrentRegion != 0 || CurrentRoomInRegion != 0 || Energy != 0 || - SamusX != 0 || SamusY != 0; + public bool IsValid => Energy is > 0 and < 1999 && ReserveTanks is >= 0 and < 600 && (CurrentRoom != 0 || + CurrentRegion != 0 || CurrentRoomInRegion != 0 || Energy != 0 || + SamusX != 0 || SamusY != 0); /// /// Prints debug data for the state diff --git a/src/TrackerCouncil.Smz3.Data/Tracking/AutoTrackerZeldaState.cs b/src/TrackerCouncil.Smz3.Data/Tracking/AutoTrackerZeldaState.cs index ceff60d77..26e611aa0 100644 --- a/src/TrackerCouncil.Smz3.Data/Tracking/AutoTrackerZeldaState.cs +++ b/src/TrackerCouncil.Smz3.Data/Tracking/AutoTrackerZeldaState.cs @@ -104,6 +104,8 @@ public bool IsWithinRegion(int topLeftX, int topLeftY, int bottomRightX, int bot /// public int? ReadUInt16(int address) => _data.ReadUInt16(address); + public bool IsValid => State is >= 0x01 and <= 0x1b && LinkState is >= 0x00 and <= 0x1E; + /// /// Get debug string /// diff --git a/src/TrackerCouncil.Smz3.Data/Tracking/DungeonTrackedEventArgs.cs b/src/TrackerCouncil.Smz3.Data/Tracking/DungeonTrackedEventArgs.cs index f90ec4cc1..e0e6a3110 100644 --- a/src/TrackerCouncil.Smz3.Data/Tracking/DungeonTrackedEventArgs.cs +++ b/src/TrackerCouncil.Smz3.Data/Tracking/DungeonTrackedEventArgs.cs @@ -14,7 +14,7 @@ public class DungeonTrackedEventArgs : TrackerEventArgs /// The dungeon that was tracked. /// The speech recognition confidence. /// If the location was automatically tracked - public DungeonTrackedEventArgs(IDungeon? dungeon, float? confidence, bool autoTracked) + public DungeonTrackedEventArgs(IHasTreasure? dungeon, float? confidence, bool autoTracked) : base(confidence, autoTracked) { Dungeon = dungeon; @@ -23,5 +23,5 @@ public DungeonTrackedEventArgs(IDungeon? dungeon, float? confidence, bool autoTr /// /// Gets the boss that was tracked. /// - public IDungeon? Dungeon { get; } + public IHasTreasure? Dungeon { get; } } diff --git a/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowItemsViewModel.cs b/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowItemsViewModel.cs index 039d6df64..46746466b 100644 --- a/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowItemsViewModel.cs +++ b/src/TrackerCouncil.Smz3.Data/ViewModels/GenerationWindowItemsViewModel.cs @@ -100,7 +100,7 @@ public void Init(RandomizerOptions options, LocationConfig locations) LocationOptions.Add(new GenerationWindowLocationViewModel() { LocationId = location.Id, - LocationName = locationDetails.Name?.FirstOrDefault() ?? location.Id.ToString(), + LocationName = location.Name, Options = LocationItemOptions, Region = location.Region, SelectedOption = options.SeedOptions.LocationItems.TryGetValue(location.Id, out var locationSetting) diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Boss.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Boss.cs index 1c1e180d8..5ca43f814 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Boss.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Boss.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.Services; @@ -13,21 +14,22 @@ namespace TrackerCouncil.Smz3.Data.WorldData; /// public class Boss { + private Accessibility _accessibility; + /// /// Constructor /// /// The type of boss /// The world this boss belongs to - /// The name of the boss /// The metadata object with additional boss info - /// The tracking state of the boss - public Boss(BossType type, World world, string name, BossInfo metadata, TrackerBossState state) + /// + public Boss(BossType type, World world, BossInfo metadata, TrackerBossState? bossState = null) { Type = type; World = world; - Name = name; + Name = metadata.Boss; Metadata = metadata; - State = state; + State = bossState ?? new TrackerBossState(); } /// @@ -35,17 +37,15 @@ public Boss(BossType type, World world, string name, BossInfo metadata, TrackerB /// /// The type of boss /// The world this boss belongs to - /// The region where this boss is locatedd /// The metadata service for looking up additional boss info - /// The tracking state for this run - public Boss(BossType type, World world, IHasBoss region, IMetadataService? metadata, TrackerState? trackerState) + /// + public Boss(BossType type, World world, IMetadataService? metadata, TrackerBossState? bossState = null) { Type = type; World = world; - Region = region; Name = type.GetDescription(); Metadata = metadata?.Boss(type) ?? new BossInfo(Name); - State = trackerState?.BossStates.First(x => x.WorldId == world.Id && x.BossName == Name) ?? new TrackerBossState(); + State = bossState ?? new TrackerBossState(); } public string Name { get; set; } @@ -60,6 +60,61 @@ public Boss(BossType type, World world, IHasBoss region, IMetadataService? metad public IHasBoss? Region { get; set; } + public bool Defeated + { + get => State.Defeated; + set + { + State.Defeated = value; + UpdatedBossState?.Invoke(this, EventArgs.Empty); + } + } + + public bool AutoTracked + { + get => State.AutoTracked; + set => State.AutoTracked = value; + } + + public Accessibility Accessibility + { + get => _accessibility; + set + { + if (_accessibility == value) return; + _accessibility = value; + UpdatedAccessibility?.Invoke(this, EventArgs.Empty); + } + } + + public void UpdateAccessibility(Progression actualProgression, Progression withKeysProgression) + { + if (Defeated) + { + Accessibility = Accessibility.Cleared; + } + else if (Region == null) + { + Accessibility = Accessibility.Unknown; + } + else if (Region.CanBeatBoss(actualProgression)) + { + Accessibility = Accessibility.Available; + } + else if (Region.CanBeatBoss(withKeysProgression)) + { + Accessibility = Accessibility.AvailableWithKeys; + } + else + { + Accessibility = Accessibility.OutOfLogic; + } + } + + public event EventHandler? UpdatedBossState; + + public event EventHandler? UpdatedAccessibility; + /// /// Determines if an item matches the type or name /// diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Item.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Item.cs index a25299590..d82d5374a 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Item.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Item.cs @@ -84,6 +84,21 @@ public Item(ItemType itemType, World world, string name, ItemData metadata, Trac /// public TrackerItemState State { get; set; } + public int TrackingState + { + get => State.TrackingState; + set + { + if (value == State.TrackingState) + { + return; + } + + State.TrackingState = value; + UpdatedItemState?.Invoke(this, EventArgs.Empty); + } + } + /// /// Indicates whether the item is a dungeon-specific item. /// @@ -118,15 +133,15 @@ public Item(ItemType itemType, World world, string name, ItemData metadata, Trac /// public bool IsKeycard => Type.IsInCategory(ItemCategory.Keycard); + public bool IsTreasure => !IsDungeonItem; + /// /// Gets the number of actual items as displayed or mentioned by /// tracker, or 0 if the item does not have copies. /// - public int Counter => State == null || Metadata == null - ? 0 - : Metadata.Multiple && !Metadata.HasStages - ? State.TrackingState * (Metadata.CounterMultiplier ?? 1) - : 0; + public int Counter => Metadata is { Multiple: true, HasStages: false } + ? TrackingState * (Metadata.CounterMultiplier ?? 1) + : 0; /// /// Tracks the item. @@ -144,11 +159,11 @@ public bool Track() if (State == null) throw new InvalidOperationException($"State not loaded for item '{Name}'"); - if (State.TrackingState == 0 // Item hasn't been tracked yet (any case) - || (Metadata?.HasStages == false && Metadata?.Multiple == true) // State.Multiple items always track - || (Metadata?.HasStages == true && State.TrackingState < Metadata?.MaxStage)) // Hasn't reached max. stage yet + if (TrackingState == 0 // Item hasn't been tracked yet (any case) + || Metadata is { HasStages: false, Multiple: true } // State.Multiple items always track + || (Metadata.HasStages && TrackingState < Metadata.MaxStage)) // Hasn't reached max. stage yet { - State.TrackingState++; + TrackingState++; return true; } @@ -167,10 +182,10 @@ public bool Untrack() if (State == null) throw new InvalidOperationException($"State not loaded for item '{Name}'"); - if (State.TrackingState == 0) + if (TrackingState == 0) return false; - State.TrackingState--; + TrackingState--; return true; } @@ -190,15 +205,15 @@ public bool Track(int stage) if (State == null) throw new InvalidOperationException($"State not loaded for item '{Name}'"); - if (Metadata?.HasStages == false) + if (Metadata.HasStages == false) throw new ArgumentException($"The item '{Name}' does not have Multiple stages."); - if (stage > Metadata?.MaxStage) + if (stage > Metadata.MaxStage) throw new ArgumentOutOfRangeException($"Cannot advance item '{Name}' to stage {stage} as the highest state is {Metadata.MaxStage}."); - if (State?.TrackingState < stage) + if (TrackingState < stage) { - State.TrackingState = stage; + TrackingState = stage; return true; } @@ -221,7 +236,7 @@ public bool TryGetTrackingResponse([NotNullWhen(true)] out SchrodingersString? r { if (Metadata == null || State == null) throw new InvalidOperationException($"State or metadata not loaded item '{Name}'"); - return Metadata.TryGetTrackingResponse(State.TrackingState, out response); + return Metadata.TryGetTrackingResponse(TrackingState, out response); } /// @@ -262,6 +277,15 @@ public bool IsNot(ItemType type, World world) public bool Is(ItemType type, string name) => (Type != ItemType.Nothing && Type == type) || (Type == ItemType.Nothing && Name == name); + /// + /// Determines if an item matches name + /// + /// The name to compare against if the item type is set to Nothing + /// if the item matches the given name otherwise, . + public bool Is(string name) + => Name == name || Metadata.Name?.Contains(name, StringComparison.OrdinalIgnoreCase) == true || + Metadata.GetStage(name) != null; + /// /// Updates an item in the middle of world generation /// @@ -272,6 +296,8 @@ public void UpdateItemType(ItemType type) Name = type.GetDescription(); } + public event EventHandler? UpdatedItemState; + /// /// Returns a string that represents the item. /// diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Location.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Location.cs index fe19d1208..16d3cc06f 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Location.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Location.cs @@ -1,9 +1,11 @@ -using System.Linq; +using System; +using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.Logic; using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Shared.Models; @@ -104,6 +106,64 @@ public Location(Region region, LocationId id, int romAddress, LocationType type, /// public TrackerLocationState State { get; set; } + public ItemType ItemType => Item.Type; + + public bool Cleared + { + get => State.Cleared; + set + { + if (State.Cleared == value) return; + State.Cleared = value; + ClearedUpdated?.Invoke(this, EventArgs.Empty); + } + } + + public bool Autotracked + { + get => State.Autotracked; + set => State.Autotracked = value; + } + + public ItemType? MarkedItem + { + get => State.MarkedItem; + set + { + if (State.MarkedItem == value) return; + State.MarkedItem = value; + + if (value != null) + { + State.MarkedUsefulness = + Item.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity) + ? LocationUsefulness.NiceToHave + : LocationUsefulness.Useless; + } + else + { + State.MarkedUsefulness = null; + } + + MarkedItemUpdated?.Invoke(this, EventArgs.Empty); + } + } + + public LocationUsefulness? MarkedUsefulness + { + get => State.MarkedUsefulness; + set + { + if (State.MarkedUsefulness == value) return; + State.MarkedUsefulness = value; + MarkedItemUpdated?.Invoke(this, EventArgs.Empty); + } + } + + public bool HasMarkedItem => State.HasMarkedItem; + + public bool HasMarkedCorrectItem => State.HasMarkedCorrectItem; + /// /// Gets the type of location. /// @@ -235,17 +295,48 @@ public bool IsAvailable(Progression items, bool applyTrackerLogic = false) /// name="items"/>; otherwise, . public bool IsRelevant(Progression items) => Region.CanEnter(items, false) && _relevanceRequirement(items) && _trackerLogic(items); + public Accessibility Accessibility { get; private set; } + + public Accessibility GetKeysanityAdjustedAccessibility() + { + return Region.GetKeysanityAdjustedAccessibility(Accessibility); + } + + public void SetAccessibility(Accessibility newValue) + { + if (Accessibility == newValue) return; + Accessibility = newValue; + AccessibilityUpdated?.Invoke(this, EventArgs.Empty); + } + + /// + /// Returns the status of a location based on the given items + /// + /// The available items + /// The available items plus additional keys + /// The LocationStatus enum of the location + public Accessibility GetAccessibility(Progression actualProgression, Progression withKeysProgression) + { + if (State.Cleared) return Accessibility.Cleared; + else if (IsAvailable(actualProgression) && _trackerLogic(actualProgression)) return Accessibility.Available; + else if (IsAvailable(withKeysProgression) && _trackerLogic(withKeysProgression)) return Accessibility.AvailableWithKeys; + else if (IsRelevant(actualProgression) && _trackerLogic(actualProgression)) return Accessibility.Relevant; + else if (IsRelevant(withKeysProgression) && _trackerLogic(withKeysProgression)) return Accessibility.RelevantWithKeys; + else return Accessibility.OutOfLogic; + } + /// /// Returns the status of a location based on the given items /// - /// The available items + /// The available items + /// The available items plus additional keys /// The LocationStatus enum of the location - public LocationStatus GetStatus(Progression items) + public void UpdateAccessibility(Progression actualProgression, Progression withKeysProgression) { - if (State.Cleared) return LocationStatus.Cleared; - else if (IsAvailable(items) && _trackerLogic(items)) return LocationStatus.Available; - else if (IsRelevant(items) && _trackerLogic(items)) return LocationStatus.Relevant; - else return LocationStatus.OutOfLogic; + var newValue = GetAccessibility(actualProgression, withKeysProgression); + if (newValue == Accessibility) return; + Accessibility = newValue; + AccessibilityUpdated?.Invoke(this, EventArgs.Empty); } /// @@ -290,7 +381,6 @@ public bool IsPotentiallyImportant(KeysanityMode? keysanity = null) || (Item.IsKeycard && keysanity is KeysanityMode.Both or KeysanityMode.SuperMetroid); } - /// /// Returns a string that represents the location. /// @@ -301,4 +391,10 @@ public override string ToString() ? $"{Room} - {Name}" : $"{Region} - {Name}"; } + + public IHasTreasure? GetTreasureRegion() => Region as IHasTreasure; + + public event EventHandler? ClearedUpdated; + public event EventHandler? MarkedItemUpdated; + public event EventHandler? AccessibilityUpdated; } diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IDungeon.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IDungeon.cs deleted file mode 100644 index 0cb4c663c..000000000 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IDungeon.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System.Linq; -using TrackerCouncil.Smz3.Shared; -using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; -using TrackerCouncil.Smz3.Shared.Enums; -using TrackerCouncil.Smz3.Shared.Models; - -namespace TrackerCouncil.Smz3.Data.WorldData.Regions; - -/// -/// Defines a region that offers a reward for completing it, e.g. a Zelda -/// dungeon or a Super Metroid boss. -/// -public interface IDungeon -{ - /// - /// Gets or sets the reward for completing the region, e.g. pendant or - /// crystal. - /// - DungeonInfo DungeonMetadata { get; set; } - - /// - /// The current tracking state of the dungeon - /// - TrackerDungeonState DungeonState { get; set; } - - /// - /// Calculates the number of treasures in the dungeon - /// - /// - public int GetTreasureCount() - { - var region = (Region)this; - return region.Locations.Count(x => x.Item.Type != ItemType.Nothing && (!x.Item.IsDungeonItem || region.World.Config.ZeldaKeysanity) && x.Type != LocationType.NotInDungeon); - } - - /// - /// Retrieves the base name of the dungeon - /// - public string DungeonName => ((Region)this).Name; - - /// - /// The reward object for the dungeon, if any - /// - public Reward? DungeonReward => HasReward ? ((IHasReward)this).Reward : null; - - /// - /// The type of reward in the dungeon, if any - /// - public RewardType? DungeonRewardType => DungeonReward?.Type; - - /// - /// If the dungeon has a pendant in it or not - /// - public bool IsPendantDungeon => DungeonRewardType is RewardType.PendantGreen or RewardType.PendantRed or RewardType.PendantBlue; - - /// - /// If the dungeon has a crystal in it or not - /// - public bool IsCrystalDungeon => DungeonRewardType is RewardType.CrystalBlue or RewardType.CrystalRed; - - /// - /// The MSU song index for the dungeon - /// - public int SongIndex { get; init; } - - public string Abbreviation { get; } - - public LocationId? BossLocationId { get; } - - /// - /// The reward marked by the player - /// - public RewardType MarkedReward - { - get - { - return DungeonState.MarkedReward ?? RewardType.None; - } - set - { - if (DungeonState != null) - { - DungeonState.MarkedReward = value; - } - } - } - - /// - /// If this dungeon has a reward in it - /// - public bool HasReward => this is IHasReward; - - /// - /// If this dungeon needs a medallion - /// - public bool NeedsMedallion => this is INeedsMedallion; - - /// - /// The medallion required to entered the dungeon, if any - /// - public ItemType Medallion => NeedsMedallion ? ((INeedsMedallion)this).Medallion : ItemType.Nothing; - - /// - /// The required medallion marked by the player - /// - public ItemType MarkedMedallion - { - get - { - return DungeonState.MarkedMedallion ?? ItemType.Nothing; - } - set - { - if (DungeonState != null) - { - DungeonState.MarkedMedallion = value; - } - } - } - - /// - /// The overworld region that this dungeon is within - /// - public Region ParentRegion { get; } -} diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasBoss.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasBoss.cs index 773601c5f..a5c14b6bf 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasBoss.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasBoss.cs @@ -1,4 +1,9 @@ -using TrackerCouncil.Smz3.Shared.Enums; +using System; +using System.Linq; +using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; +using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; +using TrackerCouncil.Smz3.Shared.Enums; +using TrackerCouncil.Smz3.Shared.Models; namespace TrackerCouncil.Smz3.Data.WorldData.Regions; @@ -7,15 +12,70 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions; /// public interface IHasBoss { - /// - /// Gets or sets the SM golden boss for the region - /// - Boss Boss { get; set; } + string Name { get; } - /// - /// The boss type - /// - BossType BossType => Boss?.Type ?? BossType.None; + RegionInfo Metadata { get; set; } + + World World { get; } + + Boss Boss { get; protected set; } + + BossInfo BossMetadata => Boss.Metadata; + + BossType BossType => Boss.Type; + + BossType DefaultBossType { get; } + + TrackerBossState BossState => Boss.State; + + LocationId? BossLocationId { get; } + + Region Region => (Region)this; + + public bool BossDefeated + { + get => Boss.Defeated; + set => Boss.Defeated = value; + } + + public Accessibility BossAccessibility + { + get => Boss.Accessibility; + set => Boss.Accessibility = value; + } + + public Accessibility GetKeysanityAdjustedBossAccessibility() + { + return Region.GetKeysanityAdjustedAccessibility(BossAccessibility); + } + + public void SetBossType(BossType bossType) + { + Boss = World.Bosses.First(x => x.Type == bossType && x.Region == null); + Boss.Region = this; + } + + public void ApplyState(TrackerState? state) + { + if (state == null) + { + SetBossType(DefaultBossType); + Boss.State = new TrackerBossState + { + WorldId = World.Id, + RegionName = GetType().Name, + Type = DefaultBossType, + BossName = Boss.Name + }; + } + else + { + var bossState = state.BossStates.First(x => + x.WorldId == World.Id && x.RegionName == GetType().Name); + SetBossType(bossState.Type); + Boss.State = bossState; + } + } /// /// Determines whether the boss for the region can be defeated. diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasLocations.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasLocations.cs index 4f46dbcc8..9fa9366fa 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasLocations.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasLocations.cs @@ -21,4 +21,25 @@ public interface IHasLocations /// Gets all locations in the area. /// IEnumerable Locations { get; } + + public IHasTreasure? GetTreasureRegion() + { + return Region as IHasTreasure; + } + + public Region? Region => this switch + { + Room room => room.Region, + Region region => region, + _ => null + }; + + public bool IsKeysanityForArea + { + get + { + if (Region is Z3Region && Region.Config.ZeldaKeysanity) return true; + return Region is SMRegion && Region.Config.MetroidKeysanity; + } + } } diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasPrerequisite.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasPrerequisite.cs new file mode 100644 index 000000000..269fcb4c7 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasPrerequisite.cs @@ -0,0 +1,83 @@ +using System; +using System.Linq; +using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; +using TrackerCouncil.Smz3.Shared.Enums; +using TrackerCouncil.Smz3.Shared.Models; + +namespace TrackerCouncil.Smz3.Data.WorldData.Regions; + +/// +/// Defines a region that requires an item (Bombos, Ether, Quake) to be +/// accessible. +/// +public interface IHasPrerequisite +{ + public string Name { get; } + + public RegionInfo Metadata { get; set; } + + public World World { get; } + + /// + /// The required item type to enter this region + /// + ItemType RequiredItem + { + get => PrerequisiteState.RequiredItem; + set => PrerequisiteState.RequiredItem = value; + } + + /// + /// The item marked by the player + /// + ItemType? MarkedItem + { + get => PrerequisiteState.MarkedItem; + set + { + if (PrerequisiteState.MarkedItem == value) + { + return; + } + + PrerequisiteState.MarkedItem = value; + OnUpdatedPrerequisite(); + } + } + + /// + /// Gets the default medallion for the dungeon + /// + ItemType DefaultRequiredItem { get; } + + public TrackerPrerequisiteState PrerequisiteState { get; set; } + + public bool HasMarkedCorrectly => RequiredItem == MarkedItem; + + public void ApplyState(TrackerState? state) + { + var region = (Region)this; + + if (state == null) + { + PrerequisiteState = new TrackerPrerequisiteState + { + WorldId = region.World.Id, RegionName = GetType().Name, RequiredItem = DefaultRequiredItem + }; + RequiredItem = DefaultRequiredItem; + } + else + { + PrerequisiteState = state.PrerequisiteStates.FirstOrDefault(x => + x.WorldId == region.World.Id && x.RegionName == GetType().Name) ?? new TrackerPrerequisiteState + { + WorldId = region.World.Id, RegionName = GetType().Name, RequiredItem = DefaultRequiredItem + }; + RequiredItem = PrerequisiteState?.RequiredItem ?? DefaultRequiredItem; + } + } + + event EventHandler? UpdatedPrerequisite; + + void OnUpdatedPrerequisite(); +} diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasReward.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasReward.cs index 7d67aa1c7..398cd87b6 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasReward.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasReward.cs @@ -1,5 +1,8 @@ -using TrackerCouncil.Smz3.Shared; +using System; +using System.Linq; +using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Shared.Enums; +using TrackerCouncil.Smz3.Shared.Models; namespace TrackerCouncil.Smz3.Data.WorldData.Regions; @@ -9,15 +12,64 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions; /// public interface IHasReward { + string Name { get; } + + RegionInfo Metadata { get; set; } + + World World { get; } + /// /// Gets or sets the reward for completing the region, e.g. pendant or /// crystal. /// RewardType RewardType => Reward.Type; - Reward Reward { get; set; } + Reward Reward { get; protected set; } + + RewardInfo RewardMetadata => Reward.Metadata; + + RewardType DefaultRewardType { get; } + + TrackerRewardState RewardState { get; set; } + + public bool IsShuffledReward { get; } + + Region Region => (Region)this; + + public void SetReward(Reward reward) + { + Reward = reward; + Reward.Region = this; + RewardState.RewardType = reward.Type; + } + + public void SetRewardType(RewardType rewardType) + { + var region = (Region)this; + Reward = region.World.Rewards.First(x => x.Type == rewardType && x.Region == null); + Reward.Region = this; + RewardState.RewardType = rewardType; + } + + public RewardType MarkedReward + { + get => Reward.MarkedReward ?? RewardType.None; + set => Reward.MarkedReward = value; + } + + public Accessibility RewardAccessibility + { + get => Reward.Accessibility; + set => Reward.Accessibility = value; + } - RewardType DefaultRewardType { get; } + public bool HasCorrectlyMarkedReward => Reward.HasCorrectlyMarkedReward; + + public bool HasReceivedReward + { + get => Reward.HasReceivedReward; + set => Reward.HasReceivedReward = value; + } /// /// Determines whether the reward for the region can be obtained. @@ -27,5 +79,42 @@ public interface IHasReward /// if the region can be completed; otherwise, /// . /// - bool CanComplete(Progression items); + bool CanRetrieveReward(Progression items); + + /// + /// Determines if the user can see what the reward is + /// + /// The items currently available + /// + /// if the reward can be seen; otherwise, + /// . + /// + bool CanSeeReward(Progression items); + + public Accessibility GetKeysanityAdjustedBossAccessibility() + { + return Region.GetKeysanityAdjustedAccessibility(RewardAccessibility); + } + + public bool HasReward(params RewardType[] types) => types.Contains(RewardType); + + public void ApplyState(TrackerState? state) + { + var region = (Region)this; + + if (state == null) + { + RewardState = new TrackerRewardState + { + WorldId = region.World.Id, RegionName = GetType().Name + }; + SetRewardType(DefaultRewardType); + } + else + { + RewardState = state.RewardStates.First(x => + x.WorldId == region.World.Id && x.RegionName == GetType().Name); + SetRewardType(RewardState.RewardType); + } + } } diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasTreasure.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasTreasure.cs new file mode 100644 index 000000000..88818b12f --- /dev/null +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/IHasTreasure.cs @@ -0,0 +1,82 @@ +using System; +using System.Linq; +using TrackerCouncil.Smz3.Shared; +using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; +using TrackerCouncil.Smz3.Shared.Enums; +using TrackerCouncil.Smz3.Shared.Models; + +namespace TrackerCouncil.Smz3.Data.WorldData.Regions; + +/// +/// Defines a region that has treasure (tray-sure) in it +/// +public interface IHasTreasure +{ + public string Name { get; } + + public RegionInfo Metadata { get; set; } + + public World World { get; } + + /// + /// The current tracking state of the treasures + /// + TrackerTreasureState TreasureState { get; set; } + + /// + /// Gets the total treasure of this region + /// + public int TotalTreasure => TreasureState.TotalTreasure; + + public bool HasManuallyClearedTreasure + { + get => TreasureState.HasManuallyClearedTreasure; + set => TreasureState.HasManuallyClearedTreasure = value; + } + + /// + /// Gets or sets the amount of remaining treasure in this region + /// + public int RemainingTreasure + { + get => TreasureState.RemainingTreasure; + set + { + TreasureState.RemainingTreasure = value; + OnUpdatedTreasure(); + } + } + + /// + /// Calculates the number of treasures in the dungeon + /// + /// + public int GetTreasureCount() + { + var region = (Region)this; + return region.Locations.Count(x => x.Item.Type != ItemType.Nothing && (!x.Item.IsDungeonItem || region.World.Config.ZeldaKeysanity) && x.Type != LocationType.NotInDungeon); + } + + public void ApplyState(TrackerState? state) + { + var region = (Region)this; + + if (state == null) + { + var totalTreasure = GetTreasureCount(); + TreasureState = new TrackerTreasureState() + { + WorldId = region.World.Id, RegionName = GetType().Name, TotalTreasure = totalTreasure, RemainingTreasure = totalTreasure + }; + } + else + { + TreasureState = state.TreasureStates.First(x => + x.WorldId == region.World.Id && x.RegionName == GetType().Name); + } + } + + event EventHandler? UpdatedTreasure; + + void OnUpdatedTreasure(); +} diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/INeedsMedallion.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/INeedsMedallion.cs deleted file mode 100644 index f90646300..000000000 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/INeedsMedallion.cs +++ /dev/null @@ -1,21 +0,0 @@ -using TrackerCouncil.Smz3.Shared; -using TrackerCouncil.Smz3.Shared.Enums; - -namespace TrackerCouncil.Smz3.Data.WorldData.Regions; - -/// -/// Defines a region that requires a medallion (Bombos, Ether, Quake) to be -/// accessible. -/// -public interface INeedsMedallion -{ - /// - /// Gets or sets the type of medallion required to access the region. - /// - ItemType Medallion { get; set; } - - /// - /// Gets the default medallion for the dungeon - /// - ItemType DefaultMedallion { get; } -} diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Region.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Region.cs index 2b232a5e0..af8fd1988 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Region.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Region.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -181,18 +182,42 @@ public IEnumerable GetStandaloneLocations() protected IEnumerable GetRooms() => GetType().GetPropertyValues(this); - public bool CheckDungeonMedallion(Progression items, IDungeon dungeon) + public Accessibility GetKeysanityAdjustedAccessibility(Accessibility accessibility) + { + if (Config.KeysanityForRegion(this)) + { + return accessibility; + } + else if (accessibility == Accessibility.AvailableWithKeys) + { + return Accessibility.Available; + } + else if (accessibility == Accessibility.RelevantWithKeys) + { + return Accessibility.Relevant; + } + + return accessibility; + } + + /// + /// Returns if the region matches the LocationFilter + /// + /// The filter to apply + /// True if the region matches, false otherwise + public bool MatchesFilter(RegionFilter filter) => filter switch + { + RegionFilter.None => true, + RegionFilter.ZeldaOnly => this is Z3Region, + RegionFilter.MetroidOnly => this is SMRegion, + _ => throw new InvalidEnumArgumentException(nameof(filter), (int)filter, typeof(RegionFilter)), + }; + + /*public bool CheckDungeonMedallion(Progression items, IDungeon dungeon) { if (!dungeon.NeedsMedallion) return true; var medallionItem = dungeon.MarkedMedallion; return (medallionItem != ItemType.Nothing && items.Contains(medallionItem)) || (items.Bombos && items.Ether && items.Quake); - } - - public int CountReward(Progression items, RewardType reward) - { - return World.Dungeons - .Where(x => x is IHasReward rewardRegion && x.MarkedReward == reward) - .Count(x => x.DungeonState.Cleared); - } + }*/ } diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Brinstar/KraidsLair.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Brinstar/KraidsLair.cs index 47f11c347..b6ca005b5 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Brinstar/KraidsLair.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Brinstar/KraidsLair.cs @@ -8,7 +8,7 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid.Brinstar; -public class KraidsLair : SMRegion, IHasBoss +public class KraidsLair : SMRegion, IHasBoss, IHasReward { public KraidsLair(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { @@ -16,21 +16,32 @@ public KraidsLair(World world, Config config, IMetadataService? metadata, Tracke WarehouseKihunter = new WarehouseKihunterRoom(this, metadata, trackerState); VariaSuit = new VariaSuitRoom(this, metadata, trackerState); MemoryRegionId = 1; - Boss = new Boss(Shared.Enums.BossType.Kraid, World, this, metadata, trackerState); Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Kraid's Lair"); MapName = "Brinstar"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Kraid's Lair"; public override string Area => "Brinstar"; - public override List AlsoKnownAs { get; } = new List() - { - "Warehouse" - }; + public override List AlsoKnownAs { get; } = ["Warehouse"]; + + public Boss Boss { get; set; } = null!; + + public BossType DefaultBossType => BossType.Kraid; + + public LocationId? BossLocationId => LocationId.KraidsLairVariaSuit; - public Boss Boss{ get; set; } + public Reward Reward { get; set; } = null!; + + public RewardType DefaultRewardType => RewardType.KraidToken; + + public TrackerRewardState RewardState { get; set; } = null!; + + public bool IsShuffledReward => false; public WarehouseEnergyTankRoom WarehouseEnergyTank { get; } @@ -50,6 +61,10 @@ public bool CanBeatBoss(Progression items) return CanEnter(items, true) && items.CardBrinstarBoss; } + public bool CanRetrieveReward(Progression items) => CanBeatBoss(items); + + public bool CanSeeReward(Progression items) => true; + public class WarehouseEnergyTankRoom : Room { public WarehouseEnergyTankRoom(KraidsLair region, IMetadataService? metadata, TrackerState? trackerState) diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Maridia/InnerMaridia.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Maridia/InnerMaridia.cs index c574637a2..27363434c 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Maridia/InnerMaridia.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Maridia/InnerMaridia.cs @@ -9,7 +9,7 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid.Maridia; -public class InnerMaridia : SMRegion, IHasBoss +public class InnerMaridia : SMRegion, IHasBoss, IHasReward { public InnerMaridia(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { @@ -24,16 +24,30 @@ public InnerMaridia(World world, Config config, IMetadataService? metadata, Trac Botwoons = new BotwoonsRoom(this, metadata, trackerState); SpaceJump = new SpaceJumpRoom(this, metadata, trackerState); MemoryRegionId = 4; - Boss = new Boss(Shared.Enums.BossType.Draygon, world, this, metadata, trackerState); Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Inner Maridia"); MapName = "Maridia"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Inner Maridia"; public override string Area => "Maridia"; - public Boss Boss{ get; set; } + public Boss Boss { get; set; } = null!; + + public BossType DefaultBossType => BossType.Draygon; + + public LocationId? BossLocationId => LocationId.InnerMaridiaSpaceJump; + + public Reward Reward { get; set; } = null!; + + public RewardType DefaultRewardType => RewardType.DraygonToken; + + public TrackerRewardState RewardState { get; set; } = null!; + + public bool IsShuffledReward => false; public WateringHoleRoom WateringHole { get; } @@ -63,6 +77,10 @@ public override bool CanEnter(Progression items, bool requireRewards) public bool CanBeatBoss(Progression items) => CanEnter(items, true) && CanDefeatDraygon(items, true); + public bool CanRetrieveReward(Progression items) => CanBeatBoss(items); + + public bool CanSeeReward(Progression items) => true; + private static bool CanReachAqueduct(Progression items, ILogic logic, bool requireRewards) => (items.CardMaridiaL1 && CanPassMountDeath(items, logic)) || (items.CardMaridiaL2 && logic.CanAccessMaridiaPortal(items, requireRewards)); diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Norfair/LowerNorfairEast.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Norfair/LowerNorfairEast.cs index 6aa145811..fdc478df4 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Norfair/LowerNorfairEast.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/Norfair/LowerNorfairEast.cs @@ -8,7 +8,7 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid.Norfair; -public class LowerNorfairEast : SMRegion, IHasBoss +public class LowerNorfairEast : SMRegion, IHasBoss, IHasReward { public LowerNorfairEast(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { @@ -19,16 +19,30 @@ public LowerNorfairEast(World world, Config config, IMetadataService? metadata, RidleyTank = new RidleyTankRoom(this, metadata, trackerState); Fireflea = new FirefleaRoom(this, metadata, trackerState); MemoryRegionId = 2; - Boss = new Boss(Shared.Enums.BossType.Ridley, world, this, metadata, trackerState); Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Lower Norfair East"); MapName = "Norfair"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Lower Norfair, East"; public override string Area => "Lower Norfair"; - public Boss Boss { get; set; } + public Boss Boss { get; set; } = null!; + + public BossType DefaultBossType => BossType.Ridley; + + public LocationId? BossLocationId => LocationId.LowerNorfairRidleyTank; + + public Reward Reward { get; set; } = null!; + + public RewardType DefaultRewardType => RewardType.RidleyToken; + + public TrackerRewardState RewardState { get; set; } = null!; + + public bool IsShuffledReward => false; public SpringBallMazeRoom SpringBallMaze { get; } @@ -58,6 +72,10 @@ public bool CanBeatBoss(Progression items) return CanEnter(items, true) && CanExit(items) && items.CardLowerNorfairBoss && Logic.CanUsePowerBombs(items) && items.Super; } + public bool CanRetrieveReward(Progression items) => CanBeatBoss(items); + + public bool CanSeeReward(Progression items) => true; + private bool CanExit(Progression items) { return items.CardNorfairL2 /*Bubble Mountain*/ || diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/WreckedShip.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/WreckedShip.cs index 1cb14033e..97458b4b8 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/WreckedShip.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/SuperMetroid/WreckedShip.cs @@ -8,7 +8,7 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid; -public class WreckedShip : SMRegion, IHasBoss +public class WreckedShip : SMRegion, IHasBoss, IHasReward { public WreckedShip(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { @@ -20,17 +20,30 @@ public WreckedShip(World world, Config config, IMetadataService? metadata, Track EastSuper = new EastSuperRoom(this, metadata, trackerState); GravitySuit = new GravitySuitRoom(this, metadata, trackerState); MemoryRegionId = 3; - Boss = new Boss(Shared.Enums.BossType.Phantoon, world, this, metadata, trackerState); Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Wrecked Ship"); MapName = "Wrecked Ship"; + ((IHasReward)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Wrecked Ship"; public override string Area => "Wrecked Ship"; - public Boss Boss { get; set; } + public Boss Boss { get; set; } = null!; + + public BossType DefaultBossType => BossType.Phantoon; + + public LocationId? BossLocationId => LocationId.WreckedShipWestSuper; + + public Reward Reward { get; set; } = null!; + + public RewardType DefaultRewardType => RewardType.PhantoonToken; + + public TrackerRewardState RewardState { get; set; } = null!; + + public bool IsShuffledReward => false; public MainShaftRoom MainShaft { get; } @@ -92,6 +105,10 @@ public bool CanUnlockShip(Progression items) return items.CardWreckedShipBoss && Logic.CanPassBombPassages(items); } + public bool CanRetrieveReward(Progression items) => CanEnter(items, true) && CanUnlockShip(items); + + public bool CanSeeReward(Progression items) => true; + public class MainShaftRoom : Room { public MainShaftRoom(WreckedShip region, IMetadataService? metadata, TrackerState? trackerState) diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/CastleTower.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/CastleTower.cs index a553685f8..e6b05201e 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/CastleTower.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/CastleTower.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.Options; @@ -8,57 +9,65 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class CastleTower : Z3Region, IHasReward, IDungeon +public class CastleTower : Z3Region, IHasReward, IHasTreasure, IHasBoss { public CastleTower(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeyCT }; + RegionItems = [ItemType.KeyCT]; Foyer = new FoyerRoom(this, metadata, trackerState); DarkMaze = new DarkMazeRoom(this, metadata, trackerState); StartingRooms = new List() { 224 }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Castle Tower"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Castle Tower", "Agahnim"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(RewardType.Agahnim, world, this, metadata, DungeonState); MapName = "Light World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Castle Tower"; public RewardType DefaultRewardType => RewardType.Agahnim; - public override List AlsoKnownAs { get; } - = new List() { "Agahnim's Tower", "Hyrule Castle Tower" }; + public BossType DefaultBossType => BossType.Agahnim; + + public bool IsShuffledReward => false; + + public override List AlsoKnownAs { get; } = ["Agahnim's Tower", "Hyrule Castle Tower"]; - public int SongIndex { get; init; } = 2; - public string Abbreviation => "AT"; public LocationId? BossLocationId => null; - public Reward Reward { get; set; } + public Reward Reward { get; set; } = null!; public FoyerRoom Foyer { get; } public DarkMazeRoom DarkMaze { get; } - public DungeonInfo DungeonMetadata { get; set; } + public TrackerRewardState RewardState { get; set; } = null!; - public TrackerDungeonState DungeonState { get; set; } + public TrackerTreasureState TreasureState { get; set; } = null!; + public event EventHandler? UpdatedTreasure; + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); - public Region ParentRegion => World.LightWorldNorthEast; + public Boss Boss { get; set; } = null!; public override bool CanEnter(Progression items, bool requireRewards) { return Logic.CanKillManyEnemies(items) && (items.Cape || items.MasterSword); } - public bool CanComplete(Progression items) + public bool CanBeatBoss(Progression items) => CanRetrieveReward(items); + + public bool CanRetrieveReward(Progression items) { return CanEnter(items, true) && items.Lamp && items.KeyCT >= 2 && items.Sword; } + public bool CanSeeReward(Progression items) => true; + public class FoyerRoom : Room { public FoyerRoom(Region region, IMetadataService? metadata, TrackerState? trackerState) 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 c1ae6dea5..58f11b912 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DarkWorld/DarkWorldNorthEast.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DarkWorld/DarkWorldNorthEast.cs @@ -76,7 +76,7 @@ public PyramidFairyChamber(Region region, IMetadataService? metadata, TrackerSta relevanceRequirement: items => CanAccessPyramidFairy(items, requireRewards: false), memoryAddress: 0x116, memoryFlag: 0x4, - trackerLogic: items => region.CountReward(items, RewardType.CrystalRed) == 2, + trackerLogic: items => World.CountReceivedReward(items, RewardType.CrystalRed) == 2, metadata: metadata, trackerState: trackerState), new Location(this, LocationId.PyramidFairyRight, 0x1E983, LocationType.Regular, @@ -86,7 +86,7 @@ public PyramidFairyChamber(Region region, IMetadataService? metadata, TrackerSta relevanceRequirement: items => CanAccessPyramidFairy(items, requireRewards: false), memoryAddress: 0x116, memoryFlag: 0x5, - trackerLogic: items => region.CountReward(items, RewardType.CrystalRed) == 2, + trackerLogic: items => World.CountReceivedReward(items, RewardType.CrystalRed) == 2, metadata: metadata, trackerState: trackerState) }; diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DesertPalace.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DesertPalace.cs index ab7fe6e43..f96e8985d 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DesertPalace.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/DesertPalace.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,18 +10,11 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class DesertPalace : Z3Region, IHasReward, IDungeon +public class DesertPalace : Z3Region, IHasReward, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D59B, - 0x02D59C, - 0x02D59D, - 0x02D59E - }; - public DesertPalace(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeyDP, ItemType.BigKeyDP, ItemType.MapDP, ItemType.CompassDP }; + RegionItems = [ItemType.KeyDP, ItemType.BigKeyDP, ItemType.MapDP, ItemType.CompassDP]; BigChest = new Location(this, LocationId.DesertPalaceBigChest, 0x1E98F, LocationType.Regular, name: "Big Chest", @@ -72,7 +66,7 @@ public DesertPalace(World world, Config config, IMetadataService? metadata, Trac access: items => ( Logic.CanLiftLight(items) || (Logic.CanAccessMiseryMirePortal(items) && items.Mirror) - ) && items.BigKeyDP && items.KeyDP && Logic.CanLightTorches(items) && CanBeatBoss(items), + ) && items.BigKeyDP && items.KeyDP && Logic.CanLightTorches(items) && CanDefeatLanmolas(items), memoryAddress: 0x33, memoryFlag: 0xB, metadata: metadata, @@ -81,32 +75,37 @@ public DesertPalace(World world, Config config, IMetadataService? metadata, Trac MemoryAddress = 0x33; MemoryFlag = 0xB; StartingRooms = new List() { 99, 131, 132, 133 }; - Reward = new Reward(RewardType.None, world, this); Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Desert Palace"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Desert Palace", "Lanmolas"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(DungeonState.Reward ?? RewardType.None, world, this, metadata, DungeonState); MapName = "Light World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Desert Palace"; public RewardType DefaultRewardType => RewardType.PendantBlue; - public override List AlsoKnownAs { get; } - = new List() { "Dessert Palace" }; + public BossType DefaultBossType => BossType.Lanmolas; + + public bool IsShuffledReward => true; + + public override List AlsoKnownAs { get; } = ["Dessert Palace"]; - public int SongIndex { get; init; } = 1; - public string Abbreviation => "DP"; public LocationId? BossLocationId => LocationId.DesertPalaceLanmolas; - public Reward Reward { get; set; } + public Reward Reward { get; set; } = null!; + + public TrackerRewardState RewardState { get; set; } = null!; - public DungeonInfo DungeonMetadata { get; set; } + public TrackerTreasureState TreasureState { get; set; } = null!; - public TrackerDungeonState DungeonState { get; set; } + public event EventHandler? UpdatedTreasure; - public Region ParentRegion => World.LightWorldSouth; + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); + + public Boss Boss { get; set; } = null!; public Location BigChest { get; } @@ -127,12 +126,13 @@ public override bool CanEnter(Progression items, bool requireRewards) Logic.CanAccessMiseryMirePortal(items) && items.Mirror; } - public bool CanComplete(Progression items) - { - return LanmolasReward.IsAvailable(items); - } + public bool CanBeatBoss(Progression items) => LanmolasReward.IsAvailable(items); + + public bool CanRetrieveReward(Progression items) => LanmolasReward.IsAvailable(items); + + public bool CanSeeReward(Progression items) => !World.Config.ZeldaKeysanity || items.Contains(ItemType.MapDP); - private bool CanBeatBoss(Progression items) + private bool CanDefeatLanmolas(Progression items) { return items.Sword || items.Hammer || items.Bow || items.FireRod || items.IceRod || diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/EasternPalace.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/EasternPalace.cs index 9e68dad52..b1736a51e 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/EasternPalace.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/EasternPalace.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,15 +10,11 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class EasternPalace : Z3Region, IHasReward, IDungeon +public class EasternPalace : Z3Region, IHasReward, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D59A - }; - public EasternPalace(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.BigKeyEP, ItemType.MapEP, ItemType.CompassEP }; + RegionItems = [ItemType.BigKeyEP, ItemType.MapEP, ItemType.CompassEP]; CannonballChest = new Location(this, LocationId.EasternPalaceCannonballChest, 0x1E9B3, LocationType.Regular, name: "Cannonball Chest", @@ -74,29 +71,34 @@ public EasternPalace(World world, Config config, IMetadataService? metadata, Tra MemoryFlag = 0xB; StartingRooms = new List { 201 }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Eastern Palace"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Eastern Palace", "Armos Knights"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(DungeonState.Reward ?? RewardType.None, world, this, metadata, DungeonState); MapName = "Light World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Eastern Palace"; public RewardType DefaultRewardType => RewardType.PendantGreen; - public Reward Reward { get; set; } + public BossType DefaultBossType => BossType.ArmosKnights; - public DungeonInfo DungeonMetadata { get; set; } + public bool IsShuffledReward => true; - public TrackerDungeonState DungeonState { get; set; } + public Reward Reward { get; set; } = null!; - public int SongIndex { get; init; } = 0; + public TrackerTreasureState TreasureState { get; set; } = null!; - public string Abbreviation => "EP"; + public event EventHandler? UpdatedTreasure; - public LocationId? BossLocationId => LocationId.EasternPalaceArmosKnights; + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); - public Region ParentRegion => World.LightWorldNorthEast; + public TrackerRewardState RewardState { get; set; } = null!; + + public Boss Boss { get; set; } = null!; + + public LocationId? BossLocationId => LocationId.EasternPalaceArmosKnights; public Location CannonballChest { get; } @@ -110,6 +112,11 @@ public EasternPalace(World world, Config config, IMetadataService? metadata, Tra public Location ArmosKnightsRewards { get; } - public bool CanComplete(Progression items) + public bool CanRetrieveReward(Progression items) + => ArmosKnightsRewards.IsAvailable(items); + + public bool CanSeeReward(Progression items) => !World.Config.ZeldaKeysanity || items.Contains(ItemType.MapEP); + + public bool CanBeatBoss(Progression items) => ArmosKnightsRewards.IsAvailable(items); } diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/GanonsTower.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/GanonsTower.cs index 80b847676..8552bf26a 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/GanonsTower.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/GanonsTower.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,15 +10,11 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class GanonsTower : Z3Region, IDungeon +public class GanonsTower : Z3Region, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D5C9 - }; - public GanonsTower(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeyGT, ItemType.BigKeyGT, ItemType.MapGT, ItemType.CompassGT }; + RegionItems = [ItemType.KeyGT, ItemType.BigKeyGT, ItemType.MapGT, ItemType.CompassGT]; BobsTorch = new Location(this, LocationId.GanonsTowerBobsTorch, 0x308161, LocationType.Regular, name: "Bob's Torch", @@ -116,20 +113,25 @@ public GanonsTower(World world, Config config, IMetadataService? metadata, Track MiniHelmasaurRoom = new MiniHelmasaurRoomRoom(this, metadata, trackerState); StartingRooms = new List { 0xC }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Ganon's Tower"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Ganon's Tower", "Ganon"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); MapName = "Dark World"; + + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Ganon's Tower"; - public int SongIndex { get; init; } = 11; - public string Abbreviation => "GT"; public LocationId? BossLocationId => null; - public DungeonInfo DungeonMetadata { get; set; } + public BossType DefaultBossType => BossType.Ganon; + + public TrackerTreasureState TreasureState { get; set; } = null!; + + public event EventHandler? UpdatedTreasure; - public TrackerDungeonState DungeonState { get; set; } + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); + + public Boss Boss { get; set; } = null!; public Location BobsTorch { get; } @@ -159,8 +161,6 @@ public GanonsTower(World world, Config config, IMetadataService? metadata, Track public MiniHelmasaurRoomRoom MiniHelmasaurRoom { get; } - public Region ParentRegion => World.DarkWorldDeathMountainWest; - public override bool CanEnter(Progression items, bool requireRewards) { var smBosses = new[] { BossType.Kraid, BossType.Phantoon, BossType.Draygon, BossType.Ridley }; @@ -191,6 +191,9 @@ public override bool CanFill(Item item, Progression items) return base.CanFill(item, items); } + public bool CanBeatBoss(Progression items) => MoldormChest.IsAvailable(items) && items.MasterSword && + items.Contains(ItemType.SilverArrows); + private bool TowerAscend(Progression items) { return items.BigKeyGT && items.KeyGT >= 3 && items.Bow && Logic.CanLightTorches(items); diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/HyruleCastle.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/HyruleCastle.cs index ac0e49beb..15dc98748 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/HyruleCastle.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/HyruleCastle.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,13 +10,13 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class HyruleCastle : Z3Region, IDungeon +public class HyruleCastle : Z3Region, IHasTreasure, IHasBoss { public const int SphereOne = -10; public HyruleCastle(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeyHC, ItemType.MapHC }; + RegionItems = [ItemType.KeyHC, ItemType.MapHC]; Sanctuary = new Location(this, LocationId.Sanctuary, 0x1EA79, LocationType.Regular, name: "Sanctuary", @@ -80,20 +81,27 @@ public HyruleCastle(World world, Config config, IMetadataService? metadata, Trac StartingRooms = new List { 96, 97, 98 }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Hyrule Castle"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Hyrule Castle", "Ball and Chain Soldier"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); MapName = "Light World"; + + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Hyrule Castle"; - public int SongIndex { get; init; } = 0; - public string Abbreviation => "HC"; + public BossType DefaultBossType => BossType.CastleGuard; + public LocationId? BossLocationId => null; - public DungeonInfo DungeonMetadata { get; set; } + public TrackerTreasureState TreasureState { get; set; } = null!; + + public event EventHandler? UpdatedTreasure; - public TrackerDungeonState DungeonState { get; set; } + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); + + public Boss Boss { get; set; } = null!; + + public bool CanBeatBoss(Progression items) => ZeldasCell.IsAvailable(items); public Location Sanctuary { get; } @@ -109,8 +117,6 @@ public HyruleCastle(World world, Config config, IMetadataService? metadata, Trac public BackOfEscapeRoom BackOfEscape { get; } - public Region ParentRegion => World.LightWorldNorthEast; - public class BackOfEscapeRoom : Room { public BackOfEscapeRoom(Region region, IMetadataService? metadata, TrackerState? trackerState) diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/IcePalace.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/IcePalace.cs index 3aa042dfd..be8378536 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/IcePalace.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/IcePalace.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,14 +10,11 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class IcePalace : Z3Region, IHasReward, IDungeon +public class IcePalace : Z3Region, IHasReward, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D5BF - }; public IcePalace(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeyIP, ItemType.BigKeyIP, ItemType.MapIP, ItemType.CompassIP }; + RegionItems = [ItemType.KeyIP, ItemType.BigKeyIP, ItemType.MapIP, ItemType.CompassIP]; CompassChest = new Location(this, LocationId.IcePalaceCompassChest, 0x1E9D4, LocationType.Regular, name: "Compass Chest", @@ -99,27 +97,34 @@ public IcePalace(World world, Config config, IMetadataService? metadata, Tracker MemoryFlag = 0xB; StartingRooms = new List { 14 }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Ice Palace"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Ice Palace", "Kholdstare"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(DungeonState.Reward ?? RewardType.None, world, this, metadata, DungeonState); MapName = "Dark World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Ice Palace"; public RewardType DefaultRewardType => RewardType.CrystalRed; - public int SongIndex { get; init; } = 7; - public string Abbreviation => "IP"; + public BossType DefaultBossType => BossType.Kholdstare; + + public bool IsShuffledReward => true; + public LocationId? BossLocationId => LocationId.IcePalaceKholdstare; - public Reward Reward { get; set; } + public Reward Reward { get; set; } = null!; + + public TrackerTreasureState TreasureState { get; set; } = null!; + + public event EventHandler? UpdatedTreasure; - public DungeonInfo DungeonMetadata { get; set; } + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); - public TrackerDungeonState DungeonState { get; set; } + public TrackerRewardState RewardState { get; set; } = null!; - public Region ParentRegion => World.DarkWorldSouth; + public Boss Boss { get; set; } = null!; public Location CompassChest { get; } @@ -142,10 +147,11 @@ public override bool CanEnter(Progression items, bool requireRewards) return items.MoonPearl && items.Flippers && Logic.CanLiftHeavy(items) && Logic.CanMeltFreezors(items); } - public bool CanComplete(Progression items) - { - return KholdstareReward.IsAvailable(items); - } + public bool CanBeatBoss(Progression items) => KholdstareReward.IsAvailable(items); + + public bool CanRetrieveReward(Progression items) => KholdstareReward.IsAvailable(items); + + public bool CanSeeReward(Progression items) => !World.Config.ZeldaKeysanity || items.Contains(ItemType.MapIP); private bool CanNotWasteKeysBeforeAccessible(Progression items, params Location[] locations) { diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/LightWorld/DeathMountain/LightWorldDeathMountainEast.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/LightWorld/DeathMountain/LightWorldDeathMountainEast.cs index fd42356b1..345ae1e57 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/LightWorld/DeathMountain/LightWorldDeathMountainEast.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/LightWorld/DeathMountain/LightWorldDeathMountainEast.cs @@ -36,7 +36,7 @@ public LightWorldDeathMountainEast(World world, Config config, IMetadataService? access: items => items.Mirror && items.KeyTR >= 2 && World.TurtleRock.CanEnter(items, true), memoryAddress: 0x10C, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.DungeonState.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState); diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/LightWorld/LightWorldNorthEast.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/LightWorld/LightWorldNorthEast.cs index 77713d4e5..5ab761198 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/LightWorld/LightWorldNorthEast.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/LightWorld/LightWorldNorthEast.cs @@ -84,7 +84,7 @@ public SahasrahlasHideoutRoom(Region region, IMetadataService? metadata, Tracker memoryAddress: 0x190, memoryFlag: 0x10, memoryType: LocationMemoryType.ZeldaMisc, - trackerLogic: items => region.CountReward(items, RewardType.PendantGreen) == 1, + trackerLogic: items => World.CountReceivedReward(items, RewardType.PendantGreen) == 1, metadata: metadata, trackerState: trackerState) }; diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/MiseryMire.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/MiseryMire.cs index 7e2c2d43d..191cb7171 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/MiseryMire.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/MiseryMire.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,14 +10,11 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class MiseryMire : Z3Region, IHasReward, INeedsMedallion, IDungeon +public class MiseryMire : Z3Region, IHasReward, IHasPrerequisite, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D5B9 - }; public MiseryMire(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeyMM, ItemType.BigKeyMM, ItemType.MapMM, ItemType.CompassMM }; + RegionItems = [ItemType.KeyMM, ItemType.BigKeyMM, ItemType.MapMM, ItemType.CompassMM]; MainLobby = new Location(this, LocationId.MiseryMireMainLobby, 0x1EA5E, LocationType.Regular, name: "Main Lobby", @@ -24,7 +22,7 @@ public MiseryMire(World world, Config config, IMetadataService? metadata, Tracke access: items => items.BigKeyMM || items.KeyMM >= 1, memoryAddress: 0xC2, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState); @@ -34,7 +32,7 @@ public MiseryMire(World world, Config config, IMetadataService? metadata, Tracke access: items => items.BigKeyMM || items.KeyMM >= 1, memoryAddress: 0xC3, memoryFlag: 0x5, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState); @@ -43,7 +41,7 @@ public MiseryMire(World world, Config config, IMetadataService? metadata, Tracke vanillaItem: ItemType.KeyMM, memoryAddress: 0xA2, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState); @@ -52,7 +50,7 @@ public MiseryMire(World world, Config config, IMetadataService? metadata, Tracke vanillaItem: ItemType.KeyMM, memoryAddress: 0xB3, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState); @@ -64,7 +62,7 @@ public MiseryMire(World world, Config config, IMetadataService? metadata, Tracke && items.KeyMM >= (BigKeyChest.ItemIs(ItemType.BigKeyMM, World) ? 2 : 3), memoryAddress: 0xC1, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState); @@ -75,7 +73,7 @@ public MiseryMire(World world, Config config, IMetadataService? metadata, Tracke && items.KeyMM >= (CompassChest.ItemIs(ItemType.BigKeyMM, World) ? 2 : 3), memoryAddress: 0xD1, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState); @@ -85,7 +83,7 @@ public MiseryMire(World world, Config config, IMetadataService? metadata, Tracke access: items => items.BigKeyMM, memoryAddress: 0xC3, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState); @@ -95,41 +93,51 @@ public MiseryMire(World world, Config config, IMetadataService? metadata, Tracke access: items => items.BigKeyMM && Logic.CanPassSwordOnlyDarkRooms(items) && items.Somaria, memoryAddress: 0x90, memoryFlag: 0xB, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState); MemoryAddress = 0x90; MemoryFlag = 0xB; StartingRooms = new List { 152 }; - Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Misery Mire"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Misery Mire", "Vitreous"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(DungeonState.Reward ?? RewardType.None, world, this, metadata, DungeonState); - Medallion = DungeonState.RequiredMedallion ?? ItemType.Nothing; MapName = "Dark World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasPrerequisite)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Misery Mire"; public RewardType DefaultRewardType => RewardType.CrystalRed; - public ItemType DefaultMedallion => ItemType.Ether; + public BossType DefaultBossType => BossType.Vitreous; + + public bool IsShuffledReward => true; + + public ItemType DefaultRequiredItem => ItemType.Ether; - public int SongIndex { get; init; } = 5; - public string Abbreviation => "MM"; public LocationId? BossLocationId => LocationId.MiseryMireVitreous; - public Reward Reward { get; set; } + public Reward Reward { get; set; } = null!; + + public TrackerRewardState RewardState { get; set; } = null!; + + public TrackerTreasureState TreasureState { get; set; } = null!; - public DungeonInfo DungeonMetadata { get; set; } + public event EventHandler? UpdatedTreasure; - public TrackerDungeonState DungeonState { get; set; } + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); - public Region ParentRegion => World.DarkWorldMire; + public TrackerPrerequisiteState PrerequisiteState { get; set; } = null!; - public ItemType Medallion { get; set; } + public event EventHandler? UpdatedPrerequisite; + + public void OnUpdatedPrerequisite() => UpdatedPrerequisite?.Invoke(this, EventArgs.Empty); + + public Boss Boss { get; set; } = null!; public Location MainLobby { get; } @@ -150,12 +158,13 @@ public MiseryMire(World world, Config config, IMetadataService? metadata, Tracke // Need "CanKillManyEnemies" if implementing swordless public override bool CanEnter(Progression items, bool requireRewards) { - return items.Contains(Medallion) && items.Sword && items.MoonPearl && + return items.Contains(PrerequisiteState.RequiredItem) && items is { Sword: true, MoonPearl: true } && (items.Boots || items.Hookshot) && World.DarkWorldMire.CanEnter(items, requireRewards); } - public bool CanComplete(Progression items) - { - return VitreousReward.IsAvailable(items); - } + public bool CanBeatBoss(Progression items) => VitreousReward.IsAvailable(items); + + public bool CanRetrieveReward(Progression items) => VitreousReward.IsAvailable(items); + + public bool CanSeeReward(Progression items) => !World.Config.ZeldaKeysanity || items.Contains(ItemType.MapMM); } diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/PalaceOfDarkness.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/PalaceOfDarkness.cs index d0efef2b3..fc97ee264 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/PalaceOfDarkness.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/PalaceOfDarkness.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,15 +10,11 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class PalaceOfDarkness : Z3Region, IHasReward, IDungeon +public class PalaceOfDarkness : Z3Region, IHasReward, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D5B8 - }; - public PalaceOfDarkness(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeyPD, ItemType.BigKeyPD, ItemType.MapPD, ItemType.CompassPD }; + RegionItems = [ItemType.KeyPD, ItemType.BigKeyPD, ItemType.MapPD, ItemType.CompassPD]; ShooterRoom = new Location(this, LocationId.PalaceOfDarknessShooterRoom, 0x1EA5B, LocationType.Regular, name: "Shooter Room", @@ -120,29 +117,36 @@ public PalaceOfDarkness(World world, Config config, IMetadataService? metadata, MemoryFlag = 0xB; StartingRooms = new List { 74 }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Palace of Darkness"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Palace of Darkness", "Helmasaur King"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(DungeonState.Reward ?? RewardType.None, world, this, metadata, DungeonState); MapName = "Dark World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Palace of Darkness"; public RewardType DefaultRewardType => RewardType.CrystalBlue; + public BossType DefaultBossType => BossType.HelmasaurKing; + + public bool IsShuffledReward => true; + public override string Area => "Dark Palace"; - public int SongIndex { get; init; } = 4; - public string Abbreviation => "PD"; public LocationId? BossLocationId => LocationId.PalaceOfDarknessHelmasaurKing; - public Reward Reward { get; set; } + public Reward Reward { get; set; } = null!; + + public TrackerTreasureState TreasureState { get; set; } = null!; - public DungeonInfo DungeonMetadata { get; set; } + public event EventHandler? UpdatedTreasure; - public TrackerDungeonState DungeonState { get; set; } + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); - public Region ParentRegion => World.DarkWorldNorthEast; + public TrackerRewardState RewardState { get; set; } = null!; + + public Boss Boss { get; set; } = null!; public Location ShooterRoom { get; } @@ -173,10 +177,11 @@ public override bool CanEnter(Progression items, bool requireRewards) return items.MoonPearl && World.DarkWorldNorthEast.CanEnter(items, requireRewards); } - public bool CanComplete(Progression items) - { - return HelmasaurKingReward.IsAvailable(items); - } + public bool CanBeatBoss(Progression items) => HelmasaurKingReward.IsAvailable(items); + + public bool CanRetrieveReward(Progression items) => HelmasaurKingReward.IsAvailable(items); + + public bool CanSeeReward(Progression items) => !World.Config.ZeldaKeysanity || items.Contains(ItemType.MapPD); public class DarkMazeRoom : Room { diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/SkullWoods.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/SkullWoods.cs index 3e3ef9ee8..c873f47ba 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/SkullWoods.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/SkullWoods.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,22 +10,11 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class SkullWoods : Z3Region, IHasReward, IDungeon +public class SkullWoods : Z3Region, IHasReward, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D5BA, - 0x02D5BB, - 0x02D5BC, - 0x02D5BD, - 0x02D608, - 0x02D609, - 0x02D60A, - 0x02D60B - }; - public SkullWoods(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeySW, ItemType.BigKeySW, ItemType.MapSW, ItemType.CompassSW }; + RegionItems = [ItemType.KeySW, ItemType.BigKeySW, ItemType.MapSW, ItemType.CompassSW]; PotPrison = new Location(this, LocationId.SkullWoodsPotPrison, 0x1E9A1, LocationType.Regular, name: "Pot Prison", @@ -99,30 +89,36 @@ public SkullWoods(World world, Config config, IMetadataService? metadata, Tracke MemoryFlag = 0xB; StartingRooms = new List { 86, 87, 88, 89, 103, 104 }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Skull Woods"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Skull Woods", "Mothula"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(DungeonState.Reward ?? RewardType.None, world, this, metadata, DungeonState); MapName = "Dark World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Skull Woods"; public RewardType DefaultRewardType => RewardType.CrystalBlue; - public override List AlsoKnownAs { get; } - = new List() { "Skill Woods" }; + public BossType DefaultBossType => BossType.Mothula; + + public bool IsShuffledReward => true; + + public override List AlsoKnownAs { get; } = ["Skill Woods"]; - public int SongIndex { get; init; } = 6; - public string Abbreviation => "SW"; public LocationId? BossLocationId => LocationId.SkullWoodsMothula; - public Reward Reward { get; set; } + public Reward Reward { get; set; } = null!; + + public TrackerTreasureState TreasureState { get; set; } = null!; - public DungeonInfo DungeonMetadata { get; set; } + public event EventHandler? UpdatedTreasure; - public TrackerDungeonState DungeonState { get; set; } + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); - public Region ParentRegion => World.DarkWorldNorthWest; + public TrackerRewardState RewardState { get; set; } = null!; + + public Boss Boss { get; set; } = null!; public Location PotPrison { get; } @@ -145,8 +141,9 @@ public override bool CanEnter(Progression items, bool requireRewards) return items.MoonPearl && World.DarkWorldNorthWest.CanEnter(items, requireRewards); } - public bool CanComplete(Progression items) - { - return MothulaReward.IsAvailable(items); - } + public bool CanBeatBoss(Progression items) => MothulaReward.IsAvailable(items); + + public bool CanRetrieveReward(Progression items) => MothulaReward.IsAvailable(items); + + public bool CanSeeReward(Progression items) => !World.Config.ZeldaKeysanity || items.Contains(ItemType.MapSW); } diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/SwampPalace.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/SwampPalace.cs index 253abd435..51e1f6437 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/SwampPalace.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/SwampPalace.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,15 +10,11 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class SwampPalace : Z3Region, IHasReward, IDungeon +public class SwampPalace : Z3Region, IHasReward, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D5B7 - }; - public SwampPalace(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeySP, ItemType.BigKeySP, ItemType.MapSP, ItemType.CompassSP }; + RegionItems = [ItemType.KeySP, ItemType.BigKeySP, ItemType.MapSP, ItemType.CompassSP]; Entrance = new Location(this, LocationId.SwampPalaceEntrance, 0x1EA9D, LocationType.Regular, name: "Entrance", @@ -98,27 +95,34 @@ public SwampPalace(World world, Config config, IMetadataService? metadata, Track MemoryFlag = 0xB; StartingRooms = new List { 40 }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Swamp Palace"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Swamp Palace", "Arrghus"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(DungeonState.Reward ?? RewardType.None, world, this, metadata, DungeonState); MapName = "Dark World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Swamp Palace"; public RewardType DefaultRewardType => RewardType.CrystalBlue; - public int SongIndex { get; init; } = 3; - public string Abbreviation => "SP"; + public BossType DefaultBossType => BossType.Arrghus; + + public bool IsShuffledReward => true; + public LocationId? BossLocationId => LocationId.SwampPalaceArrghus; - public Reward Reward { get; set; } + public Reward Reward { get; set; } = null!; + + public TrackerTreasureState TreasureState { get; set; } = null!; - public DungeonInfo DungeonMetadata { get; set; } + public event EventHandler? UpdatedTreasure; - public TrackerDungeonState DungeonState { get; set; } + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); - public Region ParentRegion => World.DarkWorldSouth; + public TrackerRewardState RewardState { get; set; } = null!; + + public Boss Boss { get; set; } = null!; public Location Entrance { get; } @@ -143,10 +147,11 @@ public override bool CanEnter(Progression items, bool requireRewards) return items.MoonPearl && items.Mirror && items.Flippers && World.DarkWorldSouth.CanEnter(items, requireRewards); } - public bool CanComplete(Progression items) - { - return ArrghusReward.IsAvailable(items); - } + public bool CanBeatBoss(Progression items) => ArrghusReward.IsAvailable(items); + + public bool CanRetrieveReward(Progression items) => ArrghusReward.IsAvailable(items); + + public bool CanSeeReward(Progression items) => !World.Config.ZeldaKeysanity || items.Contains(ItemType.MapSP); public class FloodedRoomRoom : Room { diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/ThievesTown.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/ThievesTown.cs index b67726a81..dec6aca80 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/ThievesTown.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/ThievesTown.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,15 +10,11 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class ThievesTown : Z3Region, IHasReward, IDungeon +public class ThievesTown : Z3Region, IHasReward, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D5C6 - }; - public ThievesTown(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeyTT, ItemType.BigKeyTT, ItemType.MapTT, ItemType.CompassTT }; + RegionItems = [ItemType.KeyTT, ItemType.BigKeyTT, ItemType.MapTT, ItemType.CompassTT]; MapChest = new Location(this, LocationId.ThievesTownMapChest, 0x1EA01, LocationType.Regular, name: "Map Chest", @@ -82,7 +79,7 @@ public ThievesTown(World world, Config config, IMetadataService? metadata, Track BlindReward = new Location(this, LocationId.ThievesTownBlind, 0x308156, LocationType.Regular, name: "Blind", vanillaItem: ItemType.HeartContainer, - access: items => items.BigKeyTT && items.KeyTT && CanBeatBoss(items), + access: items => items.BigKeyTT && items.KeyTT && CanBeatBlind(items), memoryAddress: 0xAC, memoryFlag: 0xB, metadata: metadata, @@ -92,27 +89,34 @@ public ThievesTown(World world, Config config, IMetadataService? metadata, Track MemoryFlag = 0xB; StartingRooms = new List { 219 }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Thieves' Town"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Thieves' Town", "Blind"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(DungeonState.Reward ?? RewardType.None, world, this, metadata, DungeonState); MapName = "Dark World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Thieves' Town"; public RewardType DefaultRewardType => RewardType.CrystalBlue; - public int SongIndex { get; init; } = 9; - public string Abbreviation => "TT"; + public BossType DefaultBossType => BossType.Blind; + + public bool IsShuffledReward => true; + public LocationId? BossLocationId => LocationId.ThievesTownBlind; - public Reward Reward { get; set; } + public Reward Reward { get; set; } = null!; + + public TrackerTreasureState TreasureState { get; set; } = null!; - public DungeonInfo DungeonMetadata { get; set; } + public event EventHandler? UpdatedTreasure; - public TrackerDungeonState DungeonState { get; set; } + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); - public Region ParentRegion => World.DarkWorldNorthWest; + public TrackerRewardState RewardState { get; set; } = null!; + + public Boss Boss { get; set; } = null!; public Location MapChest { get; } @@ -135,12 +139,13 @@ public override bool CanEnter(Progression items, bool requireRewards) return items.MoonPearl && World.DarkWorldNorthWest.CanEnter(items, requireRewards); } - public bool CanComplete(Progression items) - { - return BlindReward.IsAvailable(items); - } + public bool CanBeatBoss(Progression items) =>BlindReward.IsAvailable(items); + + public bool CanRetrieveReward(Progression items) =>BlindReward.IsAvailable(items); + + public bool CanSeeReward(Progression items) => !World.Config.ZeldaKeysanity || items.Contains(ItemType.MapTT); - private bool CanBeatBoss(Progression items) + private bool CanBeatBlind(Progression items) { return items.Sword || items.Hammer || items.Somaria || items.Byrna; diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/TowerOfHera.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/TowerOfHera.cs index ecaf65d8c..19c558b3e 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/TowerOfHera.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/TowerOfHera.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,17 +10,11 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class TowerOfHera : Z3Region, IHasReward, IDungeon +public class TowerOfHera : Z3Region, IHasReward, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D5C5, - 0x02907A, - 0x028B8C - }; - public TowerOfHera(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeyTH, ItemType.BigKeyTH, ItemType.MapTH, ItemType.CompassTH }; + RegionItems = [ItemType.KeyTH, ItemType.BigKeyTH, ItemType.MapTH, ItemType.CompassTH]; BasementCage = new Location(this, LocationId.TowerOfHeraBasementCage, 0x308162, LocationType.HeraStandingKey, name: "Basement Cage", @@ -68,7 +63,7 @@ public TowerOfHera(World world, Config config, IMetadataService? metadata, Track MoldormReward = new Location(this, LocationId.TowerOfHeraMoldorm, 0x308152, LocationType.Regular, name: "Moldorm", vanillaItem: ItemType.HeartContainer, - access: items => items.BigKeyTH && CanBeatBoss(items), + access: items => items.BigKeyTH && CanBeatMoldorm(items), memoryAddress: 0x7, memoryFlag: 0xB, metadata: metadata, @@ -78,27 +73,34 @@ public TowerOfHera(World world, Config config, IMetadataService? metadata, Track MemoryFlag = 0xB; StartingRooms = new List { 119 }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Tower of Hera"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Tower of Hera", "Moldorm"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(DungeonState.Reward ?? RewardType.None, world, this, metadata, DungeonState); MapName = "Light World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Tower of Hera"; public RewardType DefaultRewardType => RewardType.PendantRed; - public int SongIndex { get; init; } = 8; - public string Abbreviation => "TH"; + public BossType DefaultBossType => BossType.Moldorm; + + public bool IsShuffledReward => true; + public LocationId? BossLocationId => LocationId.TowerOfHeraMoldorm; - public Reward Reward { get; set; } + public Reward Reward { get; set; } = null!; + + public TrackerTreasureState TreasureState { get; set; } = null!; - public DungeonInfo DungeonMetadata { get; set; } + public event EventHandler? UpdatedTreasure; - public TrackerDungeonState DungeonState { get; set; } + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); - public Region ParentRegion => World.LightWorldNorthWest; + public TrackerRewardState RewardState { get; set; } = null!; + + public Boss Boss { get; set; } = null!; public Location BasementCage { get; } @@ -118,12 +120,13 @@ public override bool CanEnter(Progression items, bool requireRewards) && World.LightWorldDeathMountainWest.CanEnter(items, requireRewards); } - public bool CanComplete(Progression items) - { - return MoldormReward.IsAvailable(items); - } + public bool CanBeatBoss(Progression items) => MoldormReward.IsAvailable(items); + + public bool CanRetrieveReward(Progression items) => MoldormReward.IsAvailable(items); + + public bool CanSeeReward(Progression items) => !World.Config.ZeldaKeysanity || items.Contains(ItemType.MapTH); - private bool CanBeatBoss(Progression items) + private bool CanBeatMoldorm(Progression items) { return items.Sword || items.Hammer; } diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/TurtleRock.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/TurtleRock.cs index 1d1fdcd00..0c21132e7 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/TurtleRock.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Regions/Zelda/TurtleRock.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -9,24 +10,18 @@ namespace TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -public class TurtleRock : Z3Region, IHasReward, INeedsMedallion, IDungeon +public class TurtleRock : Z3Region, IHasReward, IHasPrerequisite, IHasTreasure, IHasBoss { - public static readonly int[] MusicAddresses = new[] { - 0x02D5C7, - 0x02D5A7, - 0x02D5AA, - 0x02D5AB - }; public TurtleRock(World world, Config config, IMetadataService? metadata, TrackerState? trackerState) : base(world, config, metadata, trackerState) { - RegionItems = new[] { ItemType.KeyTR, ItemType.BigKeyTR, ItemType.MapTR, ItemType.CompassTR }; + RegionItems = [ItemType.KeyTR, ItemType.BigKeyTR, ItemType.MapTR, ItemType.CompassTR]; CompassChest = new Location(this, LocationId.TurtleRockCompassChest, 0x1EA22, LocationType.Regular, name: "Compass Chest", vanillaItem: ItemType.CompassTR, memoryAddress: 0xD6, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState?.MarkedItem), metadata: metadata, trackerState: trackerState); @@ -36,7 +31,7 @@ public TurtleRock(World world, Config config, IMetadataService? metadata, Tracke access: items => items.KeyTR >= 1, memoryAddress: 0xB6, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState?.MarkedItem), metadata: metadata, trackerState: trackerState); @@ -48,7 +43,7 @@ public TurtleRock(World world, Config config, IMetadataService? metadata, Tracke BigKeyChest.ItemIs(ItemType.KeyTR, World) ? 3 : 4), memoryAddress: 0x14, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState?.MarkedItem), metadata: metadata, trackerState: trackerState) .AlwaysAllow((item, items) => item.Is(ItemType.KeyTR, World) && items.KeyTR >= 3); @@ -59,7 +54,7 @@ public TurtleRock(World world, Config config, IMetadataService? metadata, Tracke access: items => items.BigKeyTR && items.KeyTR >= 2, memoryAddress: 0x24, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState?.MarkedItem), metadata: metadata, trackerState: trackerState) .Allow((item, items) => item.IsNot(ItemType.BigKeyTR, World)); @@ -70,17 +65,17 @@ public TurtleRock(World world, Config config, IMetadataService? metadata, Tracke access: items => items.BigKeyTR && items.KeyTR >= 2, memoryAddress: 0x4, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState?.MarkedItem), metadata: metadata, trackerState: trackerState); TrinexxReward = new Location(this, LocationId.TurtleRockTrinexx, 0x308159, LocationType.Regular, name: "Trinexx", vanillaItem: ItemType.HeartContainer, - access: items => items.BigKeyTR && items.KeyTR >= 4 && Logic.CanPassSwordOnlyDarkRooms(items) && CanBeatBoss(items), + access: items => items.BigKeyTR && items.KeyTR >= 4 && Logic.CanPassSwordOnlyDarkRooms(items) && CanBeatTrinexx(items), memoryAddress: 0xA4, memoryFlag: 0xB, - trackerLogic: items => items.HasMarkedMedallion(DungeonState!.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(PrerequisiteState?.MarkedItem), metadata: metadata, trackerState: trackerState); @@ -91,32 +86,43 @@ public TurtleRock(World world, Config config, IMetadataService? metadata, Tracke MemoryFlag = 0xB; StartingRooms = new List { 35, 36, 213, 214 }; Metadata = metadata?.Region(GetType()) ?? new RegionInfo("Turtle Rock"); - DungeonMetadata = metadata?.Dungeon(GetType()) ?? new DungeonInfo("Turtle Rock", "Trinexx"); - DungeonState = trackerState?.DungeonStates.First(x => x.WorldId == world.Id && x.Name == GetType().Name) ?? new TrackerDungeonState(); - Reward = new Reward(DungeonState.Reward ?? RewardType.None, world, this, metadata, DungeonState); - Medallion = DungeonState.RequiredMedallion ?? ItemType.Nothing; MapName = "Dark World"; + + ((IHasReward)this).ApplyState(trackerState); + ((IHasPrerequisite)this).ApplyState(trackerState); + ((IHasTreasure)this).ApplyState(trackerState); + ((IHasBoss)this).ApplyState(trackerState); } public override string Name => "Turtle Rock"; public RewardType DefaultRewardType => RewardType.CrystalBlue; - public ItemType DefaultMedallion => ItemType.Quake; + public bool IsShuffledReward => true; + + public ItemType DefaultRequiredItem => ItemType.Quake; + + public BossType DefaultBossType => BossType.Trinexx; - public int SongIndex { get; init; } = 10; - public string Abbreviation => "TR"; public LocationId? BossLocationId => LocationId.TurtleRockTrinexx; - public Reward Reward { get; set; } + public Reward Reward { get; set; } = null!; + + public TrackerTreasureState TreasureState { get; set; } = null!; + + public event EventHandler? UpdatedTreasure; + + public void OnUpdatedTreasure() => UpdatedTreasure?.Invoke(this, EventArgs.Empty); - public DungeonInfo DungeonMetadata { get; set; } + public TrackerRewardState RewardState { get; set; } = null!; - public TrackerDungeonState DungeonState { get; set; } + public TrackerPrerequisiteState PrerequisiteState { get; set; } = null!; - public Region ParentRegion => World.DarkWorldDeathMountainEast; + public event EventHandler? UpdatedPrerequisite; - public ItemType Medallion { get; set; } + public void OnUpdatedPrerequisite() => UpdatedPrerequisite?.Invoke(this, EventArgs.Empty); + + public Boss Boss { get; set; } = null!; public Location CompassChest { get; } @@ -136,19 +142,20 @@ public TurtleRock(World world, Config config, IMetadataService? metadata, Tracke public override bool CanEnter(Progression items, bool requireRewards) { - return items.Contains(Medallion) && items.Sword && items.MoonPearl && + return items.Contains(PrerequisiteState.RequiredItem) && items.Sword && items.MoonPearl && Logic.CanLiftHeavy(items) && items.Hammer && items.Somaria && World.LightWorldDeathMountainEast.CanEnter(items, requireRewards); } - public bool CanComplete(Progression items) - { - return TrinexxReward.IsAvailable(items); - } + public bool CanBeatBoss(Progression items) => TrinexxReward.IsAvailable(items); + + public bool CanRetrieveReward(Progression items) => TrinexxReward.IsAvailable(items); + + public bool CanSeeReward(Progression items) => !World.Config.ZeldaKeysanity || items.Contains(ItemType.MapTR); - private bool CanBeatBoss(Progression items) + private bool CanBeatTrinexx(Progression items) { - return items.FireRod && items.IceRod; + return items is { FireRod: true, IceRod: true }; } public class RollerRoomRoom : Room @@ -164,7 +171,7 @@ public RollerRoomRoom(Region region, IMetadataService? metadata, TrackerState? t access: items => items.FireRod, memoryAddress: 0xB7, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.DungeonState.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState), new Location(this, LocationId.TurtleRockRollerRoomRight, 0x1EA1F, LocationType.Regular, @@ -173,7 +180,7 @@ public RollerRoomRoom(Region region, IMetadataService? metadata, TrackerState? t access: items => items.FireRod, memoryAddress: 0xB7, memoryFlag: 0x5, - trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.DungeonState.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState) }; @@ -193,7 +200,7 @@ public LaserBridgeRoom(Region region, IMetadataService? metadata, TrackerState? access: CanAccess, memoryAddress: 0xD5, memoryFlag: 0x4, - trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.DungeonState.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState), new Location(this, LocationId.TurtleRockEyeBridgeTopLeft, 0x1EA2B, LocationType.Regular, @@ -202,7 +209,7 @@ public LaserBridgeRoom(Region region, IMetadataService? metadata, TrackerState? access: CanAccess, memoryAddress: 0xD5, memoryFlag: 0x5, - trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.DungeonState.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState), new Location(this, LocationId.TurtleRockEyeBridgeBottomRight, 0x1EA2E, LocationType.Regular, @@ -211,7 +218,7 @@ public LaserBridgeRoom(Region region, IMetadataService? metadata, TrackerState? access: CanAccess, memoryAddress: 0xD5, memoryFlag: 0x6, - trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.DungeonState.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState), new Location(this, LocationId.TurtleRockEyeBridgeBottomLeft, 0x1EA31, LocationType.Regular, @@ -220,7 +227,7 @@ public LaserBridgeRoom(Region region, IMetadataService? metadata, TrackerState? access: CanAccess, memoryAddress: 0xD5, memoryFlag: 0x7, - trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.DungeonState.MarkedMedallion), + trackerLogic: items => items.HasMarkedMedallion(World.TurtleRock.PrerequisiteState.MarkedItem), metadata: metadata, trackerState: trackerState) }; diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/Reward.cs b/src/TrackerCouncil.Smz3.Data/WorldData/Reward.cs index 6b0d0f4e6..11278af78 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/Reward.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/Reward.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; @@ -14,49 +15,84 @@ namespace TrackerCouncil.Smz3.Data.WorldData; /// public class Reward { - public Reward(RewardType type, World world, IHasReward region, IMetadataService? metadata = null, TrackerDungeonState? dungeonState = null) + private Accessibility _accessibility; + + public Reward(RewardType type, World world, IMetadataService? metadata) { Type = type; World = world; - Region = region; Metadata = metadata?.Reward(type) ?? new RewardInfo(type); - State = dungeonState ?? new TrackerDungeonState(); } - public RewardType Type { get; set; } + public RewardType Type { get; } + + public World World { get; } - public World World { get; set; } + public RewardInfo Metadata { get; } - public IHasReward Region { get; set; } + public IHasReward? Region { get; set; } - public RewardInfo Metadata { get; set; } + public TrackerRewardState State => Region?.RewardState ?? new TrackerRewardState(); - public TrackerDungeonState State { get; set; } + public bool HasReceivedReward + { + get => State.HasReceivedReward; + set + { + State.HasReceivedReward = value; + UpdatedRewardState?.Invoke(this, EventArgs.Empty); + } + } - public static ICollection CreatePool(World world) + public RewardType? MarkedReward { - var regions = world.Regions.OfType().ToList(); - - return new List() - { - CreatePlayerReward(RewardType.PendantGreen, world, regions), - CreatePlayerReward(RewardType.PendantRed, world, regions), - CreatePlayerReward(RewardType.PendantBlue, world, regions), - CreatePlayerReward(RewardType.CrystalBlue, world, regions), - CreatePlayerReward(RewardType.CrystalBlue, world, regions), - CreatePlayerReward(RewardType.CrystalBlue, world, regions), - CreatePlayerReward(RewardType.CrystalBlue, world, regions), - CreatePlayerReward(RewardType.CrystalBlue, world, regions), - CreatePlayerReward(RewardType.CrystalRed, world, regions), - CreatePlayerReward(RewardType.CrystalRed, world, regions), - CreatePlayerReward(RewardType.Agahnim, world, regions), - }; + get => State.MarkedReward; + set + { + State.MarkedReward = value; + UpdatedRewardState?.Invoke(this, EventArgs.Empty); + } } - private static Reward CreatePlayerReward(RewardType reward, World world, ICollection regions) + public Accessibility Accessibility { - var region = regions.First(x => x.RewardType == reward); - regions.Remove(region); - return new(reward, world, region); + get => _accessibility; + set + { + if (_accessibility == value) return; + _accessibility = value; + UpdatedAccessibility?.Invoke(this, EventArgs.Empty); + } } + + public void UpdateAccessibility(Progression actualProgression, Progression withKeysProgression) + { + if (HasReceivedReward) + { + Accessibility = Accessibility.Cleared; + } + else if (Region == null) + { + Accessibility = Accessibility.Unknown; + } + else if (Region.CanRetrieveReward(actualProgression)) + { + Accessibility = Accessibility.Available; + } + else if (Region.CanRetrieveReward(withKeysProgression)) + { + Accessibility = Accessibility.AvailableWithKeys; + } + else + { + Accessibility = Accessibility.OutOfLogic; + } + } + + public bool HasCorrectlyMarkedReward => MarkedReward == Type; + + public event EventHandler? UpdatedRewardState; + + public event EventHandler? UpdatedAccessibility; + } diff --git a/src/TrackerCouncil.Smz3.Data/WorldData/World.cs b/src/TrackerCouncil.Smz3.Data/WorldData/World.cs index 754710647..539480be5 100644 --- a/src/TrackerCouncil.Smz3.Data/WorldData/World.cs +++ b/src/TrackerCouncil.Smz3.Data/WorldData/World.cs @@ -34,6 +34,9 @@ public World(Config config, string player, int id, string guid, bool isLocalWorl Logic = new Smz3.Data.Logic.Logic(this); + CreateBosses(metadata, trackerState); + CreateRewards(metadata); + CastleTower = new(this, Config, metadata, trackerState); EasternPalace = new(this, Config, metadata, trackerState); DesertPalace = new(this, Config, metadata, trackerState); @@ -90,9 +93,14 @@ public World(Config config, string player, int id, string guid, bool isLocalWorl WreckedShip }; Locations = Regions.SelectMany(x => x.Locations).ToImmutableList(); + LocationMap = Locations.ToImmutableDictionary(x => x.Id, x => x); Rooms = Regions.SelectMany(x => x.Rooms).ToImmutableList(); State = trackerState ?? new TrackerState(); ItemPools = new WorldItemPools(this); + TreasureRegions = Regions.OfType().ToImmutableList(); + RewardRegions = Regions.OfType().ToImmutableList(); + BossRegions = Regions.OfType().ToImmutableList(); + PrerequisiteRegions = Regions.OfType().ToImmutableList(); } public Config Config { get; } @@ -101,18 +109,25 @@ public World(Config config, string player, int id, string guid, bool isLocalWorl public int Id { get; } public bool HasCompleted { get; set; } public bool IsLocalWorld { get; set; } + public IEnumerable RewardRegions { get; set; } + public IEnumerable TreasureRegions { get; set; } + public IEnumerable BossRegions { get; set; } + public IEnumerable PrerequisiteRegions { get; set; } public IEnumerable Regions { get; } public IEnumerable Rooms { get; } public IEnumerable Locations { get; } + public IDictionary LocationMap { get; } + public List Rewards = []; + public List Bosses = []; public IEnumerable LocationItems => Locations.Select(l => l.Item); - public List TrackerItems { get; } = new List(); - public IEnumerable AllItems => TrackerItems.Concat(LocationItems); + public List CustomItems { get; } = []; + public IEnumerable AllItems => CustomItems.Concat(LocationItems); public ILogic Logic { get; } - public IEnumerable Rewards => Regions.OfType().Select(x => x.Reward); - public List TrackerBosses { get; } = new List(); - public IEnumerable GoldenBosses => Regions.OfType().Select(x => x.Boss); - public IEnumerable AllBosses => GoldenBosses.Concat(TrackerBosses); - public IEnumerable Dungeons => Regions.OfType(); + public List CustomBosses { get; } = []; + + public IEnumerable GoldenBosses => Bosses.Where(x => + x.Type is BossType.Kraid or BossType.Phantoon or BossType.Draygon or BossType.Ridley); + public IEnumerable AllBosses => Bosses.Concat(CustomBosses); public CastleTower CastleTower { get; } public EasternPalace EasternPalace { get; } public DesertPalace DesertPalace { get; } @@ -154,7 +169,7 @@ public World(Config config, string player, int id, string guid, bool isLocalWorl public LowerNorfairEast LowerNorfairEast { get; } public WreckedShip WreckedShip { get; } public WorldItemPools ItemPools { get; } - public IEnumerable HintTiles { get; set; } = new List(); + public IEnumerable HintTiles { get; set; } = []; public IEnumerable ActiveHintTileLocations => HintTiles .Where(x => x.State?.HintState == HintState.Viewed && x.Locations?.Any() == true && x.WorldId == Id) @@ -168,6 +183,35 @@ public World(Config config, string player, int id, string guid, bool isLocalWorl ?? Locations.FirstOrDefault(x => x.ToString().Equals(name, comparisonType)); } + public void CreateRewards(IMetadataService? metadata) + { + RewardType[] rewardTypes = [ RewardType.PendantGreen, RewardType.PendantRed, RewardType.PendantBlue, RewardType.CrystalRed, RewardType.CrystalRed, + RewardType.CrystalBlue, RewardType.CrystalBlue, RewardType.CrystalBlue, RewardType.CrystalBlue, RewardType.CrystalBlue, RewardType.Agahnim, + RewardType.KraidToken, RewardType.PhantoonToken, RewardType.DraygonToken, RewardType.RidleyToken ]; + + foreach (var rewardType in rewardTypes) + { + Rewards.Add(new Reward(rewardType, this, metadata)); + } + } + + public void CreateBosses(IMetadataService? metadata, TrackerState? trackerState) + { + BossType[] bossTypes = + [ + BossType.Kraid, BossType.Phantoon, BossType.Draygon, BossType.Ridley, BossType.MotherBrain, + BossType.CastleGuard, BossType.ArmosKnights, BossType.Lanmolas, BossType.Moldorm, BossType.HelmasaurKing, + BossType.Arrghus, BossType.Blind, BossType.Mothula, BossType.Kholdstare, BossType.Vitreous, + BossType.Trinexx, BossType.Agahnim, BossType.Ganon + ]; + + foreach (var bossType in bossTypes) + { + var state = trackerState?.BossStates.FirstOrDefault(x => x.Type == bossType && x.WorldId == Id); + Bosses.Add(new Boss(bossType, this, metadata, state)); + } + } + /// /// Returns the Location object matching the given ID. /// We can be confident this won't throw an exception because we have a @@ -181,22 +225,32 @@ public Location FindLocation(LocationId id) public bool CanAquire(Progression items, RewardType reward) { var dungeonWithReward = Regions.OfType().FirstOrDefault(x => reward == x.RewardType); - return dungeonWithReward != null && dungeonWithReward.CanComplete(items); + return dungeonWithReward != null && dungeonWithReward.CanRetrieveReward(items); } public bool CanAquireAll(Progression items, params RewardType[] rewards) { - return Regions.OfType().Where(x => rewards.Contains(x.RewardType)).All(x => x.CanComplete(items)); + return Regions.OfType().Where(x => rewards.Contains(x.RewardType)).All(x => x.CanRetrieveReward(items)); } public bool CanDefeatAll(Progression items, params BossType[] bosses) { - return Regions.OfType().Where(x => bosses.Contains(x.BossType)).All(x => x.CanBeatBoss(items)); + return BossRegions.Where(x => bosses.Contains(x.BossType)).All(x => x.CanBeatBoss(items)); } public int CanDefeatBossCount(Progression items, params BossType[] bosses) { - return Regions.OfType().Where(x => bosses.Contains(x.BossType)).Count(x => x.CanBeatBoss(items)); + return BossRegions.Where(x => bosses.Contains(x.BossType)).Count(x => x.CanBeatBoss(items)); + } + + public bool HasDefeated(params BossType[] bosses) + { + return Bosses.Where(x => bosses.Contains(x.Type)).All(x => x.Defeated); + } + + public Boss GetBossOfType(BossType type) + { + return Bosses.First(x => x.Type == type); } public void Setup(Random rnd) @@ -210,9 +264,9 @@ public void Setup(Random rnd) private void SetMedallions(Random rnd) { - foreach (var region in Regions.OfType()) + foreach (var region in Regions.OfType()) { - region.Medallion = rnd.Next(3) switch + region.RequiredItem = rnd.Next(3) switch { 0 => ItemType.Bombos, 1 => ItemType.Ether, @@ -223,13 +277,11 @@ private void SetMedallions(Random rnd) private void SetRewards(Random rnd) { - var rewards = new[] { - RewardType.PendantGreen, RewardType.PendantRed, RewardType.PendantBlue, RewardType.CrystalRed, RewardType.CrystalRed, - RewardType.CrystalBlue, RewardType.CrystalBlue, RewardType.CrystalBlue, RewardType.CrystalBlue, RewardType.CrystalBlue }.Shuffle(rnd); - foreach (var region in Regions.OfType().Where(x => x.RewardType == RewardType.None)) + var rewards = Rewards.Where(x => x.Type.IsInCategory(RewardCategory.Zelda) && !x.Type.IsInCategory(RewardCategory.NonRandomized)).Shuffle(rnd); + foreach (var region in RewardRegions.Where(x => x.IsShuffledReward)) { - region.Reward = new Reward(rewards.First(), this, region); - rewards.Remove(region.RewardType); + region.SetReward(rewards.First()); + rewards.Remove(region.Reward); } } @@ -252,4 +304,16 @@ private void SetBottles(Random rnd) bottleItem.UpdateItemType(newType); } } + + public int CountReceivedReward(Progression items, RewardType reward) + { + return CountReceivedRewards(items, [reward]); + } + + public int CountReceivedRewards(Progression items, IList rewards) + { + return RewardRegions + .Where(x => rewards.Contains(x.MarkedReward)) + .Count(x => x.HasReceivedReward); + } } diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Client/EventHandlers/PlayerSyncReceivedEventHandler.cs b/src/TrackerCouncil.Smz3.Multiplayer.Client/EventHandlers/PlayerSyncReceivedEventHandler.cs index 3494de5dd..37feac7ce 100644 --- a/src/TrackerCouncil.Smz3.Multiplayer.Client/EventHandlers/PlayerSyncReceivedEventHandler.cs +++ b/src/TrackerCouncil.Smz3.Multiplayer.Client/EventHandlers/PlayerSyncReceivedEventHandler.cs @@ -14,7 +14,6 @@ public class PlayerSyncReceivedEventHandlerArgs public ICollection UpdatedLocationStates { get; init; } = null!; public ICollection<(TrackerItemState State, int TrackingValue)> UpdatedItemStates { get; init; } = null!; public ICollection UpdatedBossStates { get; init; } = null!; - public ICollection UpdatedDungeonStates { get; init; } = null!; public ICollection? ItemsToGive { get; init; } public bool IsLocalPlayer { get; init; } public bool DidForfeit { get; init; } diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Client/EventHandlers/PlayerTrackedDungeonEventHandler.cs b/src/TrackerCouncil.Smz3.Multiplayer.Client/EventHandlers/PlayerTrackedDungeonEventHandler.cs deleted file mode 100644 index b0bb5ab69..000000000 --- a/src/TrackerCouncil.Smz3.Multiplayer.Client/EventHandlers/PlayerTrackedDungeonEventHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -using TrackerCouncil.Smz3.Shared.Models; - -namespace TrackerCouncil.Smz3.Multiplayer.Client.EventHandlers; - -public delegate void PlayerTrackedDungeonEventHandler(PlayerTrackedDungeonEventHandlerArgs args); - -public class PlayerTrackedDungeonEventHandlerArgs -{ - public int PlayerId { get; init; } - public string PlayerName { get; init; } = ""; - public string PhoneticName { get; init; } = ""; - public TrackerDungeonState DungeonState { get; init; } = null!; - public bool IsLocalPlayer { get; init; } -} diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Client/EventHandlers/TrackDungeonEventHandler.cs b/src/TrackerCouncil.Smz3.Multiplayer.Client/EventHandlers/TrackDungeonEventHandler.cs deleted file mode 100644 index 75c95badc..000000000 --- a/src/TrackerCouncil.Smz3.Multiplayer.Client/EventHandlers/TrackDungeonEventHandler.cs +++ /dev/null @@ -1,5 +0,0 @@ -using TrackerCouncil.Smz3.Shared.Multiplayer; - -namespace TrackerCouncil.Smz3.Multiplayer.Client.EventHandlers; - -public delegate void TrackDungeonEventHandler(MultiplayerPlayerState playerState, string dungeonName); diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Client/GameServices/MultiplayerGameTypeService.cs b/src/TrackerCouncil.Smz3.Multiplayer.Client/GameServices/MultiplayerGameTypeService.cs index 2916fde0e..650b0391d 100644 --- a/src/TrackerCouncil.Smz3.Multiplayer.Client/GameServices/MultiplayerGameTypeService.cs +++ b/src/TrackerCouncil.Smz3.Multiplayer.Client/GameServices/MultiplayerGameTypeService.cs @@ -82,9 +82,8 @@ public MultiplayerWorldState GetPlayerDefaultState(World world, IEnumerable x.Id, _ => false); var items = playerItemTypes.ToDictionary(x => x, _ => 0); - var bosses = world.GoldenBosses.Select(x => x.Type).ToDictionary(x => x, _ => false); - var dungeons = world.Dungeons.ToDictionary(x => x.GetType().Name, _ => false); - var state = new MultiplayerWorldState(locations, items, bosses, dungeons); + var bosses = world.Bosses.Select(x => x.Type).ToDictionary(x => x, _ => false); + var state = new MultiplayerWorldState(locations, items, bosses); return state; } @@ -181,14 +180,28 @@ public MultiplayerWorldState GetPlayerDefaultState(World world, IEnumerableA unique hash for all of the location items and dungeon rewards public string GetValidationHash(IEnumerable worlds) { + var worldList = worlds.ToList(); var itemHashCode = string.Join(",", - worlds.SelectMany(x => x.Locations).OrderBy(x => x.World.Id).ThenBy(x => x.Id) + worldList.SelectMany(x => x.Locations) + .OrderBy(x => x.World.Id) + .ThenBy(x => x.Id) .Select(x => x.Item.Type.ToString())); var rewardHashCode = string.Join(",", - worlds.SelectMany(x => x.Regions).OrderBy(x => x.World.Id) + worldList.SelectMany(x => x.RewardRegions) + .OrderBy(x => x.World.Id) .ThenBy(x => x.Name) - .OfType().Select(x => x.RewardType.ToString())); - return $"{NonCryptographicHash.Fnv1a(itemHashCode)}{NonCryptographicHash.Fnv1a(rewardHashCode)}"; + .Select(x => x.RewardType.ToString())); + var bossHashCode = string.Join(",", + worldList.SelectMany(x => x.BossRegions) + .OrderBy(x => x.World.Id) + .ThenBy(x => x.Name) + .Select(x => x.BossType.ToString())); + var prereqHashCode = string.Join(",", + worldList.SelectMany(x => x.PrerequisiteRegions) + .OrderBy(x => x.World.Id) + .ThenBy(x => x.Name) + .Select(x => x.RequiredItem.ToString())); + return $"{NonCryptographicHash.Fnv1a(itemHashCode)}{NonCryptographicHash.Fnv1a(rewardHashCode)}{NonCryptographicHash.Fnv1a(bossHashCode)}{NonCryptographicHash.Fnv1a(prereqHashCode)}"; } /// @@ -206,16 +219,7 @@ public async Task TrackLocation(Location location) /// The item that has been tracked public async Task TrackItem(Item item) { - await Client.TrackItem(item.Type, item.State.TrackingState, item.World.Guid); - } - - /// - /// Notifies the server that a dungeon has been tracked by the local player - /// - /// The dungeon that has been tracked - public async Task TrackDungeon(IDungeon dungeon) - { - await Client.TrackDungeon(dungeon.DungeonState.Name, (dungeon as Region)!.World.Guid); + await Client.TrackItem(item.Type, item.TrackingState, item.World.Guid); } /// @@ -334,29 +338,6 @@ public async Task TrackDeath() }; } - /// - /// Creates arguments to send to Tracker when a player tracks a dungeon - /// - /// The player that tracked the dungeon - /// The name of the dungeon that was tracked - /// If it was the local player tracking the item - public PlayerTrackedDungeonEventHandlerArgs? PlayerTrackedDungeon(MultiplayerPlayerState player, string dungeonName, bool isLocalPlayer) - { - if (TrackerState == null || isLocalPlayer) return null; - var dungeonState = - TrackerState.DungeonStates.FirstOrDefault(x => x.WorldId == player.WorldId && x.Name == dungeonName); - if (dungeonState == null || dungeonState.AutoTracked) return null; - - return new PlayerTrackedDungeonEventHandlerArgs() - { - PlayerId = player.WorldId!.Value, - PlayerName = player.PlayerName, - PhoneticName = player.PhoneticName, - IsLocalPlayer = isLocalPlayer, - DungeonState = dungeonState, - }; - } - /// /// Creates arguments for when receiving a player state from the server by determining what locations, items, etc. /// mismatch with the tracker state @@ -368,7 +349,7 @@ public async Task TrackDeath() public PlayerSyncReceivedEventHandlerArgs? PlayerSyncReceived(MultiplayerPlayerState player, MultiplayerPlayerState? previousState, bool isLocalPlayer) { - if (TrackerState == null || isLocalPlayer || player.Locations == null || player.Items == null || player.Bosses == null || player.Dungeons == null) return null; + if (TrackerState == null || isLocalPlayer || player.Locations == null || player.Items == null || player.Bosses == null) return null; var didEndGame = player.HasForfeited || player.HasCompleted; @@ -390,11 +371,6 @@ public async Task TrackDeath() var updatedBossStates = TrackerState.BossStates.Where(x => x.WorldId == player.WorldId && x.Type != BossType.None && !x.AutoTracked && defeatedBosses.Contains(x.Type)).ToList(); - // Gather dungeons that have been cleared - var clearedDungeons = player.Dungeons.Where(x => x.Tracked).Select(x => x.Dungeon).ToList(); - var updatedDungeonStates = TrackerState.DungeonStates.Where(x => - x.WorldId == player.WorldId && !x.AutoTracked && clearedDungeons.Contains(x.Name)).ToList(); - return new PlayerSyncReceivedEventHandlerArgs() { PlayerId = player.WorldId, @@ -404,7 +380,6 @@ public async Task TrackDeath() UpdatedLocationStates = updatedLocationStates, UpdatedItemStates = updatedItemStates, UpdatedBossStates = updatedBossStates, - UpdatedDungeonStates = updatedDungeonStates, ItemsToGive = itemsToGive, DidForfeit = player.HasForfeited && previousState?.HasForfeited != true, DidComplete = player.HasCompleted && previousState?.HasCompleted != true @@ -433,11 +408,7 @@ public MultiplayerWorldState GetPlayerWorldState(MultiplayerPlayerState state, T .Where(x => x.WorldId == state.WorldId && x.Type != BossType.None) .ToDictionary(x => x.Type, x => x.AutoTracked); - var dungeons = trackerState.DungeonStates - .Where(x => x.WorldId == state.WorldId) - .ToDictionary(x => x.Name, x => x.AutoTracked); - - return new MultiplayerWorldState(locations, items, bosses, dungeons); + return new MultiplayerWorldState(locations, items, bosses); } /// diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Client/GameServices/MultiworldGameService.cs b/src/TrackerCouncil.Smz3.Multiplayer.Client/GameServices/MultiworldGameService.cs index de9590abf..02019dd86 100644 --- a/src/TrackerCouncil.Smz3.Multiplayer.Client/GameServices/MultiworldGameService.cs +++ b/src/TrackerCouncil.Smz3.Multiplayer.Client/GameServices/MultiworldGameService.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Logging; +using System.Text.Json; +using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Data.GeneratedData; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.Services; @@ -11,15 +12,13 @@ namespace TrackerCouncil.Smz3.Multiplayer.Client.GameServices; /// /// Service for handling multiworld games /// -public class MultiworldGameService : MultiplayerGameTypeService +public class MultiworldGameService( + Smz3Randomizer randomizer, + Smz3MultiplayerRomGenerator multiplayerRomGenerator, + MultiplayerClientService client, + ILogger logger) + : MultiplayerGameTypeService(randomizer, multiplayerRomGenerator, client, logger) { - private ITrackerStateService _trackerStateService; - - public MultiworldGameService(Smz3Randomizer randomizer, Smz3MultiplayerRomGenerator multiplayerRomGenerator, MultiplayerClientService client, ITrackerStateService trackerStateService, ILogger logger) : base(randomizer, multiplayerRomGenerator, client, logger) - { - _trackerStateService = trackerStateService; - } - /// /// Generates a multiworld seed /// @@ -44,6 +43,8 @@ public MultiworldGameService(Smz3Randomizer randomizer, Smz3MultiplayerRomGenera generationConfigs.Add(config); } + Logger.LogDebug("{Json}", JsonSerializer.Serialize(generationConfigs)); + return GenerateSeedInternal(generationConfigs, seed, out error); } @@ -72,6 +73,8 @@ public MultiworldGameService(Smz3Randomizer randomizer, Smz3MultiplayerRomGenera generationConfigs.Add(config); } + Logger.LogDebug("{Json}", JsonSerializer.Serialize(generationConfigs)); + return RegenerateSeedInternal(generationConfigs, seed, out error); } diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Client/MultiplayerClientService.cs b/src/TrackerCouncil.Smz3.Multiplayer.Client/MultiplayerClientService.cs index f03c9f390..52ee39258 100644 --- a/src/TrackerCouncil.Smz3.Multiplayer.Client/MultiplayerClientService.cs +++ b/src/TrackerCouncil.Smz3.Multiplayer.Client/MultiplayerClientService.cs @@ -40,7 +40,6 @@ public MultiplayerClientService(ILogger logger, Random public event GameStartedEventHandler? GameStarted; public event TrackLocationEventHandler? LocationTracked; public event TrackItemEventHandler? ItemTracked; - public event TrackDungeonEventHandler? DungeonTracked; public event TrackBossEventHandler? BossTracked; public event TrackDeathEventHandler? DeathTracked; @@ -122,8 +121,6 @@ public async Task Connect(string url) _connection.On("TrackItem", OnTrackItem); - _connection.On("TrackDungeon", OnTrackDungeon); - _connection.On("TrackBoss", OnTrackBoss); _connection.On("TrackDeath", OnTrackDeath); @@ -393,17 +390,6 @@ public async Task TrackItem(ItemType itemType, int trackedValue, string? playerG await MakeRequest("TrackItem", new TrackItemRequest(playerGuid ?? CurrentPlayerGuid!, itemType, trackedValue)); } - /// - /// Notifies the server that a dungeon has been cleared - /// - /// The name of the dungeon that has been cleared - /// The player whose dungeon was cleared. Set to null to use the local player. - public async Task TrackDungeon(string dungeonName, string? playerGuid = null) - { - playerGuid ??= CurrentPlayerGuid; - await MakeRequest("TrackDungeon", new TrackDungeonRequest(playerGuid ?? CurrentPlayerGuid!, dungeonName)); - } - /// /// Notifies the server that a boss has been defeated /// @@ -618,18 +604,6 @@ private void OnTrackDeath(TrackDeathResponse response) DeathTracked?.Invoke(player); } - /// - /// On retrieving a player clearing a dungeon - /// - /// - private void OnTrackDungeon(TrackDungeonResponse response) - { - var player = Players!.First(x => x.Guid == response.PlayerGuid); - player.TrackDungeon(response.DungeonName); - _logger.LogInformation("{Player} tracked dungeon {DungeonName}", player.PlayerName, response.DungeonName); - DungeonTracked?.Invoke(player, response.DungeonName); - } - /// /// On retrieving a player tracking an item /// diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Client/MultiplayerGameService.cs b/src/TrackerCouncil.Smz3.Multiplayer.Client/MultiplayerGameService.cs index d1ef23a39..9ad8cd207 100644 --- a/src/TrackerCouncil.Smz3.Multiplayer.Client/MultiplayerGameService.cs +++ b/src/TrackerCouncil.Smz3.Multiplayer.Client/MultiplayerGameService.cs @@ -31,7 +31,6 @@ public MultiplayerGameService(MultiplayerClientService clientService, IServicePr _client.LocationTracked += ClientOnLocationTracked; _client.ItemTracked += ClientOnItemTracked; _client.BossTracked += ClientOnBossTracked; - _client.DungeonTracked += ClientOnDungeonTracked; _client.DeathTracked += ClientOnDeathTracked; _client.PlayerUpdated += ClientOnPlayerUpdated; _client.PlayerStateRequested += ClientOnPlayerStateRequested; @@ -56,8 +55,6 @@ private async void ClientOnConnectionClosed(string error, Exception? exception) public PlayerTrackedBossEventHandler? PlayerTrackedBoss; - public PlayerTrackedDungeonEventHandler? PlayerTrackedDungeon; - public PlayerTrackedDeathEventHandler? PlayerTrackedDeath; public PlayerSyncReceivedEventHandler? PlayerSyncReceived; @@ -158,15 +155,6 @@ public async Task TrackItem(Item item) await _currentGameService.TrackItem(item); } - /// - /// Sends a dungeon tracked event to the server - /// - /// The dungeon that was tracked - public async Task TrackDungeon(IDungeon dungeon) - { - await _currentGameService.TrackDungeon(dungeon); - } - /// /// Sends a boss tracked event to the server /// @@ -226,7 +214,6 @@ public void Dispose() _client.LocationTracked -= ClientOnLocationTracked; _client.ItemTracked -= ClientOnItemTracked; _client.BossTracked -= ClientOnBossTracked; - _client.DungeonTracked -= ClientOnDungeonTracked; _client.PlayerUpdated -= ClientOnPlayerUpdated; _client.PlayerStateRequested -= ClientOnPlayerStateRequested; _client.PlayerForfeited -= ClientOnPlayerForfeited; @@ -237,13 +224,6 @@ public void Dispose() } #region Multiplayer Client Events - private void ClientOnDungeonTracked(MultiplayerPlayerState playerState, string dungeonName) - { - var args = _currentGameService.PlayerTrackedDungeon(playerState, dungeonName, - playerState.Guid == _client.CurrentPlayerGuid); - if (args != null) PlayerTrackedDungeon?.Invoke(args); - } - private void ClientOnBossTracked(MultiplayerPlayerState playerState, BossType bossType) { var args = _currentGameService.PlayerTrackedBoss(playerState, bossType, diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Server/Migrations/20241003001723_ReplaceDungeonState.Designer.cs b/src/TrackerCouncil.Smz3.Multiplayer.Server/Migrations/20241003001723_ReplaceDungeonState.Designer.cs new file mode 100644 index 000000000..a7dc0c77e --- /dev/null +++ b/src/TrackerCouncil.Smz3.Multiplayer.Server/Migrations/20241003001723_ReplaceDungeonState.Designer.cs @@ -0,0 +1,299 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TrackerCouncil.Smz3.Multiplayer.Server; + +#nullable disable + +namespace TrackerCouncil.Smz3.Multiplayer.Server.Migrations +{ + [DbContext(typeof(MultiplayerDbContext))] + [Migration("20241003001723_ReplaceDungeonState")] + partial class ReplaceDungeonState + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerBossState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Boss") + .HasColumnType("INTEGER"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Tracked") + .HasColumnType("INTEGER"); + + b.Property("TrackedTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.HasIndex("PlayerId"); + + b.ToTable("MultiplayerBossStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("DeathLink") + .HasColumnType("INTEGER"); + + b.Property("Guid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastMessage") + .HasColumnType("TEXT"); + + b.Property("SaveToDatabase") + .HasColumnType("INTEGER"); + + b.Property("Seed") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SendItemsOnComplete") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ValidationHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MultiplayerGameStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerItemState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("Item") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("TrackedTime") + .HasColumnType("TEXT"); + + b.Property("TrackingValue") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.HasIndex("PlayerId"); + + b.ToTable("MultiplayerItemStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerLocationState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Tracked") + .HasColumnType("INTEGER"); + + b.Property("TrackedTime") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.HasIndex("PlayerId"); + + b.ToTable("MultiplayerLocationStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalData") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("INTEGER"); + + b.Property("GenerationData") + .HasColumnType("TEXT"); + + b.Property("Guid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PhoneticName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PlayerName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.ToTable("MultiplayerPlayerStates"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerBossState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", "Player") + .WithMany("Bosses") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerItemState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", "Player") + .WithMany("Items") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerLocationState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", "Player") + .WithMany("Locations") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany("Players") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", b => + { + b.Navigation("Bosses"); + + b.Navigation("Items"); + + b.Navigation("Locations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Server/Migrations/20241003001723_ReplaceDungeonState.cs b/src/TrackerCouncil.Smz3.Multiplayer.Server/Migrations/20241003001723_ReplaceDungeonState.cs new file mode 100644 index 000000000..965766adf --- /dev/null +++ b/src/TrackerCouncil.Smz3.Multiplayer.Server/Migrations/20241003001723_ReplaceDungeonState.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TrackerCouncil.Smz3.Multiplayer.Server.Migrations +{ + /// + public partial class ReplaceDungeonState : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "MultiplayerDungeonStates"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "MultiplayerDungeonStates", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + GameId = table.Column(type: "INTEGER", nullable: false), + PlayerId = table.Column(type: "INTEGER", nullable: false), + Dungeon = table.Column(type: "TEXT", nullable: false), + Tracked = table.Column(type: "INTEGER", nullable: false), + TrackedTime = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MultiplayerDungeonStates", x => x.Id); + table.ForeignKey( + name: "FK_MultiplayerDungeonStates_MultiplayerGameStates_GameId", + column: x => x.GameId, + principalTable: "MultiplayerGameStates", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MultiplayerDungeonStates_MultiplayerPlayerStates_PlayerId", + column: x => x.PlayerId, + principalTable: "MultiplayerPlayerStates", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_MultiplayerDungeonStates_GameId", + table: "MultiplayerDungeonStates", + column: "GameId"); + + migrationBuilder.CreateIndex( + name: "IX_MultiplayerDungeonStates_PlayerId", + table: "MultiplayerDungeonStates", + column: "PlayerId"); + } + } +} diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Server/Migrations/MultiplayerDbContextModelSnapshot.cs b/src/TrackerCouncil.Smz3.Multiplayer.Server/Migrations/MultiplayerDbContextModelSnapshot.cs index 94af58315..5209ac516 100644 --- a/src/TrackerCouncil.Smz3.Multiplayer.Server/Migrations/MultiplayerDbContextModelSnapshot.cs +++ b/src/TrackerCouncil.Smz3.Multiplayer.Server/Migrations/MultiplayerDbContextModelSnapshot.cs @@ -7,341 +7,290 @@ #nullable disable -namespace TrackerCouncil.Smz3.Multiplayer.Server.Migrations; - -[DbContext(typeof(MultiplayerDbContext))] -partial class MultiplayerDbContextModelSnapshot : ModelSnapshot +namespace TrackerCouncil.Smz3.Multiplayer.Server.Migrations { - protected override void BuildModel(ModelBuilder modelBuilder) + [DbContext(typeof(MultiplayerDbContext))] + partial class MultiplayerDbContextModelSnapshot : ModelSnapshot { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.11"); - - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerBossState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Boss") - .HasColumnType("INTEGER"); - - b.Property("GameId") - .HasColumnType("INTEGER"); - - b.Property("PlayerId") - .HasColumnType("INTEGER"); - - b.Property("Tracked") - .HasColumnType("INTEGER"); - - b.Property("TrackedTime") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("GameId"); - - b.HasIndex("PlayerId"); - - b.ToTable("MultiplayerBossStates"); - }); - - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerDungeonState", b => + protected override void BuildModel(ModelBuilder modelBuilder) { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Dungeon") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("GameId") - .HasColumnType("INTEGER"); - - b.Property("PlayerId") - .HasColumnType("INTEGER"); - - b.Property("Tracked") - .HasColumnType("INTEGER"); - - b.Property("TrackedTime") - .HasColumnType("TEXT"); - - b.HasKey("Id"); +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); - b.HasIndex("GameId"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerBossState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.HasIndex("PlayerId"); + b.Property("Boss") + .HasColumnType("INTEGER"); - b.ToTable("MultiplayerDungeonStates"); - }); + b.Property("GameId") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerGameState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.Property("PlayerId") + .HasColumnType("INTEGER"); - b.Property("CreatedDate") - .HasColumnType("TEXT"); + b.Property("Tracked") + .HasColumnType("INTEGER"); - b.Property("DeathLink") - .HasColumnType("INTEGER"); + b.Property("TrackedTime") + .HasColumnType("TEXT"); - b.Property("Guid") - .IsRequired() - .HasColumnType("TEXT"); + b.HasKey("Id"); - b.Property("LastMessage") - .HasColumnType("TEXT"); + b.HasIndex("GameId"); - b.Property("SaveToDatabase") - .HasColumnType("INTEGER"); + b.HasIndex("PlayerId"); - b.Property("Seed") - .IsRequired() - .HasColumnType("TEXT"); + b.ToTable("MultiplayerBossStates"); + }); - b.Property("SendItemsOnComplete") - .HasColumnType("INTEGER"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("Status") - .HasColumnType("INTEGER"); + b.Property("CreatedDate") + .HasColumnType("TEXT"); - b.Property("Type") - .HasColumnType("INTEGER"); + b.Property("DeathLink") + .HasColumnType("INTEGER"); - b.Property("Url") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("Guid") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("ValidationHash") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("LastMessage") + .HasColumnType("TEXT"); - b.Property("Version") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("SaveToDatabase") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.Property("Seed") + .IsRequired() + .HasColumnType("TEXT"); - b.ToTable("MultiplayerGameStates"); - }); + b.Property("SendItemsOnComplete") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerItemState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.Property("Status") + .HasColumnType("INTEGER"); - b.Property("GameId") - .HasColumnType("INTEGER"); + b.Property("Type") + .HasColumnType("INTEGER"); - b.Property("Item") - .HasColumnType("INTEGER"); + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("PlayerId") - .HasColumnType("INTEGER"); + b.Property("ValidationHash") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("TrackedTime") - .HasColumnType("TEXT"); + b.Property("Version") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("TrackingValue") - .HasColumnType("INTEGER"); + b.HasKey("Id"); - b.HasKey("Id"); + b.ToTable("MultiplayerGameStates"); + }); - b.HasIndex("GameId"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerItemState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.HasIndex("PlayerId"); + b.Property("GameId") + .HasColumnType("INTEGER"); - b.ToTable("MultiplayerItemStates"); - }); + b.Property("Item") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerLocationState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.Property("PlayerId") + .HasColumnType("INTEGER"); - b.Property("GameId") - .HasColumnType("INTEGER"); + b.Property("TrackedTime") + .HasColumnType("TEXT"); - b.Property("LocationId") - .HasColumnType("INTEGER"); + b.Property("TrackingValue") + .HasColumnType("INTEGER"); - b.Property("PlayerId") - .HasColumnType("INTEGER"); + b.HasKey("Id"); - b.Property("Tracked") - .HasColumnType("INTEGER"); + b.HasIndex("GameId"); - b.Property("TrackedTime") - .HasColumnType("TEXT"); + b.HasIndex("PlayerId"); - b.HasKey("Id"); + b.ToTable("MultiplayerItemStates"); + }); - b.HasIndex("GameId"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerLocationState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.HasIndex("PlayerId"); + b.Property("GameId") + .HasColumnType("INTEGER"); - b.ToTable("MultiplayerLocationStates"); - }); + b.Property("LocationId") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.Property("PlayerId") + .HasColumnType("INTEGER"); - b.Property("AdditionalData") - .HasColumnType("TEXT"); + b.Property("Tracked") + .HasColumnType("INTEGER"); - b.Property("Config") - .HasColumnType("TEXT"); + b.Property("TrackedTime") + .HasColumnType("TEXT"); - b.Property("GameId") - .HasColumnType("INTEGER"); + b.HasKey("Id"); - b.Property("GenerationData") - .HasColumnType("TEXT"); + b.HasIndex("GameId"); - b.Property("Guid") - .IsRequired() - .HasColumnType("TEXT"); + b.HasIndex("PlayerId"); - b.Property("IsAdmin") - .HasColumnType("INTEGER"); + b.ToTable("MultiplayerLocationStates"); + }); - b.Property("Key") - .IsRequired() - .HasColumnType("TEXT"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("PhoneticName") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("AdditionalData") + .HasColumnType("TEXT"); - b.Property("PlayerName") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("Config") + .HasColumnType("TEXT"); - b.Property("Status") - .HasColumnType("INTEGER"); + b.Property("GameId") + .HasColumnType("INTEGER"); - b.Property("WorldId") - .HasColumnType("INTEGER"); + b.Property("GenerationData") + .HasColumnType("TEXT"); - b.HasKey("Id"); + b.Property("Guid") + .IsRequired() + .HasColumnType("TEXT"); - b.HasIndex("GameId"); + b.Property("IsAdmin") + .HasColumnType("INTEGER"); - b.ToTable("MultiplayerPlayerStates"); - }); + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerBossState", b => - { - b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerGameState", "Game") - .WithMany() - .HasForeignKey("GameId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.Property("PhoneticName") + .IsRequired() + .HasColumnType("TEXT"); - b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", "Player") - .WithMany("Bosses") - .HasForeignKey("PlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.Property("PlayerName") + .IsRequired() + .HasColumnType("TEXT"); - b.Navigation("Game"); + b.Property("Status") + .HasColumnType("INTEGER"); - b.Navigation("Player"); - }); + b.Property("WorldId") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerDungeonState", b => - { - b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerGameState", "Game") - .WithMany() - .HasForeignKey("GameId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.HasKey("Id"); - b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", "Player") - .WithMany("Dungeons") - .HasForeignKey("PlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.HasIndex("GameId"); - b.Navigation("Game"); + b.ToTable("MultiplayerPlayerStates"); + }); - b.Navigation("Player"); - }); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerBossState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerItemState", b => - { - b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerGameState", "Game") - .WithMany() - .HasForeignKey("GameId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", "Player") + .WithMany("Bosses") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", "Player") - .WithMany("Items") - .HasForeignKey("PlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.Navigation("Game"); - b.Navigation("Game"); + b.Navigation("Player"); + }); - b.Navigation("Player"); - }); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerItemState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerLocationState", b => - { - b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerGameState", "Game") - .WithMany() - .HasForeignKey("GameId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", "Player") + .WithMany("Items") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", "Player") - .WithMany("Locations") - .HasForeignKey("PlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.Navigation("Game"); - b.Navigation("Game"); + b.Navigation("Player"); + }); - b.Navigation("Player"); - }); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerLocationState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", b => - { - b.HasOne("Randomizer.Shared.Multiplayer.MultiplayerGameState", "Game") - .WithMany("Players") - .HasForeignKey("GameId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", "Player") + .WithMany("Locations") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Game"); - }); + b.Navigation("Game"); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerGameState", b => - { - b.Navigation("Players"); - }); + b.Navigation("Player"); + }); - modelBuilder.Entity("Randomizer.Shared.Multiplayer.MultiplayerPlayerState", b => - { - b.Navigation("Bosses"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", b => + { + b.HasOne("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", "Game") + .WithMany("Players") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Dungeons"); + b.Navigation("Game"); + }); - b.Navigation("Items"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerGameState", b => + { + b.Navigation("Players"); + }); + + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Multiplayer.MultiplayerPlayerState", b => + { + b.Navigation("Bosses"); + + b.Navigation("Items"); - b.Navigation("Locations"); - }); + b.Navigation("Locations"); + }); #pragma warning restore 612, 618 + } } } diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerDbContext.cs b/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerDbContext.cs index fe895dfb2..a37749a1f 100644 --- a/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerDbContext.cs +++ b/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerDbContext.cs @@ -37,19 +37,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(x => x.Items).WithOne(x => x.Player) .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity().HasMany(x => x.Dungeons).WithOne(x => x.Player) - .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(x => x.Bosses).WithOne(x => x.Player) .OnDelete(DeleteBehavior.Cascade); base.OnModelCreating(modelBuilder); } - public DbSet MultiplayerGameStates { get; set; } - public DbSet MultiplayerPlayerStates { get; set; } - public DbSet MultiplayerLocationStates { get; set; } - public DbSet MultiplayerItemStates { get; set; } - public DbSet MultiplayerBossStates { get; set; } - public DbSet MultiplayerDungeonStates { get; set; } + public DbSet MultiplayerGameStates { get; init; } + public DbSet MultiplayerPlayerStates { get; init; } + public DbSet MultiplayerLocationStates { get; init; } + public DbSet MultiplayerItemStates { get; init; } + public DbSet MultiplayerBossStates { get; init; } } diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerDbService.cs b/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerDbService.cs index ac07a7419..25a7b3527 100644 --- a/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerDbService.cs +++ b/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerDbService.cs @@ -66,9 +66,6 @@ public async Task AddGameToDatabase(MultiplayerGameState state) var gameItems = dbContext.MultiplayerItemStates .Where(x => x.GameId == gameState.Id) .ToList(); - var gameDungeons = dbContext.MultiplayerDungeonStates - .Where(x => x.GameId == gameState.Id) - .ToList(); var gameBosses = dbContext.MultiplayerBossStates .Where(x => x.GameId == gameState.Id) .ToList(); @@ -77,7 +74,6 @@ public async Task AddGameToDatabase(MultiplayerGameState state) { player.Locations = gameLocations.Where(x => x.PlayerId == player.Id).ToList(); player.Items = gameItems.Where(x => x.PlayerId == player.Id).ToList(); - player.Dungeons = gameDungeons.Where(x => x.PlayerId == player.Id).ToList(); player.Bosses = gameBosses.Where(x => x.PlayerId == player.Id).ToList(); } @@ -252,30 +248,6 @@ public async Task SaveItemState(MultiplayerGameState gameState, MultiplayerItemS } - /// - /// Saves the current status of a dungeon to the database - /// - /// The game state the dungeon belongs to - /// The dungeon state being saved - public async Task SaveDungeonState(MultiplayerGameState gameState, MultiplayerDungeonState dungeonState) - { - if (!_isDatabaseEnabled || !gameState.SaveToDatabase) return; - try - { - await using var dbContext = await _contextFactory.CreateDbContextAsync(); - var dbState = await dbContext.MultiplayerDungeonStates.FindAsync(dungeonState.Id); - if (dbState == null) return; - dbState.Tracked = dungeonState.Tracked; - dbState.TrackedTime = dungeonState.TrackedTime; - await SaveChanges(dbContext, $"Unable to save dungeon state"); - } - catch (Exception e) - { - _logger.LogError(e, "Unable to save dungeon state"); - } - - } - /// /// Saves the current status of a boss to the database /// @@ -351,25 +323,6 @@ public async Task SavePlayerWorld(MultiplayerGameState gameState, MultiplayerPla } } - // Update dungeons - updateIds = updates.Dungeons.Select(x => x.Id).ToList(); - var dbDungeons = dbContext.MultiplayerDungeonStates - .Where(x => x.PlayerId == playerState.Id && updateIds.Contains(x.Id)) - .ToDictionary(x => x.Id, x => x); - foreach (var updateData in updates.Dungeons) - { - if (dbDungeons.ContainsKey(updateData.Id)) - { - var dbData = dbDungeons[updateData.Id]; - dbData.Tracked = updateData.Tracked; - dbData.TrackedTime = updateData.TrackedTime; - } - else - { - dbContext.MultiplayerDungeonStates.Add(updateData); - } - } - // Update bosses updateIds = updates.Bosses.Select(x => x.Id).ToList(); var dbBosses = dbContext.MultiplayerBossStates diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerGame.cs b/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerGame.cs index d77e6ba7d..83b1d9306 100644 --- a/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerGame.cs +++ b/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerGame.cs @@ -459,18 +459,6 @@ public void SetPlayerGenerationData(MultiplayerPlayer player, int worldId, strin return boss; } - /// - /// Marks a dungeon as completed - /// - /// - /// - public MultiplayerDungeonState? TrackDungeon(MultiplayerPlayer player, string name) - { - var dungeon = player.State.TrackDungeon(name); - State.LastMessage = DateTimeOffset.Now; - return dungeon; - } - private static string CleanPlayerName(string name) { name = s_illegalPlayerNameCharacters.Replace(name, " "); diff --git a/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerHub.cs b/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerHub.cs index 04bba0460..0a8e50fc2 100644 --- a/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerHub.cs +++ b/src/TrackerCouncil.Smz3.Multiplayer.Server/MultiplayerHub.cs @@ -521,39 +521,6 @@ await SendPlayerTrackedResponse(playerToUpdate, "TrackBoss", if (boss != null) await _dbService.SaveBossState(playerToUpdate.Game.State, boss); } - /// - /// Request to mark a specific dungeon as tracked - /// - /// - public async Task TrackDungeon(TrackDungeonRequest request) - { - var player = MultiplayerGame.LoadPlayer(Context.ConnectionId); - if (player == null) - { - await SendErrorResponse("Unable to find player"); - return; - } - - var playerToUpdate = request.PlayerGuid != player.Guid - ? player.Game.GetPlayer(request.PlayerGuid, null, false) - : player; - - if (playerToUpdate == null) - { - await SendErrorResponse("Unable to find player"); - return; - } - - LogInfo(player, $"Tracked dungeon {request.DungeonName} for {playerToUpdate.Guid}"); - - var dungeon = player.Game.TrackDungeon(playerToUpdate, request.DungeonName); - - await SendPlayerTrackedResponse(playerToUpdate, "TrackDungeon", - new TrackDungeonResponse(playerToUpdate.Guid, request.DungeonName)); - - if (dungeon != null) await _dbService.SaveDungeonState(playerToUpdate.Game.State, dungeon); - } - /// /// Request to notify all players of a death /// diff --git a/src/TrackerCouncil.Smz3.PatchBuilder/Program.cs b/src/TrackerCouncil.Smz3.PatchBuilder/Program.cs index df5c99dc4..b6eedef7f 100644 --- a/src/TrackerCouncil.Smz3.PatchBuilder/Program.cs +++ b/src/TrackerCouncil.Smz3.PatchBuilder/Program.cs @@ -54,14 +54,14 @@ new Item(location.VanillaItem == ItemType.Nothing ? ItemType.TwentyRupees : location.VanillaItem, world); } - foreach (var dungeon in world.Dungeons.Where(x => x is IHasReward).Cast()) + foreach (var rewardRegion in world.RewardRegions) { - dungeon.Reward = new Reward(dungeon.DefaultRewardType, world, dungeon); + rewardRegion.SetRewardType(rewardRegion.DefaultRewardType); } - foreach (var dungeon in world.Dungeons.Where(x => x is INeedsMedallion).Cast()) + foreach (var dungeon in world.PrerequisiteRegions) { - dungeon.Medallion = dungeon.DefaultMedallion; + dungeon.RequiredItem = dungeon.DefaultRequiredItem; } config.PlandoConfig = new PlandoConfig(world); diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/DungeonMusicPatch.cs b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/DungeonMusicPatch.cs index e982bb2b2..a1fe92a18 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/DungeonMusicPatch.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/DungeonMusicPatch.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; using TrackerCouncil.Smz3.Shared.Enums; @@ -15,9 +14,12 @@ public override IEnumerable GetChanges(GetPatchesRequest data) if (data.World.Config.ZeldaKeysanity) { return new List(); - }; + } + + var regions = data.World.TreasureRegions.OfType().Where(x => + x.RewardType.IsInAnyCategory(RewardCategory.Crystal, RewardCategory.Pendant)) + .ToList(); - var regions = data.World.Regions.OfType().Where(x => x.RewardType != RewardType.Agahnim); var music = regions.Select(x => (byte)(x.RewardType switch { RewardType.PendantBlue => 0x11, RewardType.PendantGreen => 0x11, diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/LocationsPatch.cs b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/LocationsPatch.cs index 3201ded32..1fa217d08 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/LocationsPatch.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/LocationsPatch.cs @@ -1,15 +1,109 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid.Crateria; using TrackerCouncil.Smz3.Shared.Enums; namespace TrackerCouncil.Smz3.SeedGenerator.FileData.Patches; +public class ArchipelagoLocation +{ + public required LocationId Location { get; set; } + public required bool IsLocalPlayerItem { get; set; } + public required string PlayerName{ get; set; } + public required ItemType? ItemType { get; set; } + public required bool IsProgression { get; set; } + public required string ItemName { get; set; } +} + public class LocationsPatch : RomPatch { + private static Dictionary s_smCharMap = new Dictionary() + { + { 0x3CE0, "A" }, + { 0x3CE1, "B" }, + { 0x3CE2, "C" }, + { 0x3CE3, "D" }, + { 0x3CE4, "E" }, + { 0x3CE5, "F" }, + { 0x3CE6, "G" }, + { 0x3CE7, "H" }, + { 0x3CE8, "I" }, + { 0x3CE9, "J" }, + { 0x3CEA, "K" }, + { 0x3CEB, "L" }, + { 0x3CEC, "M" }, + { 0x3CED, "N" }, + { 0x3CEE, "O" }, + { 0x3CEF, "P" }, + { 0x3CF0, "Q" }, + { 0x3CF1, "R" }, + { 0x3CF2, "S" }, + { 0x3CF3, "T" }, + { 0x3CF4, "U" }, + { 0x3CF5, "V" }, + { 0x3CF6, "W" }, + { 0x3CF7, "X" }, + { 0x3CF8, "Y" }, + { 0x3CF9, "Z" }, + { 0x3C4E, " " }, + { 0x3CFF, "!" }, + { 0x3CFE, "?" }, + { 0x3CFD, "'" }, + { 0x3CFB, "," }, + { 0x3CFA, "." }, + { 0x3CCF, "-" }, + { 0x3C80, "1" }, + { 0x3C81, "2" }, + { 0x3C82, "3" }, + { 0x3C83, "4" }, + { 0x3C84, "5" }, + { 0x3C85, "6" }, + { 0x3C86, "7" }, + { 0x3C87, "8" }, + { 0x3C88, "9" }, + { 0x3C89, "0" }, + { 0x3C0A, "%" }, + { 0x3C90, "a" }, + { 0x3C91, "b" }, + { 0x3C92, "c" }, + { 0x3C93, "d" }, + { 0x3C94, "e" }, + { 0x3C95, "f" }, + { 0x3C96, "g" }, + { 0x3C97, "h" }, + { 0x3C98, "i" }, + { 0x3C99, "j" }, + { 0x3C9A, "k" }, + { 0x3C9B, "l" }, + { 0x3C9C, "m" }, + { 0x3C9D, "n" }, + { 0x3C9E, "o" }, + { 0x3C9F, "p" }, + { 0x3CA0, "q" }, + { 0x3CA1, "r" }, + { 0x3CA2, "s" }, + { 0x3CA3, "t" }, + { 0x3CA4, "u" }, + { 0x3CA5, "v" }, + { 0x3CA6, "w" }, + { 0x3CA7, "x" }, + { 0x3CA8, "y" }, + { 0x3CA9, "z" }, + { 0x3CAA, "\"" }, + { 0x3CAB, ":" }, + { 0x3CAC, "~" }, + { 0x3CAD, "@" }, + { 0x3CAE, "#" }, + { 0x3CAF, "+" }, + { 0x000E, "_" } + }; + public override IEnumerable GetChanges(GetPatchesRequest data) { var patches = new List(); @@ -18,6 +112,85 @@ public override IEnumerable GetChanges(GetPatchesRequest data) return patches; } + public static List GetLocationsFromRom(byte[] rom, List playerNames) + { + var toReturn = new List(); + var testWorld = new World(new Config(), "", 1, ""); + + foreach (var location in testWorld.Regions.OfType().SelectMany(x => x.Locations)) + { + toReturn.Add(GetArchipelagoLocation(rom, location.Id, true, playerNames)); + } + + foreach (var location in testWorld.Regions.OfType().SelectMany(x => x.Locations)) + { + toReturn.Add(GetArchipelagoLocation(rom, location.Id, false, playerNames)); + } + + return toReturn; + } + + private static ArchipelagoLocation GetArchipelagoLocation(byte[] rom, LocationId locationId, bool isSuperMetroidLocation, List playerNames) + { + var address = 0x386000 + (int)locationId * 8; + var bytes = rom.Skip(address).Take(8).ToArray(); + var isLocal = BitConverter.ToInt16(bytes, 0) == 0; + var itemNumber = BitConverter.ToInt16(bytes, 2); + var ownerPlayerId = BitConverter.ToInt16(bytes, 4); + var archipelagoFlags = BitConverter.ToInt16(bytes, 6); + var isProgression = archipelagoFlags > 0; + var itemName = ""; + + if (!isLocal) + { + itemName = isSuperMetroidLocation + ? GetSuperMetroidItemName(rom, archipelagoFlags, isProgression) + : GetZeldaItemName(rom, archipelagoFlags, isProgression); + } + else + { + itemName = ((ItemType)itemNumber).GetDescription(); + } + + return new ArchipelagoLocation() + { + Location = locationId, + IsLocalPlayerItem = isLocal, + ItemType = isLocal ? (ItemType)itemNumber : ItemType.OtherGameItem, + PlayerName = playerNames[ownerPlayerId], + IsProgression = isProgression, + ItemName = itemName + }; + } + + private static string GetSuperMetroidItemName(byte[] rom, short archipelagoFlags, bool isProgression) + { + var mod = isProgression ? 0 : 0x8000; + var textLocation = 0x390000 + (archipelagoFlags - 1 + mod) * 64; + var textBytes = rom.Skip(textLocation).Take(64).ToArray(); + + var text = ""; + for (var i = 0; i < 64; i += 2) + { + var value = BitConverter.ToInt16(textBytes, i); + if (s_smCharMap.TryGetValue(value, out var character)) + { + text += character; + } + } + + return text.Replace("___", "").Trim(); + } + + private static string GetZeldaItemName(byte[] rom, short archipelagoFlags, bool isProgression) + { + var mod = isProgression ? 0 : 0x8000; + var textLocation = 0x390000 + 100 * 64 + (archipelagoFlags - 1 + mod) * 20; + var textBytes = rom.Skip(textLocation).Take(20).ToArray(); + var text = Encoding.UTF8.GetString(textBytes); + return text.Replace("___", "").Replace("\0", "").Trim(); + } + private IEnumerable WriteMetroidLocations(GetPatchesRequest data) { foreach (var location in data.World.Regions.OfType().SelectMany(x => x.Locations)) @@ -95,7 +268,7 @@ private IEnumerable WriteZ3Locations(GetPatchesRequest data) { if (location.Type == LocationType.HeraStandingKey) { - yield return new GeneratedPatch(Snes(0x9E3BB), location.Item.Type == ItemType.KeyTH ? new byte[] { 0xE4 } : new byte[] { 0xEB }); + yield return new GeneratedPatch(Snes(0x9E3BB), location.Item.Type == ItemType.KeyTH ? [0xE4] : [0xEB]); } if (GetPatchesRequest.EnableMultiworld) diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/MedallionPatch.cs b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/MedallionPatch.cs index f0168a3e8..4fa854fca 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/MedallionPatch.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/MedallionPatch.cs @@ -8,12 +8,12 @@ namespace TrackerCouncil.Smz3.SeedGenerator.FileData.Patches; public class MedallionPatch : RomPatch { + private static readonly int[] s_turtleRockAddresses = { Snes(0x308023), Snes(0xD020), Snes(0xD0FF), Snes(0xD1DE) }; + private static readonly int[] s_miseryMireAddresses = { Snes(0x308022), Snes(0xCFF2), Snes(0xD0D1), Snes(0xD1B0) }; + public override IEnumerable GetChanges(GetPatchesRequest data) { - var turtleRockAddresses = new[] { 0x308023, 0xD020, 0xD0FF, 0xD1DE }; - var miseryMireAddresses = new[] { 0x308022, 0xCFF2, 0xD0D1, 0xD1B0 }; - - var turtleRockValues = data.World.TurtleRock.Medallion switch + var turtleRockValues = data.World.TurtleRock.PrerequisiteState.RequiredItem switch { ItemType.Bombos => new byte[] { 0x00, 0x51, 0x10, 0x00 }, ItemType.Ether => new byte[] { 0x01, 0x51, 0x18, 0x00 }, @@ -21,7 +21,7 @@ public override IEnumerable GetChanges(GetPatchesRequest data) var x => throw new InvalidOperationException($"Tried using {x} in place of Turtle Rock medallion") }; - var miseryMireValues = data.World.MiseryMire.Medallion switch + var miseryMireValues = data.World.MiseryMire.PrerequisiteState.RequiredItem switch { ItemType.Bombos => new byte[] { 0x00, 0x51, 0x00, 0x00 }, ItemType.Ether => new byte[] { 0x01, 0x13, 0x9F, 0xF1 }, @@ -30,8 +30,16 @@ public override IEnumerable GetChanges(GetPatchesRequest data) }; var patches = new List(); - patches.AddRange(turtleRockAddresses.Zip(turtleRockValues, (i, b) => new GeneratedPatch(Snes(i), new[] { b }))); - patches.AddRange(miseryMireAddresses.Zip(miseryMireValues, (i, b) => new GeneratedPatch(Snes(i), new[] { b }))); + patches.AddRange(s_turtleRockAddresses.Zip(turtleRockValues, (i, b) => new GeneratedPatch(i, [b]))); + patches.AddRange(s_miseryMireAddresses.Zip(miseryMireValues, (i, b) => new GeneratedPatch(i, [b]))); return patches; } + + public static (ItemType miseryMire, ItemType turtleRock) GetRequiredMedallions(byte[] rom) + { + var types = new[] { ItemType.Bombos, ItemType.Ether, ItemType.Quake }; + var mmMedallion = types[rom.Skip(s_miseryMireAddresses[0]).First()]; + var trMedallion = types[rom.Skip(s_turtleRockAddresses[0]).First()]; + return (mmMedallion, trMedallion); + } } diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/MetadataPatch.cs b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/MetadataPatch.cs index 0e1d8ca88..a4999f177 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/MetadataPatch.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/MetadataPatch.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Text; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data; @@ -8,6 +10,14 @@ namespace TrackerCouncil.Smz3.SeedGenerator.FileData.Patches; [Order(-9)] public class MetadataPatch : RomPatch { + private static readonly int s_addrMultiworldFlag = Snes(0xF47000); + private static readonly int s_addrKeysanityFlag = Snes(0xF47006); + private static readonly int s_addrPlayerNames = 0x385000; + private static readonly int s_addrPlayerIndex = Snes(0x80FF50); + private static readonly int s_addrRng = 0x420000; + private static readonly int s_addrTitle1 = Snes(0x00FFC0); + private static readonly int s_addrTitle2 = Snes(0x80FFC0); + private GetPatchesRequest _data = null!; public override IEnumerable GetChanges(GetPatchesRequest data) @@ -17,26 +27,64 @@ public override IEnumerable GetChanges(GetPatchesRequest data) patches.AddRange(WriteRngBlock()); patches.AddRange(WritePlayerNames()); - patches.AddRange( WriteSeedData()); + patches.AddRange(WriteSeedData()); patches.AddRange(WriteGameTitle()); patches.AddRange(WriteCommonFlags()); return patches; } + public static bool IsRomMultiworldEnabled(byte[] rom) + { + return BitConverter.ToInt16(rom, s_addrMultiworldFlag) == 1; + } + + public static bool IsRomKeysanityEnabled(byte[] rom) + { + return BitConverter.ToInt16(rom, s_addrKeysanityFlag) == 1; + } + + public static string GetGameTitle(byte[] rom) + { + var nameBytes = rom.Skip(s_addrTitle1).Take(21).ToArray(); + return Encoding.ASCII.GetString(nameBytes); + } + + public static List GetPlayerNames(byte[] rom) + { + var toReturn = new List(); + for (var i = 0; i < 128; i++) + { + var nameBytes = rom.Skip(s_addrPlayerNames + i * 16).Take(16).ToArray(); + var name = Encoding.ASCII.GetString(nameBytes); + if (name == "123456789012\0\0\0\0") + { + break; + } + toReturn.Add(name.Trim()); + } + + return toReturn; + } + + public static int GetPlayerIndex(byte[] rom) + { + return BitConverter.ToInt16(rom, s_addrPlayerIndex); + } + private IEnumerable WriteRngBlock() { // RNG Block - yield return new GeneratedPatch(0x420000, Enumerable.Range(0, 1024).Select(_ => (byte)_data.Random.Next(0x100)).ToArray()); + yield return new GeneratedPatch(s_addrRng, Enumerable.Range(0, 1024).Select(_ => (byte)_data.Random.Next(0x100)).ToArray()); } private IEnumerable WritePlayerNames() { foreach (var world in _data.Worlds) { - yield return new GeneratedPatch(0x385000 + (world.Id * 16), PlayerNameBytes(world.Player)); + yield return new GeneratedPatch(s_addrPlayerNames + (world.Id * 16), PlayerNameBytes(world.Player)); } - yield return new GeneratedPatch(0x385000 + (_data.Worlds.Count * 16), PlayerNameBytes("Tracker")); + yield return new GeneratedPatch(s_addrPlayerNames + (_data.Worlds.Count * 16), PlayerNameBytes("Tracker")); } private byte[] PlayerNameBytes(string name) @@ -61,7 +109,7 @@ private IEnumerable WriteSeedData() (RandomizerVersion.Version.Major << 4) | (RandomizerVersion.Version.Minor << 0); - yield return new GeneratedPatch(Snes(0x80FF50), UshortBytes(_data.World.Id)); + yield return new GeneratedPatch(s_addrPlayerIndex, UshortBytes(_data.World.Id)); yield return new GeneratedPatch(Snes(0x80FF52), UshortBytes(configField)); yield return new GeneratedPatch(Snes(0x80FF54), UintBytes(_data.Seed)); /* Reserve the rest of the space for future use */ @@ -73,8 +121,8 @@ private IEnumerable WriteSeedData() private IEnumerable WriteGameTitle() { var title = AsAscii($"SMZ3 Cas' [{_data.Seed:X8}]".PadRight(21)[..21]); - yield return new GeneratedPatch(Snes(0x00FFC0), title); - yield return new GeneratedPatch(Snes(0x80FFC0), title); + yield return new GeneratedPatch(s_addrTitle1, title); + yield return new GeneratedPatch(s_addrTitle2, title); } private IEnumerable WriteCommonFlags() diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/ZeldaRewardsPatch.cs b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/ZeldaRewardsPatch.cs index 2a45a664f..85a034a44 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/ZeldaRewardsPatch.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/FileData/Patches/ZeldaRewardsPatch.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid; +using TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid.Brinstar; +using TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid.Maridia; +using TrackerCouncil.Smz3.Data.WorldData.Regions.SuperMetroid.Norfair; using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; using TrackerCouncil.Smz3.Shared.Enums; @@ -34,29 +39,75 @@ public override IEnumerable GetChanges(GetPatchesRequest data) } } + public static void ApplyRewardsFromRom(byte[] rom, World world) + { + var regions = world.Regions.Where(x => x is (IHasBoss or IHasReward) and not CastleTower); + var rewardValues = new Dictionary() + { + { string.Join(",", PendantValues(1)), RewardType.PendantGreen }, + { string.Join(",", PendantValues(2)), RewardType.PendantRed }, + { string.Join(",", PendantValues(3)), RewardType.PendantBlue }, + { string.Join(",", CrystalValues(1)), RewardType.CrystalBlue }, + { string.Join(",", CrystalValues(2)), RewardType.CrystalBlue }, + { string.Join(",", CrystalValues(3)), RewardType.CrystalBlue }, + { string.Join(",", CrystalValues(4)), RewardType.CrystalBlue }, + { string.Join(",", CrystalValues(5)), RewardType.CrystalRed }, + { string.Join(",", CrystalValues(6)), RewardType.CrystalRed }, + { string.Join(",", CrystalValues(7)), RewardType.CrystalBlue }, + { string.Join(",", BossTokenValues(1)), RewardType.KraidToken }, + { string.Join(",", BossTokenValues(2)), RewardType.PhantoonToken }, + { string.Join(",", BossTokenValues(3)), RewardType.DraygonToken }, + { string.Join(",", BossTokenValues(4)), RewardType.RidleyToken }, + }; + + var regionRewards = new Dictionary(); + + foreach (var region in regions) + { + try + { + var addresses = string.Join(",", RewardAddresses(region).Select(x => rom[Snes(x)])); + var reward = rewardValues[addresses]; + regionRewards[region] = reward; + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + } + + // var a = "1"; + } + private IEnumerable RewardPatches(IEnumerable regions, IEnumerable rewards, Func rewardValues) { - var addresses = regions.Select(RewardAddresses); + var addresses = regions.Select(x => RewardAddresses((Region)x)); var values = rewards.Select(rewardValues); var associations = addresses.Zip(values, (a, v) => (a, v)); - return associations.SelectMany(x => x.a.Zip(x.v, (i, b) => new GeneratedPatch(Snes(i), new[] { b }))); + return associations.SelectMany(x => x.a.Zip(x.v, (i, b) => new GeneratedPatch(Snes(i), [b]))); } - private static int[] RewardAddresses(IHasReward region) + private static int[] RewardAddresses(Region region) { return region switch { - EasternPalace _ => new[] { 0x2A09D, 0xABEF8, 0xABEF9, 0x308052, 0x30807C, 0x1C6FE }, - DesertPalace _ => new[] { 0x2A09E, 0xABF1C, 0xABF1D, 0x308053, 0x308078, 0x1C6FF }, - TowerOfHera _ => new[] { 0x2A0A5, 0xABF0A, 0xABF0B, 0x30805A, 0x30807A, 0x1C706 }, - PalaceOfDarkness _ => new[] { 0x2A0A1, 0xABF00, 0xABF01, 0x308056, 0x30807D, 0x1C702 }, - SwampPalace _ => new[] { 0x2A0A0, 0xABF6C, 0xABF6D, 0x308055, 0x308071, 0x1C701 }, - SkullWoods _ => new[] { 0x2A0A3, 0xABF12, 0xABF13, 0x308058, 0x30807B, 0x1C704 }, - ThievesTown _ => new[] { 0x2A0A6, 0xABF36, 0xABF37, 0x30805B, 0x308077, 0x1C707 }, - IcePalace _ => new[] { 0x2A0A4, 0xABF5A, 0xABF5B, 0x308059, 0x308073, 0x1C705 }, - MiseryMire _ => new[] { 0x2A0A2, 0xABF48, 0xABF49, 0x308057, 0x308075, 0x1C703 }, - TurtleRock _ => new[] { 0x2A0A7, 0xABF24, 0xABF25, 0x30805C, 0x308079, 0x1C708 }, - var x => throw new InvalidOperationException($"Region {x} should not be a dungeon reward region") + EasternPalace => [0x2A09D, 0xABEF8, 0xABEF9, 0x308052, 0x30807C, 0x1C6FE], + DesertPalace => [0x2A09E, 0xABF1C, 0xABF1D, 0x308053, 0x308078, 0x1C6FF], + TowerOfHera => [0x2A0A5, 0xABF0A, 0xABF0B, 0x30805A, 0x30807A, 0x1C706], + PalaceOfDarkness => [0x2A0A1, 0xABF00, 0xABF01, 0x308056, 0x30807D, 0x1C702], + SwampPalace => [0x2A0A0, 0xABF6C, 0xABF6D, 0x308055, 0x308071, 0x1C701], + SkullWoods => [0x2A0A3, 0xABF12, 0xABF13, 0x308058, 0x30807B, 0x1C704], + ThievesTown => [0x2A0A6, 0xABF36, 0xABF37, 0x30805B, 0x308077, 0x1C707], + IcePalace => [0x2A0A4, 0xABF5A, 0xABF5B, 0x308059, 0x308073, 0x1C705], + MiseryMire => [0x2A0A2, 0xABF48, 0xABF49, 0x308057, 0x308075, 0x1C703], + TurtleRock => [0x2A0A7, 0xABF24, 0xABF25, 0x30805C, 0x308079, 0x1C708], + KraidsLair => [0xF26002, 0xF26004, 0xF26005, 0xF26000, 0xF26006, 0xF26007], + WreckedShip => [0xF2600A, 0xF2600C, 0xF2600D, 0xF26008, 0xF2600E, 0xF2600F], + InnerMaridia => [0xF26012, 0xF26014, 0xF26015, 0xF26010, 0xF26016, 0xF26017], + LowerNorfairEast => [0xF2601A, 0xF2601C, 0xF2601D, 0xF26018, 0xF2601E, 0xF2601F], + _ => throw new InvalidOperationException($"Region {region} should not be a dungeon reward region") }; } @@ -64,14 +115,14 @@ private static byte[] CrystalValues(int crystal) { return crystal switch { - 1 => new byte[] { 0x02, 0x34, 0x64, 0x40, 0x7F, 0x06 }, - 2 => new byte[] { 0x10, 0x34, 0x64, 0x40, 0x79, 0x06 }, - 3 => new byte[] { 0x40, 0x34, 0x64, 0x40, 0x6C, 0x06 }, - 4 => new byte[] { 0x20, 0x34, 0x64, 0x40, 0x6D, 0x06 }, - 5 => new byte[] { 0x04, 0x32, 0x64, 0x40, 0x6E, 0x06 }, - 6 => new byte[] { 0x01, 0x32, 0x64, 0x40, 0x6F, 0x06 }, - 7 => new byte[] { 0x08, 0x34, 0x64, 0x40, 0x7C, 0x06 }, - var x => throw new InvalidOperationException($"Tried using {x} as a crystal number") + 1 => [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06], + 2 => [0x10, 0x34, 0x64, 0x40, 0x79, 0x06], + 3 => [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06], + 4 => [0x20, 0x34, 0x64, 0x40, 0x6D, 0x06], + 5 => [0x04, 0x32, 0x64, 0x40, 0x6E, 0x06], + 6 => [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06], + 7 => [0x08, 0x34, 0x64, 0x40, 0x7C, 0x06], + _ => throw new InvalidOperationException($"Tried using {crystal} as a crystal number") }; } @@ -79,10 +130,22 @@ private static byte[] PendantValues(int pendant) { return pendant switch { - 1 => new byte[] { 0x04, 0x38, 0x62, 0x00, 0x69, 0x01 }, - 2 => new byte[] { 0x01, 0x32, 0x60, 0x00, 0x69, 0x03 }, - 3 => new byte[] { 0x02, 0x34, 0x60, 0x00, 0x69, 0x02 }, - var x => throw new InvalidOperationException($"Tried using {x} as a pendant number") + 1 => [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], + 2 => [0x01, 0x32, 0x60, 0x00, 0x69, 0x03], + 3 => [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], + _ => throw new InvalidOperationException($"Tried using {pendant} as a pendant number") + }; + } + + private static byte[] BossTokenValues(int pendant) + { + return pendant switch + { + 1 => [0x01, 0x38, 0x40, 0x80, 0x69, 0x80], + 2 => [0x02, 0x34, 0x42, 0x80, 0x69, 0x81], + 3 => [0x04, 0x34, 0x44, 0x80, 0x69, 0x82], + 4 => [0x08, 0x32, 0x46, 0x80, 0x69, 0x83], + _ => throw new InvalidOperationException($"Tried using {pendant} as a pendant number") }; } } diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs index 177bb41ce..6b8ce3bdd 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/GameHintService.cs @@ -221,8 +221,11 @@ PlayerHintTile GetProgressionItemHint(Location location) /// private IEnumerable GetDungeonHints(World hintPlayerWorld, List allWorlds, List importantLocations) { - var dungeons = hintPlayerWorld.Dungeons - .Where(x => x.IsPendantDungeon || x is HyruleCastle or GanonsTower); + var dungeons = hintPlayerWorld.RewardRegions.Where(x => + x.RewardType is RewardType.PendantBlue or RewardType.PendantGreen or RewardType.PendantRed) + .Cast() + .Concat([hintPlayerWorld.HyruleCastle, hintPlayerWorld.GanonsTower]) + .OfType(); var hints = new List(); foreach (var dungeon in dungeons) @@ -232,7 +235,7 @@ private IEnumerable GetDungeonHints(World hintPlayerWorld, List all return hints; - PlayerHintTile GetDungeonHint(IDungeon dungeon) + PlayerHintTile GetDungeonHint(IHasTreasure dungeon) { var dungeonRegion = (Region)dungeon; var locations = dungeonRegion.Locations.Where(x => x.Type != LocationType.NotInDungeon).ToList(); @@ -241,7 +244,7 @@ PlayerHintTile GetDungeonHint(IDungeon dungeon) { Type = HintTileType.Dungeon, WorldId = hintPlayerWorld.Id, - LocationKey = dungeon.DungeonName, + LocationKey = dungeon.Name, LocationWorldId = locations.First().World.Id, Locations = locations.Select(x => x.Id), Usefulness = usefulness @@ -420,8 +423,8 @@ private LocationUsefulness CheckIfLocationsAreImportant(List allWorlds, I { numCrystalsNeeded = world.Config.GanonsTowerCrystalCount; } - var currentCrystalCount = world.Dungeons.Count(d => - d.IsCrystalDungeon && sphereLocations.Any(l => + var currentCrystalCount = world.RewardRegions.Count(d => + d.RewardType is RewardType.CrystalBlue or RewardType.CrystalRed && sphereLocations.Any(l => l.World.Id == world.Id && l.Id == s_dungeonBossLocations[d.GetType()])) + (ignoredReward == null ? 0 : 1); if (currentCrystalCount < numCrystalsNeeded) { @@ -475,7 +478,7 @@ private List GetMedallionHints(World hintPlayerWorld) hints.Add(() => { - var mmMedallion = hintPlayerWorld.MiseryMire.Medallion; + var mmMedallion = hintPlayerWorld.MiseryMire.PrerequisiteState.RequiredItem; return new PlayerHintTile() { Type = HintTileType.Requirement, @@ -487,7 +490,7 @@ private List GetMedallionHints(World hintPlayerWorld) hints.Add(() => { - var trMedallion = hintPlayerWorld.TurtleRock.Medallion; + var trMedallion = hintPlayerWorld.TurtleRock.PrerequisiteState.RequiredItem; return new PlayerHintTile() { Type = HintTileType.Requirement, @@ -525,9 +528,9 @@ private List GetMedallionHints(World hintPlayerWorld) } } - private string GetDungeonName(World hintPlayerWorld, IDungeon dungeon, Region region) + private string GetDungeonName(World hintPlayerWorld, IHasTreasure hasTreasure, Region region) { - var dungeonName = _metadataService.Dungeon(dungeon).Name?.ToString() ?? dungeon.DungeonName; + var dungeonName = _metadataService.Dungeon(hasTreasure).Name?.ToString() ?? hasTreasure.Name; return $"{dungeonName}{GetMultiworldSuffix(hintPlayerWorld, region.World)}"; } @@ -657,7 +660,7 @@ private IEnumerable GetImportantLocations(List allWorlds) else if (tile.Type == HintTileType.Dungeon) { var region = world.Regions.First(x => x.Name == areaName); - var dungeon = world.Dungeons.First(x => x.DungeonName == areaName); + var dungeon = world.TreasureRegions.First(x => x.Name == areaName); areaName = GetDungeonName(hintPlayerWorld, dungeon, region); } else if (tile.Type == HintTileType.Room) diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/MultiplayerFiller.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/MultiplayerFiller.cs index 3e8c47077..91074ba41 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/MultiplayerFiller.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/MultiplayerFiller.cs @@ -47,7 +47,9 @@ public void Fill(List worlds, Config config, CancellationToken cancellati foreach (var world in worlds) { FillItems(world, worlds); - FillDungeonData(world); + FillRewards(world); + FillBosses(world); + FillPrerequisites(world); } } @@ -56,68 +58,74 @@ public void SetRandom(Random random) _random = random ?? throw new ArgumentNullException(nameof(random)); } - private static void EnsureDungeonsHaveMedallions(World world) + private static void EnsureLocationsHaveItems(World world) { - var emptyMedallions = world.Regions.Where(x => x is INeedsMedallion { Medallion: ItemType.Nothing }).ToList(); - if (emptyMedallions.Any()) + var emptyLocations = world.Locations.Where(x => x.Item.Type == ItemType.Nothing).ToList(); + if (emptyLocations.Any()) { - throw new PlandoConfigurationException($"Not all dungeons have had their medallions set. Missing:\n" - + string.Join('\n', emptyMedallions.Select(x => x.Name))); + throw new PlandoConfigurationException($"Not all locations have been filled. Missing:\n" + + string.Join('\n', emptyLocations.Select(x => x.Name))); } } - private static void EnsureDungeonsHaveRewards(World world) + private void FillItems(World world, List worlds) { - var emptyDungeons = world.Regions.Where(x => x is IHasReward { RewardType: RewardType.None }).ToList(); - if (emptyDungeons.Any()) + var generatedLocationData = world.Config.MultiplayerPlayerGenerationData!.Locations; + foreach (var location in world.Locations) { - throw new PlandoConfigurationException($"Not all dungeons have had their rewards set. Missing:\n" - + string.Join('\n', emptyDungeons.Select(x => x.Name))); + var generatedData = generatedLocationData.Single(x => x.Id == location.Id); + var itemType = generatedData.Item; + var itemWorld = worlds.Single(x => x.Id == generatedData.ItemWorldId); + location.Item = new Item(itemType, itemWorld, isProgression: itemType.IsPossibleProgression(itemWorld.Config.ZeldaKeysanity, itemWorld.Config.MetroidKeysanity)); + _logger.LogDebug("Fast-filled {Item} at {Location}", generatedData.Item, location.Name); } + EnsureLocationsHaveItems(world); } - private static void EnsureLocationsHaveItems(World world) + private void FillRewards(World world) { - var emptyLocations = world.Locations.Where(x => x.Item.Type == ItemType.Nothing).ToList(); - if (emptyLocations.Any()) + foreach (var reward in world.Rewards) { - throw new PlandoConfigurationException($"Not all locations have been filled. Missing:\n" - + string.Join('\n', emptyLocations.Select(x => x.Name))); + reward.Region = null; } - } - private void FillDungeonData(World world) - { - var generatedDungeonData = world.Config.MultiplayerPlayerGenerationData!.Dungeons; - foreach (var dungeon in world.Dungeons) + var rewards = world.Config.MultiplayerPlayerGenerationData!.Rewards; + + foreach (var region in world.RewardRegions) { - var generatedData = generatedDungeonData.Single(x => x.Name == dungeon.DungeonName); - if (generatedData.Medallion != ItemType.Nothing && dungeon is INeedsMedallion medallionRegion) - { - medallionRegion.Medallion = generatedData.Medallion; - _logger.LogDebug("Marked {Dungeon} as requiring {Medallion}", generatedData.Name, generatedData.Medallion); - } - if (generatedData.Reward != null && dungeon is IHasReward rewardRegion) + var rewardType = rewards[region.GetType().Name]; + region.SetRewardType(rewardType); + + if (rewardType.IsInAnyCategory(RewardCategory.Metroid, RewardCategory.NonRandomized)) { - rewardRegion.Reward = new Reward(generatedData.Reward.Value, world, rewardRegion); - _logger.LogDebug("Marked {Dungeon} as having {Medallion}", generatedData.Name, generatedData.Reward); + region.MarkedReward = rewardType; } } - EnsureDungeonsHaveMedallions(world); - EnsureDungeonsHaveRewards(world); } - private void FillItems(World world, List worlds) + private void FillBosses(World world) { - var generatedLocationData = world.Config.MultiplayerPlayerGenerationData!.Locations; - foreach (var location in world.Locations) + foreach (var boss in world.Bosses) { - var generatedData = generatedLocationData.Single(x => x.Id == location.Id); - var itemType = generatedData.Item; - var itemWorld = worlds.Single(x => x.Id == generatedData.ItemWorldId); - location.Item = new Item(itemType, itemWorld, isProgression: itemType.IsPossibleProgression(itemWorld.Config.ZeldaKeysanity, itemWorld.Config.MetroidKeysanity)); - _logger.LogDebug("Fast-filled {Item} at {Location}", generatedData.Item, location.Name); + boss.Region = null; + } + + var bosses = world.Config.MultiplayerPlayerGenerationData!.Bosses; + + foreach (var region in world.BossRegions) + { + var bossType = bosses[region.GetType().Name]; + region.SetBossType(bossType); + } + } + + private void FillPrerequisites(World world) + { + var items = world.Config.MultiplayerPlayerGenerationData!.Prerequisites; + foreach (var region in world.PrerequisiteRegions) + { + var item = items[region.GetType().Name]; + region.RequiredItem = item; } - EnsureLocationsHaveItems(world); } } diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/PlandoFiller.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/PlandoFiller.cs index 83c36052a..22640fc59 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/PlandoFiller.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/PlandoFiller.cs @@ -70,7 +70,7 @@ public void SetRandom(Random random) private static void EnsureDungeonsHaveMedallions(World world) { - var emptyMedallions = world.Regions.Where(x => x is INeedsMedallion { Medallion: ItemType.Nothing }).ToList(); + var emptyMedallions = world.Regions.Where(x => x is IHasPrerequisite { RequiredItem: ItemType.Nothing }).ToList(); if (emptyMedallions.Any()) { throw new PlandoConfigurationException($"Not all dungeons have had their medallions set. Missing:\n" @@ -107,10 +107,10 @@ private void AssignMedallions(World world) if (region == null) throw new PlandoConfigurationException($"Could not find a region with the specified name.\nName: '{regionName}'"); - if (region is not INeedsMedallion dungeon) + if (region is not IHasPrerequisite dungeon) throw new PlandoConfigurationException($"{region.Name} is configured with a medallion but that region cannot be configured with medallions."); - dungeon.Medallion = medallion switch + dungeon.RequiredItem = medallion switch { ItemType.Bombos => ItemType.Bombos, ItemType.Ether => ItemType.Ether, @@ -124,6 +124,11 @@ private void AssignMedallions(World world) private void AssignRewards(World world) { + foreach (var reward in world.Rewards) + { + reward.Region = null; + } + foreach (var regionName in PlandoConfig.Rewards.Keys) { var rewardType = PlandoConfig.Rewards[regionName]; @@ -131,10 +136,10 @@ private void AssignRewards(World world) if (region == null) throw new PlandoConfigurationException($"Could not find a region with the specified name.\nName: '{regionName}'"); - if (region is not IHasReward dungeon) + if (region is not IHasReward rewardRegion) throw new PlandoConfigurationException($"{region.Name} is configured with a reward but that region cannot be configured with rewards."); - dungeon.Reward = new Reward(rewardType, world, dungeon); + rewardRegion.SetRewardType(rewardType); } EnsureDungeonsHaveRewards(world); diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomGenerationService.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomGenerationService.cs index f79d6cb0c..85c3ebf15 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomGenerationService.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomGenerationService.cs @@ -165,99 +165,104 @@ public byte[] GenerateRomBytes(RandomizerOptions options, SeedData? seed) localConfig.LinkName = linkSpriteName; } - if (options.PatchOptions.CasPatches.Respin) + ApplyCasPatches(rom, options.PatchOptions); + + Rom.UpdateChecksum(rom); + + return rom; + } + + public void ApplyCasPatches(byte[] rom, PatchOptions options) + { + if (options.CasPatches.Respin) { using var patch = IpsPatch.Respin(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.NerfedCharge) + if (options.CasPatches.NerfedCharge) { using var patch = IpsPatch.NerfedCharge(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.RefillAtSaveStation) + if (options.CasPatches.RefillAtSaveStation) { using var patch = IpsPatch.RefillAtSaveStation(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.FastDoors) + if (options.CasPatches.FastDoors) { using var patch = IpsPatch.FastDoors(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.FastElevators) + if (options.CasPatches.FastElevators) { using var patch = IpsPatch.FastElevators(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.AimAnyButton) + if (options.CasPatches.AimAnyButton) { using var patch = IpsPatch.AimAnyButton(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.Speedkeep) + if (options.CasPatches.Speedkeep) { using var patch = IpsPatch.SpeedKeep(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.DisableFlashing) + if (options.CasPatches.DisableFlashing) { using var patch = IpsPatch.DisableMetroidFlashing(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.DisableScreenShake) + if (options.CasPatches.DisableScreenShake) { using var patch = IpsPatch.DisableMetroidScreenShake(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.EasierWallJumps) + if (options.CasPatches.EasierWallJumps) { using var patch = IpsPatch.EasierWallJumps(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.SnapMorph) + if (options.CasPatches.SnapMorph) { using var patch = IpsPatch.SnapMorph(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.CasPatches.SandPitPlatforms) + if (options.CasPatches.SandPitPlatforms) { using var patch = IpsPatch.SandPitPlatforms(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.MetroidControls.RunButtonBehavior == RunButtonBehavior.AutoRun) + if (options.MetroidControls.RunButtonBehavior == RunButtonBehavior.AutoRun) { using var patch = IpsPatch.AutoRun(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.MetroidControls.ItemCancelBehavior != ItemCancelBehavior.Vanilla) + if (options.MetroidControls.ItemCancelBehavior != ItemCancelBehavior.Vanilla) { - using var patch = options.PatchOptions.MetroidControls.ItemCancelBehavior == ItemCancelBehavior.Toggle ? IpsPatch.ItemCancelToggle() : IpsPatch.ItemCancelHoldFire(); + using var patch = options.MetroidControls.ItemCancelBehavior == ItemCancelBehavior.Toggle ? IpsPatch.ItemCancelToggle() : IpsPatch.ItemCancelHoldFire(); Rom.ApplySuperMetroidIps(rom, patch); } - if (options.PatchOptions.MetroidControls.AimButtonBehavior == AimButtonBehavior.UnifiedAim) + if (options.MetroidControls.AimButtonBehavior == AimButtonBehavior.UnifiedAim) { using var patch = IpsPatch.UnifiedAim(); Rom.ApplySuperMetroidIps(rom, patch); } - - Rom.UpdateChecksum(rom); - - return rom; } diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomParserService.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomParserService.cs new file mode 100644 index 000000000..16682dcac --- /dev/null +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomParserService.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using System.Linq; +using TrackerCouncil.Smz3.Data.Options; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.SeedGenerator.FileData.Patches; +using TrackerCouncil.Smz3.Shared; + +namespace TrackerCouncil.Smz3.SeedGenerator.Generation; + +public class RomParserService +{ + public void ParseRomFile(string filePath) + { + if (!File.Exists(filePath)) + { + throw new InvalidOperationException(); + } + + var world = new World(new Config(), "", 1, ""); + var rom = File.ReadAllBytes(filePath); + var multiworld = MetadataPatch.IsRomMultiworldEnabled(rom); + var keysanity = MetadataPatch.IsRomKeysanityEnabled(rom); + var players = MetadataPatch.GetPlayerNames(rom); + var playerIndex = MetadataPatch.GetPlayerIndex(rom); + var romTitle = MetadataPatch.GetGameTitle(rom); + var medallions = MedallionPatch.GetRequiredMedallions(rom); + ZeldaRewardsPatch.ApplyRewardsFromRom(rom, world); + var hardLogic = false; + + if (romTitle.StartsWith("ZSM")) + { + var flags = romTitle[3..]; + var flagIndex = flags.IndexOf(flags.FirstOrDefault(char.IsLetter)); + flags = flags.Substring(flagIndex, 2); + hardLogic = flags[1] == 'H'; + } + + var playerName = players[playerIndex]; + var locations = LocationsPatch.GetLocationsFromRom(rom, players); + } +} diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomTextService.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomTextService.cs index 870bf2810..7dee2ae06 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomTextService.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/RomTextService.cs @@ -265,8 +265,8 @@ private string GetSpoilerLog(RandomizerOptions options, SeedData seed, bool conf foreach (var region in world.Regions) { - if (region is INeedsMedallion medallionRegion) - log.AppendLine($"{region.Name}: {medallionRegion.Medallion}"); + if (region is IHasPrerequisite medallionRegion) + log.AppendLine($"{region.Name}: {medallionRegion.RequiredItem}"); } log.AppendLine(); } diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3GeneratedRomLoader.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3GeneratedRomLoader.cs index 3d435f2e6..602c784e9 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3GeneratedRomLoader.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3GeneratedRomLoader.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using TrackerCouncil.Smz3.Data; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.SeedGenerator.Contracts; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Shared.Models; @@ -44,13 +46,18 @@ public List LoadGeneratedRom(GeneratedRom rom) _randomizerContext.Entry(trackerState).Collection(x => x.LocationStates).Load(); _randomizerContext.Entry(trackerState).Collection(x => x.ItemStates).Load(); - _randomizerContext.Entry(trackerState).Collection(x => x.DungeonStates).Load(); + _randomizerContext.Entry(trackerState).Collection(x => x.TreasureStates).Load(); _randomizerContext.Entry(trackerState).Collection(x => x.BossStates).Load(); + _randomizerContext.Entry(trackerState).Collection(x => x.RewardStates).Load(); + _randomizerContext.Entry(trackerState).Collection(x => x.PrerequisiteStates).Load(); _randomizerContext.Entry(trackerState).Collection(x => x.History).Load(); _randomizerContext.Entry(trackerState).Collection(x => x.Hints).Load(); + UpdateGeneratedRom(rom); + var configs = Config.FromConfigString(rom.Settings); - var worlds = configs.Select(config => new World(config, config.PlayerName, config.Id, config.PlayerGuid, config.Id == trackerState.LocalWorldId, _metadata, trackerState)).ToList(); + var worlds = configs.Select(config => new World(config, config.PlayerName, config.Id, config.PlayerGuid, + config.Id == trackerState.LocalWorldId, _metadata, trackerState)).ToList(); // Load world items from state foreach (var location in worlds.SelectMany(x => x.Locations)) @@ -74,7 +81,7 @@ public List LoadGeneratedRom(GeneratedRom rom) new ItemData(new SchrodingersString(itemState.ItemName), itemState.Type ?? ItemType.Nothing, new SchrodingersString()); var world = worlds.First(w => w.Id == itemState.WorldId); - world.TrackerItems.Add(new Item(itemState.Type ?? ItemType.Nothing, world, itemState.ItemName, itemMetadata, itemState)); + world.CustomItems.Add(new Item(itemState.Type ?? ItemType.Nothing, world, itemState.ItemName, itemMetadata, itemState)); } // Create items for metadata items not in the world @@ -91,23 +98,23 @@ public List LoadGeneratedRom(GeneratedRom rom) WorldId = world.Id }; - world.TrackerItems.Add(new Item(itemMetadata.InternalItemType, world, itemState.ItemName, itemMetadata, itemState)); + world.CustomItems.Add(new Item(itemMetadata.InternalItemType, world, itemState.ItemName, itemMetadata, itemState)); trackerState.ItemStates.Add(itemState); } } - // Create bosses for saved state bosses not in the world - foreach (var bossState in trackerState.BossStates.Where(s => worlds.First(w => w.Id == s.WorldId).GoldenBosses.All(b => b.Type != s.Type))) + // Create custom bosses from the tracker states + foreach (var bossState in trackerState.BossStates.Where(x => x.Type == BossType.None)) { var bossMetadata = _metadata.Boss(bossState.BossName) ?? new BossInfo(bossState.BossName); var world = worlds.First(w => w.Id == bossState.WorldId); - world.TrackerBosses.Add(new Boss(bossMetadata.Type, world, bossMetadata.Boss, bossMetadata, bossState)); + world.CustomBosses.Add(new Boss(BossType.None, world, bossMetadata, bossState)); } - // Create bosses for metadata items not in the world + // Create custom bosses for metadata items not in the world foreach (var world in worlds) { - foreach (var bossMetadata in _metadata.Bosses.Where(m => !world.AllBosses.Any(b => b.Is(m.Type, m.Boss)))) + foreach (var bossMetadata in _metadata.Bosses.Where(m => m.Type == BossType.None && !world.AllBosses.Any(b => b.Is(BossType.None, m.Boss)))) { var bossState = new TrackerBossState() { @@ -117,7 +124,7 @@ public List LoadGeneratedRom(GeneratedRom rom) WorldId = world.Id }; - world.TrackerBosses.Add(new Boss(bossMetadata.Type, world, bossMetadata.Boss, bossMetadata, bossState)); + world.CustomBosses.Add(new Boss(BossType.None, world, bossMetadata, bossState)); trackerState.BossStates.Add(bossState); } } @@ -126,4 +133,128 @@ public List LoadGeneratedRom(GeneratedRom rom) _worldAccessor.World = worlds.First(x => x.IsLocalWorld); return worlds; } + +#pragma warning disable CS0618 // Type or member is obsolete + private void UpdateGeneratedRom(GeneratedRom rom) + { + if (!RandomizerVersion.IsVersionPreReplaceDungeonState(rom.GeneratorVersion) || rom.TrackerState?.PrerequisiteStates.Count > 0) + { + return; + } + + var trackerState = rom.TrackerState!; + + _randomizerContext.Entry(trackerState).Collection(x => x.DungeonStates).Load(); + + foreach (var boss in trackerState.BossStates.Where(x => x.Type != BossType.None)) + { + boss.RegionName = MetroidBossRegions[boss.Type]; + + trackerState.RewardStates.Add(new TrackerRewardState() + { + TrackerState = trackerState, + RewardType = MetroidBossRewards[boss.Type], + MarkedReward = MetroidBossRewards[boss.Type], + HasReceivedReward = boss.Defeated, + RegionName = MetroidBossRegions[boss.Type], + AutoTracked = boss.AutoTracked, + WorldId = boss.WorldId + }); + } + + var motherBrain = + trackerState.BossStates.FirstOrDefault(x => x is { BossName: "Mother Brain", Type: BossType.None }); + if (motherBrain != null) + { + motherBrain.Type = BossType.MotherBrain; + } + + foreach (var dungeon in trackerState.DungeonStates) + { + var bossType = DungeonBosses[dungeon.Name]; + + trackerState.BossStates.Add(new TrackerBossState() + { + TrackerState = trackerState, + RegionName = dungeon.Name, + BossName = bossType.GetDescription(), + Defeated = dungeon.Cleared, + AutoTracked = dungeon.AutoTracked, + Type = bossType, + WorldId = dungeon.WorldId + }); + + trackerState.TreasureStates.Add(new TrackerTreasureState() + { + TrackerState = trackerState, + RegionName = dungeon.Name, + RemainingTreasure = dungeon.RemainingTreasure, + TotalTreasure = dungeon.RemainingTreasure, + HasManuallyClearedTreasure = dungeon.HasManuallyClearedTreasure, + WorldId = dungeon.WorldId + }); + + if (dungeon.RequiredMedallion != null) + { + trackerState.PrerequisiteStates.Add(new TrackerPrerequisiteState() + { + TrackerState = trackerState, + RegionName = dungeon.Name, + WorldId = dungeon.WorldId, + AutoTracked = dungeon.AutoTracked, + RequiredItem = dungeon.RequiredMedallion!.Value, + MarkedItem = dungeon.MarkedMedallion + }); + } + + if (dungeon.Reward != null) + { + trackerState.RewardStates.Add(new TrackerRewardState() + { + TrackerState = trackerState, + RewardType = dungeon.Reward!.Value, + MarkedReward = dungeon.MarkedReward, + HasReceivedReward = dungeon.Cleared, + RegionName = dungeon.Name, + AutoTracked = dungeon.AutoTracked, + WorldId = dungeon.WorldId + }); + } + } + } + + private Dictionary DungeonBosses = new() + { + { "CastleTower", BossType.Agahnim }, + { "EasternPalace", BossType.ArmosKnights }, + { "DesertPalace", BossType.Lanmolas }, + { "TowerOfHera", BossType.Moldorm }, + { "PalaceOfDarkness", BossType.HelmasaurKing }, + { "SwampPalace", BossType.Arrghus }, + { "SkullWoods", BossType.Mothula }, + { "ThievesTown", BossType.Blind }, + { "IcePalace", BossType.Kholdstare }, + { "MiseryMire", BossType.Vitreous }, + { "TurtleRock", BossType.Trinexx }, + { "GanonsTower", BossType.Ganon }, + { "HyruleCastle", BossType.CastleGuard }, + }; + + private Dictionary MetroidBossRegions = new() + { + { BossType.Kraid, "KraidsLair" }, + { BossType.Phantoon, "WreckedShip" }, + { BossType.Draygon, "InnerMaridia" }, + { BossType.Ridley, "LowerNorfairEast" }, + }; + + private Dictionary MetroidBossRewards = new() + { + { BossType.Kraid, RewardType.KraidToken }, + { BossType.Phantoon, RewardType.PhantoonToken }, + { BossType.Draygon, RewardType.DraygonToken }, + { BossType.Ridley, RewardType.RidleyToken }, + }; + +#pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3Randomizer.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3Randomizer.cs index 3dd2675e1..b7a242a1b 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3Randomizer.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/Smz3Randomizer.cs @@ -8,6 +8,7 @@ using TrackerCouncil.Smz3.Data.GeneratedData; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.SeedGenerator.Contracts; using TrackerCouncil.Smz3.SeedGenerator.FileData; using TrackerCouncil.Smz3.SeedGenerator.Infrastructure; @@ -115,6 +116,15 @@ public SeedData GenerateSeed(List configs, string? seed, CancellationTok seedData.WorldGenerationData.Add(worldGenerationData); } + // Mark Agahnim and the Golden Four Metroid bosses as known + var bossRewardRegions = worlds.SelectMany(x => x.BossRegions) + .Where(x => x.BossType is BossType.Agahnim || x.BossType.IsInCategory(BossCategory.GoldenFour)) + .OfType(); + foreach (var region in bossRewardRegions) + { + region.MarkedReward = region.RewardType; + } + Debug.WriteLine("Generated seed on randomizer instance " + GetHashCode()); _logger.LogInformation("Generated seed successfully"); _worldAccessor.World = worlds.First(x => x.IsLocalWorld); diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/StandardFiller.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/StandardFiller.cs index 88b80e3a9..122b26b6a 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/StandardFiller.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/StandardFiller.cs @@ -285,7 +285,7 @@ private void AssumedFill(List itemPool, List initialInventory, { var worldsList = worlds.ToList(); var locationsList = locations.ToList(); - var allRewards = worldsList.SelectMany(Reward.CreatePool).ToList(); + var allRewards = worldsList.SelectMany(x => x.Rewards).ToList(); var allBosses = worldsList.SelectMany(w => w.GoldenBosses).ToList(); var itemsToAdd = new List(itemPool); var failedAttempts = new Dictionary(); @@ -354,7 +354,7 @@ private IEnumerable CollectRewards(IEnumerable items, IEnumerable< return worldsList .SelectMany(w => w.Regions) - .Where(r => r is IHasReward reward && reward.CanComplete(progressions[r.World])) + .Where(r => r is IHasReward reward && reward.CanRetrieveReward(progressions[r.World])) .SelectMany(r => rewardPool.Where(p => p.Type == ((IHasReward)r).RewardType && p.Region == r)); } diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/StatGenerator.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/StatGenerator.cs index a3c4fc7ff..1a3245ed1 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Generation/StatGenerator.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Generation/StatGenerator.cs @@ -140,7 +140,7 @@ private void WriteMegaSpoilerLog(ConcurrentDictionary<(int itemId, LocationId lo var total = (double)numberOfSeeds; var dungeonItems = locations - .Where(x => x.Region is IDungeon) + .Where(x => x.Region is IHasTreasure) .OrderBy(x => x.Region.Name) .Select(location => new { diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/Infrastructure/PlaythroughService.cs b/src/TrackerCouncil.Smz3.SeedGenerator/Infrastructure/PlaythroughService.cs index c04b3e928..5ef72b953 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/Infrastructure/PlaythroughService.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/Infrastructure/PlaythroughService.cs @@ -68,11 +68,13 @@ public Playthrough Generate(IReadOnlyCollection worlds, Config config) /// /// List of locations to iterate through /// + /// /// A list of the spheres /// When not all locations are public IEnumerable GenerateSpheres(IEnumerable allLocations, Reward? defaultReward = null) { - var worlds = allLocations.Select(x => x.Region.World).Distinct(); + var allLocationsList = allLocations.ToList(); + var worlds = allLocationsList.Select(x => x.Region.World).Distinct().ToList(); var spheres = new List(); var locations = new List(); @@ -83,13 +85,11 @@ public Playthrough Generate(IReadOnlyCollection worlds, Config config) defaultRewards.Add(defaultReward); } - var allRewards = worlds.SelectMany(w => w.Regions).OfType().Select(x => x.Reward); - var regions = new List(); - var rewards = defaultRewards; + var rewardRegions = worlds.SelectMany(x => x.RewardRegions).ToList(); + List rewards; - var allBosses = worlds.SelectMany(w => w.GoldenBosses); - var bossRegions = worlds.SelectMany(w => w.GoldenBosses).Select(x => x.Region).Cast(); - var bosses = new List(); + var bossRegions = worlds.SelectMany(x => x.BossRegions).ToList(); + List bosses; foreach (var world in worlds) { @@ -103,17 +103,22 @@ public Playthrough Generate(IReadOnlyCollection worlds, Config config) } var initInventoryCount = items.Count; - var totalItemCount = allLocations.Select(x => x.Item).Count(); + var totalItemCount = allLocationsList.Select(x => x.Item).Count(); var prevRewardCount = 0; while (items.Count - initInventoryCount < totalItemCount) { var sphere = new Playthrough.Sphere(); var tempProgression = new Progression(items, new List(), new List()); - rewards = allRewards.Where(x => x.Region.CanComplete(tempProgression)).Concat(defaultRewards).ToList(); - bosses = bossRegions.Where(x => x.CanBeatBoss(tempProgression)).Select(x => x.Boss).ToList(); - - var accessibleLocations = allLocations.Where(l => l.IsAvailable(new Progression(items.Where(i => i.World == l.World), rewards.Where(r => r.World == l.World), bosses.Where(b => b.World == l.World)))); + rewards = rewardRegions.Where(x => x.CanRetrieveReward(tempProgression)) + .Select(x => x.Reward) + .Concat(defaultRewards).ToList(); + bosses = bossRegions.Where(x => x.CanBeatBoss(tempProgression)) + .Select(x => x.Boss).ToList(); + + var accessibleLocations = allLocationsList.Where(l => + l.IsAvailable(new Progression(items.Where(i => i.World == l.World), + rewards.Where(r => r.World == l.World), bosses.Where(b => b.World == l.World)))).ToList(); var newLocations = accessibleLocations.Except(locations).ToList(); var newItems = newLocations.Select(l => l.Item).ToList(); @@ -125,7 +130,7 @@ public Playthrough Generate(IReadOnlyCollection worlds, Config config) if (!newItems.Any() && prevRewardCount == rewards.Count) { /* With no new items added we might have a problem, so list inaccessable items */ - var inaccessibleLocations = allLocations.Where(l => !locations.Contains(l)).ToList(); + var inaccessibleLocations = allLocationsList.Where(l => !locations.Contains(l)).ToList(); _logger.LogDebug("Inaccesible locations: {}", string.Join(", ", inaccessibleLocations.Select(x => x.Id.ToString()))); @@ -133,7 +138,7 @@ public Playthrough Generate(IReadOnlyCollection worlds, Config config) // We determine this on if all players can beat all 4 golden bosses, access the if (inaccessibleLocations.Select(l => l.Item).Count() >= (15 * worlds.Count())) { - var vitalLocations = allLocations.Where(x => x.Id is LocationId.GanonsTowerMoldormChest or LocationId.KraidsLairVariaSuit or LocationId.WreckedShipEastSuper or LocationId.InnerMaridiaSpaceJump or LocationId.LowerNorfairRidleyTank).ToList(); + var vitalLocations = allLocationsList.Where(x => x.Id is LocationId.GanonsTowerMoldormChest or LocationId.KraidsLairVariaSuit or LocationId.WreckedShipEastSuper or LocationId.InnerMaridiaSpaceJump or LocationId.LowerNorfairRidleyTank).ToList(); var crateriaBossKeys = items.Count(x => x.Type == ItemType.CardCrateriaBoss); if (accessibleLocations.Count(x => vitalLocations.Contains(x)) != vitalLocations.Count() || crateriaBossKeys != worlds.Count()) { diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/RandomizerServiceCollectionExtensions.cs b/src/TrackerCouncil.Smz3.SeedGenerator/RandomizerServiceCollectionExtensions.cs index 48b7f9bb0..83001d598 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/RandomizerServiceCollectionExtensions.cs +++ b/src/TrackerCouncil.Smz3.SeedGenerator/RandomizerServiceCollectionExtensions.cs @@ -26,6 +26,7 @@ public static IServiceCollection AddRandomizerServices(this IServiceCollection s services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); return services; } diff --git a/src/TrackerCouncil.Smz3.SeedGenerator/TrackerCouncil.Smz3.SeedGenerator.csproj b/src/TrackerCouncil.Smz3.SeedGenerator/TrackerCouncil.Smz3.SeedGenerator.csproj index 6a4c19801..2e95dc45c 100644 --- a/src/TrackerCouncil.Smz3.SeedGenerator/TrackerCouncil.Smz3.SeedGenerator.csproj +++ b/src/TrackerCouncil.Smz3.SeedGenerator/TrackerCouncil.Smz3.SeedGenerator.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/LocationStatus.cs b/src/TrackerCouncil.Smz3.Shared/Enums/Accessibility.cs similarity index 69% rename from src/TrackerCouncil.Smz3.Shared/Enums/LocationStatus.cs rename to src/TrackerCouncil.Smz3.Shared/Enums/Accessibility.cs index 091569477..dff55bc7a 100644 --- a/src/TrackerCouncil.Smz3.Shared/Enums/LocationStatus.cs +++ b/src/TrackerCouncil.Smz3.Shared/Enums/Accessibility.cs @@ -3,7 +3,7 @@ /// /// Indicates the current tracking status of the location /// -public enum LocationStatus +public enum Accessibility { /// /// The location's status is currently unknown @@ -20,12 +20,23 @@ public enum LocationStatus /// Available, + /// + /// Currently in logic if keys are assumed + /// + AvailableWithKeys, + /// /// Currently in logic, but the player must do something else before /// it's available /// Relevant, + /// + /// Currently in logic, but the player must do something else before + /// it's available + /// + RelevantWithKeys, + /// /// This location is not currently available to the player /// diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/BossCategory.cs b/src/TrackerCouncil.Smz3.Shared/Enums/BossCategory.cs new file mode 100644 index 000000000..a6defc4d5 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Enums/BossCategory.cs @@ -0,0 +1,9 @@ +namespace TrackerCouncil.Smz3.Shared.Enums; + +public enum BossCategory +{ + Zelda, + HasReward, + GoldenFour, + Metroid +} diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/BossCategoryAttribute.cs b/src/TrackerCouncil.Smz3.Shared/Enums/BossCategoryAttribute.cs new file mode 100644 index 000000000..36806867a --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Enums/BossCategoryAttribute.cs @@ -0,0 +1,27 @@ +using System; + +namespace TrackerCouncil.Smz3.Shared.Enums; + +/// +/// Specifies the boss categories for a boss type. +/// +[AttributeUsage(AttributeTargets.Field)] +public sealed class BossCategoryAttribute : Attribute +{ + /// + /// Initializes a new instance of the class with the specified categories. + /// + /// + /// The categories to assign to the boss type. + /// + public BossCategoryAttribute(params BossCategory[] categories) + { + Categories = categories; + } + + /// + /// Gets the categories for the type of boss. + /// + public BossCategory[] Categories { get; } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/BossType.cs b/src/TrackerCouncil.Smz3.Shared/Enums/BossType.cs index 002372af8..90fd175dd 100644 --- a/src/TrackerCouncil.Smz3.Shared/Enums/BossType.cs +++ b/src/TrackerCouncil.Smz3.Shared/Enums/BossType.cs @@ -5,12 +5,76 @@ namespace TrackerCouncil.Smz3.Shared.Enums; public enum BossType { None, + [Description("Kraid")] + [BossCategory(BossCategory.Metroid, BossCategory.GoldenFour, BossCategory.HasReward)] Kraid, + [Description("Phantoon")] + [BossCategory(BossCategory.Metroid, BossCategory.GoldenFour, BossCategory.HasReward)] Phantoon, + [Description("Draygon")] + [BossCategory(BossCategory.Metroid, BossCategory.GoldenFour, BossCategory.HasReward)] Draygon, + [Description("Ridley")] + [BossCategory(BossCategory.Metroid, BossCategory.GoldenFour, BossCategory.HasReward)] Ridley, + + [Description("Mother Brain")] + [BossCategory(BossCategory.Metroid)] + MotherBrain, + + [Description("Castle Guard")] + [BossCategory(BossCategory.Zelda)] + CastleGuard, + + [Description("Armos Knights")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + ArmosKnights, + + [Description("Lanmolas")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + Lanmolas, + + [Description("Moldorm")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + Moldorm, + + [Description("Helmasaur King")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + HelmasaurKing, + + [Description("Arrghus")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + Arrghus, + + [Description("Blind")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + Blind, + + [Description("Mothula")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + Mothula, + + [Description("Kholdstare")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + Kholdstare, + + [Description("Vitreous")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + Vitreous, + + [Description("Trinexx")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + Trinexx, + + [Description("Agahnim")] + [BossCategory(BossCategory.Zelda, BossCategory.HasReward)] + Agahnim, + + [Description("Ganon")] + [BossCategory(BossCategory.Zelda)] + Ganon, } diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/BossTypeExtensions.cs b/src/TrackerCouncil.Smz3.Shared/Enums/BossTypeExtensions.cs new file mode 100644 index 000000000..42e626796 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Enums/BossTypeExtensions.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace TrackerCouncil.Smz3.Shared.Enums; + +public static class BossTypeExtensions +{ + /// + /// Determines whether the boss type is in the specified category. + /// + /// The type of boss. + /// The category to match. + /// + /// if is in + /// ; otherwise, . + /// + public static bool IsInCategory(this BossType bossType, BossCategory category) + { + return GetCategories(bossType).Any(x => x == category); + } + + /// + /// Determines whether the boss type matches any of the specified + /// categories. + /// + /// The type of boss. + /// The categories to match. + /// + /// if matches any of + /// ; otherwise, . + /// + public static bool IsInAnyCategory(this BossType bossType, params BossCategory[] categories) + { + return GetCategories(bossType).Any(categories.Contains); + } + + /// + /// Determines the categories for the specified boss type. + /// + /// The type of boss. + /// + /// A collection of categories for , or an + /// empty array. + /// + public static BossCategory[] GetCategories(this BossType bossType) + { + var valueType = bossType.GetType().GetField(bossType.ToString()); + if (valueType != null) + { + var attrib = valueType.GetCustomAttribute(inherit: false); + if (attrib != null) return attrib.Categories; + } + + return Array.Empty(); + } + +} diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/ItemCategory.cs b/src/TrackerCouncil.Smz3.Shared/Enums/ItemCategory.cs index e7dfc7e4e..b1b6d1780 100644 --- a/src/TrackerCouncil.Smz3.Shared/Enums/ItemCategory.cs +++ b/src/TrackerCouncil.Smz3.Shared/Enums/ItemCategory.cs @@ -102,4 +102,8 @@ public enum ItemCategory /// If this should not be given out when completing multiplayer games /// IgnoreOnMultiplayerCompletion, + + NeverProgression, + + ProgressionOnlyOnFirst } diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/ItemCategoryAttribute.cs b/src/TrackerCouncil.Smz3.Shared/Enums/ItemCategoryAttribute.cs index 960382bb1..53e7e91b6 100644 --- a/src/TrackerCouncil.Smz3.Shared/Enums/ItemCategoryAttribute.cs +++ b/src/TrackerCouncil.Smz3.Shared/Enums/ItemCategoryAttribute.cs @@ -5,8 +5,7 @@ namespace TrackerCouncil.Smz3.Shared.Enums; /// /// Specifies the item categories for an item type. /// -[AttributeUsage(AttributeTargets.Field, - Inherited = false, AllowMultiple = false)] +[AttributeUsage(AttributeTargets.Field)] public sealed class ItemCategoryAttribute : Attribute { /// @@ -14,7 +13,7 @@ public sealed class ItemCategoryAttribute : Attribute /// cref="ItemCategoryAttribute"/> class with the specified categories. /// /// - /// The categories to assing to the item type. + /// The categories to assign to the item type. /// public ItemCategoryAttribute(params ItemCategory[] categories) { diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/ItemType.cs b/src/TrackerCouncil.Smz3.Shared/Enums/ItemType.cs index b733101cf..337daa272 100644 --- a/src/TrackerCouncil.Smz3.Shared/Enums/ItemType.cs +++ b/src/TrackerCouncil.Smz3.Shared/Enums/ItemType.cs @@ -8,95 +8,95 @@ public enum ItemType : byte Nothing, [Description("Hyrule Castle Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapHC = 0x7F, [Description("Eastern Palace Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapEP = 0x7D, [Description("Desert Palace Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapDP = 0x7C, [Description("Tower of Hera Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapTH = 0x75, [Description("Palace of Darkness Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapPD = 0x79, [Description("Swamp Palace Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapSP = 0x7A, [Description("Skull Woods Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapSW = 0x77, [Description("Thieves Town Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapTT = 0x74, [Description("Ice Palace Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapIP = 0x76, [Description("Misery Mire Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapMM = 0x78, [Description("Turtle Rock Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapTR = 0x73, [Description("Ganons Tower Map")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Map)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Map, ItemCategory.NeverProgression)] MapGT = 0x72, [Description("Eastern Palace Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassEP = 0x8D, [Description("Desert Palace Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassDP = 0x8C, [Description("Tower of Hera Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassTH = 0x85, [Description("Palace of Darkness Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassPD = 0x89, [Description("Swamp Palace Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassSP = 0x8A, [Description("Skull Woods Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassSW = 0x87, [Description("Thieves Town Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassTT = 0x84, [Description("Ice Palace Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassIP = 0x86, [Description("Misery Mire Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassMM = 0x88, [Description("Turtle Rock Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassTR = 0x83, [Description("Ganons Tower Compass")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Compass, ItemCategory.NeverProgression)] CompassGT = 0x82, [Description("Eastern Palace Big Key")] @@ -208,7 +208,7 @@ public enum ItemType : byte Map = 0x33, [Description("Progressive Mail")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Nice)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Nice, ItemCategory.NeverProgression)] ProgressiveTunic = 0x60, [Description("Progressive Shield")] @@ -228,11 +228,11 @@ public enum ItemType : byte SilverArrows = 0x58, [Description("Blue Boomerang")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.NeverProgression)] BlueBoomerang = 0x0C, [Description("Red Boomerang")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.NeverProgression)] RedBoomerang = 0x2A, [Description("Hookshot")] @@ -284,7 +284,7 @@ public enum ItemType : byte Flute = 0x14, [Description("Bug Catching Net")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.NeverProgression)] Bugnet = 0x21, [Description("Book of Mudora")] @@ -344,31 +344,31 @@ public enum ItemType : byte HeartContainerRefill = 0x3F, [Description("Three Bombs")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.Plentiful, ItemCategory.IgnoreOnMultiplayerCompletion)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.Plentiful, ItemCategory.IgnoreOnMultiplayerCompletion, ItemCategory.NeverProgression)] ThreeBombs = 0x28, [Description("Single Arrow")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.IgnoreOnMultiplayerCompletion)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.IgnoreOnMultiplayerCompletion, ItemCategory.NeverProgression)] Arrow = 0x43, [Description("Ten Arrows")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.IgnoreOnMultiplayerCompletion)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.IgnoreOnMultiplayerCompletion, ItemCategory.NeverProgression)] TenArrows = 0x44, [Description("One Rupee")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.IgnoreOnMultiplayerCompletion)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.IgnoreOnMultiplayerCompletion, ItemCategory.NeverProgression)] OneRupee = 0x34, [Description("Five Rupees")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.IgnoreOnMultiplayerCompletion)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.IgnoreOnMultiplayerCompletion, ItemCategory.NeverProgression)] FiveRupees = 0x35, [Description("Twenty Rupees")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.Plentiful, ItemCategory.IgnoreOnMultiplayerCompletion)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.Plentiful, ItemCategory.IgnoreOnMultiplayerCompletion, ItemCategory.NeverProgression)] TwentyRupees = 0x36, [Description("Twenty Rupees")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.IgnoreOnMultiplayerCompletion)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.IgnoreOnMultiplayerCompletion, ItemCategory.NeverProgression)] TwentyRupees2 = 0x47, [Description("Fifty Rupees")] @@ -384,19 +384,19 @@ public enum ItemType : byte ThreeHundredRupees = 0x46, [Description("+5 Bomb Capacity")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.NeverProgression)] BombUpgrade5 = 0x51, [Description("+10 Bomb Capacity")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.NeverProgression)] BombUpgrade10 = 0x52, [Description("+5 Arrow Capacity")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.NeverProgression)] ArrowUpgrade5 = 0x53, [Description("+10 Arrow Capacity")] - [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk)] + [ItemCategory(ItemCategory.Zelda, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.NeverProgression)] ArrowUpgrade10 = 0x54, [Description("Crateria Level 1 Keycard")] @@ -464,11 +464,11 @@ public enum ItemType : byte CardLowerNorfairBoss = 0xDF, [Description("Missile")] - [ItemCategory(ItemCategory.Metroid, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.Plentiful)] + [ItemCategory(ItemCategory.Metroid, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.Plentiful, ItemCategory.ProgressionOnlyOnFirst)] Missile = 0xC2, [Description("Super Missile")] - [ItemCategory(ItemCategory.Metroid, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.Plentiful)] + [ItemCategory(ItemCategory.Metroid, ItemCategory.Scam, ItemCategory.Junk, ItemCategory.Plentiful, ItemCategory.ProgressionOnlyOnFirst)] Super = 0xC3, [Description("Power Bomb")] @@ -480,7 +480,7 @@ public enum ItemType : byte Grapple = 0xB0, [Description("X-Ray Scope")] - [ItemCategory(ItemCategory.Metroid, ItemCategory.Scam)] + [ItemCategory(ItemCategory.Metroid, ItemCategory.Scam, ItemCategory.NeverProgression)] XRay = 0xB1, [Description("Energy Tank")] @@ -504,7 +504,7 @@ public enum ItemType : byte Wave = 0xBD, [Description("Spazer")] - [ItemCategory(ItemCategory.Metroid, ItemCategory.Nice)] + [ItemCategory(ItemCategory.Metroid, ItemCategory.Nice, ItemCategory.NeverProgression)] Spazer = 0xBE, [Description("Plasma Beam")] @@ -598,4 +598,7 @@ public enum ItemType : byte [Description("Boss Keycard")] [ItemCategory(ItemCategory.Metroid, ItemCategory.NonRandomized, ItemCategory.Keycard, ItemCategory.KeycardBoss)] CardBoss = 0xF2, + + [ItemCategory(ItemCategory.NonRandomized)] + OtherGameItem = 0xFF } diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/RewardCategory.cs b/src/TrackerCouncil.Smz3.Shared/Enums/RewardCategory.cs new file mode 100644 index 000000000..471e94ff8 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Enums/RewardCategory.cs @@ -0,0 +1,10 @@ +namespace TrackerCouncil.Smz3.Shared.Enums; + +public enum RewardCategory +{ + Zelda, + Crystal, + Pendant, + Metroid, + NonRandomized +} diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/RewardCategoryAttribute.cs b/src/TrackerCouncil.Smz3.Shared/Enums/RewardCategoryAttribute.cs new file mode 100644 index 000000000..c4be1eb5e --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Enums/RewardCategoryAttribute.cs @@ -0,0 +1,27 @@ +using System; + +namespace TrackerCouncil.Smz3.Shared.Enums; + +/// +/// Specifies the reward categories for a reward type. +/// +[AttributeUsage(AttributeTargets.Field)] +public sealed class RewardCategoryAttribute : Attribute +{ + /// + /// Initializes a new instance of the class with the specified categories. + /// + /// + /// The categories to assign to the reward type. + /// + public RewardCategoryAttribute(params RewardCategory[] categories) + { + Categories = categories; + } + + /// + /// Gets the categories for the type of reward. + /// + public RewardCategory[] Categories { get; } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/RewardType.cs b/src/TrackerCouncil.Smz3.Shared/Enums/RewardType.cs index 32f9bd64e..4d6306fc9 100644 --- a/src/TrackerCouncil.Smz3.Shared/Enums/RewardType.cs +++ b/src/TrackerCouncil.Smz3.Shared/Enums/RewardType.cs @@ -9,16 +9,48 @@ public enum RewardType { [Description("Unknown")] None, + [Description("Agahnim")] + [RewardCategory(RewardCategory.Zelda, RewardCategory.NonRandomized)] Agahnim, + [Description("Green Pendant")] + [RewardCategory(RewardCategory.Zelda, RewardCategory.Pendant)] PendantGreen, + [Description("Red Pendant")] + [RewardCategory(RewardCategory.Zelda, RewardCategory.Pendant)] PendantRed, + [Description("Blue Crystal")] + [RewardCategory(RewardCategory.Zelda, RewardCategory.Crystal)] CrystalBlue, + [Description("Red Crystal")] + [RewardCategory(RewardCategory.Zelda, RewardCategory.Crystal)] CrystalRed, + [Description("Blue Pendant")] + [RewardCategory(RewardCategory.Zelda, RewardCategory.Pendant)] PendantBlue, + + [Description("Metroid Boss Token")] + [RewardCategory(RewardCategory.Metroid)] + MetroidBoss, + + [Description("Kraid Boss Token")] + [RewardCategory(RewardCategory.Metroid)] + KraidToken, + + [Description("Phantoon Boss Token")] + [RewardCategory(RewardCategory.Metroid)] + PhantoonToken, + + [Description("Draygon Boss Token")] + [RewardCategory(RewardCategory.Metroid)] + DraygonToken, + + [Description("Ridley Boss Token")] + [RewardCategory(RewardCategory.Metroid)] + RidleyToken, } diff --git a/src/TrackerCouncil.Smz3.Shared/Enums/RewardTypeExtensions.cs b/src/TrackerCouncil.Smz3.Shared/Enums/RewardTypeExtensions.cs new file mode 100644 index 000000000..d99cd4bd5 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Enums/RewardTypeExtensions.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace TrackerCouncil.Smz3.Shared.Enums; + +public static class RewardTypeExtensions +{ + /// + /// Determines whether the reward type is in the specified category. + /// + /// The type of reward. + /// The category to match. + /// + /// if is in + /// ; otherwise, . + /// + public static bool IsInCategory(this RewardType rewardType, RewardCategory category) + { + return GetCategories(rewardType).Any(x => x == category); + } + + /// + /// Determines whether the reward type matches any of the specified + /// categories. + /// + /// The type of reward. + /// The categories to match. + /// + /// if matches any of + /// ; otherwise, . + /// + public static bool IsInAnyCategory(this RewardType rewardType, params RewardCategory[] categories) + { + return GetCategories(rewardType).Any(categories.Contains); + } + + /// + /// Determines the categories for the specified reward type. + /// + /// The type of reward. + /// + /// A collection of categories for , or an + /// empty array. + /// + public static RewardCategory[] GetCategories(this RewardType rewardType) + { + var valueType = rewardType.GetType().GetField(rewardType.ToString()); + if (valueType != null) + { + var attrib = valueType.GetCustomAttribute(inherit: false); + if (attrib != null) return attrib.Categories; + } + + return Array.Empty(); + } + +} diff --git a/src/TrackerCouncil.Smz3.Shared/Migrations/20241003001617_ReplaceDungeonState.Designer.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/20241003001617_ReplaceDungeonState.Designer.cs new file mode 100644 index 000000000..578573f5d --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/20241003001617_ReplaceDungeonState.Designer.cs @@ -0,0 +1,704 @@ +// +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("20241003001617_ReplaceDungeonState")] + partial class ReplaceDungeonState + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); + + 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() + .HasColumnType("TEXT"); + + b.Property("LocationKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LocationString") + .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") + .HasColumnType("TEXT"); + + b.Property("ObjectName") + .IsRequired() + .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() + .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("ItemWorldId") + .HasColumnType("INTEGER"); + + b.Property("LocationId") + .HasColumnType("INTEGER"); + + b.Property("MarkedItem") + .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() + .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() + .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("LocalWorldId") + .HasColumnType("INTEGER"); + + b.Property("PercentageCleared") + .HasColumnType("INTEGER"); + + b.Property("SecondsElapsed") + .HasColumnType("REAL"); + + b.Property("StartDateTime") + .HasColumnType("TEXT"); + + 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("AutoTracked") + .HasColumnType("INTEGER"); + + b.Property("Cleared") + .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/20241003001617_ReplaceDungeonState.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/20241003001617_ReplaceDungeonState.cs new file mode 100644 index 000000000..a0924ae4a --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/20241003001617_ReplaceDungeonState.cs @@ -0,0 +1,129 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TrackerCouncil.Smz3.Shared.Migrations +{ + /// + public partial class ReplaceDungeonState : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RegionName", + table: "TrackerBossStates", + type: "TEXT", + maxLength: 50, + nullable: false, + defaultValue: ""); + + migrationBuilder.CreateTable( + name: "TrackerPrerequisiteStates", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TrackerStateId = table.Column(type: "INTEGER", nullable: true), + RegionName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + WorldId = table.Column(type: "INTEGER", nullable: false), + AutoTracked = table.Column(type: "INTEGER", nullable: false), + RequiredItem = table.Column(type: "INTEGER", nullable: false), + MarkedItem = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TrackerPrerequisiteStates", x => x.Id); + table.ForeignKey( + name: "FK_TrackerPrerequisiteStates_TrackerStates_TrackerStateId", + column: x => x.TrackerStateId, + principalTable: "TrackerStates", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "TrackerRewardStates", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TrackerStateId = table.Column(type: "INTEGER", nullable: true), + RewardType = table.Column(type: "INTEGER", nullable: false), + MarkedReward = table.Column(type: "INTEGER", nullable: true), + HasReceivedReward = table.Column(type: "INTEGER", nullable: false), + RegionName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + AutoTracked = table.Column(type: "INTEGER", nullable: false), + WorldId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TrackerRewardStates", x => x.Id); + table.ForeignKey( + name: "FK_TrackerRewardStates_TrackerStates_TrackerStateId", + column: x => x.TrackerStateId, + principalTable: "TrackerStates", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "TrackerTreasureStates", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TrackerStateId = table.Column(type: "INTEGER", nullable: true), + RegionName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + Cleared = table.Column(type: "INTEGER", nullable: false), + AutoTracked = table.Column(type: "INTEGER", nullable: false), + RemainingTreasure = table.Column(type: "INTEGER", nullable: false), + TotalTreasure = table.Column(type: "INTEGER", nullable: false), + HasManuallyClearedTreasure = table.Column(type: "INTEGER", nullable: false), + WorldId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TrackerTreasureStates", x => x.Id); + table.ForeignKey( + name: "FK_TrackerTreasureStates_TrackerStates_TrackerStateId", + column: x => x.TrackerStateId, + principalTable: "TrackerStates", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_TrackerPrerequisiteStates_TrackerStateId", + table: "TrackerPrerequisiteStates", + column: "TrackerStateId"); + + migrationBuilder.CreateIndex( + name: "IX_TrackerRewardStates_TrackerStateId", + table: "TrackerRewardStates", + column: "TrackerStateId"); + + migrationBuilder.CreateIndex( + name: "IX_TrackerTreasureStates_TrackerStateId", + table: "TrackerTreasureStates", + column: "TrackerStateId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TrackerPrerequisiteStates"); + + migrationBuilder.DropTable( + name: "TrackerRewardStates"); + + migrationBuilder.DropTable( + name: "TrackerTreasureStates"); + + migrationBuilder.DropColumn( + name: "RegionName", + table: "TrackerBossStates"); + } + } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Migrations/20241006235611_StoreLocationUsefulness.Designer.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/20241006235611_StoreLocationUsefulness.Designer.cs new file mode 100644 index 000000000..8f543094e --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/20241006235611_StoreLocationUsefulness.Designer.cs @@ -0,0 +1,702 @@ +// +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("20241006235611_StoreLocationUsefulness")] + partial class StoreLocationUsefulness + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); + + 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() + .HasColumnType("TEXT"); + + b.Property("LocationKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LocationString") + .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") + .HasColumnType("TEXT"); + + b.Property("ObjectName") + .IsRequired() + .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("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() + .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() + .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("LocalWorldId") + .HasColumnType("INTEGER"); + + b.Property("PercentageCleared") + .HasColumnType("INTEGER"); + + b.Property("SecondsElapsed") + .HasColumnType("REAL"); + + b.Property("StartDateTime") + .HasColumnType("TEXT"); + + 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/20241006235611_StoreLocationUsefulness.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/20241006235611_StoreLocationUsefulness.cs new file mode 100644 index 000000000..77f7506a7 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/20241006235611_StoreLocationUsefulness.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TrackerCouncil.Smz3.Shared.Migrations +{ + /// + public partial class StoreLocationUsefulness : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AutoTracked", + table: "TrackerTreasureStates"); + + migrationBuilder.DropColumn( + name: "Cleared", + table: "TrackerTreasureStates"); + + migrationBuilder.AddColumn( + name: "MarkedUsefulness", + table: "TrackerLocationStates", + type: "INTEGER", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MarkedUsefulness", + table: "TrackerLocationStates"); + + migrationBuilder.AddColumn( + name: "AutoTracked", + table: "TrackerTreasureStates", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "Cleared", + table: "TrackerTreasureStates", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Migrations/20241009025556_UpdateColumnSizes.Designer.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/20241009025556_UpdateColumnSizes.Designer.cs new file mode 100644 index 000000000..243af1ecc --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/20241009025556_UpdateColumnSizes.Designer.cs @@ -0,0 +1,709 @@ +// +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("20241009025556_UpdateColumnSizes")] + partial class UpdateColumnSizes + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); + + 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("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("LocalWorldId") + .HasColumnType("INTEGER"); + + b.Property("PercentageCleared") + .HasColumnType("INTEGER"); + + b.Property("SecondsElapsed") + .HasColumnType("REAL"); + + b.Property("StartDateTime") + .HasColumnType("TEXT"); + + 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/20241009025556_UpdateColumnSizes.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/20241009025556_UpdateColumnSizes.cs new file mode 100644 index 000000000..77e4e911e --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/20241009025556_UpdateColumnSizes.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TrackerCouncil.Smz3.Shared.Migrations +{ + /// + public partial class UpdateColumnSizes : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs b/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs index 033fa3108..41dc7c7a5 100644 --- a/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs +++ b/src/TrackerCouncil.Smz3.Shared/Migrations/RandomizerContextModelSnapshot.cs @@ -7,543 +7,700 @@ #nullable disable -namespace TrackerCouncil.Smz3.Shared.Migrations; - -[DbContext(typeof(RandomizerContext))] -partial class RandomizerContextModelSnapshot : ModelSnapshot +namespace TrackerCouncil.Smz3.Shared.Migrations { - protected override void BuildModel(ModelBuilder modelBuilder) + [DbContext(typeof(RandomizerContext))] + partial class RandomizerContextModelSnapshot : ModelSnapshot { + protected override void BuildModel(ModelBuilder modelBuilder) + { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.3"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); - modelBuilder.Entity("Randomizer.Shared.Models.GeneratedRom", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.GeneratedRom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("Date") - .HasColumnType("TEXT"); + b.Property("Date") + .HasColumnType("TEXT"); - b.Property("GeneratorVersion") - .HasColumnType("INTEGER"); + b.Property("GeneratorVersion") + .HasColumnType("INTEGER"); - b.Property("Label") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("Label") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("MsuPaths") - .HasColumnType("TEXT"); + b.Property("MsuPaths") + .HasColumnType("TEXT"); - b.Property("MsuRandomizationStyle") - .HasColumnType("INTEGER"); + b.Property("MsuRandomizationStyle") + .HasColumnType("INTEGER"); - b.Property("MsuShuffleStyle") - .HasColumnType("INTEGER"); + b.Property("MsuShuffleStyle") + .HasColumnType("INTEGER"); - b.Property("MultiplayerGameDetailsId") - .HasColumnType("INTEGER"); + b.Property("MultiplayerGameDetailsId") + .HasColumnType("INTEGER"); - b.Property("RomPath") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("RomPath") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("Seed") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("Seed") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("Settings") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("Settings") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("SpoilerPath") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("SpoilerPath") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("TrackerStateId") - .HasColumnType("INTEGER"); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("MultiplayerGameDetailsId"); + b.HasIndex("MultiplayerGameDetailsId"); - b.HasIndex("TrackerStateId"); + b.HasIndex("TrackerStateId"); - b.ToTable("GeneratedRoms"); - }); + b.ToTable("GeneratedRoms"); + }); - modelBuilder.Entity("Randomizer.Shared.Models.MultiplayerGameDetails", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.MultiplayerGameDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("ConnectionUrl") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("ConnectionUrl") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("GameGuid") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("GameGuid") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("GameUrl") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("GameUrl") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("GeneratedRomId") - .HasColumnType("INTEGER"); + b.Property("GeneratedRomId") + .HasColumnType("INTEGER"); - b.Property("JoinedDate") - .HasColumnType("TEXT"); + b.Property("JoinedDate") + .HasColumnType("TEXT"); - b.Property("PlayerGuid") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("PlayerGuid") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("PlayerKey") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("PlayerKey") + .IsRequired() + .HasColumnType("TEXT"); - b.Property("Status") - .HasColumnType("INTEGER"); + b.Property("Status") + .HasColumnType("INTEGER"); - b.Property("Type") - .HasColumnType("INTEGER"); + b.Property("Type") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.HasKey("Id"); - b.HasIndex("GeneratedRomId") - .IsUnique(); + b.HasIndex("GeneratedRomId") + .IsUnique(); - b.ToTable("MultiplayerGames"); - }); + b.ToTable("MultiplayerGames"); + }); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerBossState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerBossState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("AutoTracked") - .HasColumnType("INTEGER"); + b.Property("AutoTracked") + .HasColumnType("INTEGER"); - b.Property("BossName") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("BossName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.Property("Defeated") - .HasColumnType("INTEGER"); + b.Property("Defeated") + .HasColumnType("INTEGER"); - b.Property("TrackerStateId") - .HasColumnType("INTEGER"); + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.Property("Type") - .HasColumnType("INTEGER"); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - b.Property("WorldId") - .HasColumnType("INTEGER"); + b.Property("Type") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.Property("WorldId") + .HasColumnType("INTEGER"); - b.HasIndex("TrackerStateId"); + b.HasKey("Id"); - b.ToTable("TrackerBossStates"); - }); + b.HasIndex("TrackerStateId"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerDungeonState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.ToTable("TrackerBossStates"); + }); - b.Property("AutoTracked") - .HasColumnType("INTEGER"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerDungeonState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("Cleared") - .HasColumnType("INTEGER"); + b.Property("AutoTracked") + .HasColumnType("INTEGER"); - b.Property("HasManuallyClearedTreasure") - .HasColumnType("INTEGER"); + b.Property("Cleared") + .HasColumnType("INTEGER"); - b.Property("MarkedMedallion") - .HasColumnType("INTEGER"); + b.Property("HasManuallyClearedTreasure") + .HasColumnType("INTEGER"); - b.Property("MarkedReward") - .HasColumnType("INTEGER"); + b.Property("MarkedMedallion") + .HasColumnType("INTEGER"); - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("MarkedReward") + .HasColumnType("INTEGER"); - b.Property("RemainingTreasure") - .HasColumnType("INTEGER"); + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.Property("RequiredMedallion") - .HasColumnType("INTEGER"); + b.Property("RemainingTreasure") + .HasColumnType("INTEGER"); - b.Property("Reward") - .HasColumnType("INTEGER"); + b.Property("RequiredMedallion") + .HasColumnType("INTEGER"); - b.Property("TrackerStateId") - .HasColumnType("INTEGER"); + b.Property("Reward") + .HasColumnType("INTEGER"); - b.Property("WorldId") - .HasColumnType("INTEGER"); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.Property("WorldId") + .HasColumnType("INTEGER"); - b.HasIndex("TrackerStateId"); + b.HasKey("Id"); - b.ToTable("TrackerDungeonStates"); - }); + b.HasIndex("TrackerStateId"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerHintState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.ToTable("TrackerDungeonStates"); + }); - b.Property("HintState") - .HasColumnType("INTEGER"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerHintState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("HintTileCode") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("HintState") + .HasColumnType("INTEGER"); - b.Property("LocationKey") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("HintTileCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.Property("LocationString") - .HasColumnType("TEXT"); + b.Property("LocationKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.Property("LocationWorldId") - .HasColumnType("INTEGER"); + b.Property("LocationString") + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.Property("MedallionType") - .HasColumnType("INTEGER"); + b.Property("LocationWorldId") + .HasColumnType("INTEGER"); - b.Property("TrackerStateId") - .HasColumnType("INTEGER"); + b.Property("MedallionType") + .HasColumnType("INTEGER"); - b.Property("Type") - .HasColumnType("INTEGER"); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - b.Property("Usefulness") - .HasColumnType("INTEGER"); + b.Property("Type") + .HasColumnType("INTEGER"); - b.Property("WorldId") - .HasColumnType("INTEGER"); + b.Property("Usefulness") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.Property("WorldId") + .HasColumnType("INTEGER"); - b.HasIndex("TrackerStateId"); + b.HasKey("Id"); - b.ToTable("TrackerHintStates"); - }); + b.HasIndex("TrackerStateId"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerHistoryEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.ToTable("TrackerHintStates"); + }); - b.Property("IsImportant") - .HasColumnType("INTEGER"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerHistoryEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("IsUndone") - .HasColumnType("INTEGER"); + b.Property("IsImportant") + .HasColumnType("INTEGER"); - b.Property("LocationId") - .HasColumnType("INTEGER"); + b.Property("IsUndone") + .HasColumnType("INTEGER"); - b.Property("LocationName") - .HasColumnType("TEXT"); + b.Property("LocationId") + .HasColumnType("INTEGER"); - b.Property("ObjectName") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("LocationName") + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.Property("Time") - .HasColumnType("REAL"); + b.Property("ObjectName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.Property("TrackerStateId") - .HasColumnType("INTEGER"); + b.Property("Time") + .HasColumnType("REAL"); - b.Property("Type") - .HasColumnType("INTEGER"); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.Property("Type") + .HasColumnType("INTEGER"); - b.HasIndex("TrackerStateId"); + b.HasKey("Id"); - b.ToTable("TrackerHistoryEvents"); - }); + b.HasIndex("TrackerStateId"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerItemState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.ToTable("TrackerHistoryEvents"); + }); - b.Property("ItemName") - .IsRequired() - .HasColumnType("TEXT"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerItemState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("TrackerStateId") - .HasColumnType("INTEGER"); + b.Property("ItemName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.Property("TrackingState") - .HasColumnType("INTEGER"); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - b.Property("Type") - .HasColumnType("INTEGER"); + b.Property("TrackingState") + .HasColumnType("INTEGER"); - b.Property("WorldId") - .HasColumnType("INTEGER"); + b.Property("Type") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.Property("WorldId") + .HasColumnType("INTEGER"); - b.HasIndex("TrackerStateId"); + b.HasKey("Id"); - b.ToTable("TrackerItemStates"); - }); + b.HasIndex("TrackerStateId"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerLocationState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.ToTable("TrackerItemStates"); + }); - b.Property("Autotracked") - .HasColumnType("INTEGER"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerLocationState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("Cleared") - .HasColumnType("INTEGER"); + b.Property("Autotracked") + .HasColumnType("INTEGER"); - b.Property("Item") - .HasColumnType("INTEGER"); + b.Property("Cleared") + .HasColumnType("INTEGER"); - b.Property("ItemWorldId") - .HasColumnType("INTEGER"); + b.Property("Item") + .HasColumnType("INTEGER"); - b.Property("LocationId") - .HasColumnType("INTEGER"); + b.Property("ItemWorldId") + .HasColumnType("INTEGER"); - b.Property("MarkedItem") - .HasColumnType("INTEGER"); + b.Property("LocationId") + .HasColumnType("INTEGER"); - b.Property("TrackerStateId") - .HasColumnType("INTEGER"); + b.Property("MarkedItem") + .HasColumnType("INTEGER"); - b.Property("WorldId") - .HasColumnType("INTEGER"); + b.Property("MarkedUsefulness") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - b.HasIndex("TrackerStateId"); + b.Property("WorldId") + .HasColumnType("INTEGER"); - b.ToTable("TrackerLocationStates"); - }); + b.HasKey("Id"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerMarkedLocation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.HasIndex("TrackerStateId"); - b.Property("ItemName") - .IsRequired() - .HasColumnType("TEXT"); + b.ToTable("TrackerLocationStates"); + }); - b.Property("LocationId") - .HasColumnType("INTEGER"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerMarkedLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("TrackerStateId") - .HasColumnType("INTEGER"); + b.Property("ItemName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.HasKey("Id"); + b.Property("LocationId") + .HasColumnType("INTEGER"); - b.HasIndex("TrackerStateId"); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - b.ToTable("TrackerMarkedLocations"); - }); + b.HasKey("Id"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerRegionState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.HasIndex("TrackerStateId"); - b.Property("Medallion") - .HasColumnType("INTEGER"); + b.ToTable("TrackerMarkedLocations"); + }); - b.Property("Reward") - .HasColumnType("INTEGER"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerPrerequisiteState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("TrackerStateId") - .HasColumnType("INTEGER"); + b.Property("AutoTracked") + .HasColumnType("INTEGER"); - b.Property("TypeName") - .IsRequired() - .HasColumnType("TEXT"); + b.Property("MarkedItem") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.HasIndex("TrackerStateId"); + b.Property("RequiredItem") + .HasColumnType("INTEGER"); - b.ToTable("TrackerRegionStates"); - }); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + b.Property("WorldId") + .HasColumnType("INTEGER"); - b.Property("LocalWorldId") - .HasColumnType("INTEGER"); + b.HasKey("Id"); - b.Property("PercentageCleared") - .HasColumnType("INTEGER"); + b.HasIndex("TrackerStateId"); - b.Property("SecondsElapsed") - .HasColumnType("REAL"); + b.ToTable("TrackerPrerequisiteStates"); + }); - b.Property("StartDateTime") - .HasColumnType("TEXT"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerRegionState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Property("UpdatedDateTime") - .HasColumnType("TEXT"); + b.Property("Medallion") + .HasColumnType("INTEGER"); - b.HasKey("Id"); + b.Property("Reward") + .HasColumnType("INTEGER"); - b.ToTable("TrackerStates"); - }); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Models.GeneratedRom", b => - { - b.HasOne("Randomizer.Shared.Models.MultiplayerGameDetails", "MultiplayerGameDetails") - .WithMany() - .HasForeignKey("MultiplayerGameDetailsId"); + b.Property("TypeName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.HasOne("Randomizer.Shared.Models.TrackerState", "TrackerState") - .WithMany() - .HasForeignKey("TrackerStateId"); + b.HasKey("Id"); - b.Navigation("MultiplayerGameDetails"); + b.HasIndex("TrackerStateId"); - b.Navigation("TrackerState"); - }); + b.ToTable("TrackerRegionStates"); + }); - modelBuilder.Entity("Randomizer.Shared.Models.MultiplayerGameDetails", b => - { - b.HasOne("Randomizer.Shared.Models.GeneratedRom", "GeneratedRom") - .WithOne() - .HasForeignKey("Randomizer.Shared.Models.MultiplayerGameDetails", "GeneratedRomId"); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerRewardState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.Navigation("GeneratedRom"); - }); + b.Property("AutoTracked") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerBossState", b => - { - b.HasOne("Randomizer.Shared.Models.TrackerState", "TrackerState") - .WithMany("BossStates") - .HasForeignKey("TrackerStateId") - .OnDelete(DeleteBehavior.Cascade); + b.Property("HasReceivedReward") + .HasColumnType("INTEGER"); - b.Navigation("TrackerState"); - }); + b.Property("MarkedReward") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerDungeonState", b => - { - b.HasOne("Randomizer.Shared.Models.TrackerState", "TrackerState") - .WithMany("DungeonStates") - .HasForeignKey("TrackerStateId") - .OnDelete(DeleteBehavior.Cascade); + b.Property("RegionName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); - b.Navigation("TrackerState"); - }); + b.Property("RewardType") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerHintState", b => - { - b.HasOne("Randomizer.Shared.Models.TrackerState", "TrackerState") - .WithMany("Hints") - .HasForeignKey("TrackerStateId") - .OnDelete(DeleteBehavior.Cascade); + b.Property("TrackerStateId") + .HasColumnType("INTEGER"); - b.Navigation("TrackerState"); - }); + b.Property("WorldId") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerHistoryEvent", b => - { - b.HasOne("Randomizer.Shared.Models.TrackerState", "TrackerState") - .WithMany("History") - .HasForeignKey("TrackerStateId") - .OnDelete(DeleteBehavior.Cascade); + b.HasKey("Id"); - b.Navigation("TrackerState"); - }); + b.HasIndex("TrackerStateId"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerItemState", b => - { - b.HasOne("Randomizer.Shared.Models.TrackerState", "TrackerState") - .WithMany("ItemStates") - .HasForeignKey("TrackerStateId") - .OnDelete(DeleteBehavior.Cascade); + b.ToTable("TrackerRewardStates"); + }); - b.Navigation("TrackerState"); - }); + modelBuilder.Entity("TrackerCouncil.Smz3.Shared.Models.TrackerState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerLocationState", b => - { - b.HasOne("Randomizer.Shared.Models.TrackerState", "TrackerState") - .WithMany("LocationStates") - .HasForeignKey("TrackerStateId") - .OnDelete(DeleteBehavior.Cascade); + b.Property("LocalWorldId") + .HasColumnType("INTEGER"); - b.Navigation("TrackerState"); - }); + b.Property("PercentageCleared") + .HasColumnType("INTEGER"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerMarkedLocation", b => - { - b.HasOne("Randomizer.Shared.Models.TrackerState", "TrackerState") - .WithMany("MarkedLocations") - .HasForeignKey("TrackerStateId") - .OnDelete(DeleteBehavior.Cascade); + b.Property("SecondsElapsed") + .HasColumnType("REAL"); - b.Navigation("TrackerState"); - }); + b.Property("StartDateTime") + .HasColumnType("TEXT"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerRegionState", b => - { - b.HasOne("Randomizer.Shared.Models.TrackerState", "TrackerState") - .WithMany("RegionStates") - .HasForeignKey("TrackerStateId") - .OnDelete(DeleteBehavior.Cascade); + b.Property("UpdatedDateTime") + .HasColumnType("TEXT"); - b.Navigation("TrackerState"); - }); + b.HasKey("Id"); - modelBuilder.Entity("Randomizer.Shared.Models.TrackerState", b => - { - b.Navigation("BossStates"); + 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("DungeonStates"); + b.Navigation("ItemStates"); - b.Navigation("Hints"); + b.Navigation("LocationStates"); - b.Navigation("History"); + b.Navigation("MarkedLocations"); - b.Navigation("ItemStates"); + b.Navigation("PrerequisiteStates"); - b.Navigation("LocationStates"); + b.Navigation("RegionStates"); - b.Navigation("MarkedLocations"); + b.Navigation("RewardStates"); - b.Navigation("RegionStates"); - }); + b.Navigation("TreasureStates"); + }); #pragma warning restore 612, 618 + } } } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/PlayerHintTile.cs b/src/TrackerCouncil.Smz3.Shared/Models/PlayerHintTile.cs index 68c2fefef..ad89e1d4d 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/PlayerHintTile.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/PlayerHintTile.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using TrackerCouncil.Smz3.Shared.Enums; @@ -19,10 +20,23 @@ public class PlayerHintTile public string HintTileCode { get; set; } = ""; public TrackerHintState? State { get; set; } + public HintState HintState + { + get => State?.HintState ?? HintState.Default; + set + { + if (State == null || State.HintState == value) return; + State.HintState = value; + HintStateUpdated?.Invoke(this, EventArgs.Empty); + } + } + public PlayerHintTile GetHintTile(string code) { var clone = MemberwiseClone() as PlayerHintTile; clone!.HintTileCode = code; return clone; } + + public event EventHandler? HintStateUpdated; } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/RandomizerContext.cs b/src/TrackerCouncil.Smz3.Shared/Models/RandomizerContext.cs index 6ba954590..ff9a66938 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/RandomizerContext.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/RandomizerContext.cs @@ -30,12 +30,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasKey(x => x.Id); modelBuilder.Entity().HasMany(x => x.ItemStates).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(x => x.LocationStates).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity().HasMany(x => x.RegionStates).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity().HasMany(x => x.DungeonStates).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity().HasMany(x => x.MarkedLocations).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(x => x.BossStates).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(x => x.TreasureStates).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(x => x.RewardStates).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(x => x.PrerequisiteStates).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(x => x.History).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasMany(x => x.Hints).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); + +#pragma warning disable CS0618 // Type or member is obsolete + modelBuilder.Entity().HasMany(x => x.RegionStates).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(x => x.DungeonStates).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasMany(x => x.MarkedLocations).WithOne(x => x.TrackerState!).OnDelete(DeleteBehavior.Cascade); +#pragma warning restore CS0618 // Type or member is obsolete + modelBuilder.Entity().HasOne(x => x.GeneratedRom); base.OnModelCreating(modelBuilder); @@ -46,12 +53,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public DbSet TrackerStates { get; set; } public DbSet TrackerItemStates { get; set; } public DbSet TrackerLocationStates { get; set; } - public DbSet TrackerRegionStates { get; set; } - public DbSet TrackerDungeonStates { get; set; } - public DbSet TrackerMarkedLocations { get; set; } public DbSet TrackerBossStates { get; set; } + public DbSet TrackerTreasureStates { get; set; } + public DbSet TrackerRewardStates { get; set; } + public DbSet TrackerPrerequisiteStates { get; set; } public DbSet TrackerHistoryEvents { get; set; } public DbSet TrackerHintStates { get; set; } + public DbSet TrackerRegionStates { get; set; } + public DbSet TrackerDungeonStates { get; set; } + public DbSet TrackerMarkedLocations { get; set; } public DbSet MultiplayerGames { get; set; } #if DEBUG diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerBossState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerBossState.cs index 701486e05..b87b1ac9e 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/TrackerBossState.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerBossState.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using TrackerCouncil.Smz3.Shared.Enums; @@ -10,9 +11,12 @@ public class TrackerBossState [Key] public long Id { get; set; } public TrackerState? TrackerState { get; init; } + [MaxLength(50)] + public string RegionName { get; set; } = string.Empty; + [MaxLength(50)] public string BossName { get; init; } = string.Empty; public bool Defeated { get; set; } public bool AutoTracked { get; set; } - public BossType Type { get; init; } + public BossType Type { get; set; } public int WorldId { get; set; } } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerDungeonState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerDungeonState.cs index 501b41c15..eb1487428 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/TrackerDungeonState.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerDungeonState.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using TrackerCouncil.Smz3.Shared.Enums; @@ -9,19 +10,38 @@ public class TrackerDungeonState [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Key] public long Id { get; set; } - public TrackerState? TrackerState { get; set; } + public int WorldId { get; set; } + + [MaxLength(50)] public string Name { get; set; } = string.Empty; public bool Cleared { get; set; } public bool AutoTracked { get; set; } + + [Obsolete("Use TrackerTreasureState/IHasTreasure Instead")] public int RemainingTreasure { get; set; } + + [Obsolete("Use TrackerTreasureState/IHasTreasure Instead")] + public bool HasManuallyClearedTreasure { get; set; } + + [Obsolete("Use TrackerRewardState/IHasReward Instead")] public RewardType? Reward { get; set; } - public ItemType? RequiredMedallion { get; set; } + + [Obsolete("Use TrackerRewardState/IHasReward Instead")] public RewardType? MarkedReward { get; set; } - public ItemType? MarkedMedallion { get; set; } - public bool HasManuallyClearedTreasure { get; set; } - public int WorldId { get; set; } + + [Obsolete("Use TrackerRewardState/IHasReward Instead")] public bool HasReward => Reward != null && Reward != RewardType.None; + + [Obsolete("Use TrackerRewardState/IHasReward Instead")] public bool HasMarkedReward => MarkedReward != null && MarkedReward != RewardType.None; + + [Obsolete("Use TrackerPrerequisiteState/IHasPrerequisite Instead")] + public ItemType? RequiredMedallion { get; set; } + + [Obsolete("Use TrackerPrerequisiteState/IHasPrerequisite Instead")] + public ItemType? MarkedMedallion { get; set; } + + [Obsolete("Use TrackerPrerequisiteState/IHasPrerequisite Instead")] public bool RequiresMedallion => RequiredMedallion != null && RequiredMedallion.Value.IsInCategory(ItemCategory.Medallion); } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerHintState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerHintState.cs index d0a16a473..7dfba3e50 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/TrackerHintState.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerHintState.cs @@ -12,11 +12,14 @@ public class TrackerHintState public TrackerState? TrackerState { get; set; } public HintTileType Type { get; set; } public int WorldId { get; set; } + [MaxLength(50)] public string LocationKey { get; set; } = string.Empty; public int? LocationWorldId { get; set; } + [MaxLength(50)] public string? LocationString { get; set; } public LocationUsefulness? Usefulness { get; set; } public ItemType? MedallionType { get; set; } + [MaxLength(50)] public string HintTileCode { get; set; } = string.Empty; public HintState HintState { get; set; } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerHistoryEvent.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerHistoryEvent.cs index 763bc7f55..aaeaff25c 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/TrackerHistoryEvent.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerHistoryEvent.cs @@ -12,7 +12,9 @@ public class TrackerHistoryEvent public TrackerState? TrackerState { get; set; } public HistoryEventType Type { get; set; } public LocationId? LocationId { get; set; } + [MaxLength(50)] public string? LocationName { get; set; } + [MaxLength(50)] public string ObjectName { get; set; } = string.Empty; public bool IsImportant { get; set; } public bool IsUndone { get; set; } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerItemState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerItemState.cs index 2696d6c23..cacd27a48 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/TrackerItemState.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerItemState.cs @@ -9,9 +9,10 @@ public class TrackerItemState [DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Key] public long Id { get; set; } - public TrackerState? TrackerState { get; set; } - public ItemType? Type { get; set; } - public string ItemName { get; set; } = string.Empty; + public TrackerState? TrackerState { get; init; } + public ItemType? Type { get; init; } + [MaxLength(50)] + public string ItemName { get; init; } = string.Empty; public int TrackingState { get; set; } - public int WorldId { get; set; } + public int WorldId { get; init; } } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerLocationState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerLocationState.cs index 555fce77e..5f1f0e18a 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/TrackerLocationState.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerLocationState.cs @@ -13,9 +13,11 @@ public class TrackerLocationState public LocationId LocationId { get; init; } public ItemType Item { get; init; } public ItemType? MarkedItem { get; set; } + public LocationUsefulness? MarkedUsefulness { get; set; } public bool Cleared { get; set; } public bool Autotracked { get; set; } public int WorldId { get; init; } public int ItemWorldId { get; init; } public bool HasMarkedItem => MarkedItem != null && MarkedItem != ItemType.Nothing; + public bool HasMarkedCorrectItem => Item.IsEquivalentTo(MarkedItem); } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerMarkedLocation.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerMarkedLocation.cs index d3710cd1c..c21be1c32 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/TrackerMarkedLocation.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerMarkedLocation.cs @@ -10,5 +10,6 @@ public class TrackerMarkedLocation public long Id { get; set; } public TrackerState? TrackerState { get; set; } public int LocationId { get; set; } + [MaxLength(50)] public string ItemName { get; set; } = string.Empty; } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerMedallionState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerMedallionState.cs new file mode 100644 index 000000000..6fd58519e --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerMedallionState.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Shared.Models; + +public class TrackerMedallionState +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + public long Id { get; set; } + public TrackerState? TrackerState { get; set; } + [MaxLength(50)] + public string RegionName { get; set; } = string.Empty; + public ItemType RequiredMedallion { get; set; } + public ItemType? MarkedMedallion { get; set; } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerPrerequisiteState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerPrerequisiteState.cs new file mode 100644 index 000000000..f857df7a6 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerPrerequisiteState.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Shared.Models; + +public class TrackerPrerequisiteState +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + public long Id { get; set; } + public TrackerState? TrackerState { get; set; } + [MaxLength(50)] + public string RegionName { get; set; } = string.Empty; + public int WorldId { get; set; } + public bool AutoTracked { get; set; } + public ItemType RequiredItem { get; set; } + public ItemType? MarkedItem { get; set; } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerRegionState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerRegionState.cs index e1452d5bd..1d87f0d15 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/TrackerRegionState.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerRegionState.cs @@ -10,7 +10,9 @@ public class TrackerRegionState [Key] public long Id { get; set; } public TrackerState? TrackerState { get; set; } + [MaxLength(50)] public string TypeName { get; set; } = string.Empty; public RewardType? Reward { get; set; } public ItemType? Medallion { get; set; } + } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerRewardState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerRewardState.cs new file mode 100644 index 000000000..6de6b0d52 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerRewardState.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Shared.Models; + +public class TrackerRewardState +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + public long Id { get; set; } + public TrackerState? TrackerState { get; set; } + public RewardType RewardType { get; set; } + public RewardType? MarkedReward { get; set; } + public bool HasReceivedReward { get; set; } + [MaxLength(50)] + public string RegionName { get; set; } = string.Empty; + public bool AutoTracked { get; set; } + public int WorldId { get; set; } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerState.cs index 533f21c54..bd6d4090d 100644 --- a/src/TrackerCouncil.Smz3.Shared/Models/TrackerState.cs +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerState.cs @@ -17,10 +17,19 @@ public class TrackerState public int LocalWorldId { get; set; } public ICollection ItemStates { get; set; } = new List(); public ICollection LocationStates { get; set; } = new List(); - public ICollection RegionStates { get; set; } = new List(); - public ICollection DungeonStates { get; set; } = new List(); - public ICollection MarkedLocations { get; set; } = new List(); + public ICollection RewardStates { get; set; } = new List(); + public ICollection PrerequisiteStates { get; set; } = new List(); + public ICollection TreasureStates { get; set; } = new List(); public ICollection BossStates { get; set; } = new List(); public ICollection History { get; set; } = new List(); public ICollection Hints { get; set; } = new List(); + + [Obsolete("Use TreasureStates, RewardStates, PrerequisiteStates, and BossStates")] + public ICollection DungeonStates { get; set; } = new List(); + + [Obsolete("Use TreasureStates, RewardStates, PrerequisiteStates, and BossStates")] + public ICollection RegionStates { get; set; } = new List(); + + [Obsolete("Use LocationStates")] + public ICollection MarkedLocations { get; set; } = new List(); } diff --git a/src/TrackerCouncil.Smz3.Shared/Models/TrackerTreasureState.cs b/src/TrackerCouncil.Smz3.Shared/Models/TrackerTreasureState.cs new file mode 100644 index 000000000..b34a7b27f --- /dev/null +++ b/src/TrackerCouncil.Smz3.Shared/Models/TrackerTreasureState.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TrackerCouncil.Smz3.Shared.Models; + +public class TrackerTreasureState +{ + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + public long Id { get; set; } + public TrackerState? TrackerState { get; init; } + [MaxLength(50)] + public string RegionName { get; init; } = string.Empty; + public int RemainingTreasure { get; set; } + public int TotalTreasure { get; init; } + public bool HasManuallyClearedTreasure { get; set; } + public int WorldId { get; init; } +} diff --git a/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerPlayerGenerationData.cs b/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerPlayerGenerationData.cs index 7524e2306..16b824538 100644 --- a/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerPlayerGenerationData.cs +++ b/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerPlayerGenerationData.cs @@ -10,28 +10,27 @@ namespace TrackerCouncil.Smz3.Shared.Multiplayer; -public class MultiplayerPlayerGenerationData +public class MultiplayerPlayerGenerationData( + string playerGuid, + int worldId, + List locations, + Dictionary bosses, + Dictionary rewards, + Dictionary prerequisites, + List hints) { private static readonly JsonSerializerOptions s_options = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; - public MultiplayerPlayerGenerationData(string playerGuid, int worldId, List locations, - List dungeons, List hints) - { - PlayerGuid = playerGuid; - WorldId = worldId; - Locations = locations; - Dungeons = dungeons; - Hints = hints; - } - - public string PlayerGuid { get; } - public int WorldId { get; } - public List Locations { get; } - public List Dungeons { get; } - public List Hints { get; } + public string PlayerGuid { get; } = playerGuid; + public int WorldId { get; } = worldId; + public List Locations { get; } = locations; + public Dictionary Bosses { get; } = bosses; + public Dictionary Rewards { get; } = rewards; + public Dictionary Prerequisites { get; } = prerequisites; + public List Hints { get; } = hints; /// /// Converts the generation data into a compressed string of the json @@ -84,30 +83,9 @@ public static string ToString(MultiplayerPlayerGenerationData data) } } -public class PlayerGenerationLocationData -{ - public PlayerGenerationLocationData(LocationId id, int itemWorldId, ItemType item) - { - Id = id; - ItemWorldId = itemWorldId; - Item = item; - } - - public LocationId Id { get; } - public int ItemWorldId { get; } - public ItemType Item { get; } -} - -public class PlayerGenerationDungeonData +public class PlayerGenerationLocationData(LocationId id, int itemWorldId, ItemType item) { - public PlayerGenerationDungeonData(string name, RewardType? reward, ItemType medallion) - { - Name = name; - Reward = reward; - Medallion = medallion; - } - - public string Name { get; } = ""; - public RewardType? Reward { get; } - public ItemType Medallion { get; } + public LocationId Id { get; } = id; + public int ItemWorldId { get; } = itemWorldId; + public ItemType Item { get; } = item; } diff --git a/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerPlayerState.cs b/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerPlayerState.cs index f81c4899a..f1fdd663a 100644 --- a/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerPlayerState.cs +++ b/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerPlayerState.cs @@ -30,15 +30,13 @@ public class MultiplayerPlayerState public ICollection? Locations { get; set; } public ICollection? Items { get; set; } public ICollection? Bosses { get; set; } - public ICollection? Dungeons { get; set; } + public string? AdditionalData { get; set; } [JsonIgnore] public string? GenerationData { get; set; } public MultiplayerLocationState? GetLocation(LocationId id) => Locations?.FirstOrDefault(x => x.LocationId == id); public MultiplayerItemState? GetItem(ItemType type) => Items?.FirstOrDefault(x => x.Item == type); public MultiplayerBossState? GetBoss(BossType type) => Bosses?.FirstOrDefault(x => x.Boss == type); - public MultiplayerDungeonState? GetDungeon(string name) => Dungeons?.FirstOrDefault(x => x.Dungeon == name); - /// /// Marks a location as accessed @@ -86,26 +84,10 @@ public class MultiplayerPlayerState return boss; } - /// - /// Marks a dungeon as completed - /// - /// - public MultiplayerDungeonState? TrackDungeon(string name) - { - var dungeon = GetDungeon(name); - if (dungeon != null) - { - dungeon.Tracked = true; - dungeon.TrackedTime = DateTimeOffset.Now; - } - return dungeon; - } - public PlayerWorldUpdates SyncPlayerWorld(MultiplayerWorldState world) { if (Locations == null) Locations = new List(); if (Items == null) Items = new List(); - if (Dungeons == null) Dungeons = new List(); if (Bosses == null) Bosses = new List(); var worldUpdate = new PlayerWorldUpdates(); @@ -165,34 +147,6 @@ public PlayerWorldUpdates SyncPlayerWorld(MultiplayerWorldState world) } } - // Sync dungeons - foreach (var playerData in world.Dungeons) - { - var dbData = Dungeons!.FirstOrDefault(x => x.Dungeon == playerData.Key); - if (dbData != null) - { - if (playerData.Value && !dbData.Tracked) - { - dbData.Tracked = playerData.Value; - dbData.TrackedTime = DateTimeOffset.Now; - worldUpdate.Dungeons.Add(dbData); - } - } - else - { - dbData = new MultiplayerDungeonState() - { - GameId = GameId, - PlayerId = Id, - Dungeon = playerData.Key, - Tracked = playerData.Value, - TrackedTime = playerData.Value ? DateTimeOffset.Now : null - }; - Dungeons!.Add(dbData); - worldUpdate.Dungeons.Add(dbData); - } - } - // Sync bosses foreach (var playerData in world.Bosses) { diff --git a/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerWorldState.cs b/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerWorldState.cs index 8e519e417..385c6cb03 100644 --- a/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerWorldState.cs +++ b/src/TrackerCouncil.Smz3.Shared/Multiplayer/MultiplayerWorldState.cs @@ -6,11 +6,9 @@ namespace TrackerCouncil.Smz3.Shared.Multiplayer; public class MultiplayerWorldState( Dictionary locations, Dictionary items, - Dictionary bosses, - Dictionary dungeons) + Dictionary bosses) { public Dictionary Locations { get; set; } = locations; public Dictionary Items { get; set; } = items; public Dictionary Bosses { get; set; } = bosses; - public Dictionary Dungeons { get; set; } = dungeons; } diff --git a/src/TrackerCouncil.Smz3.Shared/Multiplayer/PlayerWorldUpdates.cs b/src/TrackerCouncil.Smz3.Shared/Multiplayer/PlayerWorldUpdates.cs index 7596ba583..e91c01d03 100644 --- a/src/TrackerCouncil.Smz3.Shared/Multiplayer/PlayerWorldUpdates.cs +++ b/src/TrackerCouncil.Smz3.Shared/Multiplayer/PlayerWorldUpdates.cs @@ -6,8 +6,7 @@ public class PlayerWorldUpdates { public readonly List Locations = new(); public readonly List Items = new(); - public readonly List Dungeons = new(); public readonly List Bosses = new(); - public bool HasUpdates => Locations.Count > 0 || Items.Count > 0 || Dungeons.Count > 0 || Bosses.Count > 0; + public bool HasUpdates => Locations.Count > 0 || Items.Count > 0 || Bosses.Count > 0; } diff --git a/src/TrackerCouncil.Smz3.Shared/Multiplayer/TrackDungeonRequest.cs b/src/TrackerCouncil.Smz3.Shared/Multiplayer/TrackDungeonRequest.cs deleted file mode 100644 index 360c57f91..000000000 --- a/src/TrackerCouncil.Smz3.Shared/Multiplayer/TrackDungeonRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TrackerCouncil.Smz3.Shared.Multiplayer; - -public class TrackDungeonRequest(string playerGuid, string dungeonName) -{ - public string PlayerGuid { get; } = playerGuid; - public string DungeonName { get; } = dungeonName; -} diff --git a/src/TrackerCouncil.Smz3.Shared/Multiplayer/TrackDungeonResponse.cs b/src/TrackerCouncil.Smz3.Shared/Multiplayer/TrackDungeonResponse.cs deleted file mode 100644 index 039ed7967..000000000 --- a/src/TrackerCouncil.Smz3.Shared/Multiplayer/TrackDungeonResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TrackerCouncil.Smz3.Shared.Multiplayer; - -public class TrackDungeonResponse(string playerGuid, string dungeonName) -{ - public string PlayerGuid { get; init; } = playerGuid; - public string DungeonName { get; init; } = dungeonName; -} diff --git a/src/TrackerCouncil.Smz3.Tools/TrackerCouncil.Smz3.Tools.csproj b/src/TrackerCouncil.Smz3.Tools/TrackerCouncil.Smz3.Tools.csproj index feba22cc0..a01b45ddc 100644 --- a/src/TrackerCouncil.Smz3.Tools/TrackerCouncil.Smz3.Tools.csproj +++ b/src/TrackerCouncil.Smz3.Tools/TrackerCouncil.Smz3.Tools.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/AutoTrackerModule.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/AutoTrackerModule.cs index 352d74612..febcccf00 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/AutoTrackerModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/AutoTrackerModule.cs @@ -29,8 +29,8 @@ public abstract class AutoTrackerModule(TrackerBase tracker, ISnesConnectorServi protected void TrackLocation(Location location) { var item = location.Item; - location.State.Autotracked = true; - Tracker.TrackItem(item: item, trackedAs: null, confidence: null, tryClear: true, autoTracked: true, location: location); + location.Autotracked = true; + Tracker.ItemTracker.TrackItem(item: item, trackedAs: null, confidence: null, tryClear: true, autoTracked: true, location: location); Logger.LogInformation("Auto tracked {ItemName} from {LocationName}", location.Item.Name, location.Name); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/FinalBossCheck.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/FinalBossCheck.cs index e261f06f7..035348f07 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/FinalBossCheck.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/FinalBossCheck.cs @@ -7,6 +7,7 @@ using SnesConnectorLibrary.Responses; using SNI; using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Shared.Enums; namespace TrackerCouncil.Smz3.Tracking.AutoTracking.AutoTrackerModules; @@ -33,53 +34,34 @@ private void CheckBeatFinalBosses(SnesData data, SnesData? prevData) if (prevData == null) return; var didUpdate = false; - if (prevData.ReadUInt8(0x2) == 0 && data.ReadUInt8(0x2) > 0) + var hasNewlyDefeatedFinalBoss = (prevData.ReadUInt8(0x2) == 0 && data.ReadUInt8(0x2) > 0) || + (prevData.ReadUInt8(0x106) == 0 && data.ReadUInt8(0x106) > 0); + + if (!hasNewlyDefeatedFinalBoss) { - if (IsInZelda) - { - var gt = Tracker.World.GanonsTower; - if (gt.DungeonState.Cleared == false) - { - Logger.LogInformation("Auto tracked Ganon's Tower"); - Tracker.MarkDungeonAsCleared(gt, confidence: null, autoTracked: true); - didUpdate = true; - } - } - else if (IsInMetroid) - { - var motherBrain = Tracker.World.AllBosses.First(x => x.Name == "Mother Brain"); - if (motherBrain.State.Defeated != true) - { - Logger.LogInformation("Auto tracked Mother Brain"); - Tracker.MarkBossAsDefeated(motherBrain, admittedGuilt: true, confidence: null, autoTracked: true); - _ = CountHyperBeamShots(); - didUpdate = true; - } - } + return; } - if (prevData.ReadUInt8(0x106) == 0 && data.ReadUInt8(0x106) > 0) + if (IsInZelda) { - if (IsInZelda) + var ganon = Tracker.World.GetBossOfType(BossType.Ganon); + if (!ganon.Defeated) { - var gt = Tracker.World.GanonsTower; - if (gt.DungeonState.Cleared == false) - { - Logger.LogInformation("Auto tracked Ganon's Tower"); - Tracker.MarkDungeonAsCleared(gt, confidence: null, autoTracked: true); - didUpdate = true; - } + Logger.LogInformation("Auto tracked Ganon's Tower"); + Tracker.BossTracker.MarkBossAsDefeated(Tracker.World.GanonsTower, confidence: null, autoTracked: true); + didUpdate = true; } - else if (IsInMetroid) + } + else if (IsInMetroid) + { + var motherBrain = Tracker.World.GetBossOfType(BossType.MotherBrain); + if (!motherBrain.Defeated) { - var motherBrain = Tracker.World.AllBosses.First(x => x.Name == "Mother Brain"); - if (motherBrain.State.Defeated != true) - { - Logger.LogInformation("Auto tracked Mother Brain"); - Tracker.MarkBossAsDefeated(motherBrain, admittedGuilt: true, confidence: null, autoTracked: true); - _ = CountHyperBeamShots(); - didUpdate = true; - } + 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(); + didUpdate = true; } } @@ -87,7 +69,6 @@ private void CheckBeatFinalBosses(SnesData data, SnesData? prevData) { AutoTracker.HasDefeatedBothBosses = true; } - } private async Task CountHyperBeamShots() @@ -119,7 +100,7 @@ private async Task CountHyperBeamShots() } Logger.LogInformation("Counted {Count} Hyper Beam shot(s) for {Health} health", count, health); - Tracker.CountHyperBeamShots(count); + Tracker.Say(responses: Tracker.Responses.CountHyperBeamShots, tieredKey: count, args: [count]); } } catch (Exception e) diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/GameMonitor.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/GameMonitor.cs index 7087d3097..8227792f5 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/GameMonitor.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/GameMonitor.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Shared; using SnesConnectorLibrary; @@ -7,13 +8,16 @@ using SnesConnectorLibrary.Responses; using SNI; using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Tracking; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Tracking.Services; namespace TrackerCouncil.Smz3.Tracking.AutoTracking.AutoTrackerModules; -public class GameMonitor(TrackerBase tracker, ISnesConnectorService snesConnector, ILogger logger, IWorldService worldService) : AutoTrackerModule(tracker, snesConnector, logger) +public class GameMonitor(TrackerBase tracker, ISnesConnectorService snesConnector, ILogger logger, IWorldQueryService worldQueryService) : AutoTrackerModule(tracker, snesConnector, logger) { + private bool bIsCheckingGameStart; + public override void Initialize() { // Check if the game has started or not @@ -47,42 +51,112 @@ public override void Initialize() private void GameStart(SnesData data, SnesData? prevData) { var value = data.ReadUInt8(0); - if (value != 0 && prevData?.ReadUInt8(0) != 0 && !HasStartedGame) + if (value != 0 && prevData?.ReadUInt8(0) != 0 && !HasStartedGame && !bIsCheckingGameStart) + { + bIsCheckingGameStart = true; + _ = CheckValidGameStart(); + } + } + + private async Task CheckValidGameStart() + { + var response = await SnesConnector.MakeMemoryRequestAsync(new SnesSingleMemoryRequest() + { + MemoryRequestType = SnesMemoryRequestType.RetrieveMemory, + SnesMemoryDomain = SnesMemoryDomain.CartridgeSave, + AddressFormat = AddressFormat.Snes9x, + SniMemoryMapping = MemoryMapping.ExHiRom, + Address = 0xA173FE, + Length = 2 + }); + + if (!response.Successful) + { + bIsCheckingGameStart = false; + return; + } + + var game = GetGame(response.Data); + + if (game is Game.Neither or Game.Credits) + { + bIsCheckingGameStart = false; + return; + } + + if (game == Game.Zelda) + { + response = await SnesConnector.MakeMemoryRequestAsync(new SnesSingleMemoryRequest() + { + MemoryRequestType = SnesMemoryRequestType.RetrieveMemory, + SnesMemoryDomain = SnesMemoryDomain.ConsoleRAM, + AddressFormat = AddressFormat.Snes9x, + SniMemoryMapping = MemoryMapping.ExHiRom, + Address = 0x7e0000, + Length = 0x250 + }); + + if (!response.Successful) + { + bIsCheckingGameStart = false; + return; + } + + var zeldaState = new AutoTrackerZeldaState(response.Data); + if (zeldaState.IsValid) + { + MarkAsStated(); + } + bIsCheckingGameStart = false; + } + else if (game == Game.SM) { - Logger.LogInformation("Game started"); - AutoTracker.HasStarted = true; + response = await SnesConnector.MakeMemoryRequestAsync(new SnesSingleMemoryRequest() + { + MemoryRequestType = SnesMemoryRequestType.RetrieveMemory, + SnesMemoryDomain = SnesMemoryDomain.ConsoleRAM, + AddressFormat = AddressFormat.Snes9x, + SniMemoryMapping = MemoryMapping.ExHiRom, + Address = 0x7e0750, + Length = 0x400, + }); - if (Tracker.World.Config.MultiWorld && worldService.Worlds.Count > 1) + if (!response.Successful) { - var worldCount = worldService.Worlds.Count; - var otherPlayerName = worldService.Worlds.Where(x => x != worldService.World).Random(new Random())!.Config.PhoneticName; - Tracker.Say(x => x.AutoTracker.GameStartedMultiplayer, args: [worldCount, otherPlayerName]); + bIsCheckingGameStart = false; + return; } - else + + var metroidState = new AutoTrackerMetroidState(response.Data); + if (metroidState.IsValid) { - Tracker.Say(x => x.AutoTracker.GameStarted, args: [Tracker.Rom?.Seed]); + MarkAsStated(); } + bIsCheckingGameStart = false; } } - private void CheckGame(SnesData data, SnesData? prevData) + private void MarkAsStated() { - var game = Game.Neither; - var value = data.ReadUInt8(0); - if (value == 0x00) - { - game = Game.Zelda; - AutoTracker.UpdateValidState(true); - } - else if (value == 0xFF) + Logger.LogInformation("Game started"); + + AutoTracker.HasStarted = true; + + if (Tracker.World.Config.MultiWorld && worldQueryService.Worlds.Count > 1) { - game = Game.SM; + var worldCount = worldQueryService.Worlds.Count; + var otherPlayerName = worldQueryService.Worlds.Where(x => x != worldQueryService.World).Random(new Random())!.Config.PhoneticName; + Tracker.Say(x => x.AutoTracker.GameStartedMultiplayer, args: [worldCount, otherPlayerName]); } - else if (value == 0x11) + else { - game = Game.Credits; - Tracker.UpdateTrackNumber(99); + Tracker.Say(x => x.AutoTracker.GameStarted, args: [Tracker.Rom?.Seed]); } + } + + private void CheckGame(SnesData data, SnesData? prevData) + { + var game = GetGame(data); if (game != AutoTracker.CurrentGame) { @@ -90,4 +164,15 @@ private void CheckGame(SnesData data, SnesData? prevData) Logger.LogInformation("Game changed to: {CurrentGame}", game); } } + + private Game GetGame(SnesData data) + { + return data.ReadUInt8(0) switch + { + 0x00 => Game.Zelda, + 0xFF => Game.SM, + 0x11 => Game.Credits, + _ => Game.Neither + }; + } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MetroidBosses.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MetroidBosses.cs index 2c17afeee..b67cf18e5 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MetroidBosses.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MetroidBosses.cs @@ -37,8 +37,7 @@ private void CheckMetroidBosses(SnesData data, SnesData? prevData) { if (data.CheckUInt8Flag(boss.Metadata.MemoryAddress ?? 0, boss.Metadata.MemoryFlag ?? 100)) { - boss.State.AutoTracked = true; - Tracker.MarkBossAsDefeated(boss, true, null, true); + Tracker.BossTracker.MarkBossAsDefeated(boss, true, null, true); Logger.LogInformation("Auto tracked {BossName} as defeated", boss.Name); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MetroidLocations.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MetroidLocations.cs index 5ed062303..8eb079c84 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MetroidLocations.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MetroidLocations.cs @@ -12,13 +12,13 @@ namespace TrackerCouncil.Smz3.Tracking.AutoTracking.AutoTrackerModules; -public class MetroidLocations(TrackerBase tracker, ISnesConnectorService snesConnector, ILogger logger, IWorldService worldService) : AutoTrackerModule(tracker, snesConnector, logger) +public class MetroidLocations(TrackerBase tracker, ISnesConnectorService snesConnector, ILogger logger, IWorldQueryService worldQueryService) : AutoTrackerModule(tracker, snesConnector, logger) { private List _locations = new(); public override void Initialize() { - _locations = worldService.AllLocations().Where(x => (int)x.Id < 256).ToList(); + _locations = worldQueryService.AllLocations().Where(x => (int)x.Id < 256).ToList(); SnesConnector.AddRecurringMemoryRequest(new SnesRecurringMemoryRequest() { @@ -49,7 +49,7 @@ private void CheckLocations(SnesData data, SnesData? prevData) var flag = location.MemoryFlag ?? 0; var currentCleared = data.CheckUInt8Flag(loc, flag); var prevCleared = prevData.CheckUInt8Flag(loc, flag); - if (location.State.Autotracked == false && currentCleared && prevCleared) + if (location.Autotracked == false && currentCleared && prevCleared) { TrackLocation(location); } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MultiplayerSync.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MultiplayerSync.cs index 16594017c..a8787ce6e 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MultiplayerSync.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/MultiplayerSync.cs @@ -17,7 +17,7 @@ public class MultiplayerSync( ISnesConnectorService snesConnector, ILogger logger, IGameService gameService, - IWorldService worldService) : AutoTrackerModule(tracker, snesConnector, logger) + IWorldQueryService worldQueryService) : AutoTrackerModule(tracker, snesConnector, logger) { public override void Initialize() { @@ -75,9 +75,9 @@ private async void OnMemorySync(SnesData firstDataSet, SnesData? firstPrevData) previouslyGiftedItems.Add((item, playerId)); } - var otherCollectedItems = worldService.Worlds.SelectMany(x => x.Locations) + var otherCollectedItems = worldQueryService.Worlds.SelectMany(x => x.Locations) .Where(x => x.State.ItemWorldId == Tracker.World.Id && x.State.WorldId != Tracker.World.Id && - x.State.Autotracked && (!x.World.HasCompleted || !x.Item.Type.IsInCategory(ItemCategory.IgnoreOnMultiplayerCompletion))) + x.Autotracked && (!x.World.HasCompleted || !x.Item.Type.IsInCategory(ItemCategory.IgnoreOnMultiplayerCompletion))) .Select(x => (x.State.Item, x.State.WorldId)).ToList(); foreach (var item in previouslyGiftedItems) diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/PlayerEnteredShip.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/PlayerEnteredShip.cs index a3f276dcf..596086406 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/PlayerEnteredShip.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/PlayerEnteredShip.cs @@ -32,7 +32,7 @@ private void CheckShip(SnesData data, SnesData? prevData) } if (data.ReadUInt16(0) == 0xAA4F) { - Tracker.GameBeaten(true); + Tracker.GameStateTracker.GameBeaten(true); } } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaLocations.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaLocations.cs index 5c0c02280..96dff613c 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaLocations.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaLocations.cs @@ -15,14 +15,14 @@ namespace TrackerCouncil.Smz3.Tracking.AutoTracking.AutoTrackerModules; -public class ZeldaLocations(TrackerBase tracker, ISnesConnectorService snesConnector, ILogger logger, IWorldService worldService) +public class ZeldaLocations(TrackerBase tracker, ISnesConnectorService snesConnector, ILogger logger, IWorldQueryService worldQueryService) : AutoTrackerModule(tracker, snesConnector, logger) { private List _locations = new(); public override void Initialize() { - _locations = worldService.AllLocations().Where(x => + _locations = worldQueryService.AllLocations().Where(x => x.MemoryType == LocationMemoryType.Default && (int)x.Id >= 256).ToList(); SnesConnector.AddRecurringMemoryRequest(new SnesRecurringMemoryRequest() @@ -60,7 +60,7 @@ private void CheckLocations(SnesData data, SnesData prevData) var flag = location.MemoryFlag ?? 0; var currentCleared = data.CheckInt16Flag(loc * 2, flag); var prevCleared = prevData.CheckInt16Flag(loc * 2, flag); - if (location.State.Autotracked == false && currentCleared && prevCleared) + if (location.Autotracked == false && currentCleared && prevCleared) { // Increment GT guessing game number if (location.Region is GanonsTower gt && location != gt.BobsTorch) @@ -71,9 +71,9 @@ private void CheckLocations(SnesData data, SnesData prevData) TrackLocation(location); // Mark HC as cleared if this was Zelda's Cell - if (location.Id == LocationId.HyruleCastleZeldasCell && Tracker.World.HyruleCastle.DungeonState.Cleared == false) + if (location.Id == LocationId.HyruleCastleZeldasCell && !Tracker.World.HyruleCastle.Boss.Defeated) { - Tracker.MarkDungeonAsCleared(Tracker.World.HyruleCastle, autoTracked: true); + Tracker.BossTracker.MarkBossAsDefeated(Tracker.World.HyruleCastle, autoTracked: true); } } @@ -88,7 +88,7 @@ private void CheckLocations(SnesData data, SnesData prevData) private void CheckDungeons(SnesData data, SnesData prevData) { - foreach (var dungeon in Tracker.World.Dungeons) + foreach (var dungeon in Tracker.World.BossRegions.Where(x => x is Z3Region)) { var region = (Z3Region)dungeon; @@ -102,17 +102,17 @@ private void CheckDungeons(SnesData data, SnesData prevData) { var prevValue = prevData.CheckInt16Flag((int)(region.MemoryAddress * 2), region.MemoryFlag ?? 0); var currentValue = data.CheckInt16Flag((int)(region.MemoryAddress * 2), region.MemoryFlag ?? 0); - if (dungeon.DungeonState.AutoTracked == false && prevValue && currentValue) + if (dungeon.BossState.AutoTracked == false && prevValue && currentValue) { - dungeon.DungeonState.AutoTracked = true; - Tracker.MarkDungeonAsCleared(dungeon, autoTracked: true); - Logger.LogInformation("Auto tracked {DungeonName} as cleared", dungeon.DungeonName); + dungeon.BossState.AutoTracked = true; + Tracker.BossTracker.MarkBossAsDefeated(dungeon, autoTracked: true, admittedGuilt: true); + Logger.LogInformation("Auto tracked {DungeonName} as cleared", dungeon.Name); } } catch (Exception e) { - Logger.LogError(e, "Unable to auto track Dungeon: {DungeonName}", dungeon.DungeonName); + Logger.LogError(e, "Unable to auto track Dungeon: {DungeonName}", dungeon.Name); Tracker.Error(); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaMiscData.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaMiscData.cs index f75cd42b8..caa414976 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaMiscData.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaMiscData.cs @@ -13,13 +13,13 @@ namespace TrackerCouncil.Smz3.Tracking.AutoTracking.AutoTrackerModules; -public class ZeldaMiscData(TrackerBase tracker, ISnesConnectorService snesConnector, ILogger logger, IWorldService worldService, IItemService itemService) : AutoTrackerModule(tracker, snesConnector, logger) +public class ZeldaMiscData(TrackerBase tracker, ISnesConnectorService snesConnector, ILogger logger, IWorldQueryService worldQueryService) : AutoTrackerModule(tracker, snesConnector, logger) { private List _locations = new(); public override void Initialize() { - _locations = worldService.AllLocations().Where(x => + _locations = worldQueryService.AllLocations().Where(x => x.MemoryType == LocationMemoryType.ZeldaMisc && (int)x.Id >= 256).ToList(); SnesConnector.AddRecurringMemoryRequest(new SnesRecurringMemoryRequest() @@ -60,10 +60,10 @@ private void CheckZeldaMisc(SnesData data, SnesData? prevData) // Activated flute if (data.CheckUInt8Flag(0x10C, 0x01) && !prevData.CheckUInt8Flag(0x10C, 0x01)) { - var duckItem = itemService.FirstOrDefault("Duck"); - if (duckItem?.State.TrackingState == 0) + var duckItem = worldQueryService.FirstOrDefault("Duck"); + if (duckItem?.TrackingState == 0) { - Tracker.TrackItem(duckItem, null, null, false, true); + Tracker.ItemTracker.TrackItem(duckItem, null, null, false, true); } } @@ -71,9 +71,9 @@ private void CheckZeldaMisc(SnesData data, SnesData? prevData) if (data.ReadUInt8(0x145) >= 3) { var castleTower = Tracker.World.CastleTower; - if (castleTower.DungeonState.Cleared == false) + if (castleTower.Boss.State.Defeated) { - Tracker.MarkDungeonAsCleared(castleTower, null, autoTracked: true); + Tracker.BossTracker.MarkBossAsDefeated(castleTower, null, autoTracked: true); Logger.LogInformation("Auto tracked {Name} as cleared", castleTower.Name); } } @@ -89,7 +89,7 @@ private void CheckLocations(SnesData data, SnesData prevData) var flag = location.MemoryFlag ?? 0; var currentCleared = data.CheckUInt8Flag(loc, flag); var prevCleared = prevData.CheckUInt8Flag(loc, flag); - if (location.State.Autotracked == false && currentCleared && prevCleared) + if (location.Autotracked == false && currentCleared && prevCleared) { TrackLocation(location); } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaStateChecks.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaStateChecks.cs index 33294748d..03693cc35 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaStateChecks.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/AutoTrackerModules/ZeldaStateChecks.cs @@ -48,7 +48,7 @@ private void CheckZeldaState(SnesData data, SnesData? prevData) { if (AutoTracker.HasDefeatedBothBosses) { - Tracker.GameBeaten(true); + Tracker.GameStateTracker.GameBeaten(true); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/GameService.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/GameService.cs index f328b9199..b6a02d1bf 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/GameService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/GameService.cs @@ -26,7 +26,6 @@ public class GameService : TrackerModule, IGameService private ISnesConnectorService _snesConnectorService; private readonly ILogger _logger; private readonly int _trackerPlayerId; - private int _itemCounter; private readonly Dictionary _emulatorActions = new(); /// @@ -34,13 +33,13 @@ public class GameService : TrackerModule, IGameService /// class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// The logger to associate with this module /// The accesor to determine the tracker player id /// - public GameService(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger, IWorldAccessor worldAccessor, ISnesConnectorService snesConnectorService) - : base(tracker, itemService, worldService, logger) + public GameService(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, IWorldAccessor worldAccessor, ISnesConnectorService snesConnectorService) + : base(tracker, playerProgressionService, worldQueryService, logger) { TrackerBase.GameService = this; _logger = logger; @@ -106,7 +105,7 @@ public async Task TryGiveItemsAsync(List items, int fromPlayerId) return false; } - TrackerBase.TrackItems(items, true, true); + TrackerBase.ItemTracker.TrackItems(items, true, true); return await TryGiveItemTypesAsync(items.Select(x => (x.Type, fromPlayerId)).ToList()); } @@ -668,11 +667,9 @@ public void SyncItems(EmulatorAction action) previouslyGiftedItems.Add((item, playerId)); } - _itemCounter = previouslyGiftedItems.Count; - - var otherCollectedItems = WorldService.Worlds.SelectMany(x => x.Locations) + var otherCollectedItems = WorldQueryService.Worlds.SelectMany(x => x.Locations) .Where(x => x.State.ItemWorldId == TrackerBase.World.Id && x.State.WorldId != TrackerBase.World.Id && - x.State.Autotracked && (!x.World.HasCompleted || !x.Item.Type.IsInCategory(ItemCategory.IgnoreOnMultiplayerCompletion))) + x.Autotracked && (!x.World.HasCompleted || !x.Item.Type.IsInCategory(ItemCategory.IgnoreOnMultiplayerCompletion))) .Select(x => (x.State.Item, x.State.WorldId)).ToList(); foreach (var item in previouslyGiftedItems) diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/ChangedMetroidRegion.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/ChangedMetroidRegion.cs index 573273905..0b82a1c70 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/ChangedMetroidRegion.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/ChangedMetroidRegion.cs @@ -23,7 +23,7 @@ public class ChangedMetroidRegion : IMetroidStateCheck public bool ExecuteCheck(TrackerBase tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState) { if ((currentState.CurrentRegion == _previousMetroidRegionValue && - tracker.CurrentRegion is not Z3Region) || currentState.CurrentRegion == null) + tracker.GameStateTracker.CurrentRegion is not Z3Region) || currentState.CurrentRegion == null) { return false; } @@ -32,7 +32,7 @@ public bool ExecuteCheck(TrackerBase tracker, AutoTrackerMetroidState currentSta var newRegion = tracker.World.Regions.OfType().FirstOrDefault(x => x.MemoryRegionId == currentState.CurrentRegion); if (newRegion != null) { - tracker.UpdateRegion(newRegion, tracker.Options.AutoTrackerChangeMap, startedAtShip); + tracker.GameStateTracker.UpdateRegion(newRegion, tracker.Options.AutoTrackerChangeMap, startedAtShip); } _previousMetroidRegionValue = currentState.CurrentRegion.Value; return true; diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Crocomire.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Crocomire.cs index 3460b3e5c..6caffa589 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Crocomire.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Crocomire.cs @@ -8,7 +8,7 @@ namespace TrackerCouncil.Smz3.Tracking.AutoTracking.MetroidStateChecks; /// Metroid state check for nearing Crocomire /// Player is in the room above Crocomire and is near the door to Crocomire /// -public class Crocomire(IItemService itemService) : IMetroidStateCheck +public class Crocomire(IPlayerProgressionService playerProgressionService) : IMetroidStateCheck { /// @@ -20,7 +20,7 @@ public class Crocomire(IItemService itemService) : IMetroidStateCheck /// True if the check was identified, false otherwise public bool ExecuteCheck(TrackerBase tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState) { - if (currentState is { CurrentRegion: 2, CurrentRoomInRegion: 9, SamusX: >= 3000, SamusY: > 500 } && (!tracker.World.Config.MetroidKeysanity || itemService.IsTracked(ItemType.CardNorfairBoss))) + if (currentState is { CurrentRegion: 2, CurrentRoomInRegion: 9, SamusX: >= 3000, SamusY: > 500 } && playerProgressionService.GetProgression(false).Contains(ItemType.CardNorfairBoss)) { tracker.Say(x => x.AutoTracker.NearCrocomire, args: [currentState.SuperMissiles, currentState.MaxSuperMissiles], once: true); return true; diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/MetroidDeath.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/MetroidDeath.cs index 1608b879c..2d9838610 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/MetroidDeath.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/MetroidDeath.cs @@ -2,6 +2,7 @@ using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Tracking; using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Tracking.Services; namespace TrackerCouncil.Smz3.Tracking.AutoTracking.MetroidStateChecks; @@ -9,22 +10,8 @@ namespace TrackerCouncil.Smz3.Tracking.AutoTracking.MetroidStateChecks; /// Metroid state check for detecting deaths in Metroid /// Checks if the player has 0 health and 0 reserve tanks /// -public class MetroidDeath : IMetroidStateCheck +public class MetroidDeath(IWorldQueryService worldQueryService) : IMetroidStateCheck { - /// - /// Constructor - /// - /// - public MetroidDeath(IItemService itemService) - { - Items = itemService; - } - - /// - /// Service for retrieving item data - /// - protected IItemService Items { get; } - /// /// Executes the check for the current state /// @@ -55,12 +42,12 @@ public bool ExecuteCheck(TrackerBase tracker, AutoTrackerMetroidState currentSta tracker.Say(response: region.Metadata.WhenDiedInRoom[currentState.CurrentRoomInRegion.Value], once: true); } - tracker.TrackDeath(true); + tracker.GameStateTracker.TrackDeath(true); - var death = Items.FirstOrDefault("Death"); + var death = worldQueryService.FirstOrDefault("Death"); if (death is not null) { - tracker.TrackItem(death, autoTracked: true, silent: silent); + tracker.ItemTracker.TrackItem(death, autoTracked: true, silent: silent); return true; } return false; diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Mockball.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Mockball.cs index 589d84528..5e99ba173 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Mockball.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Mockball.cs @@ -10,11 +10,11 @@ namespace TrackerCouncil.Smz3.Tracking.AutoTracking.MetroidStateChecks; /// public class Mockball : IMetroidStateCheck { - private readonly IItemService _itemService; + private readonly IPlayerProgressionService _playerProgressionService; - public Mockball(IItemService itemService) + public Mockball(IPlayerProgressionService playerProgressionService) { - _itemService = itemService; + _playerProgressionService = playerProgressionService; } /// @@ -26,7 +26,7 @@ public Mockball(IItemService itemService) /// True if the check was identified, false otherwise public bool ExecuteCheck(TrackerBase tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState) { - if (_itemService.IsTracked(ItemType.SpeedBooster)) + if (_playerProgressionService.GetProgression(false).Contains(ItemType.CardNorfairBoss)) return false; // Brinstar Mockball diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Shaktool.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Shaktool.cs index 476abb7a3..a0e345b98 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Shaktool.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/Shaktool.cs @@ -21,20 +21,20 @@ public class Shaktool : IMetroidStateCheck public bool ExecuteCheck(TrackerBase tracker, AutoTrackerMetroidState currentState, AutoTrackerMetroidState prevState) { if (currentState is { CurrentRegion: 4, CurrentRoomInRegion: 36 } && prevState.CurrentRoomInRegion == 28 && - tracker.World.FindLocation(LocationId.InnerMaridiaSpringBall).State.Cleared != true && - tracker.World.AllBosses.FirstOrDefault(x => x.Name == "Shaktool")?.State.Defeated != true) + tracker.World.FindLocation(LocationId.InnerMaridiaSpringBall).Cleared != true && + tracker.World.AllBosses.FirstOrDefault(x => x.Name == "Shaktool")?.Defeated != true) { tracker.ShutUp(); tracker.Say(x => x.AutoTracker.NearShaktool, once: true); - tracker.StartShaktoolMode(); + tracker.ModeTracker.StartShaktoolMode(); return true; } - if (tracker.ShaktoolMode && + if (tracker.ModeTracker.ShaktoolMode && ((currentState is { CurrentRegion: 4, CurrentRoomInRegion: 28 } && prevState.CurrentRoomInRegion == 36) || currentState.CurrentRegion != 4)) { - tracker.StopShaktoolMode(); + tracker.ModeTracker.StopShaktoolMode(); return true; } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/VisibleItemMetroidCheck.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/VisibleItemMetroidCheck.cs index 2c7666759..d3cce40c3 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/VisibleItemMetroidCheck.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/MetroidStateChecks/VisibleItemMetroidCheck.cs @@ -12,11 +12,9 @@ public class VisibleItemMetroidCheck : IMetroidStateCheck { private readonly Dictionary _items; private readonly HashSet _trackedAreas = new(); - private readonly IWorldService _worldService; - public VisibleItemMetroidCheck(IWorldService worldService) + public VisibleItemMetroidCheck(IWorldQueryService worldQueryService) { - _worldService = worldService; var visibleItems = VisibleItems.GetVisibleItems().MetroidItems; _items = visibleItems .ToDictionary(s => s.Room, s => s); @@ -57,31 +55,30 @@ private bool CheckItems(VisibleItemMetroid details, AutoTrackerMetroidState curr { var locations = area.Locations.Select(x => tracker.World.FindLocation(x)).ToList(); - if (locations.All(x => x.State.Cleared || x.State.Autotracked || x.State.MarkedItem == x.State.Item)) + if (locations.All(x => x.Cleared || x.Autotracked || x.HasMarkedCorrectItem)) { _trackedAreas.Add(area); continue; } - toClearLocations.AddRange(locations.Where(x => x.State is { Cleared: false, Autotracked: false } && - !x.State.Item.IsEquivalentTo(x.State.MarkedItem))); + toClearLocations.AddRange(locations.Where(x => x is + { Cleared: false, Autotracked: false, HasMarkedCorrectItem: false })); } foreach (var location in toClearLocations) { - tracker.MarkLocation(location, location.Item.Type.GetGenericType()); + tracker.LocationTracker.MarkLocation(location, location.ItemType.GetGenericType()); } if (toClearLocations.Any()) { - tracker.UpdateLastMarkedLocations(toClearLocations); + tracker.GameStateTracker.UpdateLastMarkedLocations(toClearLocations); } // Remove room if all items have been collected or marked if (currentState.CurrentRoom != null && _items[currentState.CurrentRoom.Value].Areas .SelectMany(x => x.Locations.Select(l => tracker.World.FindLocation(l))) - .All(x => x.State.Cleared || x.State.Autotracked || - x.State.Item.IsEquivalentTo(x.State.MarkedItem))) + .All(x => x.Cleared || x.Autotracked || x.HasMarkedCorrectItem)) { _items.Remove(currentState.CurrentRoom.Value); } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ChangedOverworld.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ChangedOverworld.cs index 3520eb283..d98459d4b 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ChangedOverworld.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ChangedOverworld.cs @@ -32,7 +32,7 @@ public bool ExecuteCheck(TrackerBase trackerBase, AutoTrackerZeldaState currentS .FirstOrDefault(x => x.StartingRooms.Count != 0 && x.StartingRooms.Contains(currentState.OverworldScreen.Value) && x.IsOverworld); if (region == null) return false; - trackerBase.UpdateRegion(region, trackerBase.Options.AutoTrackerChangeMap); + trackerBase.GameStateTracker.UpdateRegion(region, trackerBase.Options.AutoTrackerChangeMap); return true; } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs index 829267ee0..c36789845 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/EnteredDungeon.cs @@ -5,6 +5,7 @@ using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; using TrackerCouncil.Smz3.SeedGenerator.Contracts; +using TrackerCouncil.Smz3.Shared.Enums; namespace TrackerCouncil.Smz3.Tracking.AutoTracking.ZeldaStateChecks; @@ -46,11 +47,14 @@ public bool ExecuteCheck(TrackerBase trackerBase, AutoTrackerZeldaState currentS .FirstOrDefault(x => x.StartingRooms.Count > 0 && x.StartingRooms.Contains(currentState.CurrentRoom.Value) && !x.IsOverworld); // Get the dungeon info for the room - if (region is not IDungeon dungeon) return false; + if (region is not IHasTreasure dungeon) return false; - if (!_worldAccessor.World.Config.ZeldaKeysanity && !_enteredDungeons.Contains(region) && dungeon.IsPendantDungeon) + var rewardRegion = region as IHasReward; + var bossRegion = region as IHasBoss; + + if (!_worldAccessor.World.Config.ZeldaKeysanity && !_enteredDungeons.Contains(region) && rewardRegion?.HasReward(RewardType.PendantBlue, RewardType.PendantGreen, RewardType.PendantRed) == true) { - trackerBase.Say(x => x.AutoTracker.EnterPendantDungeon, args: [dungeon.DungeonMetadata.Name, dungeon.DungeonReward?.Metadata.Name]); + trackerBase.Say(x => x.AutoTracker.EnterPendantDungeon, args: [region.Metadata.Name, rewardRegion.Reward.Metadata.Name]); } else if (!_worldAccessor.World.Config.ZeldaKeysanity && region is CastleTower) { @@ -58,8 +62,8 @@ public bool ExecuteCheck(TrackerBase trackerBase, AutoTrackerZeldaState currentS } else if (region is GanonsTower) { - var clearedCrystalDungeonCount = trackerBase.World.Dungeons - .Count(x => x.DungeonState.Cleared && x.IsCrystalDungeon); + var clearedCrystalDungeonCount = trackerBase.World.RewardRegions + .Count(x => x.RewardState.HasReceivedReward && x.RewardType is RewardType.CrystalBlue or RewardType.CrystalRed); if (clearedCrystalDungeonCount < trackerBase.World.Config.GanonsTowerCrystalCount) { @@ -67,7 +71,7 @@ public bool ExecuteCheck(TrackerBase trackerBase, AutoTrackerZeldaState currentS } } - trackerBase.UpdateRegion(region, trackerBase.Options.AutoTrackerChangeMap); + trackerBase.GameStateTracker.UpdateRegion(region, trackerBase.Options.AutoTrackerChangeMap); _enteredDungeons.Add(region); return true; diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/PegWorld.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/PegWorld.cs index f40744f3f..1a224dded 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/PegWorld.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/PegWorld.cs @@ -48,7 +48,7 @@ private async Task CountPegs() if (count != null) { - tracker.SetPegs((int)count); + tracker.ModeTracker.SetPegs((int)count); } } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs index 2a7da5788..2de7f7efb 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMap.cs @@ -9,7 +9,6 @@ using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda.LightWorld.DeathMountain; using TrackerCouncil.Smz3.SeedGenerator.Contracts; using TrackerCouncil.Smz3.Shared.Enums; -using IDungeon = TrackerCouncil.Smz3.Data.WorldData.Regions.IDungeon; using IHasReward = TrackerCouncil.Smz3.Data.WorldData.Regions.IHasReward; using Z3Region = TrackerCouncil.Smz3.Data.WorldData.Regions.Z3Region; @@ -19,22 +18,14 @@ namespace TrackerCouncil.Smz3.Tracking.AutoTracking.ZeldaStateChecks; /// Zelda State check for viewing the map /// Checks if the game state is viewing the map and the value for viewing the full map is set /// -public class ViewedMap : IZeldaStateCheck +public class ViewedMap(IWorldAccessor worldAccessor, IPlayerProgressionService playerProgressionService) : IZeldaStateCheck { private TrackerBase? _tracker; - private readonly IWorldAccessor _worldAccessor; private bool _lightWorldUpdated; private bool _darkWorldUpdated; - public ViewedMap(IWorldAccessor worldAccessor, IItemService itemService) - { - _worldAccessor = worldAccessor; - Items = itemService; - } + private World World => worldAccessor.World; - protected World World => _worldAccessor.World; - - protected IItemService Items { get; } /// /// Executes the check for the current state @@ -76,30 +67,17 @@ private void UpdateLightWorldRewards() { if (_tracker == null || _lightWorldUpdated) return; - var rewards = new List(); - var dungeons = new (Data.WorldData.Regions.Region Region, ItemType Map)[] { + var dungeons = new (IHasReward Region, ItemType Map)[] { (World.EasternPalace, ItemType.MapEP), (World.DesertPalace, ItemType.MapDP), (World.TowerOfHera, ItemType.MapTH) }; - foreach (var (region, map) in dungeons) - { - if (World.Config.ZeldaKeysanity && !Items.IsTracked(map)) - continue; - - var dungeon = (IDungeon)region; - var rewardRegion = (IHasReward)region; - if (dungeon.DungeonState.MarkedReward != dungeon.DungeonState.Reward) - { - rewards.Add(rewardRegion.RewardType); - _tracker.SetDungeonReward(dungeon, rewardRegion.RewardType); - } - } + var rewards = CheckDungeonRewards(dungeons); if (!World.Config.ZeldaKeysanity) { - if (rewards.Count(x => x == RewardType.CrystalRed || x == RewardType.CrystalBlue) == 3) + if (rewards.Count(x => x is RewardType.CrystalRed or RewardType.CrystalBlue) == 3) { _tracker.Say(x => x.AutoTracker.LightWorldAllCrystals, once: true); } @@ -110,8 +88,7 @@ private void UpdateLightWorldRewards() } // If all dungeons are marked, save the light world as updated - if (dungeons.Select(x => x.Region as IDungeon).Count(x => x?.DungeonState.MarkedReward != null) >= - dungeons.Length) + if (dungeons.Count(x => x.Region.HasCorrectlyMarkedReward) >= dungeons.Length) { _lightWorldUpdated = true; } @@ -121,12 +98,11 @@ private void UpdateLightWorldRewards() /// /// Marks all of the rewards for the dark world dungeons /// - protected void UpdateDarkWorldRewards() + private void UpdateDarkWorldRewards() { if (_tracker == null || _darkWorldUpdated) return; - var rewards = new List(); - var dungeons = new (Data.WorldData.Regions.Region Region, ItemType Map)[] { + var dungeons = new (IHasReward Region, ItemType Map)[] { (World.PalaceOfDarkness, ItemType.MapPD), (World.SwampPalace, ItemType.MapSP), (World.SkullWoods, ItemType.MapSW), @@ -136,25 +112,14 @@ protected void UpdateDarkWorldRewards() (World.TurtleRock, ItemType.MapTR) }; - foreach (var (region, map) in dungeons) - { - if (World.Config.ZeldaKeysanity && !Items.IsTracked(map)) - continue; - - var dungeon = (IDungeon)region; - var rewardRegion = (IHasReward)region; - if (dungeon.DungeonState.MarkedReward != dungeon.DungeonState.Reward) - { - rewards.Add(rewardRegion.Reward.Type); - _tracker.SetDungeonReward(dungeon, rewardRegion.Reward.Type); - } - } - - var isMiseryMirePendant = (World.MiseryMire as IDungeon).IsPendantDungeon; - var isTurtleRockPendant = (World.TurtleRock as IDungeon).IsPendantDungeon; + var rewards = CheckDungeonRewards(dungeons); if (!World.Config.ZeldaKeysanity) { + RewardType[] pendants = [RewardType.PendantBlue, RewardType.PendantGreen, RewardType.PendantRed]; + var isMiseryMirePendant = pendants.Contains(World.MiseryMire.Reward.Type); + var isTurtleRockPendant = pendants.Contains(World.TurtleRock.Reward.Type); + if (isMiseryMirePendant && isTurtleRockPendant) { _tracker.Say(x => x.AutoTracker.DarkWorldNoMedallions, once: true); @@ -166,11 +131,27 @@ protected void UpdateDarkWorldRewards() } // If all dungeons are marked, save the light world as updated - if (dungeons.Select(x => x.Region as IDungeon).Count(x => x?.DungeonState.MarkedReward != null) >= - dungeons.Length) + if (dungeons.Count(x => x.Region.HasCorrectlyMarkedReward) >= dungeons.Length) { _darkWorldUpdated = true; } } + + private List CheckDungeonRewards((IHasReward Region, ItemType Map)[] dungeons) + { + var rewards = new List(); + + foreach (var (region, map) in dungeons) + { + if (World.Config.ZeldaKeysanity && !playerProgressionService.IsTracked(map)) + continue; + + if (region.HasCorrectlyMarkedReward) continue; + rewards.Add(region.RewardType); + _tracker!.RewardTracker.SetAreaReward(region, region.RewardType); + } + + return rewards; + } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs index 01a42cb27..a820fb951 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedMedallion.cs @@ -1,6 +1,7 @@ using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Tracking; using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.SeedGenerator.Contracts; namespace TrackerCouncil.Smz3.Tracking.AutoTracking.ZeldaStateChecks; @@ -8,22 +9,12 @@ namespace TrackerCouncil.Smz3.Tracking.AutoTracking.ZeldaStateChecks; /// /// Zelda state check for marking medallion requirements for Misery Mire or Turtle Rock /// -public class ViewedMedallion : IZeldaStateCheck +public class ViewedMedallion(IWorldAccessor worldAccessor) : IZeldaStateCheck { private TrackerBase? _tracker; - private readonly IWorldAccessor _worldAccessor; private bool _mireUpdated; private bool _turtleRockUpdated; - - public ViewedMedallion(IWorldAccessor worldAccessor, IItemService itemService) - { - _worldAccessor = worldAccessor; - Items = itemService; - } - - protected World World => _worldAccessor.World; - - protected IItemService Items { get; } + private World World => worldAccessor.World; /// /// Executes the check for the current state @@ -41,12 +32,12 @@ public bool ExecuteCheck(TrackerBase tracker, AutoTrackerZeldaState currentState var x = currentState.LinkX; var y = currentState.LinkY; - if (!_mireUpdated && currentState.OverworldScreen == 112 && x is >= 172 and <= 438 && y is >= 3200 and <= 3432) + if (!_mireUpdated && currentState.OverworldScreen == 112 && x is >= 172 and <= 438 && y is >= 3200 and <= 3432 && !((IHasPrerequisite)World.MiseryMire).HasMarkedCorrectly) { tracker.AutoTracker.SetLatestViewAction("MarkMiseryMireMedallion", MarkMiseryMireMedallion); return true; } - else if (!_turtleRockUpdated && currentState.OverworldScreen == 71 && x is >= 3708 and <= 4016 && y is >= 128 and <= 368) + else if (!_turtleRockUpdated && currentState.OverworldScreen == 71 && x is >= 3708 and <= 4016 && y is >= 128 and <= 368 && !((IHasPrerequisite)World.TurtleRock).HasMarkedCorrectly) { tracker.AutoTracker.SetLatestViewAction("MarkTurtleRockMedallion", MarkTurtleRockMedallion); return true; @@ -59,7 +50,7 @@ private void MarkMiseryMireMedallion() { if (_tracker == null || _mireUpdated) return; var dungeon = _tracker.World.MiseryMire; - _tracker.SetDungeonRequirement(dungeon, dungeon.DungeonState.RequiredMedallion, null, true); + _tracker.PrerequisiteTracker.SetDungeonRequirement(dungeon, dungeon.PrerequisiteState.RequiredItem, null, true); _mireUpdated = true; } @@ -67,7 +58,7 @@ private void MarkTurtleRockMedallion() { if (_tracker == null || _turtleRockUpdated) return; var dungeon = _tracker.World.TurtleRock; - _tracker.SetDungeonRequirement(dungeon, dungeon.DungeonState.RequiredMedallion, null, true); + _tracker.PrerequisiteTracker.SetDungeonRequirement(dungeon, dungeon.PrerequisiteState.RequiredItem, null, true); _turtleRockUpdated = true; } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs index 0d70c460e..b67a8c738 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ViewedText.cs @@ -24,17 +24,14 @@ public class ViewedText : IZeldaStateCheck private readonly Dictionary _hintTiles; private HashSet _viewedHintTileRooms = new(); private HintTile? _lastHintTile; - private Dictionary> _pendingHintTiles; + private Dictionary> _pendingHintTiles = []; public ViewedText(IWorldAccessor worldAccessor, TrackerBase tracker, HintTileConfig hintTileConfig) { _worldAccessor = worldAccessor; - _hintTiles = hintTileConfig.HintTiles?.ToDictionary(x => x.Room, x => x) ?? new(); + _hintTiles = hintTileConfig.HintTiles?.ToDictionary(x => x.Room, x => x) ?? []; _tracker = tracker; - _tracker.LocationCleared += TrackerOnLocationCleared; - _pendingHintTiles = _worldAccessor.World.HintTiles - .Where(x => x.State is { HintState: HintState.Viewed } && x.Locations?.Any() == true).ToDictionary(h => h, - h => h.Locations!.Select(l => _worldAccessor.World.FindLocation(l)).ToList()); + InitHintTiles(_worldAccessor.World.HintTiles); } private World World => _worldAccessor.World; @@ -69,13 +66,26 @@ public bool ExecuteCheck(TrackerBase tracker, AutoTrackerZeldaState currentState return false; } + private void InitHintTiles(IEnumerable hintTiles) + { + foreach (var hintTile in hintTiles) + { + if (hintTile.HintState != HintState.Viewed || hintTile.Locations?.Any() != true) + { + continue; + } + + AddPendingHintTile(hintTile); + } + } + /// /// Marks the dungeon with the green pendant /// private void MarkGreenPendantDungeons() { - var dungeon = World.Dungeons.FirstOrDefault(x => - x.DungeonRewardType == RewardType.PendantGreen && x.MarkedReward != RewardType.PendantGreen); + var dungeon = World.RewardRegions.FirstOrDefault(x => + x is { RewardType: RewardType.PendantGreen, HasCorrectlyMarkedReward: false }); if (dungeon == null) { @@ -83,7 +93,7 @@ private void MarkGreenPendantDungeons() return; } - _tracker.SetDungeonReward(dungeon, dungeon.DungeonRewardType); + _tracker.RewardTracker.SetAreaReward(dungeon, dungeon.RewardType); _greenPendantUpdated = true; } @@ -92,10 +102,10 @@ private void MarkGreenPendantDungeons() /// private void MarkRedCrystalDungeons() { - var dungeons = World.Dungeons.Where(x => - x.DungeonRewardType == RewardType.CrystalRed && x.MarkedReward != RewardType.CrystalRed).ToList(); + var dungeons = World.RewardRegions.Where(x => + x is { RewardType: RewardType.PendantGreen, HasCorrectlyMarkedReward: false }).ToList(); - if (!dungeons.Any()) + if (dungeons.Count == 0) { _redCrystalsUpdated = true; return; @@ -103,7 +113,7 @@ private void MarkRedCrystalDungeons() foreach (var dungeon in dungeons) { - _tracker.SetDungeonReward(dungeon, dungeon.DungeonRewardType); + _tracker.RewardTracker.SetAreaReward(dungeon, dungeon.RewardType); } _redCrystalsUpdated = true; @@ -122,30 +132,34 @@ private void MarkHintTileAsViewed() _viewedHintTileRooms.Add(_lastHintTile.Room); - if (hintTile.State.HintState != HintState.Default) + if (hintTile.HintState != HintState.Default) { return; } - _tracker.UpdateHintTile(hintTile); + _tracker.GameStateTracker.UpdateHintTile(hintTile); - if (hintTile.State.HintState == HintState.Viewed && hintTile.Locations?.Any() == true) + if (hintTile.HintState == HintState.Viewed && hintTile.Locations?.Any() == true) { - var locations = hintTile.Locations!.Select(x => World.FindLocation(x)).ToList(); - _pendingHintTiles.Add(hintTile, locations); + AddPendingHintTile(hintTile); } } - private void TrackerOnLocationCleared(object? sender, LocationClearedEventArgs e) + private void AddPendingHintTile(PlayerHintTile hintTile) { - foreach (var (hintTile, locations) in _pendingHintTiles) + var locations = hintTile.Locations!.Select(x => World.FindLocation(x)).ToList(); + _pendingHintTiles.Add(hintTile, locations); + + foreach (var location in locations) { - if (locations.All(x => x.State.Autotracked || x.State.Cleared)) + location.ClearedUpdated += (sender, args) => { - hintTile.State!.HintState = HintState.Cleared; + locations.Remove(location); + if (locations.Count != 0) return; + hintTile.HintState = HintState.Cleared; _pendingHintTiles.Remove(hintTile); - _tracker.UpdateHintTile(hintTile); - } + _tracker.GameStateTracker.UpdateHintTile(hintTile); + }; } } } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/VisibleItemZeldaCheck.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/VisibleItemZeldaCheck.cs index ab87cfea0..5df7f828a 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/VisibleItemZeldaCheck.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/VisibleItemZeldaCheck.cs @@ -13,11 +13,11 @@ public class VisibleItemZeldaCheck : IZeldaStateCheck private readonly Dictionary _overworldVisibleItems; private readonly Dictionary _underworldVisibleItems; private readonly HashSet _trackedAreas = new(); - private readonly IWorldService _worldService; + private readonly IWorldQueryService _worldQueryService; - public VisibleItemZeldaCheck(IWorldService worldService) + public VisibleItemZeldaCheck(IWorldQueryService worldQueryService) { - _worldService = worldService; + _worldQueryService = worldQueryService; var visibleItems = VisibleItems.GetVisibleItems().ZeldaItems; _overworldVisibleItems = visibleItems.Where(x => x.OverworldScreen > 0) .ToDictionary(s => (int)s.OverworldScreen!, s => s); @@ -57,24 +57,24 @@ private bool CheckItems(VisibleItemZelda details, Dictionary tracker.World.FindLocation(x)).ToList(); - if (locations.All(x => x.State.Cleared || x.State.Autotracked || x.State.MarkedItem == x.State.Item)) + if (locations.All(x => x.Cleared || x.Autotracked || x.HasMarkedItem)) { _trackedAreas.Add(area); continue; } - toClearLocations.AddRange(locations.Where(x => x.State is { Cleared: false, Autotracked: false } && - !x.State.Item.IsEquivalentTo(x.State.MarkedItem))); + toClearLocations.AddRange(locations.Where(x => x is + { Cleared: false, Autotracked: false, HasMarkedCorrectItem: false })); } foreach (var location in toClearLocations) { - tracker.MarkLocation(location, location.Item.Type.GetGenericType()); + tracker.LocationTracker.MarkLocation(location, location.Item.Type.GetGenericType()); } if (toClearLocations.Any()) { - tracker.UpdateLastMarkedLocations(toClearLocations); + tracker.GameStateTracker.UpdateLastMarkedLocations(toClearLocations); } // Remove overworld/dungeon room if all items have been collected or marked @@ -87,8 +87,7 @@ private bool CheckItems(VisibleItemZelda details, Dictionary x.Locations.Select(l => tracker.World.FindLocation(l))) - .All(x => x.State.Cleared || x.State.Autotracked || - x.State.Item.IsEquivalentTo(x.State.MarkedItem))) + .All(x => x.Cleared || x.Autotracked || x.HasMarkedCorrectItem)) { itemsDictionary.Remove(key.Value); } diff --git a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ZeldaDeath.cs b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ZeldaDeath.cs index bd8b6a96f..05745d935 100644 --- a/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ZeldaDeath.cs +++ b/src/TrackerCouncil.Smz3.Tracking/AutoTracking/ZeldaStateChecks/ZeldaDeath.cs @@ -1,6 +1,7 @@ using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Tracking; using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Tracking.Services; namespace TrackerCouncil.Smz3.Tracking.AutoTracking.ZeldaStateChecks; @@ -8,10 +9,8 @@ namespace TrackerCouncil.Smz3.Tracking.AutoTracking.ZeldaStateChecks; /// Zelda State check for when dying /// Checks if the player is in the death spiral animation without a fairy /// -public class ZeldaDeath(IItemService itemService) : IZeldaStateCheck +public class ZeldaDeath(IWorldQueryService worldQueryService) : IZeldaStateCheck { - public IItemService Items { get; } = itemService; - /// /// Executes the check for the current state /// @@ -26,25 +25,25 @@ public bool ExecuteCheck(TrackerBase tracker, AutoTrackerZeldaState currentState var silent = tracker.GameService!.PlayerRecentlyKilled; // Say specific message for dying in the particular screen/room the player is in - if (!silent && tracker.CurrentRegion?.Metadata is { WhenDiedInRoom: not null }) + if (!silent && tracker.GameStateTracker.CurrentRegion?.Metadata is { WhenDiedInRoom: not null }) { - var region = tracker.CurrentRegion as Z3Region; - if (region is { IsOverworld: true } && prevState.OverworldScreen != null && tracker.CurrentRegion?.Metadata.WhenDiedInRoom?.TryGetValue(prevState.OverworldScreen.Value, out var locationResponse) == true) + var region = tracker.GameStateTracker.CurrentRegion as Z3Region; + if (region is { IsOverworld: true } && prevState.OverworldScreen != null && tracker.GameStateTracker.CurrentRegion?.Metadata.WhenDiedInRoom?.TryGetValue(prevState.OverworldScreen.Value, out var locationResponse) == true) { tracker.Say(response: locationResponse); } - else if (region is { IsOverworld: false } && prevState.CurrentRoom != null && tracker.CurrentRegion?.Metadata.WhenDiedInRoom?.TryGetValue(prevState.CurrentRoom.Value, out locationResponse) == true) + else if (region is { IsOverworld: false } && prevState.CurrentRoom != null && tracker.GameStateTracker.CurrentRegion?.Metadata.WhenDiedInRoom?.TryGetValue(prevState.CurrentRoom.Value, out locationResponse) == true) { tracker.Say(response: locationResponse); } } - tracker.TrackDeath(true); + tracker.GameStateTracker.TrackDeath(true); - var death = Items.FirstOrDefault("Death"); + var death = worldQueryService.FirstOrDefault("Death"); if (death is not null) { - tracker.TrackItem(death, autoTracked: true, silent: silent); + tracker.ItemTracker.TrackItem(death, autoTracked: true, silent: silent); return true; } return false; diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/IUIService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/IUIService.cs index e2c37caa5..c18afe0a0 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/IUIService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/IUIService.cs @@ -47,9 +47,9 @@ public interface IUIService /// /// Returns the path of the sprite for the dungeon /// - /// The dungeon requested + /// The dungeon requested /// The full path of the sprite or null if it's not found - public string? GetSpritePath(IDungeon dungeon); + public string? GetSpritePath(IHasTreasure hasTreasure); /// /// Returns the path of the sprite for the reward diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/IWorldService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/IWorldQueryService.cs similarity index 72% rename from src/TrackerCouncil.Smz3.Tracking/Services/IWorldService.cs rename to src/TrackerCouncil.Smz3.Tracking/Services/IWorldQueryService.cs index 811eb7873..f3034d0f2 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/IWorldService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/IWorldQueryService.cs @@ -6,7 +6,11 @@ namespace TrackerCouncil.Smz3.Tracking.Services; -public interface IWorldService +/// +/// Service for looking up various information about the world +/// (or worlds for multiworld games) +/// +public interface IWorldQueryService { /// /// Retrieves the world for the current player @@ -120,4 +124,54 @@ public interface IWorldService /// Returns the list of hint tiles that have been viewed but not cleared /// public IEnumerable ViewedHintTiles { get; } + + /// + /// Enumerates all items that can be tracked for all players. + /// + /// A collection of items. + IEnumerable AllItems(); + + /// + /// Enumerates all items that can be tracked for the local player. + /// + /// A collection of items. + IEnumerable LocalPlayersItems(); + + /// + /// Finds the item with the specified name for the local player. + /// + /// + /// The name of the item or item stage to find. + /// + /// + /// An representing the item with the specified + /// name, or if there is no item that has the + /// specified name. + /// + Item? FirstOrDefault(string name); + + /// + /// Finds an item with the specified item type for the local player. + /// + /// The type of item to find. + /// + /// An representing the item. If there are + /// multiple configured items with the same type, this method returns + /// one at random. If there no configured items with the specified type, + /// this method returns . + /// + Item? FirstOrDefault(ItemType itemType); + + /// + /// Finds an reward with the specified item type. + /// + /// The type of reward to find. + /// + /// An representing the reward. If there are + /// multiple configured rewards with the same type, this method returns + /// one at random. If there no configured rewards with the specified type, + /// this method returns . + /// + Reward? FirstOrDefault(RewardType rewardType); + } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/ItemService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/ItemService.cs deleted file mode 100644 index 1be6b7438..000000000 --- a/src/TrackerCouncil.Smz3.Tracking/Services/ItemService.cs +++ /dev/null @@ -1,288 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using TrackerCouncil.Smz3.Abstractions; -using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; -using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; -using TrackerCouncil.Smz3.Data.WorldData; -using TrackerCouncil.Smz3.Data.WorldData.Regions; -using TrackerCouncil.Smz3.SeedGenerator.Contracts; -using TrackerCouncil.Smz3.Shared; -using TrackerCouncil.Smz3.Shared.Enums; - -namespace TrackerCouncil.Smz3.Tracking.Services; - -/// -/// Manages items and their tracking state. -/// -public class ItemService : IItemService -{ - private readonly IWorldAccessor _world; - private readonly Dictionary _progression = new(); - - /// - /// Initializes a new instance of the class - /// with the specified dependencies. - /// - /// - /// Specifies the configuration that contains the item data to be - /// managed. - /// - /// - /// Specifies the configuration that contains the reward data - /// - /// Accessor to get data of the world - public ItemService(ItemConfig items, RewardConfig rewards, IWorldAccessor world) - { - Items = items; - Rewards = rewards; - _world = world; - } - - /// - /// Gets a collection of trackable items. - /// - protected IReadOnlyCollection Items { get; } - - /// - /// Gets a collection of rewards - /// - protected IReadOnlyCollection Rewards { get; } - - /// - /// Finds the item with the specified name for the local player. - /// - /// - /// The name of the item or item stage to find. - /// - /// - /// An representing the item with the specified - /// name, or if there is no item that has the - /// specified name. - /// - public Item? FirstOrDefault(string name) - => LocalPlayersItems().FirstOrDefault(x => x.Name == name) - ?? LocalPlayersItems().FirstOrDefault(x => x.Metadata.Name?.Contains(name, StringComparison.OrdinalIgnoreCase) == true) - ?? LocalPlayersItems().FirstOrDefault(x => x.Metadata.GetStage(name) != null); - - /// - /// Finds an item with the specified item type for the local player. - /// - /// The type of item to find. - /// - /// An representing the item. If there are - /// multiple configured items with the same type, this method returns - /// one at random. If there no configured items with the specified type, - /// this method returns . - /// - public Item? FirstOrDefault(ItemType itemType) - => LocalPlayersItems().FirstOrDefault(x => x.Type == itemType); - - /// - /// Indicates whether an item of the specified type has been tracked - /// for the local player. - /// - /// The type of item to check. - /// - /// if an item with the specified type has been - /// tracked at least once; otherwise, . - /// - public virtual bool IsTracked(ItemType itemType) - => LocalPlayersItems().Any(x => x.Type == itemType && x.State.TrackingState > 0); - - /// - /// Enumerates all items that can be tracked for all players. - /// - /// A collection of items. - public IEnumerable AllItems() // I really want to discourage this, but necessary for now - => _world.Worlds.SelectMany(x => x.AllItems); - - /// - /// Enumerates all items that can be tracked for the local player. - /// - /// A collection of items. - public IEnumerable LocalPlayersItems() - => _world.Worlds.SelectMany(x => x.AllItems).Where(x => x.World == _world.World); - - /// - /// Enumarates all currently tracked items for the local player. - /// - /// - /// A collection of items that have been tracked at least once. - /// - public IEnumerable TrackedItems() - => LocalPlayersItems().Where(x => x.State.TrackingState > 0); - - /// - /// Returns a random name for the specified item including article, e.g. - /// "an E-Tank" or "the Book of Mudora". - /// - /// The type of item whose name to get. - /// - /// The name of the type of item, including "a", "an" or "the" if - /// applicable. - /// - public virtual string GetName(ItemType itemType) - { - var item = FirstOrDefault(itemType); - return item?.Metadata.NameWithArticle ?? itemType.GetDescription(); - } - - - /// - /// Finds an reward with the specified item type. - /// - /// The type of reward to find. - /// - /// An representing the reward. If there are - /// multiple configured rewards with the same type, this method returns - /// one at random. If there no configured rewards with the specified type, - /// this method returns . - /// - public virtual Reward? FirstOrDefault(RewardType rewardType) - => LocalPlayersRewards().FirstOrDefault(x => x.Type == rewardType); - - /// - /// Returns a random name for the specified item including article, e.g. - /// "a blue crystal" or "the green pendant". - /// - /// The reward of item whose name to get. - /// - /// The name of the reward of item, including "a", "an" or "the" if - /// applicable. - /// - public virtual string GetName(RewardType rewardType) - { - var reward = FirstOrDefault(rewardType); - return reward?.Metadata.NameWithArticle ?? rewardType.GetDescription(); - } - - /// - /// Enumerates all rewards that can be tracked for all players. - /// - /// A collection of rewards. - - public virtual IEnumerable AllRewards() - => _world.Worlds.SelectMany(x => x.Rewards); - - /// - /// Enumerates all rewards that can be tracked for the local player. - /// - /// A collection of rewards. - - public virtual IEnumerable LocalPlayersRewards() - => _world.World.Rewards; - - /// - /// Enumarates all currently tracked rewards for the local player. - /// This uses what the player marked as the reward for dungeons, - /// not the actual dungeon reward. - /// - /// - /// A collection of reward that have been tracked. - /// - public virtual IEnumerable TrackedRewards() - => _world.World.Dungeons.Where(x => x.HasReward && x.DungeonState.Cleared).Select(x => new Reward(x.MarkedReward, _world.World, (IHasReward)x)); - - /// - /// Enumerates all bosses that can be tracked for all players. - /// - /// A collection of bosses. - - public virtual IEnumerable AllBosses() - => _world.Worlds.SelectMany(x => x.AllBosses); - - /// - /// Enumerates all bosses that can be tracked for the local player. - /// - /// A collection of bosses. - - public virtual IEnumerable LocalPlayersBosses() - => _world.World.AllBosses; - - /// - /// Enumarates all currently tracked bosses for the local player. - /// - /// - /// A collection of bosses that have been tracked. - /// - public virtual IEnumerable TrackedBosses() - => LocalPlayersBosses().Where(x => x.State.Defeated); - - /// - /// Gets the current progression based on the items the user has collected, - /// bosses that the user has beaten, and rewards that the user has received - /// - /// If it should be assumed that the player has all keys - /// The progression object - public Progression GetProgression(bool assumeKeys) - { - var key = $"{assumeKeys}"; - - if (_progression.ContainsKey(key)) - { - return _progression[key]; - } - - var progression = new Progression(); - - if (!_world.World.Config.MetroidKeysanity || assumeKeys) - { - progression.AddRange(_world.World.ItemPools.Keycards); - if (assumeKeys) - progression.AddRange(_world.World.ItemPools.Dungeon); - } - - foreach (var item in TrackedItems().Select(x => x.State).Distinct()) - { - if (item.Type == null || item.Type == ItemType.Nothing) continue; - progression.AddRange(Enumerable.Repeat(item.Type.Value, item.TrackingState)); - } - - foreach (var reward in TrackedRewards()) - { - progression.Add(reward); - } - - foreach (var boss in TrackedBosses()) - { - progression.Add(boss); - } - - _progression[key] = progression; - return progression; - } - - /// - /// Gets the current progression based on the items the user has collected, - /// bosses that the user has beaten, and rewards that the user has received - /// - /// The area to check to see if keys should be assumed - /// or not - /// The progression object - public Progression GetProgression(IHasLocations area) - { - switch (area) - { - case Z3Region: - case Room { Region: Z3Region }: - return GetProgression(assumeKeys: !_world.World.Config.ZeldaKeysanity); - case SMRegion: - case Room { Region: SMRegion }: - return GetProgression(assumeKeys: !_world.World.Config.MetroidKeysanity); - default: - return GetProgression(assumeKeys: _world.World.Config.KeysanityMode == KeysanityMode.None); - } - } - - /// - /// Clears the progression cache after collecting new items, rewards, or bosses - /// - public void ResetProgression() - { - _progression.Clear(); - } - - - // TODO: Tracking methods -} diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/PlayerProgressionService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/PlayerProgressionService.cs new file mode 100644 index 000000000..0d3c945e0 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/Services/PlayerProgressionService.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; +using System.Linq; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.SeedGenerator.Contracts; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Tracking.Services; + +/// +/// Manages items and their tracking state. +/// +internal class PlayerProgressionService (IWorldAccessor world) : IPlayerProgressionService +{ + private IWorldAccessor _world => world; + private readonly Dictionary _progression = new(); + + /// + /// Enumarates all currently tracked items for the local player. + /// + /// + /// A collection of items that have been tracked at least once. + /// + public IEnumerable TrackedItems() + => _world.Worlds.SelectMany(x => x.AllItems).Where(x => x.World == _world.World && x.TrackingState > 0); + + public bool IsTracked(ItemType itemType) => _world.Worlds.SelectMany(x => x.AllItems) + .FirstOrDefault(x => x.World == _world.World && x.Type == itemType)?.TrackingState > 0; + + public IEnumerable TrackedRewards() + => _world.World.Rewards.Where(x => x.HasReceivedReward); + + public IEnumerable TrackedBosses() + => _world.World.Bosses.Where(x => x.Defeated); + + /// + /// Gets the current progression based on the items the user has collected, + /// bosses that the user has beaten, and rewards that the user has received + /// + /// If it should be assumed that the player has all keys + /// The progression object + public Progression GetProgression(bool assumeKeys) + { + var key = $"{assumeKeys}"; + + if (_progression.TryGetValue(key, out var prevProgression)) + { + return prevProgression; + } + + var progression = new Progression(); + + if (!_world.World.Config.MetroidKeysanity || assumeKeys) + { + progression.AddRange(_world.World.ItemPools.Keycards); + if (assumeKeys) + progression.AddRange(_world.World.ItemPools.Dungeon); + } + + foreach (var item in TrackedItems().Select(x => x.State).Distinct()) + { + if (item.Type is null or ItemType.Nothing) continue; + progression.AddRange(Enumerable.Repeat(item.Type.Value, item.TrackingState)); + } + + foreach (var reward in TrackedRewards()) + { + progression.Add(reward); + } + + foreach (var boss in TrackedBosses()) + { + progression.Add(boss); + } + + _progression[key] = progression; + return progression; + } + + /// + /// Gets the current progression based on the items the user has collected, + /// bosses that the user has beaten, and rewards that the user has received + /// + /// The area to check to see if keys should be assumed + /// or not + /// The progression object + public Progression GetProgression(IHasLocations area) + { + switch (area) + { + case Z3Region: + case Room { Region: Z3Region }: + return GetProgression(assumeKeys: !_world.World.Config.ZeldaKeysanity); + case SMRegion: + case Room { Region: SMRegion }: + return GetProgression(assumeKeys: !_world.World.Config.MetroidKeysanity); + default: + return GetProgression(assumeKeys: _world.World.Config.KeysanityMode == KeysanityMode.None); + } + } + + public Progression GetProgression(Location location) + { + return location.Region is Z3Region + ? GetProgression(assumeKeys: !_world.World.Config.ZeldaKeysanity) + : GetProgression(assumeKeys: !_world.World.Config.MetroidKeysanity); + } + + /// + /// Clears the progression cache after collecting new items, rewards, or bosses + /// + public void ResetProgression() + { + _progression.Clear(); + } + + + // TODO: Tracking methods +} diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/UIService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/UIService.cs index 5b83f6928..1c803dc54 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/UIService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/UIService.cs @@ -79,7 +79,7 @@ UIConfig uiConfig if (item.Metadata.HasStages || item.Metadata.Multiple) { var baseFileName = GetSpritePath("Items", $"{item.Metadata.Item.ToLowerInvariant()}.png", out var profilePath); - fileName = GetSpritePath("Items", $"{item.Metadata.Item.ToLowerInvariant()} ({item.State.TrackingState}).png", out _, profilePath); + fileName = GetSpritePath("Items", $"{item.Metadata.Item.ToLowerInvariant()} ({item.TrackingState}).png", out _, profilePath); if (File.Exists(fileName)) return fileName; else @@ -111,10 +111,10 @@ UIConfig uiConfig /// /// Returns the path of the sprite for the dungeon /// - /// The dungeon requested + /// The dungeon requested /// The full path of the sprite or null if it's not found - public string? GetSpritePath(IDungeon dungeon) => GetSpritePath("Dungeons", - $"{dungeon.DungeonName.ToLowerInvariant()}.png", out _); + public string? GetSpritePath(IHasTreasure hasTreasure) => GetSpritePath("Dungeons", + $"{hasTreasure.Name.ToLowerInvariant()}.png", out _); /// /// Returns the path of the sprite for the reward diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/WorldService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/WorldQueryService.cs similarity index 61% rename from src/TrackerCouncil.Smz3.Tracking/Services/WorldService.cs rename to src/TrackerCouncil.Smz3.Tracking/Services/WorldQueryService.cs index 756536423..c20abcef5 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/WorldService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/WorldQueryService.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.ComponentModel; using System.Linq; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.WorldData; @@ -14,20 +13,20 @@ namespace TrackerCouncil.Smz3.Tracking.Services; /// /// Service for finding locations in the world /// -public class WorldService : IWorldService +public class WorldQueryService : IWorldQueryService { private readonly IWorldAccessor _worldAccessor; - private readonly IItemService _itemService; + private readonly IPlayerProgressionService _playerProgressionService; /// /// Constructor /// /// - /// - public WorldService(IWorldAccessor world, IItemService itemService) + /// + public WorldQueryService(IWorldAccessor world, IPlayerProgressionService playerProgressionService) { _worldAccessor = world; - _itemService = itemService; + _playerProgressionService = playerProgressionService; } /// @@ -81,7 +80,7 @@ public IEnumerable AccessibleLocations(bool assumeKeys) /// /// public IEnumerable UnclearedLocations() - => AllLocations().Where(x => !x.State.Cleared).ToImmutableList(); + => AllLocations().Where(x => !x.Cleared).ToImmutableList(); /// /// Retrieves all locations for the current player's world that has been marked as @@ -89,7 +88,7 @@ public IEnumerable UnclearedLocations() /// /// public IEnumerable MarkedLocations() - => AllLocations().Where(x => !x.State.Cleared && x.State.MarkedItem != null && x.State.MarkedItem != ItemType.Nothing).ToImmutableList(); + => AllLocations().Where(x => x is { Cleared: false, MarkedItem: not null } && x.MarkedItem != ItemType.Nothing).ToImmutableList(); /// /// Retrieves a collection of locations for the current player's world that match the given filter criteria @@ -114,7 +113,7 @@ public IEnumerable Locations(bool unclearedOnly = true, bool outOfLogi // Then get that region with all of its valid locations for the filter options // Order by last cleared region, then by location count per region return Regions - .Where(x => RegionMatchesFilter(regionFilter, x)) + .Where(x => x.MatchesFilter(regionFilter)) .Select(x => (Region: x, Locations: x.Locations.Where(l => IsValidLocation(l, unclearedOnly, outOfLogic, progression, itemFilter, inRegion)))) .OrderBy(x => x.Region != World.LastClearedLocation?.Region) .ThenByDescending(x => x.Locations.Count()) @@ -132,9 +131,118 @@ public IEnumerable Locations(bool unclearedOnly = true, bool outOfLogi private bool IsValidLocation(Location location, bool unclearedOnly, bool outOfLogic, Progression? progression, ItemType itemFilter, Region? inRegion) { - return (!unclearedOnly || location.State.Cleared == false) && (outOfLogic || IsAvailable(location, progression ?? Progression(location.Region))) && (itemFilter == ItemType.Nothing || location.Item.Is(itemFilter, World)) && (inRegion == null || location.Region == inRegion); + return (!unclearedOnly || location.Cleared == false) && (outOfLogic || IsAvailable(location, progression ?? Progression(location.Region))) && (itemFilter == ItemType.Nothing || location.Item.Is(itemFilter, World)) && (inRegion == null || location.Region == inRegion); } + /// + /// Enumerates all items that can be tracked for all players. + /// + /// A collection of items. + public IEnumerable AllItems() // I really want to discourage this, but necessary for now + => Worlds.SelectMany(x => x.AllItems); + + /// + /// Enumerates all items that can be tracked for the local player. + /// + /// A collection of items. + public IEnumerable LocalPlayersItems() + => AllItems().Where(x => x.World.Id == World.Id); + + /// + /// Enumerates all rewards that can be tracked for all players. + /// + /// A collection of rewards. + + public virtual IEnumerable AllRewards() + => Worlds.SelectMany(x => x.Rewards); + + /// + /// Enumerates all rewards that can be tracked for the local player. + /// + /// A collection of rewards. + + public virtual IEnumerable LocalPlayersRewards() + => World.Rewards; + + /// + /// Enumarates all currently tracked rewards for the local player. + /// This uses what the player marked as the reward for dungeons, + /// not the actual dungeon reward. + /// + /// + /// A collection of reward that have been tracked. + /// + public virtual IEnumerable TrackedRewards() + => World.RewardRegions.Where(x => x.HasReceivedReward) + .Select(x => x.Reward); + + /// + /// Enumerates all bosses that can be tracked for all players. + /// + /// A collection of bosses. + + public virtual IEnumerable AllBosses() + => Worlds.SelectMany(x => x.AllBosses); + + /// + /// Enumerates all bosses that can be tracked for the local player. + /// + /// A collection of bosses. + + public virtual IEnumerable LocalPlayersBosses() + => World.AllBosses; + + /// + /// Enumarates all currently tracked bosses for the local player. + /// + /// + /// A collection of bosses that have been tracked. + /// + public virtual IEnumerable TrackedBosses() + => LocalPlayersBosses().Where(x => x.Defeated); + + + /// + /// Finds the item with the specified name for the local player. + /// + /// + /// The name of the item or item stage to find. + /// + /// + /// An representing the item with the specified + /// name, or if there is no item that has the + /// specified name. + /// + public Item? FirstOrDefault(string name) + => LocalPlayersItems().FirstOrDefault(x => x.Is(name)); + + /// + /// Finds an item with the specified item type for the local player. + /// + /// The type of item to find. + /// + /// An representing the item. If there are + /// multiple configured items with the same type, this method returns + /// one at random. If there no configured items with the specified type, + /// this method returns . + /// + public Item? FirstOrDefault(ItemType itemType) + => LocalPlayersItems().FirstOrDefault(x => x.Type == itemType); + + /// + /// Finds an reward with the specified item type. + /// + /// The type of reward to find. + /// + /// An representing the reward. If there are + /// multiple configured rewards with the same type, this method returns + /// one at random. If there no configured rewards with the specified type, + /// this method returns . + /// + public Reward? FirstOrDefault(RewardType rewardType) + => World.Rewards.FirstOrDefault(x => x.Type == rewardType); + + /// /// Returns the specific location matching the given id /// @@ -186,30 +294,16 @@ public IEnumerable Regions => World.Regions.FirstOrDefault(x => x.Name == name || x.GetType().FullName == name); public IEnumerable ViewedHintTiles - => World.HintTiles.Where(x => x.State?.HintState == HintState.Viewed); - - /// - /// Returns if a given region matches the LocationFilter - /// - /// The filter to apply - /// The SMZ3 region to check - /// True if the region matches, false otherwise - private static bool RegionMatchesFilter(RegionFilter filter, Region region) => filter switch - { - RegionFilter.None => true, - RegionFilter.ZeldaOnly => region is Z3Region, - RegionFilter.MetroidOnly => region is SMRegion, - _ => throw new InvalidEnumArgumentException(nameof(filter), (int)filter, typeof(RegionFilter)), - }; + => World.HintTiles.Where(x => x.HintState == HintState.Viewed); private bool IsKeysanityForLocation(Location location) => World.Config.KeysanityForRegion(location.Region); private Progression Progression(bool assumeKeys) - => _itemService.GetProgression(assumeKeys); + => _playerProgressionService.GetProgression(assumeKeys); private Progression Progression(Region region) - => _itemService.GetProgression(region); + => _playerProgressionService.GetProgression(region); } diff --git a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs index cc90dbe07..a955d81ee 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Tracker.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Tracker.cs @@ -1,35 +1,29 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Speech.Recognition; using System.Text; using System.Threading; using System.Threading.Tasks; - using BunLabs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using MSURandomizerLibrary.Configs; using TrackerCouncil.Smz3.Chat.Integration; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration; using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; -using TrackerCouncil.Smz3.Data.Logic; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.Data.Tracking; using TrackerCouncil.Smz3.Data.WorldData; -using TrackerCouncil.Smz3.Data.WorldData.Regions; -using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; using TrackerCouncil.Smz3.SeedGenerator.Contracts; -using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Shared.Models; using TrackerCouncil.Smz3.Tracking.Services; using TrackerCouncil.Smz3.Tracking.Services.Speech; +using TrackerCouncil.Smz3.Tracking.TrackingServices; using TrackerCouncil.Smz3.Tracking.VoiceCommands; namespace TrackerCouncil.Smz3.Tracking; @@ -51,16 +45,12 @@ public sealed class Tracker : TrackerBase, IDisposable private readonly Stack<(Action Action, DateTime UndoTime)> _undoHistory = new(); private readonly ICommunicator _communicator; private readonly ITrackerStateService _stateService; - private readonly IWorldService _worldService; private readonly ITrackerTimerService _timerService; - private readonly IMetadataService _metadataService; private readonly ISpeechRecognitionService _recognizer; private bool _disposed; private string? _lastSpokenText; private readonly bool _alternateTracker; private readonly HashSet _saidLines = new(); - private IEnumerable? _previousMissingItems; - private List _pendingSpeechItems = []; /// /// Initializes a new instance of the class. @@ -74,49 +64,46 @@ public sealed class Tracker : TrackerBase, IDisposable /// /// Used to write logging information. /// Provides Tracker preferences. - /// + /// /// /// Service for /// /// - /// /// - /// /// public Tracker(IWorldAccessor worldAccessor, TrackerModuleFactory moduleFactory, IChatClient chatClient, ILogger logger, TrackerOptionsAccessor trackerOptions, - IItemService itemService, + IPlayerProgressionService playerProgressionService, ICommunicator communicator, IHistoryService historyService, Configs configs, ITrackerStateService stateService, - IWorldService worldService, ITrackerTimerService timerService, - IMetadataService metadataService, IServiceProvider serviceProvider) { if (trackerOptions.Options == null) throw new InvalidOperationException("Tracker options have not yet been activated."); + LoadServices(serviceProvider); + _worldAccessor = worldAccessor; _moduleFactory = moduleFactory; _chatClient = chatClient; _logger = logger; _trackerOptions = trackerOptions; - ItemService = itemService; + PlayerProgressionService = playerProgressionService; _communicator = communicator; _stateService = stateService; - _worldService = worldService; _timerService = timerService; // Initialize the tracker configuration Configs = configs; Responses = configs.Responses; Requests = configs.Requests; - ItemService.ResetProgression(); + PlayerProgressionService.ResetProgression(); History = historyService; @@ -154,14 +141,12 @@ public Tracker(IWorldAccessor worldAccessor, { _recognizer = serviceProvider.GetRequiredService(); } + _recognizer.SpeechRecognized += Recognizer_SpeechRecognized; - _metadataService = metadataService; InitializeMicrophone(); World = _worldAccessor.World; Options = _trackerOptions.Options; - _communicator.SpeakCompleted += CommunicatorOnSpeakCompleted; - Mood = configs.CurrentMood ?? ""; } @@ -171,12 +156,12 @@ public Tracker(IWorldAccessor worldAccessor, } /// - /// Attempts to replace a user name with a pronunciation-corrected + /// Attempts to replace a username with a pronunciation-corrected /// version of it. /// - /// The user name to correct. + /// The username to correct. /// - /// The corrected user name, or . + /// The corrected username, or . /// public override string CorrectUserNamePronunciation(string userName) { @@ -223,13 +208,44 @@ public override bool Load(GeneratedRom rom, string romPath) if (trackerState != null) { + UpdateAllAccessibility(true); _timerService.SetSavedTime(TimeSpan.FromSeconds(trackerState.SecondsElapsed)); OnStateLoaded(); return true; } + return false; } + private void LoadServices(IServiceProvider serviceProvider) + { + var interfaceNamespace = typeof(TrackerBase).Namespace; + if (string.IsNullOrEmpty(interfaceNamespace)) + { + throw new InvalidOperationException("Could not determine TrackerBase namespace"); + } + + var properties = GetType().GetProperties() + .Where(pi => pi.PropertyType.IsInterface && pi.PropertyType.Namespace == interfaceNamespace) + .ToDictionary( + pi => pi.PropertyType, + pi => pi); + + var trackerService = serviceProvider.GetServices().ToList(); + foreach (var service in trackerService) + { + service.Tracker = this; + service.Initialize(); + var serviceInterface = service.GetType().GetInterfaces() + .FirstOrDefault(x => x.Namespace == interfaceNamespace); + if (serviceInterface != null && properties.TryGetValue(serviceInterface, out var property)) + { + property.SetValue(this, service); + } + } + + } + /// /// Saves the state of the tracker to the database /// @@ -240,6 +256,7 @@ public override async Task SaveAsync() { throw new NullReferenceException("No rom loaded into tracker"); } + IsDirty = false; await _stateService.SaveStateAsync(_worldAccessor.Worlds, Rom, _timerService.SecondsElapsed); } @@ -270,245 +287,6 @@ public override void Undo(float confidence) } } - /// - /// Toggles Go Mode on. - /// - /// The speech recognition confidence. - public override void ToggleGoMode(float? confidence = null) - { - ShutUp(); - - if (s_random.NextDouble(0, 1) < 0.95) - { - Say(text: "Toggled Go Mode ", wait: true); - } - else - { - Say(text: "Toggled Go Mode ", wait: true); - } - - GoMode = true; - OnGoModeToggledOn(new TrackerEventArgs(confidence)); - Say(text: "on."); - - AddUndo(() => - { - GoMode = false; - if (Responses.GoModeToggledOff != null) - Say(response: Responses.GoModeToggledOff); - OnGoModeToggledOff(new TrackerEventArgs(confidence)); - }); - } - - /// - /// Removes one or more items from the available treasure in the - /// specified dungeon. - /// - /// The dungeon. - /// The number of treasures to track. - /// The speech recognition confidence. - /// If this was called by the auto tracker - /// If tracker should state the treasure ammount - /// - /// true if treasure was tracked; false if there is no - /// treasure left to track. - /// - /// - /// This method adds to the undo history if the return value is - /// true. - /// - /// - /// is less than 1. - /// - public override bool TrackDungeonTreasure(IDungeon dungeon, float? confidence = null, int amount = 1, bool autoTracked = false, bool stateResponse = true) - { - if (amount < 1) - throw new ArgumentOutOfRangeException(nameof(amount), "The amount of items must be greater than zero."); - - if (amount > dungeon.DungeonState.RemainingTreasure && !dungeon.DungeonState.HasManuallyClearedTreasure) - { - _logger.LogWarning("Trying to track {Amount} treasures in a dungeon with only {Left} treasures left.", amount, dungeon.DungeonState.RemainingTreasure); - Say(response: Responses.DungeonTooManyTreasuresTracked, args: [dungeon.DungeonMetadata.Name, dungeon.DungeonState.RemainingTreasure, amount]); - return false; - } - - if (dungeon.DungeonState.RemainingTreasure > 0) - { - dungeon.DungeonState.RemainingTreasure -= amount; - - // If there are no more treasures and the boss is defeated, clear all locations in the dungeon - var clearedLocations = new List(); - if (dungeon.DungeonState is { RemainingTreasure: 0, Cleared: true }) - { - foreach (var location in ((Region)dungeon).Locations.Where(x => !x.State.Cleared)) - { - location.State.Cleared = true; - if (autoTracked) - { - location.State.Autotracked = true; - } - clearedLocations.Add(location); - } - } - - // Always add a response if there's treasure left, even when - // clearing a dungeon (because that means it was out of logic - // and could be relevant) - if (stateResponse && (confidence != null || dungeon.DungeonState.RemainingTreasure >= 1 || autoTracked)) - { - // Try to get the response based on the amount of items left - if (Responses.DungeonTreasureTracked?.TryGetValue(dungeon.DungeonState.RemainingTreasure, out var response) == true) - Say(response: response, args: [dungeon.DungeonMetadata.Name, dungeon.DungeonState.RemainingTreasure]); - // If we don't have a response for the exact amount and we - // have multiple left, get the one for 2 (considered - // generic) - else if (dungeon.DungeonState.RemainingTreasure >= 2 && Responses.DungeonTreasureTracked?.TryGetValue(2, out response) == true) - Say(response: response, args: [dungeon.DungeonMetadata.Name, dungeon.DungeonState.RemainingTreasure]); - } - - OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked)); - AddUndo(() => - { - dungeon.DungeonState.RemainingTreasure += amount; - foreach (var location in clearedLocations) - { - location.State.Cleared = false; - } - OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked)); - }); - - return true; - } - else if (stateResponse && confidence != null && Responses.DungeonTreasureTracked?.TryGetValue(-1, out var response) == true) - { - // Attempted to track treasure when all treasure items were - // already cleared out - Say(response: response, args: [dungeon.DungeonMetadata.Name]); - } - - return false; - } - - /// - /// Sets the dungeon's reward to the specific pendant or crystal. - /// - /// The dungeon to mark. - /// - /// The type of pendant or crystal, or null to cycle through the - /// possible rewards. - /// - /// The speech recognition confidence. - /// If this was called by the auto tracker - public override void SetDungeonReward(IDungeon dungeon, RewardType? reward = null, float? confidence = null, bool autoTracked = false) - { - var originalReward = dungeon.DungeonState.MarkedReward; - if (reward == null) - { - var currentValue = dungeon.DungeonState.MarkedReward ?? RewardType.None; - dungeon.DungeonState.MarkedReward = Enum.IsDefined(currentValue + 1) ? currentValue + 1 : RewardType.None; - // Cycling through rewards is done via UI, so speaking the - // reward out loud for multiple clicks is kind of annoying - } - else - { - dungeon.DungeonState.MarkedReward = reward.Value; - var rewardObj = ItemService.FirstOrDefault(reward.Value); - Say(response: Responses.DungeonRewardMarked, args: [dungeon.DungeonMetadata.Name, rewardObj?.Metadata.Name ?? reward.GetDescription()]); - } - - OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked)); - - if (!autoTracked) AddUndo(() => - { - dungeon.DungeonState.MarkedReward = originalReward; - OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked)); - }); - } - - /// - /// Sets the reward of all unmarked dungeons. - /// - /// The reward to set. - /// The speech recognition confidence. - public override void SetUnmarkedDungeonReward(RewardType reward, float? confidence = null) - { - var unmarkedDungeons = World.Dungeons - .Where(x => x.DungeonState is { HasReward: true, HasMarkedReward: false }) - .ToImmutableList(); - - if (unmarkedDungeons.Count > 0) - { - Say(response: Responses.RemainingDungeonsMarked, args: [ItemService.GetName(reward)]); - unmarkedDungeons.ForEach(dungeon => dungeon.DungeonState.MarkedReward = reward); - AddUndo(() => - { - unmarkedDungeons.ForEach(dungeon => dungeon.DungeonState.MarkedReward = RewardType.None); - OnDungeonUpdated(new DungeonTrackedEventArgs(null, confidence, false)); - }); - OnDungeonUpdated(new DungeonTrackedEventArgs(null, confidence, false)); - } - else - { - Say(response: Responses.NoRemainingDungeons); - } - } - - /// - /// Sets the dungeon's medallion requirement to the specified item. - /// - /// The dungeon to mark. - /// The medallion that is required. - /// The speech recognition confidence. - /// If the marked dungeon requirement was autotracked - public override void SetDungeonRequirement(IDungeon dungeon, ItemType? medallion = null, float? confidence = null, bool autoTracked = false) - { - var region = World.Regions.SingleOrDefault(x => dungeon.DungeonMetadata.Name?.Contains(x.Name, StringComparison.OrdinalIgnoreCase) == true); - if (region == null) - { - Say(text: "Strange, I can't find that dungeon in this seed."); - } - else if (region is not INeedsMedallion) - { - Say(response: Responses.DungeonRequirementInvalid, args: [dungeon.DungeonMetadata.Name]); - return; - } - - var originalRequirement = dungeon.DungeonState.MarkedMedallion ?? ItemType.Nothing; - if (medallion == null) - { - var medallionItems = new List(Enum.GetValues()); - medallionItems.Insert(0, ItemType.Nothing); - var index = (medallionItems.IndexOf(originalRequirement) + 1) % medallionItems.Count; - dungeon.DungeonState.MarkedMedallion = medallionItems[index]; - OnDungeonUpdated(new DungeonTrackedEventArgs(null, confidence, false)); - } - else - { - if (region is INeedsMedallion medallionRegion - && medallionRegion.Medallion != ItemType.Nothing - && medallionRegion.Medallion != medallion.Value - && confidence >= Options.MinimumSassConfidence) - { - Say(response: Responses.DungeonRequirementMismatch, - args: [ - HintsEnabled ? "a different medallion" : medallionRegion.Medallion.ToString(), - dungeon.DungeonMetadata.Name, - medallion.Value.ToString() - ]); - } - - dungeon.DungeonState.MarkedMedallion = medallion.Value; - Say(response: Responses.DungeonRequirementMarked, args: [medallion.ToString(), dungeon.DungeonMetadata.Name]); - OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, false)); - } - - AddUndo(() => - { - dungeon.DungeonState.MarkedMedallion = originalRequirement; - OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, false)); - }); - } - /// /// Starts voice recognition. /// @@ -549,7 +327,7 @@ public override bool TryStartTracking() /// /// Connects Tracker to chat. /// - /// The user name to connect as. + /// The username to connect as. /// /// The OAuth token for . /// @@ -647,7 +425,7 @@ public override void StopTracking() DisableVoiceRecognition(); _communicator.Abort(); _chatClient.Disconnect(); - Say(response: GoMode ? Responses.StoppedTrackingPostGoMode : Responses.StoppedTracking, wait: true); + Say(response: ModeTracker.GoMode ? Responses.StoppedTrackingPostGoMode : Responses.StoppedTracking, wait: true); foreach (var timer in _idleTimers.Values) timer.Change(Timeout.Infinite, Timeout.Infinite); @@ -727,7 +505,7 @@ public override bool Say( selectedResponse = response ?? selectResponse?.Invoke(Responses); } - if (once == true && selectedResponse != null && !_saidLines.Add(selectedResponse)) + if (once && selectedResponse != null && !_saidLines.Add(selectedResponse)) { // True because we did successfully select a response, even though it was already said before return true; @@ -740,6 +518,7 @@ public override bool Say( { return false; } + text = trackerSpeechDetails.Value.SpeechText; trackerImage = trackerSpeechDetails.Value.TrackerImage; } @@ -824,1844 +603,86 @@ public void Dispose() } /// - /// Tracks the specifies item. + /// Resets the timers for tracker mentioning nothing has happened /// - /// The item data to track. - /// - /// The text that was tracked, when triggered by voice command. - /// - /// The speech recognition confidence. - /// - /// to attempt to clear a location for the - /// tracked item; if that is done by the caller. - /// - /// If this was tracked by the auto tracker - /// The location an item was tracked from - /// If the item was gifted to the player by tracker or another player - /// If tracker should not say anything - /// - /// if the item was actually tracked; if the item could not be tracked, e.g. when - /// tracking Bow twice. - /// - public override bool TrackItem(Item item, string? trackedAs = null, float? confidence = null, bool tryClear = true, bool autoTracked = false, Location? location = null, bool giftedItem = false, bool silent = false) + public override void RestartIdleTimers() { - var didTrack = false; - var accessibleBefore = _worldService.AccessibleLocations(false); - var itemName = item.Name; - var originalTrackingState = item.State.TrackingState; - ItemService.ResetProgression(); - - var isGTPreBigKey = !World.Config.ZeldaKeysanity - && autoTracked - && location?.Region.GetType() == typeof(GanonsTower) - && !ItemService.GetProgression(false).BigKeyGT; - var stateResponse = !isGTPreBigKey && !silent && (!autoTracked - || !item.Metadata.IsDungeonItem() - || World.Config.ZeldaKeysanity); - - if (stateResponse && _communicator.IsSpeaking) - { - _pendingSpeechItems.Add(item); - stateResponse = false; - } - - // Actually track the item if it's for the local player's world - if (item.World == World) - { - if (item.Metadata.HasStages) - { - if (trackedAs != null && item.Metadata.GetStage(trackedAs) != null) - { - var stage = item.Metadata.GetStage(trackedAs)!; - - // Tracked by specific stage name (e.g. Tempered Sword), set - // to that stage specifically - var stageName = item.Metadata.Stages[stage.Value].ToString(); - - didTrack = item.Track(stage.Value); - if (stateResponse) - { - if (didTrack) - { - if (item.TryGetTrackingResponse(out var response)) - { - Say(response: response, args: [item.Counter]); - } - else - { - Say(response: Responses.TrackedItemByStage, args: [itemName, stageName]); - } - } - else - { - Say(response: Responses.TrackedOlderProgressiveItem, args: [itemName, item.Metadata.Stages[item.State.TrackingState].ToString()]); - } - } - } - else - { - // Tracked by regular name, upgrade by one step - didTrack = item.Track(); - if (stateResponse) - { - if (didTrack) - { - if (item.TryGetTrackingResponse(out var response)) - { - Say(response: response, args: [item.Counter]); - } - else - { - var stageName = item.Metadata.Stages[item.State.TrackingState].ToString(); - Say(response: Responses.TrackedProgressiveItem, args: [itemName, stageName]); - } - } - else - { - Say(response: Responses.TrackedTooManyOfAnItem, args: [itemName]); - } - } - } - } - else if (item.Metadata.Multiple) - { - didTrack = item.Track(); - if (item.TryGetTrackingResponse(out var response)) - { - if (stateResponse) - Say(response: response, args: [item.Counter]); - } - else if (item.Counter == 1) - { - if (stateResponse) - Say(response: Responses.TrackedItem, args: [itemName, item.Metadata.NameWithArticle]); - } - else if (item.Counter > 1) - { - if (stateResponse) - Say(response: Responses.TrackedItemMultiple, args: [item.Metadata.Plural ?? $"{itemName}s", item.Counter, item.Name]); - } - else - { - _logger.LogWarning("Encountered multiple item with counter 0: {Item} has counter {Counter}", item, item.Counter); - if (stateResponse) - Say(response: Responses.TrackedItem, args: [itemName, item.Metadata.NameWithArticle]); - } - } - else - { - didTrack = item.Track(); - if (stateResponse) - { - if (didTrack) - { - if (item.TryGetTrackingResponse(out var response)) - { - Say(response: response, args: [item.Counter]); - } - else - { - Say(response: Responses.TrackedItem, args: [itemName, item.Metadata.NameWithArticle]); - } - } - else - { - Say(response: Responses.TrackedAlreadyTrackedItem, args: [itemName]); - } - } - } - } - - var undoTrack = () => - { - item.State.TrackingState = originalTrackingState; ItemService.ResetProgression(); - OnItemTracked(new ItemTrackedEventArgs(item, trackedAs, confidence, autoTracked)); - }; - OnItemTracked(new ItemTrackedEventArgs(item, trackedAs, confidence, autoTracked)); - - // Check if we can clear a location - Action? undoClear = null; - Action? undoTrackDungeonTreasure = null; - - - // If this was not gifted to the player, try to clear the location - if (!giftedItem && item.Type != ItemType.Nothing) - { - if (location == null && !World.Config.MultiWorld) - { - location = _worldService.Locations(outOfLogic: true, itemFilter: item.Type).TrySingle(); - } - - // Clear the location if it's for the local player's world - if (location != null && location.World == World && location.State.Cleared == false) - { - if (stateResponse) - { - GiveLocationComment(item.Type, location, isTracking: true, confidence, item.Metadata); - GivePreConfiguredLocationSass(location); - } - - if (tryClear) - { - // If this item was in a dungeon, track treasure count - undoTrackDungeonTreasure = TryTrackDungeonTreasure(location, confidence, autoTracked, stateResponse); - - // Important: clear only after tracking dungeon treasure, as - // the "guess dungeon from location" algorithm excludes - // cleared items - location.State.Cleared = true; - World.LastClearedLocation = location; - OnLocationCleared(new(location, confidence, autoTracked)); - - undoClear = () => location.State.Cleared = false; - if ((location.State.MarkedItem ?? ItemType.Nothing) != ItemType.Nothing) - { - location.State.MarkedItem = null; - OnMarkedLocationsUpdated(new TrackerEventArgs(confidence)); - } - } - - var isKeysanityForLocation = (location.Region is Z3Region && World.Config.ZeldaKeysanity) || (location.Region is SMRegion && World.Config.MetroidKeysanity); - var items = ItemService.GetProgression(!isKeysanityForLocation); - if (stateResponse && !location.IsAvailable(items) && (confidence >= Options.MinimumSassConfidence || autoTracked)) - { - var locationInfo = location.Metadata; - var roomInfo = location.Room?.Metadata; - var regionInfo = location.Region.Metadata; - - if (locationInfo.OutOfLogic != null) - { - Say(response: locationInfo.OutOfLogic); - } - else if (roomInfo?.OutOfLogic != null) - { - Say(response: roomInfo.OutOfLogic); - } - else if (regionInfo.OutOfLogic != null) - { - Say(response: regionInfo.OutOfLogic); - } - else - { - var allMissingCombinations = Logic.GetMissingRequiredItems(location, items, out var allMissingItems); - allMissingItems = allMissingItems.OrderBy(x => x); - - var missingItems = allMissingCombinations.MinBy(x => x.Length); - var allPossibleMissingItems = allMissingItems.ToList(); - if (missingItems == null) - { - Say(x => x.TrackedOutOfLogicItemTooManyMissing, args: [item.Metadata.Name, locationInfo.Name]); - } - // Do not say anything if the only thing missing are keys - else - { - var itemsChanged = _previousMissingItems == null || !allPossibleMissingItems.SequenceEqual(_previousMissingItems); - var onlyKeys = allPossibleMissingItems.All(x => x.IsInAnyCategory(ItemCategory.BigKey, ItemCategory.SmallKey, ItemCategory.Keycard)); - _previousMissingItems = allPossibleMissingItems; - - if (itemsChanged && !onlyKeys) - { - var missingItemNames = NaturalLanguage.Join(missingItems.Select(ItemService.GetName)); - Say(x => x.TrackedOutOfLogicItem, args: [item.Metadata.Name, locationInfo.Name, missingItemNames]); - } - } - - _previousMissingItems = allPossibleMissingItems; - } - - } - } - } - - var addedEvent = History.AddEvent( - HistoryEventType.TrackedItem, - item.Metadata.IsProgression(World.Config), - item.Metadata.NameWithArticle, - location - ); - - IsDirty = true; - - if (!autoTracked) + foreach (var item in _idleTimers) { - AddUndo(() => - { - undoTrack(); - undoClear?.Invoke(); - undoTrackDungeonTreasure?.Invoke(); - ItemService.ResetProgression(); - addedEvent.IsUndone = true; - }); - } - - GiveLocationHint(accessibleBefore); - RestartIdleTimers(); - - return didTrack; - } + var timeout = Parse.AsTimeSpan(item.Key, s_random) ?? Timeout.InfiniteTimeSpan; + var timer = item.Value; - /// - /// Makes Tracker respond to a location if it was pre-configured by the user. - /// - /// The location at which an item was marked or tracked. - /// if marking, if tracking. - private void GivePreConfiguredLocationSass(Location location, bool marking = false) - { - // "What a surprise." - if (LocalConfig != null && LocalConfig.LocationItems.ContainsKey(location.Id)) - { - // I guess we're not storing the other options? We could respond to those, too, if we had those here. - Say(x => marking ? x.LocationMarkedPreConfigured : x.TrackedPreConfigured, args: [location.Metadata.Name]); + timer.Change(timeout, Timeout.InfiniteTimeSpan); } } /// - /// Tracks multiple items at the same time + /// Adds an action to be invoked to undo the last operation. /// - /// The items to track - /// If the items were tracked via auto tracker - /// If the items were gifted to the player - public override void TrackItems(List items, bool autoTracked, bool giftedItem) - { - if (items.Count == 1) - { - TrackItem(items.First(), null, null, false, autoTracked, null, giftedItem); - return; - } - - ItemService.ResetProgression(); - - foreach (var item in items) - { - item.Track(); - } + /// + /// The action to invoke to undo the last operation. + /// + public override void AddUndo(Action undo) => _undoHistory.Push((undo, DateTime.Now)); - AnnounceTrackedItems(items); + public override (Action Action, DateTime UndoTime) PopUndo() => _undoHistory.Pop(); - OnItemTracked(new ItemTrackedEventArgs(null, null, null, true)); - IsDirty = true; - RestartIdleTimers(); - } + public override void MarkAsDirty(bool isDirty = true) => IsDirty = isDirty; - /// - /// Removes an item from the tracker. - /// - /// The item to untrack. - /// The speech recognition confidence. - public override void UntrackItem(Item item, float? confidence = null) + public override void UpdateAllAccessibility(bool forceRefreshAll, params Item[] items) { - var originalTrackingState = item.State.TrackingState; - ItemService.ResetProgression(); - - if (!item.Untrack()) + // Skip if the items don't affect anything + if (items.Length > 0 && !forceRefreshAll && items.All(x => + x.Type.IsInCategory(ItemCategory.NeverProgression) || + (x.TrackingState > 1 && x.Type.IsInCategory(ItemCategory.ProgressionOnlyOnFirst)))) { - Say(response: Responses.UntrackedNothing, args: [item.Name, item.Metadata.NameWithArticle]); return; } - if (item.Metadata.HasStages) - { - Say(response: Responses.UntrackedProgressiveItem, args: [item.Name, item.Metadata.NameWithArticle]); - } - else if (item.Metadata.Multiple) - { - if (item.State.TrackingState > 0) - { - if (item.Metadata.CounterMultiplier > 1) - Say(text: Responses.UntrackedItemMultiple?.Format($"{item.Metadata.CounterMultiplier} {item.Metadata.Plural}", $"{item.Metadata.CounterMultiplier} {item.Metadata.Plural}")); - else - Say(response: Responses.UntrackedItemMultiple, args: [item.Name, item.Metadata.NameWithArticle]); - } - else - Say(response: Responses.UntrackedItemMultipleLast, args: [item.Name, item.Metadata.NameWithArticle]); - } - else - { - Say(response: Responses.UntrackedItem, args: [item.Name, item.Metadata.NameWithArticle]); - } + PlayerProgressionService.ResetProgression(); - IsDirty = true; - OnItemTracked(new(item, null, confidence, false)); - AddUndo(() => - { - item.State.TrackingState = originalTrackingState; ItemService.ResetProgression(); - OnItemTracked(new(item, null, confidence, false)); - }); + var actualProgression = PlayerProgressionService.GetProgression(false); + var assumedKeysProgression = PlayerProgressionService.GetProgression(true); + + LocationTracker.UpdateAccessibility(!forceRefreshAll, actualProgression, assumedKeysProgression); + RewardTracker.UpdateAccessibility(actualProgression, assumedKeysProgression); + BossTracker.UpdateAccessibility(actualProgression, assumedKeysProgression); } /// - /// Tracks the specifies item and clears it from the specified dungeon. + /// Cleans up resources used by this class. /// - /// The item data to track. - /// - /// The text that was tracked, when triggered by voice command. + /// + /// true to dispose of managed resources. /// - /// The dungeon the item was tracked in. - /// The speech recognition confidence. - public override void TrackItem(Item item, IDungeon dungeon, string? trackedAs = null, float? confidence = null) + private void Dispose(bool disposing) { - var tracked = TrackItem(item, trackedAs, confidence, tryClear: false); - var undoTrack = _undoHistory.Pop(); - ItemService.ResetProgression(); - - // Check if we can remove something from the remaining treasures in - // a dungeon - Action? undoTrackTreasure = null; - if (tracked) // Only track treasure if we actually tracked anything - { - dungeon = GetDungeonFromItem(item, dungeon)!; - if (TrackDungeonTreasure(dungeon, confidence)) - undoTrackTreasure = _undoHistory.Pop().Action; - } - - IsDirty = true; - - // Check if we can remove something from the marked location - var location = _worldService.Locations(itemFilter: item.Type, inRegion: dungeon as Region).TrySingle(); - if (location != null) + if (!_disposed) { - location.State.Cleared = true; - World.LastClearedLocation = location; - OnLocationCleared(new(location, confidence, false)); - - if (location.State.HasMarkedItem) + if (disposing) { - location.State.MarkedItem = null; - OnMarkedLocationsUpdated(new TrackerEventArgs(confidence)); + (_recognizer as IDisposable)?.Dispose(); + (_communicator as IDisposable)?.Dispose(); + + foreach (var timer in _idleTimers.Values) + timer.Dispose(); } - AddUndo(() => - { - undoTrack.Action(); - undoTrackTreasure?.Invoke(); - location.State.Cleared = false; - ItemService.ResetProgression(); - OnLocationCleared(new(location, confidence, false)); - }); - } - else - { - AddUndo(() => - { - undoTrack.Action(); - undoTrackTreasure?.Invoke(); - ItemService.ResetProgression(); - }); + _disposed = true; } } - /// - /// Tracks the specified item and clears it from the specified room. - /// - /// The item data to track. - /// - /// The text that was tracked, when triggered by voice command. - /// - /// The area the item was found in. - /// The speech recognition confidence. - public override void TrackItem(Item item, IHasLocations area, string? trackedAs = null, float? confidence = null) + private void IdleTimerElapsed(object? state) { - var locations = area.Locations - .Where(x => x.Item.Type == item.Type) - .ToImmutableList(); - ItemService.ResetProgression(); + var key = (string)state!; + Say(response: Responses.Idle?[key]); + } - if (locations.Count == 0) - { - Say(response: Responses.AreaDoesNotHaveItem, args: [item.Name, area.Name, item.Metadata.NameWithArticle]); - } - else if (locations.Count > 1) - { - // Consider tracking/clearing everything? - Say(response: Responses.AreaHasMoreThanOneItem, args: [item.Name, area.Name, item.Metadata.NameWithArticle]); - } - - IsDirty = true; - - TrackItem(item, trackedAs, confidence, tryClear: false); - if (locations.Count == 1) - { - Clear(locations.Single()); - var undoClear = _undoHistory.Pop(); - var undoTrack = _undoHistory.Pop(); - AddUndo(() => - { - undoClear.Action(); - undoTrack.Action(); - ItemService.ResetProgression(); - }); - } - } - - /// - /// Sets the item count for the specified item. - /// - /// The item to track. - /// - /// The amount of the item that is in the player's inventory now. - /// - /// The speech recognition confidence. - public override void TrackItemAmount(Item item, int count, float confidence) - { - ItemService.ResetProgression(); - - var newItemCount = count; - if (item.Metadata.CounterMultiplier > 1 - && count % item.Metadata.CounterMultiplier == 0) - { - newItemCount = count / item.Metadata.CounterMultiplier.Value; - } - - var oldItemCount = item.State.TrackingState; - if (newItemCount == oldItemCount) - { - Say(response: Responses.TrackedExactAmountDuplicate, args: [item.Metadata.Plural, count]); - return; - } - - item.State.TrackingState = newItemCount; - if (item.TryGetTrackingResponse(out var response)) - { - Say(response: response, args: [item.Counter]); - } - else if (newItemCount > oldItemCount) - { - Say(text: Responses.TrackedItemMultiple?.Format(item.Metadata.Plural ?? $"{item.Name}s", item.Counter, item.Name)); - } - else - { - Say(text: Responses.UntrackedItemMultiple?.Format(item.Metadata.Plural ?? $"{item.Name}s", item.Metadata.Plural ?? $"{item.Name}s")); - } - - IsDirty = true; - - AddUndo(() => - { - item.State.TrackingState = oldItemCount; ItemService.ResetProgression(); - OnItemTracked(new(item, null, confidence, false)); - }); - OnItemTracked(new(item, null, confidence, false)); - } - - /// - /// Clears every item in the specified area, optionally tracking the - /// cleared items. - /// - /// The area whose items to clear. - /// - /// true to track any items found; false to only clear the - /// affected locations. - /// - /// - /// true to include every item in , even - /// those that are not in logic. false to only include chests - /// available with current items. - /// - /// The speech recognition confidence. - /// - /// Set to true to ignore keys when clearing the location. - /// - public override void ClearArea(IHasLocations area, bool trackItems, bool includeUnavailable = false, float? confidence = null, bool assumeKeys = false) - { - var locations = area.Locations - .Where(x => x.State.Cleared == false) - .WhereUnless(includeUnavailable, x => x.IsAvailable(ItemService.GetProgression(area))) - .ToImmutableList(); - - ItemService.ResetProgression(); - - if (locations.Count == 0) - { - var outOfLogicLocations = area.Locations - .Count(x => x.State.Cleared == false); - - if (outOfLogicLocations > 0) - Say(responses: Responses.TrackedNothingOutOfLogic, tieredKey: outOfLogicLocations, args: [area.Name, outOfLogicLocations]); - else - Say(response: Responses.TrackedNothing, args: [area.Name]); - } - else - { - // If there is only one (available) item here, just call the - // regular TrackItem instead - var onlyLocation = locations.TrySingle(); - if (onlyLocation != null) - { - if (!trackItems) - { - Clear(onlyLocation, confidence); - } - else - { - var item = onlyLocation.Item; - TrackItem(item: item, trackedAs: null, confidence: confidence, tryClear: true, autoTracked: false, location: onlyLocation); - } - } - else - { - // Otherwise, start counting - var itemsCleared = 0; - var itemsTracked = new List(); - var treasureTracked = 0; - foreach (var location in locations) - { - itemsCleared++; - if (!trackItems) - { - if (IsTreasure(location.Item) || World.Config.ZeldaKeysanity) - treasureTracked++; - location.State.Cleared = true; - World.LastClearedLocation = location; - OnLocationCleared(new(location, confidence, false)); - continue; - } - - var item = location.Item; - if (!item.Track()) - _logger.LogWarning("Failed to track {ItemType} in {Area}.", item.Name, area.Name); // Probably the compass or something, who cares - else - itemsTracked.Add(item); - if (IsTreasure(location.Item) || World.Config.ZeldaKeysanity) - treasureTracked++; - - location.State.Cleared = true; - } - - if (trackItems) - { - var itemNames = confidence >= Options.MinimumSassConfidence - ? NaturalLanguage.Join(itemsTracked, World.Config) - : $"{itemsCleared} items"; - Say(x => x.TrackedMultipleItems, args: [itemsCleared, area.Name, itemNames]); - - var roomInfo = area is Room room ? room.Metadata : null; - var regionInfo = area is Region region ? region.Metadata : null; - - if (roomInfo?.OutOfLogic != null) - { - Say(response: roomInfo.OutOfLogic); - } - else if (regionInfo?.OutOfLogic != null) - { - Say(response: regionInfo.OutOfLogic); - } - else - { - var progression = ItemService.GetProgression(area); - var someOutOfLogicLocation = locations.Where(x => !x.IsAvailable(progression)).Random(s_random); - if (someOutOfLogicLocation != null && confidence >= Options.MinimumSassConfidence) - { - var someOutOfLogicItem = someOutOfLogicLocation.Item; - var missingItems = Logic.GetMissingRequiredItems(someOutOfLogicLocation, progression, out _).MinBy(x => x.Length); - if (missingItems != null) - { - var missingItemNames = NaturalLanguage.Join(missingItems.Select(ItemService.GetName)); - Say(x => x.TrackedOutOfLogicItem, args: [someOutOfLogicItem.Metadata.Name, someOutOfLogicLocation.Metadata.Name, missingItemNames]); - } - else - { - Say(x => x.TrackedOutOfLogicItemTooManyMissing, args: [someOutOfLogicItem.Metadata.Name, someOutOfLogicLocation.Metadata.Name]); - } - } - } - } - else - { - Say(x => x.ClearedMultipleItems, args: [itemsCleared, area.Name]); - } - - if (treasureTracked > 0) - { - var dungeon = GetDungeonFromArea(area); - if (dungeon != null) - { - TrackDungeonTreasure(dungeon, amount: treasureTracked); - } - } - } - - OnItemTracked(new ItemTrackedEventArgs(null, null, confidence, false)); - } - - IsDirty = true; - - AddUndo(() => - { - foreach (var location in locations) - { - if (trackItems) - { - var item = location.Item; - if (item.Type != ItemType.Nothing && item.State.TrackingState > 0) - item.State.TrackingState--; - } - - location.State.Cleared = false; - } - ItemService.ResetProgression(); - }); - } - - /// - /// Marks all locations and treasure within a dungeon as cleared. - /// - /// The dungeon to clear. - /// The speech recognition confidence. - public override void ClearDungeon(IDungeon dungeon, float? confidence = null) - { - var remaining = dungeon.DungeonState.RemainingTreasure; - if (remaining > 0) - { - dungeon.DungeonState.RemainingTreasure = 0; - } - - // Clear the dungeon only if there's no bosses to defeat - if (!dungeon.DungeonState.HasReward) - dungeon.DungeonState.Cleared = true; - - var region = (Region)dungeon; - var progress = ItemService.GetProgression(assumeKeys: !World.Config.ZeldaKeysanity); - var locations = region.Locations.Where(x => x.State.Cleared == false).ToList(); - var inaccessibleLocations = locations.Where(x => !x.IsAvailable(progress)).ToList(); - if (locations.Count > 0) - { - foreach (var state in locations.Select(x => x.State).NonNull()) - { - state.Cleared = true; - } - } - - if (remaining <= 0 && locations.Count <= 0) - { - // We didn't do anything - Say(x => x.DungeonAlreadyCleared, args: [dungeon.DungeonMetadata.Name]); - return; - } - - Say(x => x.DungeonCleared, args: [dungeon.DungeonMetadata.Name]); - if (inaccessibleLocations.Count > 0 && confidence >= Options.MinimumSassConfidence) - { - var anyMissedLocation = inaccessibleLocations.Random(s_random) ?? inaccessibleLocations.First(); - var locationInfo = anyMissedLocation.Metadata; - var missingItemCombinations = Logic.GetMissingRequiredItems(anyMissedLocation, progress, out _).ToList(); - if (missingItemCombinations.Any()) - { - var missingItems = (missingItemCombinations.Random(s_random) ?? missingItemCombinations.First()) - .Select(ItemService.FirstOrDefault) - .NonNull(); - var missingItemsText = NaturalLanguage.Join(missingItems, World.Config); - Say(x => x.DungeonClearedWithInaccessibleItems, args: [dungeon.DungeonMetadata.Name, locationInfo.Name, missingItemsText]); - } - else - { - Say(x => x.DungeonClearedWithTooManyInaccessibleItems, args: [dungeon.DungeonMetadata.Name, locationInfo.Name]); - } - } - ItemService.ResetProgression(); - - OnDungeonUpdated(new(dungeon, confidence, false)); - AddUndo(() => - { - dungeon.DungeonState.RemainingTreasure = remaining; - if (remaining > 0 && !dungeon.DungeonState.HasReward) - dungeon.DungeonState.Cleared = false; - foreach (var state in locations.Select(x => x.State).NonNull()) - { - state.Cleared = false; - } - ItemService.ResetProgression(); - OnDungeonUpdated(new(dungeon, confidence, false)); - }); - } - - /// - /// Clears an item from the specified location. - /// - /// The location to clear. - /// The speech recognition confidence. - /// If this was tracked by the auto tracker - public override void Clear(Location location, float? confidence = null, bool autoTracked = false) - { - ItemService.ResetProgression(); - location.State.Cleared = true; - - if (confidence != null) - { - // Only use TTS if called from a voice command - var locationName = location.Metadata.Name; - Say(response: Responses.LocationCleared, args: [locationName]); - } - - ItemType? prevMarkedItem = null; - if (location.State.HasMarkedItem) - { - prevMarkedItem = location.State.MarkedItem; - location.State.MarkedItem = null; - OnMarkedLocationsUpdated(new TrackerEventArgs(confidence)); - } - - var undoTrackTreasure = TryTrackDungeonTreasure(location, confidence); - - Action? undoStopPegWorldMode = null; - if (location == World.DarkWorldNorthWest.PegWorld) - { - StopPegWorldMode(); - - if (!autoTracked) - { - undoStopPegWorldMode = _undoHistory.Pop().Action; - } - } - - IsDirty = true; - - if (!autoTracked) - { - AddUndo(() => - { - location.State.Cleared = false; - location.State.MarkedItem = prevMarkedItem; - if (prevMarkedItem != null) - { - OnMarkedLocationsUpdated(new TrackerEventArgs(null)); - } - undoTrackTreasure?.Invoke(); - undoStopPegWorldMode?.Invoke(); - ItemService.ResetProgression(); - OnLocationCleared(new(location, confidence, autoTracked)); - }); - } - - World.LastClearedLocation = location; - OnLocationCleared(new(location, confidence, autoTracked)); - } - - /// - /// Clears an item from the specified locations. - /// - /// The locations to clear. - /// The speech recognition confidence - public override void Clear(List locations, float? confidence = null) - { - if (locations.Count == 1) - { - Clear(locations.First(), confidence); - return; - } - - ItemService.ResetProgression(); - - var originalClearedValues = new Dictionary(); - var originalMarkedItems = new Dictionary(); - - // Clear the locations - foreach (var location in locations) - { - originalClearedValues.Add(location, location.State.Cleared); - if (location.State.MarkedItem != null) - { - originalMarkedItems.Add(location, location.State.MarkedItem.Value); - } - location.State.Cleared = true; - location.State.MarkedItem = null; - } - - Action? undoDungeonTreasure = null; - if (locations.Select(x => x.Region).Distinct().Count() == 1) - { - Say(x => x.LocationsClearedSameRegion, args: [locations.Count, locations.First().Region.GetName()]); - if (locations.First().Region is IDungeon dungeon) - { - var treasureCount = locations.Count(x => IsTreasure(x.Item) || World.Config.ZeldaKeysanity); - if (TrackDungeonTreasure(dungeon, confidence, treasureCount)) - undoDungeonTreasure = _undoHistory.Pop().Action; - } - } - else - { - Say(x => x.LocationsCleared, args: [locations.Count]); - } - - AddUndo(() => - { - foreach (var location in locations) - { - location.State.Cleared = originalClearedValues[location]; - if (originalMarkedItems.TryGetValue(location, out var item)) - { - location.State.MarkedItem = item; - } - } - undoDungeonTreasure?.Invoke(); - ItemService.ResetProgression(); - OnLocationCleared(new(locations.First(), confidence, false)); - }); - - World.LastClearedLocation = locations.First(); - if (originalMarkedItems.Count > 0) - { - OnMarkedLocationsUpdated(new TrackerEventArgs(confidence)); - } - IsDirty = true; - OnLocationCleared(new(locations.First(), confidence, false)); - } - - /// - /// Marks a dungeon as cleared and, if possible, tracks the boss reward. - /// - /// The dungeon that was cleared. - /// The speech recognition confidence. - /// If this was cleared by the auto tracker - public override void MarkDungeonAsCleared(IDungeon dungeon, float? confidence = null, bool autoTracked = false) - { - if (dungeon.DungeonState.Cleared) - { - if (!autoTracked) - Say(response: Responses.DungeonBossAlreadyCleared, args: [dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss]); - else - OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked)); - - return; - } - - ItemService.ResetProgression(); - - var addedEvent = History.AddEvent( - HistoryEventType.BeatBoss, - true, - dungeon.DungeonMetadata.Boss?.ToString() ?? $"boss of {dungeon.DungeonMetadata.Name}" - ); - - // If all treasures have been retrieved and the boss is defeated, clear all locations in the dungeon - var clearedLocations = new List(); - if (dungeon.DungeonState.RemainingTreasure == 0) - { - foreach (var location in ((Region)dungeon).Locations.Where(x => !x.State.Cleared)) - { - location.State.Cleared = true; - if (autoTracked) - { - location.State.Autotracked = true; - } - clearedLocations.Add(location); - } - } - - // Auto track the dungeon reward if not already marked - if (autoTracked && dungeon.DungeonState.MarkedReward != dungeon.DungeonState.Reward) - { - dungeon.DungeonState.MarkedReward = dungeon.DungeonState.Reward; - SetDungeonReward(dungeon, dungeon.DungeonState.Reward); - } - - dungeon.DungeonState.Cleared = true; - Say(response: Responses.DungeonBossCleared, args: [dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss]); - IsDirty = true; - RestartIdleTimers(); - OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked)); - - if (!autoTracked) - { - AddUndo(() => - { - ItemService.ResetProgression(); - dungeon.DungeonState.Cleared = false; - addedEvent.IsUndone = true; - foreach (var location in clearedLocations) - { - location.State.Cleared = false; - } - OnDungeonUpdated(new DungeonTrackedEventArgs(dungeon, confidence, autoTracked)); - }); - } - } - - /// - /// Marks a boss as defeated. - /// - /// The boss that was defeated. - /// - /// if the command implies the boss was killed; - /// if the boss was simply "tracked". - /// - /// The speech recognition confidence. - /// If this was tracked by the auto tracker - public override void MarkBossAsDefeated(Boss boss, bool admittedGuilt = true, float? confidence = null, bool autoTracked = false) - { - if (boss.State.Defeated) - { - if (!autoTracked) - Say(x => x.BossAlreadyDefeated, args: [boss.Name]); - else - OnBossUpdated(new(boss, confidence, autoTracked)); - return; - } - - boss.State.Defeated = true; - - if (!admittedGuilt && boss.Metadata.WhenTracked != null) - Say(response: boss.Metadata.WhenTracked, args: [boss.Name]); - else - Say(response: boss.Metadata.WhenDefeated ?? Responses.BossDefeated, args: [boss.Name]); - - var addedEvent = History.AddEvent( - HistoryEventType.BeatBoss, - true, - boss.Name - ); - - IsDirty = true; - ItemService.ResetProgression(); - - RestartIdleTimers(); - OnBossUpdated(new(boss, confidence, autoTracked)); - - if (!autoTracked) - { - AddUndo(() => - { - boss.State.Defeated = false; - addedEvent.IsUndone = true; - OnBossUpdated(new(boss, confidence, autoTracked)); - }); - } - } - - /// - /// Un-marks a boss as defeated. - /// - /// The boss that should be 'revived'. - /// The speech recognition confidence. - public override void MarkBossAsNotDefeated(Boss boss, float? confidence = null) - { - if (boss.State.Defeated != true) - { - Say(x => x.BossNotYetDefeated, args: [boss.Name]); - return; - } - - boss.State.Defeated = false; - Say(response: Responses.BossUndefeated, args: [boss.Name]); - - IsDirty = true; - ItemService.ResetProgression(); - - OnBossUpdated(new(boss, confidence, false)); - AddUndo(() => - { - boss.State.Defeated = true; - OnBossUpdated(new(boss, confidence, false)); - }); - } - - /// - /// Un-marks a dungeon as cleared and, if possible, untracks the boss - /// reward. - /// - /// The dungeon that should be un-cleared. - /// The speech recognition confidence. - public override void MarkDungeonAsIncomplete(IDungeon dungeon, float? confidence = null) - { - if (!dungeon.DungeonState.Cleared) - { - Say(response: Responses.DungeonBossNotYetCleared, args: [dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss]); - return; - } - - ItemService.ResetProgression(); - dungeon.DungeonState.Cleared = false; - Say(response: Responses.DungeonBossUncleared, args: [dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss]); - - // Try to untrack the associated boss reward item - Action? undoUnclear = null; - Action? undoUntrackTreasure = null; - Action? undoUntrack = null; - if (dungeon.BossLocationId != null) - { - var rewardLocation = _worldService.Location(dungeon.BossLocationId.Value); - if (rewardLocation.Item.Type != ItemType.Nothing) - { - var item = rewardLocation.Item; - if (item.Type != ItemType.Nothing && item.State.TrackingState > 0) - { - UntrackItem(item); - undoUntrack = _undoHistory.Pop().Action; - } - - if (!rewardLocation.Item.IsDungeonItem) - { - dungeon.DungeonState.RemainingTreasure++; - undoUntrackTreasure = () => dungeon.DungeonState.RemainingTreasure--; - } - } - - if (rewardLocation.State.Cleared) - { - rewardLocation.State.Cleared = false; - OnLocationCleared(new(rewardLocation, null, false)); - undoUnclear = () => rewardLocation.State.Cleared = true; - } - } - - IsDirty = true; - - OnDungeonUpdated(new(dungeon, confidence, false)); - AddUndo(() => - { - dungeon.DungeonState.Cleared = false; - undoUntrack?.Invoke(); - undoUntrackTreasure?.Invoke(); - undoUnclear?.Invoke(); - ItemService.ResetProgression(); - OnDungeonUpdated(new(dungeon, confidence, false)); - }); - } - - /// - /// Marks an item at the specified location. - /// - /// The location to mark. - /// - /// The item that is found at . - /// - /// The speech recognition confidence. - /// If the marked location was auto tracked - public override void MarkLocation(Location location, Item item, float? confidence = null, bool autoTracked = false) - { - MarkLocation(location, item.Type, confidence, autoTracked, item.Metadata); - } - - /// - /// Marks an item at the specified location. - /// - /// The location to mark. - /// - /// The item that is found at . - /// - /// The speech recognition confidence. - /// If the marked location was auto tracked - /// The metadata of the item - public override void MarkLocation(Location location, ItemType item, float? confidence = null, bool autoTracked = false, ItemData? metadata = null) - { - var locationName = location.Metadata.Name; - - metadata ??= _metadataService.Item(item); - - GiveLocationComment(location.Item.Type, location, isTracking: false, confidence, metadata); - - if (item == ItemType.Nothing) - { - Clear(location); - Say(response: Responses.LocationMarkedAsBullshit, args: [locationName]); - } - else if (location.State.MarkedItem != null) - { - var oldType = location.State.MarkedItem; - location.State.MarkedItem = item; - Say(x => x.LocationMarkedAgain, args: [locationName, metadata?.Name ?? item.GetDescription(), oldType.GetDescription()]); - if (!autoTracked) - { - AddUndo(() => - { - location.State.MarkedItem = oldType; - OnMarkedLocationsUpdated(new TrackerEventArgs(confidence)); - }); - } - } - else - { - location.State.MarkedItem = item; - Say(x => x.LocationMarked, args: [locationName, metadata?.Name ?? item.GetDescription()]); - if (!autoTracked) - { - AddUndo(() => - { - location.State.MarkedItem = null; - OnMarkedLocationsUpdated(new TrackerEventArgs(confidence)); - }); - } - } - - GivePreConfiguredLocationSass(location); - - IsDirty = true; - - OnMarkedLocationsUpdated(new TrackerEventArgs(confidence)); - } - - /// - /// Pegs a Peg World peg. - /// - /// The speech recognition confidence. - public override void Peg(float? confidence = null) - { - if (!PegWorldMode) - return; - - PegsPegged++; - - if (PegsPegged < PegWorldModeModule.TotalPegs) - Say(response: Responses.PegWorldModePegged); - else - { - PegWorldMode = false; - Say(response: Responses.PegWorldModeDone); - OnToggledPegWorldModeOn(new TrackerEventArgs(confidence)); - } - - OnPegPegged(new TrackerEventArgs(confidence)); - AddUndo(() => - { - PegsPegged--; - OnPegPegged(new TrackerEventArgs(confidence)); - }); - - RestartIdleTimers(); - } - - public override void SetPegs(int count) - { - if (count <= PegsPegged) - return; - - if (!PegWorldMode) - { - PegWorldMode = true; - OnToggledPegWorldModeOn(new TrackerEventArgs(null, true)); - } - - if (count <= PegWorldModeModule.TotalPegs) - { - var delta = count - PegsPegged; - - PegsPegged = count; - Say(responses: Responses.PegWorldModePeggedMultiple, tieredKey: delta, wait: true); - OnPegPegged(new TrackerEventArgs(null, true)); - } - } - - /// - /// Starts Peg World mode. - /// - /// The speech recognition confidence. - public override void StartPegWorldMode(float? confidence = null) - { - ShutUp(); - PegWorldMode = true; - Say(response: Responses.PegWorldModeOn, wait: true); - OnToggledPegWorldModeOn(new TrackerEventArgs(confidence)); - AddUndo(() => - { - PegWorldMode = false; - OnToggledPegWorldModeOn(new TrackerEventArgs(confidence)); - }); - } - - /// - /// Turns Peg World mode off. - /// - /// The speech recognition confidence. - public override void StopPegWorldMode(float? confidence = null) - { - PegWorldMode = false; - Say(response: Responses.PegWorldModeDone); - OnToggledPegWorldModeOn(new TrackerEventArgs(confidence)); - AddUndo(() => - { - PegWorldMode = true; - OnToggledPegWorldModeOn(new TrackerEventArgs(confidence)); - }); - } - - /// - /// Starts Peg World mode. - /// - /// The speech recognition confidence. - public override void StartShaktoolMode(float? confidence = null) - { - ShaktoolMode = true; - OnToggledShaktoolMode(new TrackerEventArgs(confidence)); - AddUndo(() => - { - ShaktoolMode = false; - OnToggledShaktoolMode(new TrackerEventArgs(confidence)); - }); - } - - /// - /// Turns Peg World mode off. - /// - /// The speech recognition confidence. - public override void StopShaktoolMode(float? confidence = null) - { - ShaktoolMode = false; - OnToggledShaktoolMode(new TrackerEventArgs(confidence)); - AddUndo(() => - { - ShaktoolMode = true; - OnToggledShaktoolMode(new TrackerEventArgs(confidence)); - }); - } - - /// - /// Updates the region that the player is in - /// - /// The region the player is in - /// Set to true to update the map for the player to match the region - /// If the time should be reset if this is the first region update - public override void UpdateRegion(Region region, bool updateMap = false, bool resetTime = false) - { - if (region != CurrentRegion) - { - if (resetTime && !History.GetHistory().Any(x => x is { LocationId: not null, IsUndone: false })) - { - ResetTimer(true); - } - - History.AddEvent( - HistoryEventType.EnteredRegion, - true, - region?.Name?.ToString() ?? "new region" - ); - } - - CurrentRegion = region; - if (updateMap && !string.IsNullOrEmpty(region?.MapName)) - { - UpdateMap(region.MapName); - } - } - - /// - /// Updates the map to display for the user - /// - /// The name of the map - public override void UpdateMap(string map) - { - CurrentMap = map; - OnMapUpdated(); - } - - /// - /// Called when the game is beaten by entering triforce room - /// or entering the ship after beating both bosses - /// - /// If this was triggered by the auto tracker - public override void GameBeaten(bool autoTracked) - { - if (!HasBeatenGame) - { - HasBeatenGame = true; - var pauseUndo = PauseTimer(false); - Say(x => x.BeatGame); - OnBeatGame(new TrackerEventArgs(autoTracked)); - if (!autoTracked) - { - AddUndo(() => - { - HasBeatenGame = false; - if (pauseUndo != null) - { - pauseUndo(); - } - OnBeatGame(new TrackerEventArgs(autoTracked)); - }); - } - } - } - - /// - /// Called when the player has died - /// - public override void TrackDeath(bool autoTracked) - { - OnPlayerDied(new TrackerEventArgs(autoTracked)); - } - - /// - /// Updates the current track number being played - /// - /// The number of the track - public override void UpdateTrackNumber(int number) - { - if (number <= 0 || number > 200 || number == CurrentTrackNumber) return; - CurrentTrackNumber = number; - OnTrackNumberUpdated(new TrackNumberEventArgs(number)); - } - - /// - /// Updates the current track being played - /// - /// The current MSU pack - /// The current track - /// Formatted output text matching the requested style - public override void UpdateTrack(Msu msu, Track track, string outputText) - { - OnTrackChanged(new TrackChangedEventArgs(msu, track, outputText)); - } - - public override void UpdateHintTile(PlayerHintTile hintTile) - { - if (hintTile.State == null || LastViewedObject?.HintTile == hintTile) - { - return; - } - else if (hintTile.State.HintState == HintState.Cleared) - { - OnHintTileUpdated(new HintTileUpdatedEventArgs(hintTile)); - return; - } - - LastViewedObject = new ViewedObject { HintTile = hintTile }; - - if (hintTile.Type == HintTileType.Location) - { - var locationId = hintTile.Locations!.First(); - var location = World.FindLocation(locationId); - if (location.State is { Cleared: false, Autotracked: false } && location.State.MarkedItem != location.State.Item) - { - MarkLocation(location, location.Item, null, true); - hintTile.State.HintState = HintState.Viewed; - } - else - { - hintTile.State.HintState = HintState.Cleared; - } - } - else if (hintTile.Type == HintTileType.Requirement) - { - var dungeon = World.Dungeons.First(x => x.DungeonName == hintTile.LocationKey); - if (dungeon.DungeonState.MarkedMedallion != dungeon.DungeonState.RequiredMedallion) - { - SetDungeonRequirement(dungeon, hintTile.MedallionType, null, true); - hintTile.State.HintState = HintState.Viewed; - } - else - { - hintTile.State.HintState = HintState.Cleared; - } - } - else - { - var locations = hintTile.Locations!.Select(x => World.FindLocation(x)).ToList(); - if (locations.All(x => x.State.Autotracked || x.State.Cleared)) - { - hintTile.State.HintState = HintState.Cleared; - Say(response: Configs.HintTileConfig.ViewedHintTileAlreadyVisited, args: [hintTile.LocationKey]); - } - else if (hintTile.Usefulness == LocationUsefulness.Mandatory) - { - hintTile.State.HintState = HintState.Viewed; - Say(response: Configs.HintTileConfig.ViewedHintTileMandatory, args: [hintTile.LocationKey]); - } - else if (hintTile.Usefulness == LocationUsefulness.Useless) - { - hintTile.State.HintState = HintState.Viewed; - Say(response: Configs.HintTileConfig.ViewedHintTileUseless, args: [hintTile.LocationKey]); - } - else - { - hintTile.State.HintState = HintState.Viewed; - Say(response: Configs.HintTileConfig.ViewedHintTile); - } - } - - OnHintTileUpdated(new HintTileUpdatedEventArgs(hintTile)); - } - - public override void UpdateLastMarkedLocations(List locations) - { - LastViewedObject = new ViewedObject { ViewedLocations = locations }; - } - - public override void ClearLastViewedObject(float confidence) - { - if (LastViewedObject?.ViewedLocations?.Count > 0) - { - Clear(LastViewedObject.ViewedLocations, confidence); - } - else if (LastViewedObject?.HintTile != null) - { - var hintTile = LastViewedObject.HintTile; - - if (hintTile?.State == null) - { - Say(response: Configs.HintTileConfig.NoPreviousHintTile); - } - else if (hintTile.State.HintState != HintState.Cleared && hintTile.Locations?.Count() > 0) - { - var locations = hintTile.Locations.Select(x => World.FindLocation(x)) - .Where(x => x.State is { Cleared: false, Autotracked: false }).ToList(); - if (locations.Count != 0) - { - Clear(locations, confidence); - hintTile.State.HintState = HintState.Cleared; - UpdateHintTile(hintTile); - } - else - { - Say(response: Configs.HintTileConfig.ClearHintTileFailed); - } - } - else - { - Say(response: Configs.HintTileConfig.ClearHintTileFailed); - } - } - else - { - Say(x => x.NoMarkedLocations); - } - } - - public override void CountHyperBeamShots(int count) - { - Say(responses: Responses.CountHyperBeamShots, tieredKey: count, args: [count]); - } - - /// - /// Resets the timers for tracker mentioning nothing has happened - /// - public override void RestartIdleTimers() - { - foreach (var item in _idleTimers) - { - var timeout = Parse.AsTimeSpan(item.Key, s_random) ?? Timeout.InfiniteTimeSpan; - var timer = item.Value; - - timer.Change(timeout, Timeout.InfiniteTimeSpan); - } - } - - /// - /// Determines whether or not the specified reward is worth getting. - /// - /// The dungeon reward. - /// - /// if the reward leads to something good; - /// otherwise, . - /// - public override bool IsWorth(RewardType reward) - { - var sahasrahlaItem = World.FindLocation(LocationId.Sahasrahla).Item; - if (sahasrahlaItem.Type != ItemType.Nothing && reward == RewardType.PendantGreen) - { - _logger.LogDebug("{Reward} leads to {Item}...", reward, sahasrahlaItem); - if (IsWorth(sahasrahlaItem)) - { - _logger.LogDebug("{Reward} leads to {Item}, which is worth it", reward, sahasrahlaItem); - return true; - } - _logger.LogDebug("{Reward} leads to {Item}, which is junk", reward, sahasrahlaItem); - } - - var pedItem = World.LightWorldNorthWest.MasterSwordPedestal.Item; - if (pedItem.Type != ItemType.Nothing && (reward is RewardType.PendantGreen or RewardType.PendantRed or RewardType.PendantBlue)) - { - _logger.LogDebug("{Reward} leads to {Item}...", reward, pedItem); - if (IsWorth(pedItem)) - { - _logger.LogDebug("{Reward} leads to {Item}, which is worth it", reward, pedItem); - return true; - } - _logger.LogDebug("{Reward} leads to {Item}, which is junk", reward, pedItem); - } - - return false; - } - - /// - /// Determines whether or not the specified item is worth getting. - /// - /// The item whose worth to consider. - /// - /// is the item is worth getting or leads to - /// another item that is worth getting; otherwise, . - /// - public override bool IsWorth(Item item) - { - var leads = new Dictionary() - { - [ItemType.Mushroom] = new[] { World.LightWorldNorthEast.MushroomItem }, - [ItemType.Powder] = new[] { World.LightWorldNorthWest.MagicBat }, - [ItemType.Book] = new[] - { - World.LightWorldDeathMountainWest.EtherTablet, - World.LightWorldSouth.BombosTablet - }, - [ItemType.Bottle] = new[] { World.LightWorldNorthWest.SickKid }, - [ItemType.BottleWithBee] = new[] { World.LightWorldNorthWest.SickKid }, - [ItemType.BottleWithFairy] = new[] { World.LightWorldNorthWest.SickKid }, - [ItemType.BottleWithBluePotion] = new[] { World.LightWorldNorthWest.SickKid }, - [ItemType.BottleWithGoldBee] = new[] { World.LightWorldNorthWest.SickKid }, - [ItemType.BottleWithGreenPotion] = new[] { World.LightWorldNorthWest.SickKid }, - [ItemType.BottleWithRedPotion] = new[] { World.LightWorldNorthWest.SickKid }, - }; - - if (leads.TryGetValue(item.Type, out var leadsToLocation) && !ItemService.IsTracked(item.Type)) - { - foreach (var location in leadsToLocation) - { - var reward = location.Item; - if (reward.Type != ItemType.Nothing) - { - _logger.LogDebug("{Item} leads to {OtherItem}...", item, reward); - if (IsWorth(reward)) - { - _logger.LogDebug("{Item} leads to {OtherItem}, which is worth it", item, reward); - return true; - } - _logger.LogDebug("{Item} leads to {OtherItem}, which is junk", item, reward); - } - } - } - - return item.Metadata.IsGood(World.Config); - } - - /// - /// Adds an action to be invoked to undo the last operation. - /// - /// - /// The action to invoke to undo the last operation. - /// - public override void AddUndo(Action undo) => _undoHistory.Push((undo, DateTime.Now)); - - /// - /// Cleans up resources used by this class. - /// - /// - /// true to dispose of managed resources. - /// - private void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing) - { - (_recognizer as IDisposable)?.Dispose(); - (_communicator as IDisposable)?.Dispose(); - - foreach (var timer in _idleTimers.Values) - timer.Dispose(); - } - - _disposed = true; - } - } - - private static bool IsTreasure(Item? item) - => item is { IsDungeonItem: false }; - - private IDungeon? GetDungeonFromLocation(Location location) - { - if (location.Type == LocationType.NotInDungeon) - return null; - - return location.Region as IDungeon; - } - - private IDungeon? GetDungeonFromArea(IHasLocations area) - { - return area switch - { - Room room => room.Region as IDungeon, - Region region => region as IDungeon, - _ => null - }; - } - - private Action? TryTrackDungeonTreasure(Location location, float? confidence, bool autoTracked = false, bool stateResponse = true) - { - if (confidence < Options.MinimumSassConfidence) - { - // Tracker response could give away the location of an item if - // it is in a dungeon but tracker misheard. - return null; - } - - var dungeon = GetDungeonFromLocation(location); - if (dungeon != null && (IsTreasure(location.Item) || World.Config.ZeldaKeysanity)) - { - if (TrackDungeonTreasure(dungeon, confidence, 1, autoTracked, stateResponse)) - return _undoHistory.Pop().Action; - } - - IsDirty = true; - - return null; - } - - private void GiveLocationComment(ItemType item, Location location, bool isTracking, float? confidence, ItemData? metadata) - { - metadata ??= _metadataService.Item(item); - - // If the plando config specifies a specific line for this location, say it - if (World.Config.PlandoConfig?.TrackerLocationLines.ContainsKey(location.ToString()) == true) - { - Say(text: World.Config.PlandoConfig?.TrackerLocationLines[location.ToString()]); - } - // Give some sass if the user tracks or marks the wrong item at a - // location unless the user is clearing a useless item like missiles - else if (location.Item.Type != ItemType.Nothing && !item.IsEquivalentTo(location.Item.Type) && (item != ItemType.Nothing || location.Item.Metadata.IsProgression(World.Config))) - { - if (confidence == null || confidence < Options.MinimumSassConfidence) - return; - - var actualItemName = ItemService.GetName(location.Item.Type); - if (HintsEnabled) actualItemName = "another item"; - - Say(response: Responses.LocationHasDifferentItem, args: [metadata?.NameWithArticle ?? item.GetDescription(), actualItemName]); - } - else - { - if (item == location.VanillaItem && item != ItemType.Nothing) - { - Say(x => x.TrackedVanillaItem); - return; - } - - var locationInfo = location.Metadata; - var isJunk = metadata?.IsJunk(World.Config) ?? true; - if (isJunk) - { - if (!isTracking && locationInfo.WhenMarkingJunk?.Count > 0) - { - Say(text: locationInfo.WhenMarkingJunk.Random(s_random)!); - } - else if (locationInfo.WhenTrackingJunk?.Count > 0) - { - Say(text: locationInfo.WhenTrackingJunk.Random(s_random)!); - } - } - else if (!isJunk) - { - if (!isTracking && locationInfo.WhenMarkingProgression?.Count > 0) - { - Say(text: locationInfo.WhenMarkingProgression.Random(s_random)!); - } - else if (locationInfo.WhenTrackingProgression?.Count > 0) - { - Say(text: locationInfo.WhenTrackingProgression.Random(s_random)!); - } - } - } - } - - private IDungeon? GetDungeonFromItem(Item item, IDungeon? dungeon = null) - { - var locations = _worldService.Locations(itemFilter: item.Type) - .Where(x => x.Type != LocationType.NotInDungeon) - .ToImmutableList(); - - if (locations.Count == 1 && dungeon == null) - { - // User didn't have a guess and there's only one location that - // has the tracker item - return GetDungeonFromLocation(locations[0]); - } - - if (locations.Count > 0 && dungeon != null) - { - // Does the dungeon even have that item? - if (locations.All(x => dungeon != x.Region)) - { - // Be a smart-ass about it - Say(response: Responses.ItemTrackedInIncorrectDungeon, args: [dungeon.DungeonMetadata.Name, item.Metadata.NameWithArticle]); - } - } - - // - If tracker was started before generating a seed, we don't know - // better. - // - If we do know better, we should still go with the user's - // choice. - // - If there are multiple copies of the item, we don't know which - // was tracked. Either way, we have to assume `dungeon` is correct. - // If it's `null`, nobody knows. - return dungeon; - } - - private void IdleTimerElapsed(object? state) - { - var key = (string)state!; - Say(response: Responses.Idle?[key]); - } - - private void Recognizer_SpeechRecognized(object? sender, SpeechRecognizedEventArgs e) - { - if (OperatingSystem.IsWindows()) + private void Recognizer_SpeechRecognized(object? sender, SpeechRecognizedEventArgs e) + { + if (OperatingSystem.IsWindows()) { RestartIdleTimers(); OnSpeechRecognized(new(e.Result.Confidence, e.Result.Text)); } } - - private void GiveLocationHint(IEnumerable accessibleBefore) - { - var accessibleAfter = _worldService.AccessibleLocations(false); - var newlyAccessible = accessibleAfter.Except(accessibleBefore).ToList(); - if (newlyAccessible.Any()) - { - if (newlyAccessible.Contains(World.FindLocation(LocationId.InnerMaridiaSpringBall))) - Say(response: Responses.ShaktoolAvailable); - - if (newlyAccessible.Contains(World.DarkWorldNorthWest.PegWorld)) - Say(response: Responses.PegWorldAvailable); - } - else if (Responses.TrackedUselessItem != null) - { - Say(response: Responses.TrackedUselessItem); - } - } - - private void AnnounceTrackedItems(List items) - { - if (items.Count == 1) - { - var item = items[0]; - if (item.TryGetTrackingResponse(out var response)) - { - Say(text: response.Format(item.Counter)); - } - else - { - Say(text: Responses.TrackedItem?.Format(item.Name, item.Metadata.NameWithArticle)); - } - } - else if (items.Count == 2) - { - Say(x => x.TrackedTwoItems, args: [items[0].Metadata.NameWithArticle, items[1].Metadata.NameWithArticle]); - } - else if (items.Count == 3) - { - Say(x => x.TrackedThreeItems, args: [items[0].Metadata.NameWithArticle, items[1].Metadata.NameWithArticle, items[2].Metadata.NameWithArticle]); - } - else - { - var itemsToSay = items.Where(x => x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(2).ToList(); - if (itemsToSay.Count() < 2) - { - var numToTake = 2 - itemsToSay.Count(); - itemsToSay.AddRange(items.Where(x => !x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(numToTake)); - } - - Say(x => x.TrackedManyItems, args: [itemsToSay[0].Metadata.NameWithArticle, itemsToSay[1].Metadata.NameWithArticle, items.Count - 2]); - } - } - - private void CommunicatorOnSpeakCompleted(object? sender, SpeakCompletedEventArgs e) - { - if (_pendingSpeechItems.Count > 0) - { - AnnounceTrackedItems(_pendingSpeechItems); - _pendingSpeechItems.Clear(); - } - - if (e.SpeechDuration.TotalSeconds > 60) - { - Say(x => x.LongSpeechResponse); - } - } } diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj b/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj index c36c0b4af..79715438e 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj +++ b/src/TrackerCouncil.Smz3.Tracking/TrackerCouncil.Smz3.Tracking.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackerServiceCollectionExtensions.cs b/src/TrackerCouncil.Smz3.Tracking/TrackerServiceCollectionExtensions.cs index bf40afa7b..44d86feb8 100644 --- a/src/TrackerCouncil.Smz3.Tracking/TrackerServiceCollectionExtensions.cs +++ b/src/TrackerCouncil.Smz3.Tracking/TrackerServiceCollectionExtensions.cs @@ -14,6 +14,7 @@ using TrackerCouncil.Smz3.Tracking.AutoTracking.ZeldaStateChecks; using TrackerCouncil.Smz3.Tracking.Services; using TrackerCouncil.Smz3.Tracking.Services.Speech; +using TrackerCouncil.Smz3.Tracking.TrackingServices; using TrackerCouncil.Smz3.Tracking.VoiceCommands; namespace TrackerCouncil.Smz3.Tracking; @@ -32,15 +33,16 @@ public static class TrackerServiceCollectionExtensions /// A reference to . public static IServiceCollection AddTracker(this IServiceCollection services) { + services.AddTrackerServices(); services.AddBasicTrackerModules(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddSnesConnectorServices(); @@ -117,4 +119,28 @@ private static IServiceCollection AddBasicTrackerModules(this IServic return services; } + + private static IServiceCollection AddTrackerServices(this IServiceCollection services) + { + var interfaceNamespace = typeof(TrackerBase).Namespace; + if (string.IsNullOrEmpty(interfaceNamespace)) + { + throw new InvalidOperationException("Could not determine TrackerBase namespace"); + } + + var serviceTypes = typeof(TAssembly).Assembly.GetTypes() + .Where(x => x.IsSubclassOf(typeof(TrackerService))); + + foreach (var serviceType in serviceTypes) + { + services.TryAddEnumerable(ServiceDescriptor.Scoped(typeof(TrackerService), serviceType)); + var serviceInterface = serviceType.GetInterfaces().FirstOrDefault(x => x.Namespace == interfaceNamespace); + if (serviceInterface != null) + { + services.TryAddScoped(serviceInterface, serviceType); + } + } + + return services; + } } diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerBossService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerBossService.cs new file mode 100644 index 000000000..af372ae63 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerBossService.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Tracking; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Tracking.TrackingServices; + +internal class TrackerBossService(IPlayerProgressionService playerProgressionService) : TrackerService, ITrackerBossService +{ + public event EventHandler? BossUpdated; + + public void MarkBossAsDefeated(IHasBoss region, float? confidence = null, bool autoTracked = false, bool admittedGuilt = false) + { + if (region.BossDefeated && !autoTracked) + { + Tracker.Say(response: Responses.DungeonBossAlreadyCleared, args: [region.Metadata.Name, region.BossMetadata.Name]); + return; + } + + List undoActions = []; + + var addedEvent = History.AddEvent( + HistoryEventType.BeatBoss, + true, + region.BossMetadata.Name.ToString() ?? $"boss of {region.Metadata.Name}" + ); + + region.Boss.Defeated = true; + region.Boss.AutoTracked = autoTracked; + BossUpdated?.Invoke(this, new BossTrackedEventArgs(region.Boss, confidence, autoTracked)); + + // If all treasures have been retrieved and the boss is defeated, clear all locations in the dungeon + if (region is IHasTreasure treasureRegion) + { + if (treasureRegion.RemainingTreasure == 0) + { + foreach (var location in ((Region)region).Locations.Where(x => !x.Cleared)) + { + Tracker.LocationTracker.Clear(location, confidence, autoTracked, false); + undoActions.Add(PopUndo().Action); + } + } + + Tracker.Say(response: Responses.DungeonBossCleared, args: [region.Metadata.Name, region.BossMetadata.Name]); + } + else + { + if (!admittedGuilt && region.BossMetadata.WhenTracked != null) + Tracker.Say(response: region.BossMetadata.WhenTracked, args: [region.BossMetadata.Name]); + else + Tracker.Say(response: region.BossMetadata.WhenDefeated ?? Responses.BossDefeated, args: [region.BossMetadata.Name]); + } + + // Auto track the region's reward + if (region is IHasReward rewardRegion) + { + Tracker.RewardTracker.GiveAreaReward(rewardRegion, autoTracked, true); + } + + IsDirty = true; + RestartIdleTimers(); + UpdateAllAccessibility(false); + + AddUndo(autoTracked, () => + { + playerProgressionService.ResetProgression(); + region.BossDefeated = false; + BossUpdated?.Invoke(this, new BossTrackedEventArgs(region.Boss, null, false)); + foreach (var undo in undoActions) + { + undo(); + } + addedEvent.IsUndone = true; + UpdateAllAccessibility(true); + }); + } + + public void MarkBossAsDefeated(Boss boss, bool admittedGuilt = true, float? confidence = null, bool autoTracked = false) + { + if (boss.Defeated) + { + if (!autoTracked) + Tracker.Say(x => x.BossAlreadyDefeated, args: [boss.Name]); + return; + } + + if (boss.Region != null) + { + MarkBossAsDefeated(boss.Region, confidence, autoTracked, admittedGuilt); + return; + } + + boss.Defeated = true; + boss.AutoTracked = autoTracked; + BossUpdated?.Invoke(this, new BossTrackedEventArgs(boss, confidence, autoTracked)); + + if (!admittedGuilt && boss.Metadata.WhenTracked != null) + Tracker.Say(response: boss.Metadata.WhenTracked, args: [boss.Name]); + else + Tracker.Say(response: boss.Metadata.WhenDefeated ?? Responses.BossDefeated, args: [boss.Name]); + + var addedEvent = History.AddEvent( + HistoryEventType.BeatBoss, + true, + boss.Name + ); + + IsDirty = true; + UpdateAllAccessibility(false); + + RestartIdleTimers(); + + AddUndo(autoTracked, () => + { + boss.Defeated = false; + BossUpdated?.Invoke(this, new BossTrackedEventArgs(boss, null, false)); + addedEvent.IsUndone = true; + UpdateAllAccessibility(true); + }); + } + + public void MarkBossAsNotDefeated(Boss boss, float? confidence = null) + { + if (boss.Defeated != true) + { + Tracker.Say(x => x.BossNotYetDefeated, args: [boss.Name]); + return; + } + + if (boss.Region != null) + { + MarkBossAsNotDefeated(boss.Region, confidence); + return; + } + + boss.Defeated = false; + BossUpdated?.Invoke(this, new BossTrackedEventArgs(boss, confidence, false)); + Tracker.Say(response: Responses.BossUndefeated, args: [boss.Name]); + + IsDirty = true; + UpdateAllAccessibility(true); + + AddUndo(() => + { + boss.Defeated = true; + UpdateAllAccessibility(false); + BossUpdated?.Invoke(this, new BossTrackedEventArgs(boss, null, false)); + }); + } + + /// + /// Un-marks a dungeon as cleared and, if possible, untracks the boss + /// reward. + /// + /// The dungeon that should be un-cleared. + /// The speech recognition confidence. + public void MarkBossAsNotDefeated(IHasBoss region, float? confidence = null) + { + if (!region.BossDefeated) + { + Tracker.Say(response: Responses.DungeonBossNotYetCleared, args: [region.Metadata.Name, region.BossMetadata.Name]); + return; + } + + region.BossDefeated = false; + BossUpdated?.Invoke(this, new BossTrackedEventArgs(region.Boss, confidence, false)); + Tracker.Say(response: Responses.DungeonBossUncleared, args: [region.Metadata.Name, region.BossMetadata.Name]); + + // Try to untrack the associated boss reward item + List undoActions = []; + + if (region.BossLocationId != null) + { + var bossLocation = World.LocationMap[region.BossLocationId.Value]; + if (bossLocation.Cleared) + { + Tracker.LocationTracker.Unclear(bossLocation); + undoActions.Add(PopUndo().Action); + } + + if (bossLocation.Item.Type != ItemType.Nothing && bossLocation.Item.TrackingState > 0) + { + Tracker.ItemTracker.UntrackItem(bossLocation.Item); + undoActions.Add(PopUndo().Action); + } + } + + if (region is IHasReward rewardRegion) + { + Tracker.RewardTracker.RemoveAreaReward(rewardRegion, true); + } + + IsDirty = true; + UpdateAllAccessibility(true); + + AddUndo(() => + { + region.BossDefeated = true; + region.BossAccessibility = Accessibility.Cleared; + BossUpdated?.Invoke(this, new BossTrackedEventArgs(region.Boss, null, false)); + + foreach (var undo in undoActions) + { + undo.Invoke(); + } + UpdateAllAccessibility(false); + }); + } + + public void UpdateAccessibility(Progression? actualProgression = null, Progression? withKeysProgression = null) + { + actualProgression ??= playerProgressionService.GetProgression(false); + withKeysProgression ??= playerProgressionService.GetProgression(true); + foreach (var region in Tracker.World.BossRegions) + { + UpdateAccessibility(region, actualProgression, withKeysProgression); + } + } + + public void UpdateAccessibility(Boss boss, Progression? actualProgression = null, Progression? withKeysProgression = null) + { + if (boss.Region == null) return; + actualProgression ??= playerProgressionService.GetProgression(false); + withKeysProgression ??= playerProgressionService.GetProgression(true); + UpdateAccessibility(boss.Region, actualProgression, withKeysProgression); + } + + public void UpdateAccessibility(IHasBoss region, Progression? actualProgression = null, Progression? withKeysProgression = null) + { + if (region.BossDefeated) + { + region.BossAccessibility = Accessibility.Cleared; + return; + } + + actualProgression ??= playerProgressionService.GetProgression(false); + withKeysProgression ??= playerProgressionService.GetProgression(true); + + region.Boss.UpdateAccessibility(actualProgression, withKeysProgression); + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs new file mode 100644 index 000000000..3b3fd8d05 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerGameStateService.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MSURandomizerLibrary.Configs; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Tracking; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared.Enums; +using TrackerCouncil.Smz3.Shared.Models; + +namespace TrackerCouncil.Smz3.Tracking.TrackingServices; + +internal class TrackerGameStateService : TrackerService, ITrackerGameStateService +{ + public bool HasBeatenGame { get; protected set; } + public ViewedObject? LastViewedObject { get; set; } + public Region? CurrentRegion { get; protected set; } + public string CurrentMap { get; protected set; } = ""; + public int CurrentTrackNumber { get; protected set; } + + public event EventHandler? MapUpdated; + public event EventHandler? BeatGame; + public event EventHandler? PlayerDied; + public event EventHandler? HintTileUpdated; + public event EventHandler? TrackNumberUpdated; + public event EventHandler? TrackChanged; + + /// + /// Updates the region that the player is in + /// + /// The region the player is in + /// Set to true to update the map for the player to match the region + /// If the time should be reset if this is the first region update + public void UpdateRegion(Region region, bool updateMap = false, bool resetTime = false) + { + if (region != CurrentRegion) + { + if (resetTime && !History.GetHistory().Any(x => x is { LocationId: not null, IsUndone: false })) + { + Tracker.ResetTimer(true); + } + + History.AddEvent( + HistoryEventType.EnteredRegion, + true, + region.Name + ); + } + + CurrentRegion = region; + if (updateMap && !string.IsNullOrEmpty(region?.MapName)) + { + UpdateMap(region.MapName); + } + } + + /// + /// Updates the map to display for the user + /// + /// The name of the map + public void UpdateMap(string map) + { + CurrentMap = map; + MapUpdated?.Invoke(this, EventArgs.Empty); + } + + /// + /// Called when the game is beaten by entering triforce room + /// or entering the ship after beating both bosses + /// + /// If this was triggered by the auto tracker + public void GameBeaten(bool autoTracked) + { + if (!HasBeatenGame) + { + HasBeatenGame = true; + var pauseUndo = Tracker.PauseTimer(false); + Tracker.Say(x => x.BeatGame); + BeatGame?.Invoke(this, new TrackerEventArgs(autoTracked)); + AddUndo(autoTracked, () => + { + HasBeatenGame = false; + if (pauseUndo != null) + { + pauseUndo(); + } + + BeatGame?.Invoke(this, new TrackerEventArgs(autoTracked)); + }); + } + } + + /// + /// Called when the player has died + /// + public void TrackDeath(bool autoTracked) + { + PlayerDied?.Invoke(this, new TrackerEventArgs(autoTracked)); + } + + /// + /// Updates the current track number being played + /// + /// The number of the track + public void UpdateTrackNumber(int number) + { + if (number <= 0 || number > 200 || number == CurrentTrackNumber) return; + CurrentTrackNumber = number; + TrackNumberUpdated?.Invoke(this, new TrackNumberEventArgs(number)); + } + + /// + /// Updates the current track being played + /// + /// The current MSU pack + /// The current track + /// Formatted output text matching the requested style + public void UpdateTrack(Msu msu, Track track, string outputText) + { + TrackChanged?.Invoke(this, new TrackChangedEventArgs(msu, track, outputText)); + } + + public void UpdateHintTile(PlayerHintTile hintTile) + { + if (hintTile.State == null || LastViewedObject?.HintTile == hintTile) + { + return; + } + else if (hintTile.HintState == HintState.Cleared) + { + HintTileUpdated?.Invoke(this, new HintTileUpdatedEventArgs(hintTile)); + return; + } + + LastViewedObject = new ViewedObject { HintTile = hintTile }; + + if (hintTile.Type == HintTileType.Location) + { + var locationId = hintTile.Locations!.First(); + var location = World.FindLocation(locationId); + if (location is { Cleared: false, Autotracked: false, HasMarkedCorrectItem: false }) + { + Tracker.LocationTracker.MarkLocation(location, location.Item, null, true); + hintTile.HintState = HintState.Viewed; + } + else + { + hintTile.HintState = HintState.Cleared; + } + } + else if (hintTile.Type == HintTileType.Requirement) + { + var dungeon = World.PrerequisiteRegions.First(x => x.Name == hintTile.LocationKey); + if (!dungeon.HasMarkedCorrectly) + { + Tracker.PrerequisiteTracker.SetDungeonRequirement(dungeon, hintTile.MedallionType, null, true); + hintTile.HintState = HintState.Viewed; + } + else + { + hintTile.HintState = HintState.Cleared; + } + } + else + { + var locations = hintTile.Locations!.Select(x => World.FindLocation(x)).ToList(); + if (locations.All(x => x.Autotracked || x.Cleared)) + { + hintTile.HintState = HintState.Cleared; + Tracker.Say(response: Configs.HintTileConfig.ViewedHintTileAlreadyVisited, args: [hintTile.LocationKey]); + } + else + { + switch (hintTile.Usefulness) + { + case LocationUsefulness.Mandatory: + hintTile.HintState = HintState.Viewed; + Tracker.Say(response: Configs.HintTileConfig.ViewedHintTileMandatory, args: [hintTile.LocationKey]); + break; + case LocationUsefulness.Useless: + hintTile.HintState = HintState.Viewed; + Tracker.Say(response: Configs.HintTileConfig.ViewedHintTileUseless, args: [hintTile.LocationKey]); + break; + default: + hintTile.HintState = HintState.Viewed; + Tracker.Say(response: Configs.HintTileConfig.ViewedHintTile); + break; + } + + foreach (var location in locations) + { + location.MarkedUsefulness = hintTile.Usefulness; + } + } + } + + HintTileUpdated?.Invoke(this, new HintTileUpdatedEventArgs(hintTile)); + } + + public void UpdateLastMarkedLocations(List locations) + { + LastViewedObject = new ViewedObject { ViewedLocations = locations }; + } + + public void ClearLastViewedObject(float confidence) + { + if (LastViewedObject?.ViewedLocations?.Count > 0) + { + Tracker.LocationTracker.Clear(LastViewedObject.ViewedLocations, confidence); + } + else if (LastViewedObject?.HintTile != null) + { + var hintTile = LastViewedObject.HintTile; + + if (hintTile.State == null) + { + Tracker.Say(response: Configs.HintTileConfig.NoPreviousHintTile); + } + else if (hintTile.HintState != HintState.Cleared && hintTile.Locations?.Count() > 0) + { + var locations = hintTile.Locations.Select(x => World.FindLocation(x)) + .Where(x => x is { Cleared: false, Autotracked: false }).ToList(); + if (locations.Count != 0) + { + Tracker.LocationTracker.Clear(locations, confidence); + hintTile.HintState = HintState.Cleared; + UpdateHintTile(hintTile); + } + else + { + Tracker.Say(response: Configs.HintTileConfig.ClearHintTileFailed); + } + } + else + { + Tracker.Say(response: Configs.HintTileConfig.ClearHintTileFailed); + } + } + else + { + Tracker.Say(x => x.NoMarkedLocations); + } + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs new file mode 100644 index 000000000..16a088aeb --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerItemService.cs @@ -0,0 +1,504 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Extensions.Logging; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Tracking; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; +using TrackerCouncil.Smz3.Shared; +using TrackerCouncil.Smz3.Shared.Enums; +using TrackerCouncil.Smz3.Tracking.Services; + +namespace TrackerCouncil.Smz3.Tracking.TrackingServices; + +internal class TrackerItemService(ILogger logger, IPlayerProgressionService playerProgressionService, ICommunicator communicator, IWorldQueryService worldQueryService) : TrackerService, ITrackerItemService +{ + private List _pendingSpeechItems = []; + + public override void Initialize() + { + communicator.SpeakCompleted += CommunicatorOnSpeakCompleted; + } + + public event EventHandler? ItemTracked; + + public bool TrackItem(Item item, string? trackedAs = null, float? confidence = null, bool tryClear = true, bool autoTracked = false, Location? location = null, bool giftedItem = false, bool silent = false) + { + var didTrack = false; + var accessibleBefore = worldQueryService.AccessibleLocations(false); + var itemName = item.Name; + var originalTrackingState = item.TrackingState; + + var isGTPreBigKey = !World.Config.ZeldaKeysanity + && autoTracked + && location?.Region.GetType() == typeof(GanonsTower) + && !playerProgressionService.GetProgression(false).BigKeyGT; + var stateResponse = !isGTPreBigKey && !silent && (!autoTracked + || !item.Metadata.IsDungeonItem() + || World.Config.ZeldaKeysanity); + + if (stateResponse && communicator.IsSpeaking) + { + _pendingSpeechItems.Add(item); + stateResponse = false; + } + + // Actually track the item if it's for the local player's world + if (item.World == World) + { + if (item.Metadata.HasStages) + { + if (trackedAs != null && item.Metadata.GetStage(trackedAs) != null) + { + var stage = item.Metadata.GetStage(trackedAs)!; + + // Tracked by specific stage name (e.g. Tempered Sword), set + // to that stage specifically + var stageName = item.Metadata.Stages[stage.Value].ToString(); + + didTrack = item.Track(stage.Value); + if (stateResponse) + { + if (didTrack) + { + if (item.TryGetTrackingResponse(out var response)) + { + Tracker.Say(response: response, args: [item.Counter]); + } + else + { + Tracker.Say(response: Responses.TrackedItemByStage, args: [itemName, stageName]); + } + } + else + { + Tracker.Say(response: Responses.TrackedOlderProgressiveItem, args: [itemName, item.Metadata.Stages[item.TrackingState].ToString()]); + } + } + } + else + { + // Tracked by regular name, upgrade by one step + didTrack = item.Track(); + if (stateResponse) + { + if (didTrack) + { + if (item.TryGetTrackingResponse(out var response)) + { + Tracker.Say(response: response, args: [item.Counter]); + } + else + { + var stageName = item.Metadata.Stages[item.TrackingState].ToString(); + Tracker.Say(response: Responses.TrackedProgressiveItem, args: [itemName, stageName]); + } + } + else + { + Tracker.Say(response: Responses.TrackedTooManyOfAnItem, args: [itemName]); + } + } + } + } + else if (item.Metadata.Multiple) + { + didTrack = item.Track(); + if (item.TryGetTrackingResponse(out var response)) + { + if (stateResponse) + Tracker.Say(response: response, args: [item.Counter]); + } + else if (item.Counter == 1) + { + if (stateResponse) + Tracker.Say(response: Responses.TrackedItem, args: [itemName, item.Metadata.NameWithArticle]); + } + else if (item.Counter > 1) + { + if (stateResponse) + Tracker.Say(response: Responses.TrackedItemMultiple, args: [item.Metadata.Plural ?? $"{itemName}s", item.Counter, item.Name]); + } + else + { + logger.LogWarning("Encountered multiple item with counter 0: {Item} has counter {Counter}", item, item.Counter); + if (stateResponse) + Tracker.Say(response: Responses.TrackedAlreadyTrackedItem, args: [itemName]); + } + } + else + { + didTrack = item.Track(); + if (stateResponse) + { + if (didTrack) + { + if (item.TryGetTrackingResponse(out var response)) + { + Tracker.Say(response: response, args: [item.Counter]); + } + else + { + Tracker.Say(response: Responses.TrackedItem, args: [itemName, item.Metadata.NameWithArticle]); + } + } + else + { + Tracker.Say(response: Responses.TrackedAlreadyTrackedItem, args: [itemName]); + } + } + } + } + else if (location != null) + { + Tracker.LocationTracker.Clear(location, confidence, autoTracked, stateResponse: false, allowLocationComments: true, updateTreasureCount: true); + } + + if (!didTrack) + { + return false; + } + + ItemTracked?.Invoke(this, new ItemTrackedEventArgs(item, trackedAs, confidence, autoTracked)); + + List undoActions = + [ + () => { + item.TrackingState = originalTrackingState; + ItemTracked?.Invoke(this, new ItemTrackedEventArgs(item, trackedAs, null, false)); + playerProgressionService.ResetProgression(); + } + ]; + + // If this was not gifted to the player, try to clear the location + if (!giftedItem && item.Type != ItemType.Nothing && tryClear) + { + if (location == null && !World.Config.MultiWorld) + { + location = worldQueryService.Locations(outOfLogic: true, itemFilter: item.Type).TrySingle(); + } + + // Clear the location if it's for the local player's world + if (location != null && location.World == World && !location.Cleared) + { + Tracker.LocationTracker.Clear(location, confidence, autoTracked, !stateResponse, true); + undoActions.Add(autoTracked ? null : PopUndo().Action); + } + } + + var addedEvent = History.AddEvent( + HistoryEventType.TrackedItem, + item.Metadata.IsProgression(World.Config), + item.Metadata.NameWithArticle, + location + ); + + IsDirty = true; + UpdateAllAccessibility(false, item); + GiveLocationHint(accessibleBefore); + RestartIdleTimers(); + + AddUndo(autoTracked, () => + { + foreach (var undo in undoActions.NonNull()) + { + undo.Invoke(); + } + UpdateAllAccessibility(true, item); + addedEvent.IsUndone = true; + }); + + return true; + } + + public void TrackItemFrom(Item item, IHasTreasure hasTreasure, string? trackedAs = null, float? confidence = null) + { + if (!TrackItem(item, trackedAs, confidence, tryClear: false)) + { + return; + } + + List undoActions = [PopUndo().Action]; + + // Check if we can remove something from the remaining treasures in + // a dungeon + hasTreasure = GetDungeonFromItem(item, hasTreasure)!; + if (Tracker.TreasureTracker.TrackDungeonTreasure(hasTreasure, confidence)) + undoActions.Add(PopUndo().Action); + + IsDirty = true; + + // Check if we can remove something from the marked location + var location = worldQueryService.Locations(itemFilter: item.Type, inRegion: hasTreasure as Region).TrySingle(); + if (location != null) + { + Tracker.LocationTracker.Clear(location, stateResponse: false, updateTreasureCount: false); + undoActions.Add(PopUndo().Action); + } + + AddUndo(() => + { + foreach (var undo in undoActions) + { + undo(); + } + }); + } + + public void TrackItemFrom(Item item, IHasLocations area, string? trackedAs = null, float? confidence = null) + { + var locations = area.Locations + .Where(x => x.Item.Type == item.Type) + .ToImmutableList(); + + if (locations.Count == 0) + { + Tracker.Say(response: Responses.AreaDoesNotHaveItem, args: [item.Name, area.Name, item.Metadata.NameWithArticle]); + return; + } + else if (locations.Count > 1) + { + // Consider tracking/clearing everything? + Tracker.Say(response: Responses.AreaHasMoreThanOneItem, args: [item.Name, area.Name, item.Metadata.NameWithArticle]); + return; + } + + IsDirty = true; + + TrackItem(item, trackedAs, confidence, tryClear: false); + + List undoActions = + [ + PopUndo().Action + ]; + + if (locations.Count == 1) + { + Tracker.LocationTracker.Clear(locations.Single()); + undoActions.Add(PopUndo().Action); + } + else if (area is IHasTreasure treasureRegion && (item.IsTreasure || area.IsKeysanityForArea)) + { + Tracker.TreasureTracker.TrackDungeonTreasure(treasureRegion, confidence); + undoActions.Add(PopUndo().Action); + } + + AddUndo(() => + { + foreach (var undo in undoActions) + { + undo(); + } + }); + } + + public void TrackItemAmount(Item item, int count, float confidence) + { + var newItemCount = count; + if (item.Metadata.CounterMultiplier > 1 + && count % item.Metadata.CounterMultiplier == 0) + { + newItemCount = count / item.Metadata.CounterMultiplier.Value; + } + + var oldItemCount = item.TrackingState; + if (newItemCount == oldItemCount) + { + Tracker.Say(response: Responses.TrackedExactAmountDuplicate, args: [item.Metadata.Plural, count]); + return; + } + + item.TrackingState = newItemCount; + if (item.TryGetTrackingResponse(out var response)) + { + Tracker.Say(response: response, args: [item.Counter]); + } + else if (newItemCount > oldItemCount) + { + Tracker.Say(text: Responses.TrackedItemMultiple?.Format(item.Metadata.Plural ?? $"{item.Name}s", item.Counter, item.Name)); + } + else + { + Tracker.Say(text: Responses.UntrackedItemMultiple?.Format(item.Metadata.Plural ?? $"{item.Name}s", item.Metadata.Plural ?? $"{item.Name}s")); + } + + IsDirty = true; + ItemTracked?.Invoke(this, new ItemTrackedEventArgs(item, null, confidence, false)); + UpdateAllAccessibility(false, item); + + AddUndo(() => + { + item.TrackingState = oldItemCount; + ItemTracked?.Invoke(this, new ItemTrackedEventArgs(item, null, confidence, false)); + playerProgressionService.ResetProgression(); + UpdateAllAccessibility(true, item); + }); + } + + public void TrackItems(List items, bool autoTracked, bool giftedItem) + { + if (items.Count == 1) + { + TrackItem(items.First(), null, null, false, autoTracked, null, giftedItem); + return; + } + + foreach (var item in items) + { + item.Track(); + } + + AnnounceTrackedItems(items); + + UpdateAllAccessibility(false, items.ToArray()); + + IsDirty = true; + RestartIdleTimers(); + } + + public void UntrackItem(Item item, float? confidence = null) + { + var originalTrackingState = item.TrackingState; + playerProgressionService.ResetProgression(); + + if (!item.Untrack()) + { + Tracker.Say(response: Responses.UntrackedNothing, args: [item.Name, item.Metadata.NameWithArticle]); + return; + } + + if (item.Metadata.HasStages) + { + Tracker.Say(response: Responses.UntrackedProgressiveItem, args: [item.Name, item.Metadata.NameWithArticle]); + } + else if (item.Metadata.Multiple) + { + if (item.TrackingState > 0) + { + if (item.Metadata.CounterMultiplier > 1) + Tracker.Say(text: Responses.UntrackedItemMultiple?.Format($"{item.Metadata.CounterMultiplier} {item.Metadata.Plural}", $"{item.Metadata.CounterMultiplier} {item.Metadata.Plural}")); + else + Tracker.Say(response: Responses.UntrackedItemMultiple, args: [item.Name, item.Metadata.NameWithArticle]); + } + else + Tracker.Say(response: Responses.UntrackedItemMultipleLast, args: [item.Name, item.Metadata.NameWithArticle]); + } + else + { + Tracker.Say(response: Responses.UntrackedItem, args: [item.Name, item.Metadata.NameWithArticle]); + } + + UpdateAllAccessibility(true, item); + IsDirty = true; + + AddUndo(() => + { + item.TrackingState = originalTrackingState; + playerProgressionService.ResetProgression(); + UpdateAllAccessibility(false, item); + }); + } + + private void CommunicatorOnSpeakCompleted(object? sender, SpeakCompletedEventArgs e) + { + if (_pendingSpeechItems.Count > 0) + { + AnnounceTrackedItems(_pendingSpeechItems); + _pendingSpeechItems.Clear(); + } + + if (e.SpeechDuration.TotalSeconds > 60) + { + Tracker.Say(x => x.LongSpeechResponse); + } + } + + private IHasTreasure? GetDungeonFromItem(Item item, IHasTreasure? dungeon = null) + { + var locations = worldQueryService.Locations(itemFilter: item.Type) + .Where(x => x.Type != LocationType.NotInDungeon) + .ToImmutableList(); + + if (locations.Count == 1 && dungeon == null) + { + // User didn't have a guess and there's only one location that + // has the tracker item + return locations[0].GetTreasureRegion(); + } + + if (locations.Count > 0 && dungeon != null) + { + // Does the dungeon even have that item? + if (locations.All(x => dungeon != x.Region)) + { + // Be a smart-ass about it + Tracker.Say(response: Responses.ItemTrackedInIncorrectDungeon, args: [dungeon.Metadata.Name, item.Metadata.NameWithArticle]); + } + } + + // - If tracker was started before generating a seed, we don't know + // better. + // - If we do know better, we should still go with the user's + // choice. + // - If there are multiple copies of the item, we don't know which + // was tracked. Either way, we have to assume `dungeon` is correct. + // If it's `null`, nobody knows. + return dungeon; + } + + private void AnnounceTrackedItems(List items) + { + if (items.Count == 1) + { + var item = items[0]; + if (item.TryGetTrackingResponse(out var response)) + { + Tracker.Say(text: response.Format(item.Counter)); + } + else + { + Tracker.Say(text: Responses.TrackedItem?.Format(item.Name, item.Metadata.NameWithArticle)); + } + } + else if (items.Count == 2) + { + Tracker.Say(x => x.TrackedTwoItems, args: [items[0].Metadata.NameWithArticle, items[1].Metadata.NameWithArticle]); + } + else if (items.Count == 3) + { + Tracker.Say(x => x.TrackedThreeItems, args: [items[0].Metadata.NameWithArticle, items[1].Metadata.NameWithArticle, items[2].Metadata.NameWithArticle]); + } + else + { + var itemsToSay = items.Where(x => x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(2).ToList(); + if (itemsToSay.Count() < 2) + { + var numToTake = 2 - itemsToSay.Count(); + itemsToSay.AddRange(items.Where(x => !x.Type.IsPossibleProgression(World.Config.ZeldaKeysanity, World.Config.MetroidKeysanity)).Take(numToTake)); + } + + Tracker.Say(x => x.TrackedManyItems, args: [itemsToSay[0].Metadata.NameWithArticle, itemsToSay[1].Metadata.NameWithArticle, items.Count - 2]); + } + } + + private void GiveLocationHint(IEnumerable accessibleBefore) + { + var accessibleAfter = worldQueryService.AccessibleLocations(false); + var newlyAccessible = accessibleAfter.Except(accessibleBefore).ToList(); + if (newlyAccessible.Any()) + { + if (newlyAccessible.Contains(World.FindLocation(LocationId.InnerMaridiaSpringBall))) + Tracker.Say(response: Responses.ShaktoolAvailable); + + if (newlyAccessible.Contains(World.DarkWorldNorthWest.PegWorld)) + Tracker.Say(response: Responses.PegWorldAvailable); + } + else if (Responses.TrackedUselessItem != null) + { + Tracker.Say(response: Responses.TrackedUselessItem); + } + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerLocationService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerLocationService.cs new file mode 100644 index 000000000..b67dbb5e5 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerLocationService.cs @@ -0,0 +1,531 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Extensions.Logging; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; +using TrackerCouncil.Smz3.Data.Logic; +using TrackerCouncil.Smz3.Data.Services; +using TrackerCouncil.Smz3.Data.Tracking; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; +using TrackerCouncil.Smz3.Shared; +using TrackerCouncil.Smz3.Shared.Enums; +using TrackerCouncil.Smz3.Tracking.Services; + +namespace TrackerCouncil.Smz3.Tracking.TrackingServices; + +internal class TrackerLocationService(ILogger logger, IPlayerProgressionService playerProgressionService, IMetadataService metadataService, IWorldQueryService worldQueryService) : TrackerService, ITrackerLocationService +{ + private IEnumerable? _previousMissingItems; + + public event EventHandler? LocationCleared; + public event EventHandler? LocationMarked; + + public void Clear(Location location, float? confidence = null, bool autoTracked = false, bool stateResponse = true, bool allowLocationComments = false, bool updateTreasureCount = true) + { + if (!stateResponse && allowLocationComments) + { + GiveLocationComment(location.Item.Type, location, isTracking: true, confidence, location.Item.Metadata); + GivePreConfiguredLocationSass(location); + } + + if (confidence != null && stateResponse) + { + // Only use TTS if called from a voice command + var locationName = location.Metadata.Name; + Tracker.Say(response: Responses.LocationCleared, args: [locationName]); + } + + ItemType? prevMarkedItem = null; + if (location.HasMarkedItem) + { + prevMarkedItem = location.MarkedItem; + location.MarkedItem = null; + LocationMarked?.Invoke(this, new LocationClearedEventArgs(location, confidence, autoTracked)); + } + + var undoTrackTreasure = updateTreasureCount + ? Tracker.TreasureTracker.TryTrackDungeonTreasure(location, confidence, stateResponse: stateResponse) + : null; + + // Important: clear only after tracking dungeon treasure, as + // the "guess dungeon from location" algorithm excludes + // cleared items + location.Cleared = true; + location.SetAccessibility(Accessibility.Cleared); + World.LastClearedLocation = location; + + Action? undoStopPegWorldMode = null; + if (location == World.DarkWorldNorthWest.PegWorld) + { + Tracker.ModeTracker.StopPegWorldMode(); + + if (!autoTracked) + { + undoStopPegWorldMode = PopUndo().Action; + } + } + + // Comment on if the location is out of logic + if (allowLocationComments && stateResponse) + { + var item = location.Item; + var isKeysanityForLocation = (location.Region is Z3Region && World.Config.ZeldaKeysanity) || (location.Region is SMRegion && World.Config.MetroidKeysanity); + var items = playerProgressionService.GetProgression(!isKeysanityForLocation); + + if (!location.IsAvailable(items) && (confidence >= Options.MinimumSassConfidence || autoTracked)) + { + var locationInfo = location.Metadata; + var roomInfo = location.Room?.Metadata; + var regionInfo = location.Region.Metadata; + + if (locationInfo.OutOfLogic != null) + { + Tracker.Say(response: locationInfo.OutOfLogic); + } + else if (roomInfo?.OutOfLogic != null) + { + Tracker.Say(response: roomInfo.OutOfLogic); + } + else if (regionInfo.OutOfLogic != null) + { + Tracker.Say(response: regionInfo.OutOfLogic); + } + else + { + var allMissingCombinations = Logic.GetMissingRequiredItems(location, items, out var allMissingItems); + allMissingItems = allMissingItems.OrderBy(x => x); + + var missingItems = allMissingCombinations.MinBy(x => x.Length); + var allPossibleMissingItems = allMissingItems.ToList(); + if (missingItems == null) + { + Tracker.Say(x => x.TrackedOutOfLogicItemTooManyMissing, args: [item.Metadata.Name, locationInfo.Name]); + } + // Do not say anything if the only thing missing are keys + else + { + var itemsChanged = _previousMissingItems == null || !allPossibleMissingItems.SequenceEqual(_previousMissingItems); + var onlyKeys = allPossibleMissingItems.All(x => x.IsInAnyCategory(ItemCategory.BigKey, ItemCategory.SmallKey, ItemCategory.Keycard)); + _previousMissingItems = allPossibleMissingItems; + + if (itemsChanged && !onlyKeys) + { + var missingItemNames = NaturalLanguage.Join(missingItems.Select(metadataService.GetName)); + Tracker.Say(x => x.TrackedOutOfLogicItem, args: [item.Metadata.Name, locationInfo.Name, missingItemNames]); + } + } + + _previousMissingItems = allPossibleMissingItems; + } + + } + } + + LocationCleared?.Invoke(this, new LocationClearedEventArgs(location, confidence, autoTracked)); + + IsDirty = true; + + AddUndo(autoTracked, () => + { + location.Cleared = false; + + if (prevMarkedItem != null) + { + location.MarkedItem = prevMarkedItem; + LocationMarked?.Invoke(this, new LocationClearedEventArgs(location, null, false)); + } + + UpdateAccessibility(location); + undoTrackTreasure?.Invoke(); + undoStopPegWorldMode?.Invoke(); + LocationCleared?.Invoke(this, new LocationClearedEventArgs(location, null, false)); + }); + } + + public void Unclear(Location location, bool updateTreasureCount = true) + { + var undoTrackTreasure = updateTreasureCount + ? Tracker.TreasureTracker.TryUntrackDungeonTreasure(location) + : null; + + // Important: clear only after tracking dungeon treasure, as + // the "guess dungeon from location" algorithm excludes + // cleared items + location.Cleared = false; + UpdateAccessibility(location); + + LocationCleared?.Invoke(this, new LocationClearedEventArgs(location, null, false)); + + IsDirty = true; + + AddUndo(() => + { + location.Cleared = true; + location.SetAccessibility(Accessibility.Cleared); + undoTrackTreasure?.Invoke(); + }); + } + + public void Clear(List locations, float? confidence = null) + { + if (locations.Count == 1) + { + Clear(locations.First(), confidence); + return; + } + + var originalClearedValues = new Dictionary(); + var originalMarkedItems = new Dictionary(); + + // Clear the locations + foreach (var location in locations) + { + originalClearedValues.Add(location, location.Cleared); + if (location.MarkedItem != null) + { + originalMarkedItems.Add(location, location.MarkedItem.Value); + location.MarkedItem = null; + LocationMarked?.Invoke(this, new LocationClearedEventArgs(location, confidence, false)); + } + + location.Cleared = true; + location.SetAccessibility(Accessibility.Cleared); + LocationCleared?.Invoke(this, new LocationClearedEventArgs(location, confidence, false)); + } + + Action? undoDungeonTreasure = null; + var allSameRegion = false; + if (locations.Select(x => x.Region).Distinct().Count() == 1) + { + allSameRegion = true; + Tracker.Say(x => x.LocationsClearedSameRegion, args: [locations.Count, locations.First().Region.GetName()]); + if (locations.First().Region is IHasTreasure dungeon) + { + var treasureCount = locations.Count(x => x.Item.IsTreasure || World.Config.ZeldaKeysanity); + if (Tracker.TreasureTracker.TrackDungeonTreasure(dungeon, confidence, treasureCount)) + undoDungeonTreasure = PopUndo().Action; + } + } + else + { + Tracker.Say(x => x.LocationsCleared, args: [locations.Count]); + } + + AddUndo(() => + { + foreach (var location in locations) + { + location.Cleared = originalClearedValues[location]; + if (originalMarkedItems.TryGetValue(location, out var item)) + { + location.MarkedItem = item; + LocationMarked?.Invoke(this, new LocationClearedEventArgs(location, null, false)); + } + LocationCleared?.Invoke(this, new LocationClearedEventArgs(location, null, false)); + } + + UpdateAccessibility(locations, allSameRegion ? playerProgressionService.GetProgression(locations[0].Region) : null); + undoDungeonTreasure?.Invoke(); + }); + + World.LastClearedLocation = locations.First(); + IsDirty = true; + } + + public void MarkLocation(Location location, Item item, float? confidence = null, bool autoTracked = false) + { + MarkLocation(location, item.Type, confidence, autoTracked, item.Metadata); + } + + public void MarkLocation(Location location, ItemType item, float? confidence = null, bool autoTracked = false, ItemData? metadata = null) + { + var locationName = location.Metadata.Name; + + metadata ??= metadataService.Item(item); + + GiveLocationComment(location.Item.Type, location, isTracking: false, confidence, metadata); + + if (item == ItemType.Nothing) + { + Clear(location); + Tracker.Say(response: Responses.LocationMarkedAsBullshit, args: [locationName]); + } + else if (location.MarkedItem != null) + { + var oldType = location.MarkedItem; + location.MarkedItem = item; + Tracker.Say(x => x.LocationMarkedAgain, args: [locationName, metadata?.Name ?? item.GetDescription(), oldType.GetDescription()]); + LocationMarked?.Invoke(this, new LocationClearedEventArgs(location, confidence, autoTracked)); + AddUndo(autoTracked, () => + { + location.MarkedItem = oldType; + LocationMarked?.Invoke(this, new LocationClearedEventArgs(location, confidence, autoTracked)); + }); + } + else + { + location.MarkedItem = item; + Tracker.Say(x => x.LocationMarked, args: [locationName, metadata?.Name ?? item.GetDescription()]); + LocationMarked?.Invoke(this, new LocationClearedEventArgs(location, confidence, autoTracked)); + AddUndo(autoTracked, () => + { + location.MarkedItem = null; + LocationMarked?.Invoke(this, new LocationClearedEventArgs(location, confidence, autoTracked)); + }); + } + + GivePreConfiguredLocationSass(location); + + IsDirty = true; + } + + public void ClearArea(IHasLocations area, bool trackItems, bool includeUnavailable = false, float? confidence = null, bool assumeKeys = false) + { + var locations = area.Locations + .Where(x => x.Cleared == false) + .WhereUnless(includeUnavailable, x => x.IsAvailable(playerProgressionService.GetProgression(area))) + .ToImmutableList(); + + playerProgressionService.ResetProgression(); + + if (locations.Count == 0) + { + var outOfLogicLocations = area.Locations + .Count(x => x.Cleared == false); + + if (outOfLogicLocations > 0) + Tracker.Say(responses: Responses.TrackedNothingOutOfLogic, tieredKey: outOfLogicLocations, args: [area.Name, outOfLogicLocations]); + else + Tracker.Say(response: Responses.TrackedNothing, args: [area.Name]); + } + else + { + // If there is only one location... + var onlyLocation = locations.TrySingle(); + if (onlyLocation != null) + { + // Use the basic clear if we're not tracking items + if (!trackItems) + { + Tracker.LocationTracker.Clear(onlyLocation, confidence); + } + // Use the item tracker if we're tracking an item + else + { + var item = onlyLocation.Item; + Tracker.ItemTracker.TrackItem(item: item, trackedAs: null, confidence: confidence, tryClear: true, autoTracked: false, location: onlyLocation); + } + + return; + } + else + { + // Otherwise, start counting + var itemsCleared = 0; + var itemsTracked = new List(); + var treasureTracked = 0; + foreach (var location in locations) + { + itemsCleared++; + if (!trackItems) + { + if (location.Item.IsTreasure || World.Config.ZeldaKeysanity) + treasureTracked++; + location.Cleared = true; + location.SetAccessibility(Accessibility.Cleared); + World.LastClearedLocation = location; + continue; + } + + var item = location.Item; + if (!item.Track()) + logger.LogWarning("Failed to track {ItemType} in {Area}.", item.Name, area.Name); // Probably the compass or something, who cares + else + itemsTracked.Add(item); + if (location.Item.IsTreasure || World.Config.ZeldaKeysanity) + treasureTracked++; + + location.Cleared = true; + location.SetAccessibility(Accessibility.Cleared); + } + + if (trackItems) + { + var itemNames = confidence >= Options.MinimumSassConfidence + ? NaturalLanguage.Join(itemsTracked, World.Config) + : $"{itemsCleared} items"; + Tracker.Say(x => x.TrackedMultipleItems, args: [itemsCleared, area.Name, itemNames]); + + var roomInfo = area is Room room ? room.Metadata : null; + var regionInfo = area is Region region ? region.Metadata : null; + + if (roomInfo?.OutOfLogic != null) + { + Tracker.Say(response: roomInfo.OutOfLogic); + } + else if (regionInfo?.OutOfLogic != null) + { + Tracker.Say(response: regionInfo.OutOfLogic); + } + else + { + var progression = playerProgressionService.GetProgression(area); + var someOutOfLogicLocation = locations.Where(x => !x.IsAvailable(progression)).Random(Random); + if (someOutOfLogicLocation != null && confidence >= Options.MinimumSassConfidence) + { + var someOutOfLogicItem = someOutOfLogicLocation.Item; + var missingItems = Logic.GetMissingRequiredItems(someOutOfLogicLocation, progression, out _).MinBy(x => x.Length); + if (missingItems != null) + { + var missingItemNames = NaturalLanguage.Join(missingItems.Select(metadataService.GetName)); + Tracker.Say(x => x.TrackedOutOfLogicItem, args: [someOutOfLogicItem.Metadata.Name, someOutOfLogicLocation.Metadata.Name, missingItemNames]); + } + else + { + Tracker.Say(x => x.TrackedOutOfLogicItemTooManyMissing, args: [someOutOfLogicItem.Metadata.Name, someOutOfLogicLocation.Metadata.Name]); + } + } + } + } + else + { + Tracker.Say(x => x.ClearedMultipleItems, args: [itemsCleared, area.Name]); + } + + if (treasureTracked > 0) + { + var dungeon = area.GetTreasureRegion(); + if (dungeon != null) + { + Tracker.TreasureTracker.TrackDungeonTreasure(dungeon, amount: treasureTracked); + } + } + } + } + + foreach (var location in locations) + { + location.SetAccessibility(Accessibility.Cleared); + } + + IsDirty = true; + + AddUndo(() => + { + foreach (var location in locations) + { + if (trackItems) + { + var item = location.Item; + if (item.Type != ItemType.Nothing && item.TrackingState > 0) + item.TrackingState--; + } + + location.Cleared = false; + } + + playerProgressionService.ResetProgression(); + UpdateAccessibility(locations, playerProgressionService.GetProgression(area)); + Tracker.BossTracker.UpdateAccessibility(); + Tracker.RewardTracker.UpdateAccessibility(); + }); + } + + private void GiveLocationComment(ItemType item, Location location, bool isTracking, float? confidence, ItemData? metadata) + { + metadata ??= metadataService.Item(item); + + // If the plando config specifies a specific line for this location, say it + if (World.Config.PlandoConfig?.TrackerLocationLines.ContainsKey(location.ToString()) == true) + { + Tracker.Say(text: World.Config.PlandoConfig?.TrackerLocationLines[location.ToString()]); + } + // Give some sass if the user tracks or marks the wrong item at a + // location unless the user is clearing a useless item like missiles + else if (location.Item.Type != ItemType.Nothing && !item.IsEquivalentTo(location.Item.Type) && (item != ItemType.Nothing || location.Item.Metadata.IsProgression(World.Config))) + { + if (confidence == null || confidence < Options.MinimumSassConfidence) + return; + + var actualItemName = metadataService.GetName(location.Item.Type); + if (HintsEnabled) actualItemName = "another item"; + + Tracker.Say(response: Responses.LocationHasDifferentItem, args: [metadata?.NameWithArticle ?? item.GetDescription(), actualItemName]); + } + else + { + if (item == location.VanillaItem && item != ItemType.Nothing) + { + Tracker.Say(x => x.TrackedVanillaItem); + return; + } + + var locationInfo = location.Metadata; + var isJunk = metadata?.IsJunk(World.Config) ?? true; + if (isJunk) + { + if (!isTracking && locationInfo.WhenMarkingJunk?.Count > 0) + { + Tracker.Say(text: locationInfo.WhenMarkingJunk.Random(Random)!); + } + else if (locationInfo.WhenTrackingJunk?.Count > 0) + { + Tracker.Say(text: locationInfo.WhenTrackingJunk.Random(Random)!); + } + } + else if (!isJunk) + { + if (!isTracking && locationInfo.WhenMarkingProgression?.Count > 0) + { + Tracker.Say(text: locationInfo.WhenMarkingProgression.Random(Random)!); + } + else if (locationInfo.WhenTrackingProgression?.Count > 0) + { + Tracker.Say(text: locationInfo.WhenTrackingProgression.Random(Random)!); + } + } + } + } + + private void GivePreConfiguredLocationSass(Location location, bool marking = false) + { + // "What a surprise." + if (LocalConfig != null && LocalConfig.LocationItems.ContainsKey(location.Id)) + { + // I guess we're not storing the other options? We could respond to those, too, if we had those here. + Tracker.Say(x => marking ? x.LocationMarkedPreConfigured : x.TrackedPreConfigured, args: [location.Metadata.Name]); + } + } + + public void UpdateAccessibility(bool unclearedOnly = true, Progression? actualProgression = null, Progression? withKeysProgression = null) + { + actualProgression ??= playerProgressionService.GetProgression(false); + withKeysProgression ??= playerProgressionService.GetProgression(true); + UpdateAccessibility(worldQueryService.AllLocations().Where(x => !unclearedOnly || !x.Cleared), actualProgression, withKeysProgression); + } + + public void UpdateAccessibility(IEnumerable locations, Progression? actualProgression = null, Progression? withKeysProgression = null) + { + actualProgression ??= playerProgressionService.GetProgression(false); + withKeysProgression ??= playerProgressionService.GetProgression(true); + foreach (var location in locations) + { + UpdateAccessibility(location, actualProgression, withKeysProgression); + } + } + + public void UpdateAccessibility(Location location, Progression? actualProgression = null, Progression? withKeysProgression = null) + { + actualProgression ??= playerProgressionService.GetProgression(false); + withKeysProgression ??= playerProgressionService.GetProgression(true); + if (location.Region is HyruleCastle) + { + withKeysProgression = actualProgression; + } + location.UpdateAccessibility(actualProgression, withKeysProgression); + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerModeService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerModeService.cs new file mode 100644 index 000000000..7ab920aef --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerModeService.cs @@ -0,0 +1,140 @@ +using System; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Tracking; +using TrackerCouncil.Smz3.Tracking.VoiceCommands; +using BunLabs; +namespace TrackerCouncil.Smz3.Tracking.TrackingServices; + +internal class TrackerModeService : TrackerService, ITrackerModeService +{ + public bool GoMode { get; set; } + public bool PegWorldMode { get; set; } + public bool ShaktoolMode { get; set; } + public int PegsPegged { get; set; } + + public event EventHandler? ToggledPegWorldModeOn; + public event EventHandler? ToggledShaktoolMode; + public event EventHandler? PegPegged; + public event EventHandler? GoModeToggledOn; + public event EventHandler? GoModeToggledOff; + + public void ToggleGoMode(float? confidence = null) + { + Tracker.ShutUp(); + + if (Random.NextDouble(0, 1) < 0.95) + { + Tracker.Say(text: "Toggled Go Mode ", wait: true); + } + else + { + Tracker.Say(text: "Toggled Go Mode ", wait: true); + } + + GoMode = true; + GoModeToggledOn?.Invoke(this, new TrackerEventArgs(confidence)); + Tracker.Say(text: "on."); + + AddUndo(() => + { + GoMode = false; + if (Responses.GoModeToggledOff != null) + Tracker.Say(response: Responses.GoModeToggledOff); + GoModeToggledOff?.Invoke(this, new TrackerEventArgs(confidence)); + }); + } + + public void Peg(float? confidence = null) + { + if (!PegWorldMode) + return; + + PegsPegged++; + + if (PegsPegged < PegWorldModeModule.TotalPegs) + Tracker.Say(response: Responses.PegWorldModePegged); + else + { + PegWorldMode = false; + Tracker.Say(response: Responses.PegWorldModeDone); + ToggledPegWorldModeOn?.Invoke(this, new TrackerEventArgs(confidence)); + } + + PegPegged?.Invoke(this, new TrackerEventArgs(confidence)); + AddUndo(() => + { + PegsPegged--; + PegPegged?.Invoke(this, new TrackerEventArgs(confidence)); + }); + + RestartIdleTimers(); + } + + public void SetPegs(int count) + { + if (count <= PegsPegged) + return; + + if (!PegWorldMode) + { + PegWorldMode = true; + ToggledPegWorldModeOn?.Invoke(this, new TrackerEventArgs(null, true)); + } + + if (count <= PegWorldModeModule.TotalPegs) + { + var delta = count - PegsPegged; + + PegsPegged = count; + Tracker.Say(responses: Responses.PegWorldModePeggedMultiple, tieredKey: delta, wait: true); + PegPegged?.Invoke(this, new TrackerEventArgs(null, true)); + } + } + + public void StartPegWorldMode(float? confidence = null) + { + Tracker.ShutUp(); + PegWorldMode = true; + Tracker.Say(response: Responses.PegWorldModeOn, wait: true); + ToggledPegWorldModeOn?.Invoke(this, new TrackerEventArgs(confidence)); + AddUndo(() => + { + PegWorldMode = false; + ToggledPegWorldModeOn?.Invoke(this, new TrackerEventArgs(confidence)); + }); + } + + public void StopPegWorldMode(float? confidence = null) + { + PegWorldMode = false; + Tracker.Say(response: Responses.PegWorldModeDone); + ToggledPegWorldModeOn?.Invoke(this, new TrackerEventArgs(confidence)); + AddUndo(() => + { + PegWorldMode = true; + ToggledPegWorldModeOn?.Invoke(this, new TrackerEventArgs(confidence)); + }); + } + + public void StartShaktoolMode(float? confidence = null) + { + ShaktoolMode = true; + ToggledShaktoolMode?.Invoke(this, new TrackerEventArgs(confidence)); + AddUndo(() => + { + ShaktoolMode = false; + ToggledShaktoolMode?.Invoke(this, new TrackerEventArgs(confidence)); + }); + } + + public void StopShaktoolMode(float? confidence = null) + { + ShaktoolMode = false; + ToggledShaktoolMode?.Invoke(this, new TrackerEventArgs(confidence)); + AddUndo(() => + { + ShaktoolMode = true; + ToggledShaktoolMode?.Invoke(this, new TrackerEventArgs(confidence)); + }); + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerPrerequisiteService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerPrerequisiteService.cs new file mode 100644 index 000000000..d853486ec --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerPrerequisiteService.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Tracking.TrackingServices; + +internal class TrackerPrerequisiteService() : TrackerService, ITrackerPrerequisiteService +{ + public void SetDungeonRequirement(IHasPrerequisite region, ItemType? medallion = null, float? confidence = null, bool autoTracked = false) + { + var originalRequirement = region.MarkedItem ?? ItemType.Nothing; + + // If no medallion was passed, increment by one + if (medallion == null) + { + var medallionItems = new List(Enum.GetValues()); + medallionItems.Insert(0, ItemType.Nothing); + var index = (medallionItems.IndexOf(originalRequirement) + 1) % medallionItems.Count; + region.MarkedItem = medallionItems[index]; + } + else + { + if (region.RequiredItem != ItemType.Nothing + && region.RequiredItem != medallion.Value + && confidence >= Options.MinimumSassConfidence) + { + Tracker.Say(response: Responses.DungeonRequirementMismatch, + args: [ + HintsEnabled ? "a different medallion" : region.RequiredItem.ToString(), + region.Metadata.Name, + medallion.Value.ToString() + ]); + } + + region.MarkedItem = medallion.Value; + Tracker.Say(response: Responses.DungeonRequirementMarked, args: [medallion.ToString(), region.Metadata.Name]); + } + + AddUndo(autoTracked, () => + { + region.MarkedItem = originalRequirement; + }); + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerRewardService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerRewardService.cs new file mode 100644 index 000000000..beccef374 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerRewardService.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.Extensions.Logging; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Services; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared; +using TrackerCouncil.Smz3.Shared.Enums; +using TrackerCouncil.Smz3.Tracking.Services; + +namespace TrackerCouncil.Smz3.Tracking.TrackingServices; + +internal class TrackerRewardService(ILogger logger, IPlayerProgressionService playerProgressionService, IMetadataService metadataService) : TrackerService, ITrackerRewardService +{ + public void SetAreaReward(IHasReward rewardRegion, RewardType? reward = null, float? confidence = null, bool autoTracked = false) + { + var originalReward = rewardRegion.MarkedReward; + if (reward == null) + { + var currentValue = rewardRegion.MarkedReward; + rewardRegion.MarkedReward = Enum.IsDefined(currentValue + 1) ? currentValue + 1 : RewardType.None; + // Cycling through rewards is done via UI, so speaking the + // reward out loud for multiple clicks is kind of annoying + } + else + { + rewardRegion.MarkedReward = reward.Value; + + var rewardObj = World.Rewards.FirstOrDefault(x => x.Type == reward.Value); + if (rewardObj == null) + { + logger.LogError("Could not find a reward of type {Type} in the world", reward.Value); + Tracker.Error(); + return; + } + + Tracker.Say(response: Responses.DungeonRewardMarked, args: [rewardRegion.Metadata.Name, rewardObj.Metadata.Name ?? reward.GetDescription()]); + } + + AddUndo(autoTracked, () => + { + rewardRegion.MarkedReward = originalReward; + }); + } + + public void GiveAreaReward(IHasReward rewardRegion, bool isAutoTracked, bool stateResponse) + { + if (rewardRegion.HasReceivedReward) + { + return; + } + + var previousMarkedReward = rewardRegion.MarkedReward; + rewardRegion.HasReceivedReward = true; + + if (isAutoTracked && !rewardRegion.HasCorrectlyMarkedReward) + { + rewardRegion.MarkedReward = rewardRegion.RewardType; + var rewardObj = rewardRegion.Reward; + Tracker.Say(response: Responses.DungeonRewardMarked, args: [rewardRegion.Metadata.Name, rewardObj.Metadata.Name ?? rewardObj.Type.GetDescription()]); + } + + UpdateAllAccessibility(false); + + // TODO: Add a response + + AddUndo(isAutoTracked, () => + { + rewardRegion.HasReceivedReward = false; + rewardRegion.MarkedReward = previousMarkedReward; + UpdateAllAccessibility(true); + }); + } + + public void RemoveAreaReward(IHasReward rewardRegion, bool stateResponse) + { + if (!rewardRegion.HasReceivedReward) + { + return; + } + + rewardRegion.HasReceivedReward = false; + + UpdateAllAccessibility(true); + + // TODO: Add a response + + AddUndo(() => + { + rewardRegion.HasReceivedReward = false; + UpdateAllAccessibility(false); + }); + } + + public void SetUnmarkedRewards(RewardType reward, float? confidence = null) + { + var unmarkedRegions = World.RewardRegions + .Where(x => x.MarkedReward == RewardType.None) + .ToImmutableList(); + + if (unmarkedRegions.Count > 0) + { + Tracker.Say(response: Responses.RemainingDungeonsMarked, args: [metadataService.GetName(reward)]); + unmarkedRegions.ForEach(dungeon => dungeon.MarkedReward = reward); + AddUndo(() => + { + unmarkedRegions.ForEach(dungeon => dungeon.MarkedReward = RewardType.None); + }); + } + else + { + Tracker.Say(response: Responses.NoRemainingDungeons); + } + } + + public void UpdateAccessibility(Progression? actualProgression = null, Progression? withKeysProgression = null) + { + actualProgression ??= playerProgressionService.GetProgression(false); + withKeysProgression ??= playerProgressionService.GetProgression(true); + + foreach (var region in Tracker.World.RewardRegions) + { + UpdateAccessibility(region, actualProgression, withKeysProgression); + } + } + + public void UpdateAccessibility(Reward reward, Progression? actualProgression = null, Progression? withKeysProgression = null) + { + if (reward.Region == null) return; + UpdateAccessibility(reward.Region, actualProgression, withKeysProgression); + } + + public void UpdateAccessibility(IHasReward region, Progression? actualProgression = null, Progression? withKeysProgression = null) + { + if (region.HasReceivedReward) + { + region.RewardAccessibility = Accessibility.Cleared; + return; + } + + actualProgression ??= playerProgressionService.GetProgression(false); + withKeysProgression ??= playerProgressionService.GetProgression(true); + region.Reward.UpdateAccessibility(actualProgression, withKeysProgression); + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerService.cs new file mode 100644 index 000000000..62dbfc5b8 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerService.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Configuration; +using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; +using TrackerCouncil.Smz3.Data.Options; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Shared.Enums; + +namespace TrackerCouncil.Smz3.Tracking.TrackingServices; + +internal abstract class TrackerService +{ + protected static readonly Random Random = new(); + + internal TrackerBase Tracker { get; set; } = null!; + protected Configs Configs => Tracker.Configs; + protected ResponseConfig Responses => Tracker.Responses; + protected World World => Tracker.World; + protected TrackerOptions Options => Tracker.Options; + protected IHistoryService History => Tracker.History; + protected bool HintsEnabled => Tracker.HintsEnabled; + protected bool SpoilersEnabled => Tracker.SpoilersEnabled; + protected Config? LocalConfig => Tracker.LocalConfig; + + protected bool IsDirty + { + get => Tracker.IsDirty; + set => Tracker.MarkAsDirty(value); + } + + public virtual void Initialize() {} + public void RestartIdleTimers() => Tracker.RestartIdleTimers(); + internal void AddUndo(Action undo) => Tracker.AddUndo(undo); + internal void AddUndo(bool autoTracked, Action undo) + { + if (autoTracked) return; + Tracker.AddUndo(undo); + } + + internal (Action Action, DateTime UndoTime) PopUndo() => Tracker.PopUndo(); + + protected void UpdateAllAccessibility(bool itemRemoved, params Item[] items) => Tracker.UpdateAllAccessibility(itemRemoved, items); +} diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerTreasureService.cs b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerTreasureService.cs new file mode 100644 index 000000000..d20fcdf10 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/TrackingServices/TrackerTreasureService.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; +using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.Logic; +using TrackerCouncil.Smz3.Data.Services; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared; +using TrackerCouncil.Smz3.Tracking.Services; + +namespace TrackerCouncil.Smz3.Tracking.TrackingServices; + +internal class TrackerTreasureService(ILogger logger, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService) : TrackerService, ITrackerTreasureService +{ + public bool TrackDungeonTreasure(IHasTreasure region, float? confidence = null, int amount = 1, + bool autoTracked = false, bool stateResponse = true) + { + if (amount < 1) + throw new ArgumentOutOfRangeException(nameof(amount), "The amount of items must be greater than zero."); + + if (amount > region.RemainingTreasure && !region.HasManuallyClearedTreasure) + { + logger.LogWarning("Trying to track {Amount} treasures in a dungeon with only {Left} treasures left.", amount, region.RemainingTreasure); + Tracker.Say(response: Responses.DungeonTooManyTreasuresTracked, args: [region.Metadata.Name, region.RemainingTreasure, amount]); + return false; + } + + List undoActions = []; + + if (!autoTracked) + { + region.HasManuallyClearedTreasure = true; + } + + if (region.RemainingTreasure > 0) + { + region.RemainingTreasure -= amount; + + // If there are no more treasures and the boss is defeated, clear all locations in the dungeon + if (region.RemainingTreasure == 0 && region is IHasBoss { BossDefeated: true }) + { + foreach (var location in ((Region)region).Locations.Where(x => !x.Cleared)) + { + Tracker.LocationTracker.Clear(location, confidence, autoTracked, false, false, false); + undoActions.Add(PopUndo().Action); + } + } + + // Always add a response if there's treasure left, even when + // clearing a dungeon (because that means it was out of logic + // and could be relevant) + if (stateResponse && (confidence != null || region.RemainingTreasure >= 1 || autoTracked)) + { + // Try to get the response based on the amount of items left + if (Responses.DungeonTreasureTracked?.TryGetValue(region.RemainingTreasure, out var response) == true) + Tracker.Say(response: response, args: [region.Metadata.Name, region.RemainingTreasure]); + // If we don't have a response for the exact amount and we + // have multiple left, get the one for 2 (considered + // generic) + else if (region.RemainingTreasure >= 2 && Responses.DungeonTreasureTracked?.TryGetValue(2, out response) == true) + Tracker.Say(response: response, args: [region.Metadata.Name, region.RemainingTreasure]); + } + + AddUndo(autoTracked, () => + { + region.RemainingTreasure += amount; + foreach (var action in undoActions) + { + action.Invoke(); + } + }); + + return true; + } + else if (stateResponse && confidence != null && Responses.DungeonTreasureTracked?.TryGetValue(-1, out var response) == true) + { + // Attempted to track treasure when all treasure items were + // already cleared out + Tracker.Say(response: response, args: [region.Metadata.Name]); + } + + return false; + } + + public bool UntrackDungeonTreasure(IHasTreasure region, int amount = 1) + { + if (amount < 1) + throw new ArgumentOutOfRangeException(nameof(amount), "The amount of items must be greater than zero."); + + if (region.RemainingTreasure == region.TotalTreasure) + { + logger.LogWarning("Trying to untrack {Amount} treasures in a dungeon where no treasures have been removed.", amount); + return false; + } + else if (region.RemainingTreasure + amount > region.TotalTreasure) + { + amount = region.TotalTreasure - region.RemainingTreasure; + } + + IsDirty = true; + + region.RemainingTreasure += amount; + AddUndo(() => + { + region.RemainingTreasure -= amount; + }); + + return true; + } + + public Action? TryTrackDungeonTreasure(Location location, float? confidence, bool autoTracked = false, bool stateResponse = true) + { + if (!autoTracked && confidence < Options.MinimumSassConfidence) + { + // Tracker response could give away the location of an item if + // it is in a dungeon but tracker misheard. + return null; + } + + var dungeon = location.GetTreasureRegion(); + if (dungeon != null && (location.Item.IsTreasure || World.Config.ZeldaKeysanity)) + { + if (TrackDungeonTreasure(dungeon, confidence, 1, autoTracked, stateResponse)) + { + return !autoTracked ? PopUndo().Action : null; + } + } + + IsDirty = true; + + return null; + } + + public Action? TryUntrackDungeonTreasure(Location location) + { + var dungeon = location.GetTreasureRegion(); + + if (dungeon == null || (!location.Item.IsTreasure && !World.Config.ZeldaKeysanity)) + { + return null; + } + + return UntrackDungeonTreasure(dungeon) ? PopUndo().Action : null; + } + + public void ClearDungeon(IHasTreasure treasureRegion, float? confidence = null) + { + var remaining = treasureRegion.RemainingTreasure; + if (remaining > 0) + { + treasureRegion.RemainingTreasure = 0; + } + + List undoActions = []; + + var region = (Region)treasureRegion; + var progress = playerProgressionService.GetProgression(assumeKeys: !World.Config.ZeldaKeysanity); + var locations = region.Locations.Where(x => !x.Cleared).ToList(); + + if (remaining <= 0 && locations.Count <= 0) + { + // We didn't do anything + Tracker.Say(x => x.DungeonAlreadyCleared, args: [treasureRegion.Metadata.Name]); + return; + } + + foreach (var location in locations) + { + Tracker.LocationTracker.Clear(location, confidence, false, false, false); + undoActions.Add(PopUndo().Action); + } + + Tracker.Say(x => x.DungeonCleared, args: [treasureRegion.Metadata.Name]); + + var inaccessibleLocations = locations.Where(x => !x.IsAvailable(progress)).ToList(); + if (inaccessibleLocations.Count > 0 && confidence >= Options.MinimumSassConfidence) + { + var anyMissedLocation = inaccessibleLocations.Random(Random) ?? inaccessibleLocations.First(); + var locationInfo = anyMissedLocation.Metadata; + var missingItemCombinations = Logic.GetMissingRequiredItems(anyMissedLocation, progress, out _).ToList(); + if (missingItemCombinations.Any()) + { + var missingItems = (missingItemCombinations.Random(Random) ?? missingItemCombinations.First()) + .Select(worldQueryService.FirstOrDefault) + .NonNull(); + var missingItemsText = NaturalLanguage.Join(missingItems, World.Config); + Tracker.Say(x => x.DungeonClearedWithInaccessibleItems, args: [treasureRegion.Metadata.Name, locationInfo.Name, missingItemsText]); + } + else + { + Tracker.Say(x => x.DungeonClearedWithTooManyInaccessibleItems, args: [treasureRegion.Metadata.Name, locationInfo.Name]); + } + } + playerProgressionService.ResetProgression(); + + AddUndo(() => + { + treasureRegion.RemainingTreasure = remaining; + foreach (var undo in undoActions) + { + undo(); + } + playerProgressionService.ResetProgression(); + }); + } +} diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/AutoTrackerVoiceModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/AutoTrackerVoiceModule.cs index c6c98bd98..71e64d149 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/AutoTrackerVoiceModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/AutoTrackerVoiceModule.cs @@ -18,12 +18,12 @@ public class AutoTrackerVoiceModule : TrackerModule, IDisposable /// class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to write logging information. /// The auto tracker to associate with this module - public AutoTrackerVoiceModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger, AutoTrackerBase autoTrackerBase) - : base(tracker, itemService, worldService, logger) + public AutoTrackerVoiceModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, AutoTrackerBase autoTrackerBase) + : base(tracker, playerProgressionService, worldQueryService, logger) { TrackerBase.AutoTracker = autoTrackerBase; _autoTrackerBase = autoTrackerBase; diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/BossTrackingModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/BossTrackingModule.cs index db51c317f..a048216da 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/BossTrackingModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/BossTrackingModule.cs @@ -2,6 +2,7 @@ using System.Runtime.Versioning; using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.Shared; using TrackerCouncil.Smz3.Tracking.Services; @@ -17,11 +18,11 @@ public class BossTrackingModule : TrackerModule /// class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to write logging information. - public BossTrackingModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger) - : base(tracker, itemService, worldService, logger) + public BossTrackingModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { } @@ -93,63 +94,52 @@ public override void AddCommands() { AddCommand("Mark boss as defeated", GetMarkBossAsDefeatedRule(), (result) => { - var dungeon = GetBossDungeonFromResult(TrackerBase, result); - if (dungeon != null) - { - // Track boss with associated dungeon - TrackerBase.MarkDungeonAsCleared(dungeon, result.Confidence); - return; - } + var boss = GetBossFromResult(TrackerBase, result) ?? + throw new Exception($"Could not find boss or dungeon in command: '{result.Text}'"); - var boss = GetBossFromResult(TrackerBase, result); - if (boss != null) + var admittedGuilt = result.Text.ContainsAny("killed", "beat", "defeated", "dead") + && !result.Text.ContainsAny("beat off", "beaten off"); + + if (boss.Region != null) { - // Track standalone boss - var admittedGuilt = result.Text.ContainsAny("killed", "beat", "defeated", "dead") - && !result.Text.ContainsAny("beat off", "beaten off"); - TrackerBase.MarkBossAsDefeated(boss, admittedGuilt, result.Confidence); + // Track boss with associated dungeon or region + TrackerBase.BossTracker.MarkBossAsDefeated(boss.Region, result.Confidence, false, admittedGuilt); return; } - throw new Exception($"Could not find boss or dungeon in command: '{result.Text}'"); + // Track standalone boss + TrackerBase.BossTracker.MarkBossAsDefeated(boss, admittedGuilt, result.Confidence); }); AddCommand("Mark boss as alive", GetMarkBossAsNotDefeatedRule(), (result) => { - var dungeon = GetBossDungeonFromResult(TrackerBase, result); - if (dungeon != null) - { - // Untrack boss with associated dungeon - TrackerBase.MarkDungeonAsIncomplete(dungeon, result.Confidence); - return; - } - - var boss = GetBossFromResult(TrackerBase, result); - if (boss != null) + var boss = GetBossFromResult(TrackerBase, result) ?? throw new Exception($"Could not find boss or dungeon in command: '{result.Text}'"); + if (boss.Region != null) { - // Untrack standalone boss - TrackerBase.MarkBossAsNotDefeated(boss, result.Confidence); + // Untrack boss with associated dungeon or region + TrackerBase.BossTracker.MarkBossAsNotDefeated(boss.Region, result.Confidence); return; } - throw new Exception($"Could not find boss or dungeon in command: '{result.Text}'"); + // Untrack standalone boss + TrackerBase.BossTracker.MarkBossAsNotDefeated(boss, result.Confidence); }); AddCommand("Mark boss as defeated with content", GetBossDefeatedWithContentRule(), (result) => { - var contentItemData = ItemService.FirstOrDefault("Content"); + var contentItemData = WorldQueryService.FirstOrDefault("Content"); - var dungeon = GetBossDungeonFromResult(TrackerBase, result); + var dungeon = GetBossDungeonFromResult(TrackerBase, result) as IHasBoss; if (dungeon != null) { if (contentItemData != null) { TrackerBase.Say(x => x.DungeonBossClearedAddContent); - TrackerBase.TrackItem(contentItemData); + TrackerBase.ItemTracker.TrackItem(contentItemData); } // Track boss with associated dungeon - TrackerBase.MarkDungeonAsCleared(dungeon, result.Confidence); + TrackerBase.BossTracker.MarkBossAsDefeated(dungeon, result.Confidence); return; } @@ -159,13 +149,13 @@ public override void AddCommands() if (contentItemData != null) { TrackerBase.Say(x => x.DungeonBossClearedAddContent); - TrackerBase.TrackItem(contentItemData); + TrackerBase.ItemTracker.TrackItem(contentItemData); } // Track standalone boss var admittedGuilt = result.Text.ContainsAny("killed", "beat", "defeated", "dead") && !result.Text.ContainsAny("beat off", "beaten off"); - TrackerBase.MarkBossAsDefeated(boss, admittedGuilt, result.Confidence); + TrackerBase.BossTracker.MarkBossAsDefeated(boss, admittedGuilt, result.Confidence); return; } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ChatIntegrationModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ChatIntegrationModule.cs index b0f9db254..8318b8497 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ChatIntegrationModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ChatIntegrationModule.cs @@ -25,7 +25,7 @@ public class ChatIntegrationModule : TrackerModule, IDisposable private static readonly Random s_random = new(); private const string WinningGuessKey = "WinningGuess"; private readonly Dictionary _usersGreetedTimes = new(); - private readonly IItemService _itemService; + private readonly IPlayerProgressionService _playerProgressionService; private readonly ITrackerTimerService _timerService; private bool _askChatAboutContentCheckPollResults = true; private string? _askChatAboutContentPollId; @@ -41,16 +41,16 @@ public class ChatIntegrationModule : TrackerModule, IDisposable /// dependencies. /// /// The tracker instance to use. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// /// The chat client to use. /// Used to write logging information. - public ChatIntegrationModule(TrackerBase tracker, IChatClient chatClient, IItemService itemService, IWorldService worldService, ITrackerTimerService timerService, ILogger logger) - : base(tracker, itemService, worldService, logger) + public ChatIntegrationModule(TrackerBase tracker, IChatClient chatClient, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ITrackerTimerService timerService, ILogger logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { ChatClient = chatClient; - _itemService = itemService; + _playerProgressionService = playerProgressionService; _timerService = timerService; ChatClient.Connected += ChatClient_Connected; ChatClient.Reconnected += ChatClient_Reconnected; @@ -302,7 +302,7 @@ public async Task GTItemTracked(int number, bool wasBigKey) /// public async Task AskChatAboutContent() { - var contentItemData = _itemService.FirstOrDefault("Content"); + var contentItemData = WorldQueryService.FirstOrDefault("Content"); if (contentItemData == null) { Logger.LogError("Unable to determine content item data"); @@ -314,7 +314,7 @@ public async Task AskChatAboutContent() var shouldAskChat = ChatClient.IsConnected && (!_hasAskedChatAboutContent || s_random.Next(0, 3) == 0); if (!ShouldCreatePolls || !shouldAskChat) { - TrackerBase.TrackItem(contentItemData); + TrackerBase.ItemTracker.TrackItem(contentItemData); return; } @@ -322,7 +322,7 @@ public async Task AskChatAboutContent() if (string.IsNullOrEmpty(_askChatAboutContentPollId)) { - TrackerBase.TrackItem(contentItemData); + TrackerBase.ItemTracker.TrackItem(contentItemData); return; } @@ -352,7 +352,7 @@ public async Task AskChatAboutContent() if ("Yes".Equals(result.WinningChoice, StringComparison.OrdinalIgnoreCase)) { TrackerBase.Say(x => x.Chat.AskChatAboutContentYes); - TrackerBase.TrackItem(contentItemData); + TrackerBase.ItemTracker.TrackItem(contentItemData); } else { diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/CheatsModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/CheatsModule.cs index 2933c7be7..7822af510 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/CheatsModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/CheatsModule.cs @@ -33,11 +33,11 @@ public class CheatsModule : TrackerModule /// class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to write logging information. - public CheatsModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger) - : base(tracker, itemService, worldService, logger) + public CheatsModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs index 677c8ff8b..83dd69b57 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/GoModeModule.cs @@ -18,12 +18,12 @@ public class GoModeModule : TrackerModule /// Initializes a new instance of the class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to log information. /// - public GoModeModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger, ResponseConfig responseConfig) - : base(tracker, itemService, worldService, logger) + public GoModeModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, ResponseConfig responseConfig) + : base(tracker, playerProgressionService, worldQueryService, logger) { _responseConfig = responseConfig; } @@ -45,7 +45,7 @@ public override void AddCommands() AddCommand("Toggle Go Mode", GetGoModeRule(_responseConfig.GoModePrompts), (result) => { - TrackerBase.ToggleGoMode(result.Confidence); + TrackerBase.ModeTracker.ToggleGoMode(result.Confidence); }); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs index 543b2293d..bd5434ebd 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/HintTileModule.cs @@ -26,12 +26,12 @@ public class HintTileModule : TrackerModule /// Constructor /// /// - /// - /// + /// + /// /// /// /// - public HintTileModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger, IGameHintService gameHintService, Configs configs) : base(tracker, itemService, worldService, logger) + public HintTileModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, IGameHintService gameHintService, Configs configs) : base(tracker, playerProgressionService, worldQueryService, logger) { _gameHintService = gameHintService; _hintTileConfig = configs.HintTileConfig; @@ -45,12 +45,12 @@ public override void AddCommands() { AddCommand("Hint Tile", GetHintTileRules(), (result) => { - if (WorldService.World.HintTiles.Any()) + if (WorldQueryService.World.HintTiles.Any()) { var hintTile = GetHintTileFromResult(result); - var text = _gameHintService.GetHintTileText(hintTile.PlayerHintTile, WorldService.World, WorldService.Worlds); + var text = _gameHintService.GetHintTileText(hintTile.PlayerHintTile, WorldQueryService.World, WorldQueryService.Worlds); TrackerBase.Say(response: _hintTileConfig.RequestedHintTile, args: [text]); - TrackerBase.UpdateHintTile(hintTile.PlayerHintTile); + TrackerBase.GameStateTracker.UpdateHintTile(hintTile.PlayerHintTile); } else { @@ -100,7 +100,7 @@ private GrammarBuilder GetHintTileRules() var key = (string)result.Semantics[_hintTileKey].Value; var hintTile = _hintTileConfig.HintTiles?.FirstOrDefault(x => x.HintTileKey == key) ?? throw new Exception($"Could not find hint tile {key}"); - var playerHintTile = WorldService.World.HintTiles.FirstOrDefault(x => x.HintTileCode == key) ?? + var playerHintTile = WorldQueryService.World.HintTiles.FirstOrDefault(x => x.HintTileCode == key) ?? throw new Exception($"Could not find player hint tile {key}"); return (hintTile, playerHintTile); } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ItemTrackingModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ItemTrackingModule.cs index 11e9d80b9..2c34d6789 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ItemTrackingModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ItemTrackingModule.cs @@ -19,11 +19,11 @@ public class ItemTrackingModule : TrackerModule /// class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to log information. - public ItemTrackingModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger) - : base(tracker, itemService, worldService, logger) + public ItemTrackingModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { } @@ -188,7 +188,7 @@ private GrammarBuilder GetSetItemCountRule() [SupportedOSPlatform("windows")] public override void AddCommands() { - var isMultiworld = WorldService.World.Config.MultiWorld; + var isMultiworld = WorldQueryService.World.Config.MultiWorld; AddCommand("Track item", GetTrackItemRule(isMultiworld), (result) => { @@ -197,21 +197,21 @@ public override void AddCommands() if (result.Semantics.ContainsKey(DungeonKey)) { var dungeon = GetDungeonFromResult(TrackerBase, result); - TrackerBase.TrackItem(item, dungeon, + TrackerBase.ItemTracker.TrackItemFrom(item, dungeon, trackedAs: itemName, confidence: result.Confidence); } else if (result.Semantics.ContainsKey(RoomKey)) { var room = GetRoomFromResult(TrackerBase, result); - TrackerBase.TrackItem(item, room, + TrackerBase.ItemTracker.TrackItemFrom(item, room, trackedAs: itemName, confidence: result.Confidence); } else if (result.Semantics.ContainsKey(LocationKey)) { var location = GetLocationFromResult(TrackerBase, result); - TrackerBase.TrackItem(item: item, + TrackerBase.ItemTracker.TrackItem(item: item, trackedAs: itemName, confidence: result.Confidence, tryClear: true, @@ -220,7 +220,7 @@ public override void AddCommands() } else { - TrackerBase.TrackItem(item, + TrackerBase.ItemTracker.TrackItem(item, trackedAs: itemName, confidence: result.Confidence); } @@ -228,7 +228,7 @@ public override void AddCommands() AddCommand("Track death", GetTrackDeathRule(), (result) => { - var death = ItemService.FirstOrDefault("Death"); + var death = WorldQueryService.FirstOrDefault("Death"); if (death == null) { Logger.LogError("Tried to track death, but could not find an item named 'Death'."); @@ -236,7 +236,7 @@ public override void AddCommands() return; } - TrackerBase.TrackItem(death, confidence: result.Confidence, tryClear: false); + TrackerBase.ItemTracker.TrackItem(death, confidence: result.Confidence, tryClear: false); }); if (!isMultiworld) @@ -246,7 +246,7 @@ public override void AddCommands() if (result.Semantics.ContainsKey(RoomKey)) { var room = GetRoomFromResult(TrackerBase, result); - TrackerBase.ClearArea(room, + TrackerBase.LocationTracker.ClearArea(room, trackItems: true, includeUnavailable: false, confidence: result.Confidence); @@ -254,7 +254,7 @@ public override void AddCommands() else if (result.Semantics.ContainsKey(RegionKey)) { var region = GetRegionFromResult(TrackerBase, result); - TrackerBase.ClearArea(region, + TrackerBase.LocationTracker.ClearArea(region, trackItems: true, includeUnavailable: false, confidence: result.Confidence); @@ -266,7 +266,7 @@ public override void AddCommands() if (result.Semantics.ContainsKey(RoomKey)) { var room = GetRoomFromResult(TrackerBase, result); - TrackerBase.ClearArea(room, + TrackerBase.LocationTracker.ClearArea(room, trackItems: true, includeUnavailable: true, confidence: result.Confidence); @@ -274,7 +274,7 @@ public override void AddCommands() else if (result.Semantics.ContainsKey(RegionKey)) { var region = GetRegionFromResult(TrackerBase, result); - TrackerBase.ClearArea(region, + TrackerBase.LocationTracker.ClearArea(region, trackItems: true, includeUnavailable: true, confidence: result.Confidence); @@ -285,14 +285,14 @@ public override void AddCommands() AddCommand("Untrack an item", GetUntrackItemRule(), (result) => { var item = GetItemFromResult(TrackerBase, result, out _); - TrackerBase.UntrackItem(item, result.Confidence); + TrackerBase.ItemTracker.UntrackItem(item, result.Confidence); }); AddCommand("Set item count", GetSetItemCountRule(), (result) => { var item = GetItemFromResult(TrackerBase, result, out _); var count = (int)result.Semantics[ItemCountKey].Value; - TrackerBase.TrackItemAmount(item, count, result.Confidence); + TrackerBase.ItemTracker.TrackItemAmount(item, count, result.Confidence); }); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/LocationTrackingModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/LocationTrackingModule.cs index 1c25b874d..bd84920fb 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/LocationTrackingModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/LocationTrackingModule.cs @@ -15,11 +15,11 @@ public class LocationTrackingModule : TrackerModule /// cref="LocationTrackingModule"/> class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to log information. - public LocationTrackingModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger) - : base(tracker, itemService, worldService, logger) + public LocationTrackingModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { } @@ -150,13 +150,13 @@ public override void AddCommands() { var item = GetItemFromResult(TrackerBase, result, out _); var location = GetLocationFromResult(TrackerBase, result); - TrackerBase.MarkLocation(location, item, result.Confidence); + TrackerBase.LocationTracker.MarkLocation(location, item, result.Confidence); }); AddCommand("Clear specific item location", GetClearLocationRule(), (result) => { var location = GetLocationFromResult(TrackerBase, result); - TrackerBase.Clear(location, result.Confidence); + TrackerBase.LocationTracker.Clear(location, result.Confidence); }); AddCommand("Clear available items in an area", GetClearAreaRule(), (result) => @@ -164,7 +164,7 @@ public override void AddCommands() if (result.Semantics.ContainsKey(RoomKey)) { var room = GetRoomFromResult(TrackerBase, result); - TrackerBase.ClearArea(room, + TrackerBase.LocationTracker.ClearArea(room, trackItems: false, includeUnavailable: false, confidence: result.Confidence); @@ -172,12 +172,12 @@ public override void AddCommands() else if (result.Semantics.ContainsKey(DungeonKey)) { var dungeon = GetDungeonFromResult(TrackerBase, result); - TrackerBase.ClearDungeon(dungeon, result.Confidence); + TrackerBase.TreasureTracker.ClearDungeon(dungeon, result.Confidence); } else if (result.Semantics.ContainsKey(RegionKey)) { var region = GetRegionFromResult(TrackerBase, result); - TrackerBase.ClearArea(region, + TrackerBase.LocationTracker.ClearArea(region, trackItems:false, includeUnavailable: false, confidence: result.Confidence); @@ -189,7 +189,7 @@ public override void AddCommands() if (result.Semantics.ContainsKey(RoomKey)) { var room = GetRoomFromResult(TrackerBase, result); - TrackerBase.ClearArea(room, + TrackerBase.LocationTracker.ClearArea(room, trackItems: false, includeUnavailable: true, confidence: result.Confidence); @@ -197,12 +197,12 @@ public override void AddCommands() else if (result.Semantics.ContainsKey(DungeonKey)) { var dungeon = GetDungeonFromResult(TrackerBase, result); - TrackerBase.ClearDungeon(dungeon, result.Confidence); + TrackerBase.TreasureTracker.ClearDungeon(dungeon, result.Confidence); } else if (result.Semantics.ContainsKey(RegionKey)) { var region = GetRegionFromResult(TrackerBase, result); - TrackerBase.ClearArea(region, + TrackerBase.LocationTracker.ClearArea(region, trackItems:false, includeUnavailable: true, confidence: result.Confidence); @@ -211,7 +211,7 @@ public override void AddCommands() AddCommand("Clear recent marked locations", GetClearViewedObjectRule(), (result) => { - TrackerBase.ClearLastViewedObject(result.Confidence); + TrackerBase.GameStateTracker.ClearLastViewedObject(result.Confidence); }); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MapModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MapModule.cs index 01d60aade..3b2c51e37 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MapModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MapModule.cs @@ -22,12 +22,12 @@ public class MapModule : TrackerModule /// Constructor /// /// - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// /// - public MapModule(TrackerBase tracker, IItemService itemService, ILogger logger, IWorldService worldService, TrackerMapConfig config) - : base(tracker, itemService, worldService, logger) + public MapModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, ILogger logger, IWorldQueryService worldQueryService, TrackerMapConfig config) + : base(tracker, playerProgressionService, worldQueryService, logger) { _logger = logger; _config = config; @@ -91,7 +91,7 @@ public override void AddCommands() AddCommand("Update map", GetChangeMapRule(), (result) => { var mapName = (string)result.Semantics[MapKey].Value; - TrackerBase.UpdateMap(mapName); + TrackerBase.GameStateTracker.UpdateMap(mapName); TrackerBase.Say(x => x.Map.UpdateMap, args: [mapName]); }); @@ -110,18 +110,18 @@ public override void AddCommands() if (map != null) { - if (ItemService.IsTracked(ItemType.Lamp)) + if (PlayerProgressionService.IsTracked(ItemType.Lamp)) { TrackerBase.Say(x => x.Map.HasLamp); return; } - _prevMap = TrackerBase.CurrentMap; + _prevMap = TrackerBase.GameStateTracker.CurrentMap; if (string.IsNullOrEmpty(_prevMap)) { _prevMap = _config.Maps.Last().ToString(); } - TrackerBase.UpdateMap(map.ToString()); + TrackerBase.GameStateTracker.UpdateMap(map.ToString()); TrackerBase.Say(x => x.Map.ShowDarkRoomMap, args: [map.Name]); } else @@ -138,7 +138,7 @@ public override void AddCommands() } else { - TrackerBase.UpdateMap(_prevMap); + TrackerBase.GameStateTracker.UpdateMap(_prevMap); TrackerBase.Say(x => x.Map.HideDarkRoomMap, args: [_prevMap]); _prevMap = ""; } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs index ad8fb63fb..38c3ed5e9 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MetaModule.cs @@ -25,12 +25,12 @@ public class MetaModule : TrackerModule /// Initializes a new instance of the class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to write logging information. /// Used to communicate information to the user - public MetaModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger, ICommunicator communicator) - : base(tracker, itemService, worldService, logger) + public MetaModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, ICommunicator communicator) + : base(tracker, playerProgressionService, worldQueryService, logger) { _communicator = communicator; } @@ -249,7 +249,7 @@ public override void AddCommands() AddCommand("Beat game", GetBeatGameRule(), (_) => { - TrackerBase.GameBeaten(false); + TrackerBase.GameStateTracker.GameBeaten(false); }); } } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs index 828c6d25b..6d4767cf0 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MsuModule.cs @@ -34,8 +34,8 @@ public class MsuModule : TrackerModule, IDisposable /// Constructor /// /// - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// /// /// @@ -45,8 +45,8 @@ public class MsuModule : TrackerModule, IDisposable /// public MsuModule( TrackerBase tracker, - IItemService itemService, - IWorldService worldService, + IPlayerProgressionService playerProgressionService, + IWorldQueryService worldQueryService, ILogger logger, IMsuLookupService msuLookupService, IMsuMonitorService msuMonitorService, @@ -54,7 +54,7 @@ public MsuModule( IMsuUserOptionsService msuUserOptionsService, Configs config, IGameService gameService) - : base(tracker, itemService, worldService, logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { _gameService = gameService; _msuMonitorService = msuMonitorService; diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MultiplayerModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MultiplayerModule.cs index 6073d8750..32bd2e3a8 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MultiplayerModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/MultiplayerModule.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Tracking; +using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.Multiplayer.Client; using TrackerCouncil.Smz3.Multiplayer.Client.EventHandlers; using TrackerCouncil.Smz3.Shared; @@ -21,36 +22,34 @@ public class MultiplayerModule : TrackerModule /// class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to write logging information. /// The multiplayer game service /// - public MultiplayerModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, + public MultiplayerModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger, MultiplayerGameService multiplayerGameService, AutoTrackerBase autoTrackerBase) - : base(tracker, itemService, worldService, logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { _multiplayerGameService = multiplayerGameService; if (TrackerBase.Rom is { MultiplayerGameDetails: null }) return; - TrackerBase.LocationCleared += TrackerOnLocationCleared; - TrackerBase.BossUpdated += TrackerOnBossUpdated; - TrackerBase.ItemTracked += TrackerOnItemTracked; - TrackerBase.DungeonUpdated += TrackerOnDungeonUpdated; - TrackerBase.BeatGame += TrackerOnBeatGame; - TrackerBase.PlayerDied += TrackerOnPlayerDied; + TrackerBase.LocationTracker.LocationCleared += TrackerOnLocationCleared; + TrackerBase.BossTracker.BossUpdated += TrackerOnBossUpdated; + TrackerBase.ItemTracker.ItemTracked += TrackerOnItemTracked; + TrackerBase.GameStateTracker.BeatGame += TrackerOnBeatGame; + TrackerBase.GameStateTracker.PlayerDied += TrackerOnPlayerDied; autoTrackerBase.AutoTrackerConnected += AutoTrackerOnAutoTrackerConnected; _multiplayerGameService.PlayerTrackedLocation += PlayerTrackedLocation; _multiplayerGameService.PlayerTrackedItem += PlayerTrackedItem; _multiplayerGameService.PlayerTrackedBoss += PlayerTrackedBoss; - _multiplayerGameService.PlayerTrackedDungeon += PlayerTrackedDungeon; _multiplayerGameService.PlayerTrackedDeath += PlayerTrackedDeath; _multiplayerGameService.PlayerSyncReceived += PlayerSyncReceived; _multiplayerGameService.PlayerEndedGame += PlayerEndedGame; - _multiplayerGameService.SetTrackerState(worldService.World.State!); + _multiplayerGameService.SetTrackerState(worldQueryService.World.State!); _multiplayerGameService.OnTrackingStarted(); Logger.LogInformation("Multiplayer module initialized"); @@ -86,14 +85,14 @@ private void PlayerSyncReceived(PlayerSyncReceivedEventHandlerArgs args) // Ignore the sync if auto tracker is not connected as we don't want to lose out on items if (TrackerBase.AutoTracker?.HasValidState != true) return; if (args.PlayerId == null || args.ItemsToGive == null || args.ItemsToGive.Count == 0 || args.IsLocalPlayer) return; - var items = args.ItemsToGive.Select(x => ItemService.FirstOrDefault(x)).NonNull().ToList(); + var items = args.ItemsToGive.Select(x => WorldQueryService.FirstOrDefault(x)).NonNull().ToList(); Logger.LogInformation("Giving player {Count} items", items.Count()); _ = TrackerBase.GameService!.TryGiveItemsAsync(items, args.PlayerId.Value); - if ((args.DidForfeit || args.DidComplete) && WorldService.Worlds.Any(x => x.Id == args.PlayerId)) + if ((args.DidForfeit || args.DidComplete) && WorldQueryService.Worlds.Any(x => x.Id == args.PlayerId)) { - WorldService.Worlds.First(x => x.Id == args.PlayerId).HasCompleted = true; + WorldQueryService.Worlds.First(x => x.Id == args.PlayerId).HasCompleted = true; } foreach (var locationState in args.UpdatedLocationStates) @@ -112,13 +111,6 @@ private void PlayerSyncReceived(PlayerSyncReceivedEventHandlerArgs args) bossState.Defeated = true; bossState.AutoTracked = true; } - - foreach (var dungeonState in args.UpdatedDungeonStates) - { - dungeonState.Cleared = true; - dungeonState.AutoTracked = true; - } - } private void PlayerEndedGame(PlayerEndedGameEventHandlerArgs args) @@ -141,7 +133,7 @@ private void PlayerTrackedItem(PlayerTrackedItemEventHandlerArgs args) { args.ItemState.TrackingState = args.TrackingValue; if (args.ItemState.Type == null || args.IsLocalPlayer) return; - var item = ItemService.FirstOrDefault(args.ItemState.Type.Value); + var item = WorldQueryService.FirstOrDefault(args.ItemState.Type.Value); if (item == null || item.State.TrackingState >= args.TrackingValue || !item.Progression) { return; @@ -156,7 +148,7 @@ private void PlayerTrackedLocation(PlayerTrackedLocationEventHandlerArgs args) // Ignore the sync if auto tracker is not connected as we don't want to lose out on items if (TrackerBase.AutoTracker?.HasValidState != true) return; if (args.ItemToGive == ItemType.Nothing) return; - var item = ItemService.FirstOrDefault(args.ItemToGive); + var item = WorldQueryService.FirstOrDefault(args.ItemToGive); if (item == null) throw new InvalidOperationException($"Player retrieved invalid item {args.ItemToGive}"); _ = TrackerBase.GameService!.TryGiveItemAsync(item, args.PlayerId); @@ -174,36 +166,46 @@ private void PlayerTrackedLocation(PlayerTrackedLocationEventHandlerArgs args) args.LocationState.Autotracked = true; } - private void PlayerTrackedDungeon(PlayerTrackedDungeonEventHandlerArgs args) + private void PlayerTrackedBoss(PlayerTrackedBossEventHandlerArgs args) { - args.DungeonState.Cleared = true; - args.DungeonState.AutoTracked = true; - var dungeon = WorldService.GetWorld(args.PlayerId).Dungeons - .FirstOrDefault(x => x.GetType().Name == args.DungeonState.Name); - if (dungeon == null) return; - if (dungeon is { HasReward: true, DungeonReward: { } }) + args.BossState.Defeated = true; + args.BossState.AutoTracked = true; + + if (args.BossState.Type == BossType.None) + { + return; + } + + var boss = WorldQueryService.Worlds.FirstOrDefault(x => x.Id == args.PlayerId)?.Bosses + .FirstOrDefault(x => x.Type == args.BossState.Type); + if (boss?.Region == null) + { + return; + } + + // Check if the region also has treasure, thus it's a Zelda dungeon + if (boss.Region is IHasTreasure treasureRegion) { - TrackerBase.Say(x => x.Multiplayer.OtherPlayerClearedDungeonWithReward, - args: [ - args.PhoneticName, - dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss, dungeon.DungeonReward.Metadata.Name, - dungeon.DungeonReward.Metadata.NameWithArticle - ]); + if (boss.Region is IHasReward rewardRegion && rewardRegion.RewardType.IsInAnyCategory(RewardCategory.Pendant, RewardCategory.Crystal)) + { + TrackerBase.Say(x => x.Multiplayer.OtherPlayerClearedDungeonWithReward, + args: [ + args.PhoneticName, + rewardRegion.Metadata.Name, boss.Region.BossMetadata.Name, rewardRegion.RewardMetadata.Name, + rewardRegion.RewardMetadata.NameWithArticle + ]); + } + else + { + TrackerBase.Say(x => x.Multiplayer.OtherPlayerClearedDungeonWithoutReward, + args: [args.PhoneticName, treasureRegion.Metadata.Name, boss.Region.BossMetadata.Name]); + } } else { - TrackerBase.Say(x => x.Multiplayer.OtherPlayerClearedDungeonWithoutReward, - args: [args.PhoneticName, dungeon.DungeonMetadata.Name, dungeon.DungeonMetadata.Boss]); + TrackerBase.Say(x => x.Multiplayer.OtherPlayerDefeatedBoss, args: [args.PhoneticName, boss.Metadata.Name]); } - } - private void PlayerTrackedBoss(PlayerTrackedBossEventHandlerArgs args) - { - args.BossState.Defeated = true; - args.BossState.AutoTracked = true; - var boss = WorldService.World.GoldenBosses.FirstOrDefault(x => x.Type == args.BossState.Type); - if (boss == null) return; - TrackerBase.Say(x => x.Multiplayer.OtherPlayerDefeatedBoss, args: [args.PhoneticName, boss.Metadata.Name]); } private void PlayerTrackedDeath(PlayerTrackedDeathEventHandlerArgs args) @@ -221,12 +223,6 @@ private void PlayerTrackedDeath(PlayerTrackedDeathEventHandlerArgs args) } - private async void TrackerOnDungeonUpdated(object? sender, DungeonTrackedEventArgs e) - { - if (e.Dungeon == null || !e.AutoTracked || !e.Dungeon.DungeonState.Cleared) return; - await _multiplayerGameService.TrackDungeon(e.Dungeon); - } - private async void TrackerOnItemTracked(object? sender, ItemTrackedEventArgs e) { if (e.Item == null || e.Item.Type == ItemType.Nothing || !e.AutoTracked) return; @@ -246,7 +242,7 @@ private async void TrackerOnLocationCleared(object? sender, LocationClearedEvent if (!e.AutoTracked) return; await _multiplayerGameService.TrackLocation(e.Location); if (e.Location.World == e.Location.Item.World || !e.Location.Item.Progression) return; - var localItem = ItemService.FirstOrDefault(e.Location.Item.Type); + var localItem = WorldQueryService.FirstOrDefault(e.Location.Item.Type); if (localItem == null || localItem.State.TrackingState >= e.Location.Item.State.TrackingState) return; var otherPlayer = e.Location.Item.World.Config.PhoneticName; TrackerBase.Say(x => x.Multiplayer.GiftedUsefulItemToOtherPlayer, diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PegWorldModeModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PegWorldModeModule.cs index d0481a136..7606002a1 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PegWorldModeModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PegWorldModeModule.cs @@ -17,11 +17,11 @@ public class PegWorldModeModule : TrackerModule, IOptionalModule /// class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to log information. - public PegWorldModeModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger) - : base(tracker, itemService, worldService, logger) + public PegWorldModeModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { } @@ -35,7 +35,7 @@ public override void AddCommands() "Hey tracker, let's go to Peg World!" }, (result) => { - TrackerBase.StartPegWorldMode(result.Confidence); + TrackerBase.ModeTracker.StartPegWorldMode(result.Confidence); }); AddCommand("Toggle Peg World mode off", new[] { @@ -46,7 +46,7 @@ public override void AddCommands() "Hey tracker, release me from Peg World" }, (result) => { - TrackerBase.StopPegWorldMode(result.Confidence); + TrackerBase.ModeTracker.StopPegWorldMode(result.Confidence); }); AddCommand("Track Peg World peg", new[] { @@ -54,9 +54,9 @@ public override void AddCommands() "Hey tracker, peg." }, (result) => { - if (TrackerBase.PegsPegged < TotalPegs) + if (TrackerBase.ModeTracker.PegsPegged < TotalPegs) { - TrackerBase.Peg(result.Confidence); + TrackerBase.ModeTracker.Peg(result.Confidence); } }); } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PersonalityModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PersonalityModule.cs index 9d80b0272..8ea849071 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PersonalityModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/PersonalityModule.cs @@ -18,11 +18,11 @@ public class PersonalityModule : TrackerModule /// Initializes a new instance of the class. /// /// The tracker instance to use. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to write logging information. - public PersonalityModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger) - : base(tracker, itemService, worldService, logger) + public PersonalityModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/SpoilerModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/SpoilerModule.cs index f08b65e7d..98b4b2b6c 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/SpoilerModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/SpoilerModule.cs @@ -9,6 +9,7 @@ using TrackerCouncil.Smz3.Data.GeneratedData; using TrackerCouncil.Smz3.Data.Logic; using TrackerCouncil.Smz3.Data.Options; +using TrackerCouncil.Smz3.Data.Services; using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.SeedGenerator.Contracts; @@ -39,25 +40,28 @@ public class SpoilerModule : TrackerModule, IOptionalModule private readonly bool _isMultiworld; private readonly IGameHintService _gameHintService; private readonly PlaythroughService _playthroughService; + private readonly IMetadataService _metadataService; /// /// Initializes a new instance of the class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to write logging information. /// Service for retrieving the randomizer config for the world /// Service for getting hints for how important locations are /// - public SpoilerModule(TrackerBase tracker, IItemService itemService, ILogger logger, IWorldService worldService, IRandomizerConfigService randomizerConfigService, IGameHintService gameHintService, PlaythroughService playthroughService) - : base(tracker, itemService, worldService, logger) + /// + public SpoilerModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, ILogger logger, IWorldQueryService worldQueryService, IRandomizerConfigService randomizerConfigService, IGameHintService gameHintService, PlaythroughService playthroughService, IMetadataService metadataService) + : base(tracker, playerProgressionService, worldQueryService, logger) { TrackerBase.HintsEnabled = tracker.World.Config is { Race: false, DisableTrackerHints: false } && tracker.Options.HintsEnabled; TrackerBase.SpoilersEnabled = tracker.World.Config is { Race: false, DisableTrackerSpoilers: false } && tracker.Options.SpoilersEnabled; _randomizerConfigService = randomizerConfigService; _gameHintService = gameHintService; _playthroughService = playthroughService; + _metadataService = metadataService; _isMultiworld = tracker.World.Config.MultiWorld; } @@ -74,9 +78,9 @@ private void GiveProgressionHint() return; } - var locations = WorldService.Locations(keysanityByRegion: true).ToList(); + var locations = WorldQueryService.Locations(keysanityByRegion: true).ToList(); - var result = _gameHintService.FindMostValueableLocation(WorldService.Worlds, locations); + var result = _gameHintService.FindMostValueableLocation(WorldQueryService.Worlds, locations); if (result is not { Usefulness: LocationUsefulness.Mandatory }) { @@ -100,7 +104,7 @@ private void GiveAreaHint(IHasLocations area) } var locations = area.Locations - .Where(x => x.State.Cleared == false) + .Where(x => x.Cleared == false) .ToImmutableList(); if (locations.Count == 0) { @@ -119,7 +123,7 @@ private void GiveAreaHint(IHasLocations area) else if (TrackerBase.HintsEnabled) { var areaReward = GetRewardForHint(area); - var usefulness = _gameHintService.GetUsefulness(locations.ToList(), WorldService.Worlds, areaReward); + var usefulness = _gameHintService.GetUsefulness(locations.ToList(), WorldQueryService.Worlds, areaReward); if (usefulness == LocationUsefulness.Mandatory) { @@ -153,19 +157,19 @@ private void GiveAreaHint(IHasLocations area) /// The item to find. private void RevealItemLocation(Item item) { - if (item.Metadata.HasStages && item.State.TrackingState >= item.Metadata.MaxStage) + if (item.Metadata.HasStages && item.TrackingState >= item.Metadata.MaxStage) { TrackerBase.Say(x => x.Spoilers.TrackedAllItemsAlready, args: [item.Name]); return; } - else if (!item.Metadata.Multiple && item.State.TrackingState > 0) + else if (!item.Metadata.Multiple && item.TrackingState > 0) { TrackerBase.Say(x => x.Spoilers.TrackedItemAlready, args: [item.Metadata.NameWithArticle]); return; } - var markedLocation = WorldService.MarkedLocations() - .Where(x => x.State.MarkedItem == item.Type && !x.State.Cleared) + var markedLocation = WorldQueryService.MarkedLocations() + .Where(x => x.MarkedItem == item.Type && !x.Cleared) .Random(); if (markedLocation != null) { @@ -183,7 +187,7 @@ private void RevealItemLocation(Item item) } // Once we're done being a smartass, see if the item can be found at all - var locations = WorldService.Locations(unclearedOnly: false, outOfLogic: true, itemFilter: item.Type, checkAllWorlds: true) + var locations = WorldQueryService.Locations(unclearedOnly: false, outOfLogic: true, itemFilter: item.Type, checkAllWorlds: true) .ToImmutableList(); if (locations.Count == 0) { @@ -193,7 +197,7 @@ private void RevealItemLocation(Item item) TrackerBase.Say(x => x.Spoilers.ItemNotFound, args: [item.Metadata.NameWithArticle]); return; } - else if (locations.Count > 0 && locations.All(x => x.State.Cleared)) + else if (locations.Count > 0 && locations.All(x => x.Cleared)) { // The item exists, but all locations are cleared TrackerBase.Say(x => x.Spoilers.LocationsCleared, args: [item.Metadata.NameWithArticle]); @@ -228,11 +232,11 @@ private void RevealItemLocation(Item item) private void RevealLocationItem(Location location) { var locationName = location.Metadata.Name; - if (location.State.Cleared) + if (location.Cleared) { if (TrackerBase.HintsEnabled || TrackerBase.SpoilersEnabled) { - var itemName = ItemService.GetName(location.Item.Type); + var itemName = _metadataService.GetName(location.Item.Type); TrackerBase.Say(x => x.Hints.LocationAlreadyClearedSpoiler, args: [locationName, itemName]); return; } @@ -243,9 +247,9 @@ private void RevealLocationItem(Location location) } } - if (location.State.MarkedItem != null) + if (location.MarkedItem != null) { - var markedItem = ItemService.FirstOrDefault(location.State.MarkedItem.Value); + var markedItem = WorldQueryService.FirstOrDefault(location.MarkedItem.Value); if (markedItem != null) { TrackerBase.Say(x => x.Spoilers.MarkedLocation, args: [locationName, markedItem.Metadata.NameWithArticle]); @@ -379,21 +383,25 @@ protected virtual bool GiveLocationHint(SchrodingersString? hint, private Reward? GetRewardForHint(IHasLocations area) { - if (area is not IHasReward rewardArea || rewardArea.RewardType == RewardType.Agahnim || - rewardArea.RewardType == RewardType.None || area is not IDungeon dungeon) return null; + if (area is not IHasReward rewardArea || !rewardArea.RewardType.IsInAnyCategory(RewardCategory.Crystal, RewardCategory.Pendant) || + rewardArea.RewardType == RewardType.None || area is not IHasBoss bossRegion) return null; - var bossLocation = area.Locations.First(x => x.Id == dungeon.BossLocationId); + var bossLocation = area.Locations.FirstOrDefault(x => x.Id == bossRegion.BossLocationId); + + if (bossLocation == null) + { + return null; + } // For pendant dungeons, only factor it in if the player has not gotten it so that they get hints // that factor in Saha/Ped - if (rewardArea.RewardType is RewardType.PendantBlue or RewardType.PendantGreen - or RewardType.PendantRed && (bossLocation.State.Cleared || bossLocation.State.Autotracked)) + if (rewardArea.RewardType.IsInCategory(RewardCategory.Pendant) && (bossLocation.Cleared || bossLocation.Autotracked)) { return rewardArea.Reward; } // For crystal dungeons, always act like the player has them so that it only gives a hint based on // the actual items in the dungeon - else if (rewardArea.RewardType is RewardType.CrystalBlue or RewardType.CrystalRed) + else if (rewardArea.RewardType.IsInCategory(RewardCategory.Crystal)) { return rewardArea.Reward; } @@ -409,7 +417,7 @@ private bool GiveLocationHints(Location location) case 0: var areaReward = GetRewardForHint(location.Region); - var usefulness = _gameHintService.GetUsefulness([ location ], WorldService.Worlds, areaReward); + var usefulness = _gameHintService.GetUsefulness([ location ], WorldQueryService.Worlds, areaReward); var characterName = location.Item.Type.IsInCategory(ItemCategory.Metroid) ? TrackerBase.CorrectPronunciation(location.World.Config.SamusName) @@ -433,7 +441,7 @@ private bool GiveLocationHints(Location location) // Consult the Book of Mudora case 2: var pedText = location.Item.Metadata.PedestalHints; - var bookOfMudoraName = ItemService.GetName(ItemType.Book); + var bookOfMudoraName = _metadataService.GetName(ItemType.Book); return GiveLocationHint(x => x.BookHint, location, pedText, bookOfMudoraName); } @@ -454,7 +462,7 @@ private bool GiveLocationSpoiler(Location location) { if (_isMultiworld) { - TrackerBase.Say(x => location.Item.World == WorldService.World ? x.Spoilers.LocationHasItemOwnWorld : x.Spoilers.LocationHasItemOtherWorld, + TrackerBase.Say(x => location.Item.World == WorldQueryService.World ? x.Spoilers.LocationHasItemOwnWorld : x.Spoilers.LocationHasItemOtherWorld, args: [ locationName, item.Metadata.NameWithArticle, @@ -480,7 +488,7 @@ private bool GiveItemLocationSpoiler(Item item) if (item.Metadata == null) throw new InvalidOperationException($"No metadata for item '{item.Name}'"); - var reachableLocation = WorldService.Locations(itemFilter: item.Type, keysanityByRegion: true, checkAllWorlds: true) + var reachableLocation = WorldQueryService.Locations(itemFilter: item.Type, keysanityByRegion: true, checkAllWorlds: true) .Random(); if (reachableLocation != null) { @@ -491,7 +499,7 @@ private bool GiveItemLocationSpoiler(Item item) { if (item.Metadata.Multiple || item.Metadata.HasStages) { - TrackerBase.Say(x => reachableLocation.World == WorldService.World ? x.Spoilers.ItemsAreAtLocationOwnWorld : x.Spoilers.ItemsAreAtLocationOtherWorld, + TrackerBase.Say(x => reachableLocation.World == WorldQueryService.World ? x.Spoilers.ItemsAreAtLocationOwnWorld : x.Spoilers.ItemsAreAtLocationOtherWorld, args: [ item.Metadata.NameWithArticle, locationName, @@ -501,7 +509,7 @@ private bool GiveItemLocationSpoiler(Item item) } else { - TrackerBase.Say(x => reachableLocation.World == WorldService.World ? x.Spoilers.ItemIsAtLocationOwnWorld : x.Spoilers.ItemIsAtLocationOtherWorld, + TrackerBase.Say(x => reachableLocation.World == WorldQueryService.World ? x.Spoilers.ItemIsAtLocationOwnWorld : x.Spoilers.ItemIsAtLocationOtherWorld, args: [ item.Metadata.NameWithArticle, locationName, @@ -521,7 +529,7 @@ private bool GiveItemLocationSpoiler(Item item) return true; } - var worldLocation = WorldService.Locations(outOfLogic: true, itemFilter: item.Type, checkAllWorlds: true) + var worldLocation = WorldQueryService.Locations(outOfLogic: true, itemFilter: item.Type, checkAllWorlds: true) .Random(); if (worldLocation != null) { @@ -532,7 +540,7 @@ private bool GiveItemLocationSpoiler(Item item) { if (item.Metadata.Multiple || item.Metadata.HasStages) { - TrackerBase.Say(x => worldLocation.World == WorldService.World ? x.Spoilers.ItemsAreAtOutOfLogicLocationOwnWorld : x.Spoilers.ItemsAreAtOutOfLogicLocationOtherWorld, + TrackerBase.Say(x => worldLocation.World == WorldQueryService.World ? x.Spoilers.ItemsAreAtOutOfLogicLocationOwnWorld : x.Spoilers.ItemsAreAtOutOfLogicLocationOtherWorld, args: [ item.Metadata.NameWithArticle, locationName, @@ -542,7 +550,7 @@ private bool GiveItemLocationSpoiler(Item item) } else { - TrackerBase.Say(x => worldLocation.World == WorldService.World ? x.Spoilers.ItemIsAtOutOfLogicLocationOwnWorld : x.Spoilers.ItemIsAtOutOfLogicLocationOtherWorld, + TrackerBase.Say(x => worldLocation.World == WorldQueryService.World ? x.Spoilers.ItemIsAtOutOfLogicLocationOwnWorld : x.Spoilers.ItemIsAtOutOfLogicLocationOtherWorld, args: [ item.Metadata.NameWithArticle, locationName, @@ -567,7 +575,7 @@ private bool GiveItemLocationSpoiler(Item item) private bool GiveItemLocationHint(Item item) { - var itemLocations = WorldService.Locations(outOfLogic: true, itemFilter: item.Type, checkAllWorlds: true).ToList(); + var itemLocations = WorldQueryService.Locations(outOfLogic: true, itemFilter: item.Type, checkAllWorlds: true).ToList(); if (!itemLocations.Any()) { @@ -585,7 +593,7 @@ private bool GiveItemLocationHint(Item item) case 0: { - var isInLogic = itemLocations.Any(x => x.IsRelevant(ItemService.GetProgression(x.Region)) && x.World.IsLocalWorld); + var isInLogic = itemLocations.Any(x => x.IsRelevant(PlayerProgressionService.GetProgression(x.Region)) && x.World.IsLocalWorld); if (isInLogic) { var isOnlyInSuperMetroid = itemLocations.Select(x => x.Region).All(x => x is SMRegion); @@ -613,9 +621,9 @@ private bool GiveItemLocationHint(Item item) // - Exactly which player's world is the item in? case 1: { - if (itemLocations.All(x => !x.IsRelevant(ItemService.GetProgression(x.Region)))) + if (itemLocations.All(x => !x.IsRelevant(PlayerProgressionService.GetProgression(x.Region)))) { - var randomLocation = itemLocations.Where(x => !x.IsRelevant(ItemService.GetProgression(x.Region))).Random(); + var randomLocation = itemLocations.Where(x => !x.IsRelevant(PlayerProgressionService.GetProgression(x.Region))).Random(); if (randomLocation == null) { @@ -628,7 +636,7 @@ private bool GiveItemLocationHint(Item item) randomLocation.World.Config.PhoneticName); } - var progression = ItemService.GetProgression(randomLocation.Region); + var progression = PlayerProgressionService.GetProgression(randomLocation.Region); var missingItemSets = Logic.GetMissingRequiredItems(randomLocation, progression, out _); if (!missingItemSets.Any()) { @@ -639,7 +647,7 @@ private bool GiveItemLocationHint(Item item) var randomMissingItem = Logic.GetMissingRequiredItems(randomLocation, progression, out _) .SelectMany(x => x) .Where(x => x != item.Type) - .Select(x => ItemService.FirstOrDefault(x)) + .Select(x => WorldQueryService.FirstOrDefault(x)) .Random(); if (randomMissingItem != null) return GiveItemHint(x => x.ItemRequiresOtherItem, item, randomMissingItem.Metadata.NameWithArticle); @@ -673,7 +681,7 @@ private bool GiveItemLocationHint(Item item) var areaWithoutItem = TrackerBase.World.Regions .GroupBy(x => x.Area) .Where(x => x.SelectMany(r => r.Locations) - .Where(l => l.State.Cleared == false) + .Where(l => l.Cleared == false) .All(l => l.Item.Type != item.Type)) .Select(x => x.Key) .Random(); @@ -703,13 +711,13 @@ private bool GiveItemLocationHint(Item item) if (randomLocation?.Region is Z3Region and IHasReward dungeon && dungeon.RewardType != RewardType.Agahnim) { - if (randomLocation.Region.Locations.Any(x => x.State.Cleared)) + if (randomLocation.Region.Locations.Any(x => x.Cleared)) return GiveItemHint(x => x.ItemInPreviouslyVisitedDungeon, item); else return GiveItemHint(x => x.ItemInUnvisitedDungeon, item); } - if (randomLocation?.Region?.Locations.Any(x => x.State.Cleared) == true) + if (randomLocation?.Region?.Locations.Any(x => x.Cleared) == true) return GiveItemHint(x => x.ItemInPreviouslyVisitedRegion, item); else return GiveItemHint(x => x.ItemInUnvisitedRegion, item); @@ -786,7 +794,7 @@ private bool GiveItemLocationHint(Item item) return GiveItemHint(x => x.ItemHasBadVanillaLocationName, item, randomLocation.Name); } - var vanillaItem = ItemService.FirstOrDefault(randomLocation.VanillaItem); + var vanillaItem = WorldQueryService.FirstOrDefault(randomLocation.VanillaItem); return GiveItemHint(x => x.ItemIsInVanillaJunkLocation, item, vanillaItem?.Metadata.Name ?? randomLocation.VanillaItem.GetDescription()); } @@ -807,7 +815,7 @@ private bool GiveItemLocationHint(Item item) private Location? GetRandomItemLocationWithFilter(Item item, Func predicate) { - var randomLocation = WorldService.Locations(itemFilter: item.Type, keysanityByRegion: true, checkAllWorlds: true) + var randomLocation = WorldQueryService.Locations(itemFilter: item.Type, keysanityByRegion: true, checkAllWorlds: true) .Where(predicate) .Random(); @@ -815,7 +823,7 @@ private bool GiveItemLocationHint(Item item) { // If the item is not at any accessible location, try to look in // out-of-logic places, too. - randomLocation = WorldService.Locations(outOfLogic: true, itemFilter: item.Type, keysanityByRegion: true, checkAllWorlds: true) + randomLocation = WorldQueryService.Locations(outOfLogic: true, itemFilter: item.Type, keysanityByRegion: true, checkAllWorlds: true) .Where(predicate) .Random(); } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs index 2201b5a42..fb4a4c1b2 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TrackerModule.cs @@ -64,14 +64,14 @@ public abstract class TrackerModule /// with the specified tracker. /// /// The tracker instance to use. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to log information. - protected TrackerModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger) + protected TrackerModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) { TrackerBase = tracker; - ItemService = itemService; - WorldService = worldService; + PlayerProgressionService = playerProgressionService; + WorldQueryService = worldQueryService; Logger = logger; } @@ -101,12 +101,12 @@ public IReadOnlyDictionary> Syntax /// /// Service for getting item data /// - protected IItemService ItemService { get; } + protected IPlayerProgressionService PlayerProgressionService { get; } /// /// Service for getting world data /// - protected IWorldService WorldService { get; } + protected IWorldQueryService WorldQueryService { get; } /// /// Gets a list of speech recognition grammars provided by the module. @@ -144,10 +144,10 @@ public void LoadInto(SpeechRecognitionEngine engine) /// A from the recognition result. /// [SupportedOSPlatform("windows")] - protected static IDungeon GetDungeonFromResult(TrackerBase tracker, RecognitionResult result) + protected static IHasTreasure GetDungeonFromResult(TrackerBase tracker, RecognitionResult result) { var name = (string)result.Semantics[DungeonKey].Value; - var dungeon = tracker.World.Dungeons.FirstOrDefault(x => x.DungeonName == name); + var dungeon = tracker.World.TreasureRegions.FirstOrDefault(x => x.Name == name); return dungeon ?? throw new Exception($"Could not find dungeon {name} (\"{result.Text}\")."); } @@ -161,10 +161,10 @@ protected static IDungeon GetDungeonFromResult(TrackerBase tracker, RecognitionR /// A from the recognition result. /// [SupportedOSPlatform("windows")] - protected static IDungeon? GetBossDungeonFromResult(TrackerBase tracker, RecognitionResult result) + protected static IHasTreasure? GetBossDungeonFromResult(TrackerBase tracker, RecognitionResult result) { var name = (string)result.Semantics[BossKey].Value; - return tracker.World.Dungeons.FirstOrDefault(x => x.DungeonName == name); + return tracker.World.TreasureRegions.FirstOrDefault(x => x.Name == name); } /// @@ -199,7 +199,7 @@ protected static IDungeon GetDungeonFromResult(TrackerBase tracker, RecognitionR protected Item GetItemFromResult(TrackerBase tracker, RecognitionResult result, out string itemName) { itemName = (string)result.Semantics[ItemNameKey].Value; - var item = ItemService.FirstOrDefault(itemName); + var item = WorldQueryService.FirstOrDefault(itemName); return item ?? throw new Exception($"Could not find recognized item '{itemName}' (\"{result.Text}\")"); } @@ -389,7 +389,7 @@ protected void AddCommand(string ruleName, GrammarBuilder grammarBuilder, protected virtual Choices GetPluralItemNames() { var itemNames = new Choices(); - foreach (var item in ItemService.LocalPlayersItems().Where(x => x.Metadata is { Multiple: true, HasStages: false })) + foreach (var item in WorldQueryService.LocalPlayersItems().Where(x => x.Metadata is { Multiple: true, HasStages: false })) { if (item.Metadata.Plural == null) { @@ -417,7 +417,7 @@ protected virtual Choices GetItemNames(Func? where = null) where ??= _ => true; var itemNames = new Choices(); - foreach (var item in ItemService.LocalPlayersItems().Where(where)) + foreach (var item in WorldQueryService.LocalPlayersItems().Where(where)) { if (item.Metadata.Name != null) { @@ -454,19 +454,20 @@ protected virtual Choices GetItemNames(Func? where = null) protected virtual Choices GetDungeonNames(bool includeDungeonsWithoutReward = false) { var dungeonNames = new Choices(); - foreach (var dungeon in TrackerBase.World.Dungeons) + foreach (var dungeon in TrackerBase.World.TreasureRegions) { - if ((dungeon.DungeonState.HasReward || includeDungeonsWithoutReward)) + var rewardRegion = dungeon as IHasReward; + + if (rewardRegion == null && !includeDungeonsWithoutReward) continue; + + if (dungeon.Metadata.Name != null) { - if (dungeon.DungeonMetadata.Name != null) - { - foreach (var name in dungeon.DungeonMetadata.Name) - dungeonNames.Add(new SemanticResultValue(name.Text, dungeon.DungeonName)); - } - else - { - dungeonNames.Add(new SemanticResultValue(dungeon.DungeonName, dungeon.DungeonName)); - } + foreach (var name in dungeon.Metadata.Name) + dungeonNames.Add(new SemanticResultValue(name.Text, dungeon.Name)); + } + else + { + dungeonNames.Add(new SemanticResultValue(dungeon.Name, dungeon.Name)); } } @@ -484,14 +485,9 @@ protected virtual Choices GetDungeonNames(bool includeDungeonsWithoutReward = fa protected virtual Choices GetBossNames() { var bossNames = new Choices(); - foreach (var dungeon in TrackerBase.World.Dungeons.Where(x => x.DungeonMetadata.Boss != null)) - { - foreach (var name in dungeon.DungeonMetadata.Boss!) - bossNames.Add(new SemanticResultValue(name.Text, dungeon.DungeonName)); - } - foreach (var boss in TrackerBase.World.AllBosses.Where(x => x.Metadata.Name != null)) + foreach (var boss in TrackerBase.World.AllBosses) { - foreach (var name in boss.Metadata.Name!) + foreach (var name in boss.Metadata.Name) bossNames.Add(new SemanticResultValue(name.Text, boss.Name)); } return bossNames; @@ -563,7 +559,7 @@ protected virtual Choices GetRegionNames(bool excludeDungeons = false) foreach (var region in TrackerBase.World.Regions) { var regionName = region.GetType().FullName; - if (excludeDungeons && region is IDungeon || regionName == null || region.Metadata.Name == null) + if (excludeDungeons && region is IHasTreasure || regionName == null || region.Metadata.Name == null) continue; foreach (var name in region.Metadata.Name) @@ -588,7 +584,7 @@ protected virtual Choices GetMedallionNames() var medallionTypes = new List(Enum.GetValues()); foreach (var medallion in medallionTypes.Where(x => x == ItemType.Nothing || x.IsInCategory(ItemCategory.Medallion))) { - var item = ItemService.FirstOrDefault(medallion); + var item = WorldQueryService.FirstOrDefault(medallion); if (item?.Metadata?.Name != null) { foreach (var name in item.Metadata.Name) diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ZeldaDungeonTrackingModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TreasureTrackingModule.cs similarity index 77% rename from src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ZeldaDungeonTrackingModule.cs rename to src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TreasureTrackingModule.cs index ba44736fd..365fbadc6 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/ZeldaDungeonTrackingModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/TreasureTrackingModule.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; +using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Tracking.Services; @@ -14,25 +15,26 @@ namespace TrackerCouncil.Smz3.Tracking.VoiceCommands; /// Provides voice commands for marking dungeons and tracking dungeon /// progress. /// -public class ZeldaDungeonTrackingModule : TrackerModule +public class TreasureTrackingModule : TrackerModule { private const string RewardKey = "RewardName"; private const string TreasureCountKey = "NumberOfTreasures"; /// /// Initializes a new instance of the class. + /// cref="TreasureTrackingModule"/> class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to log information. - public ZeldaDungeonTrackingModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger) - : base(tracker, itemService, worldService, logger) + public TreasureTrackingModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { } + #region Voice Commands [SupportedOSPlatform("windows")] private GrammarBuilder GetMarkDungeonRewardRule() { @@ -40,7 +42,7 @@ private GrammarBuilder GetMarkDungeonRewardRule() var rewardNames = new Choices(); foreach (var reward in Enum.GetValues()) { - foreach (var name in ItemService?.FirstOrDefault(reward)?.Metadata.Name ?? new SchrodingersString()) + foreach (var name in WorldQueryService?.FirstOrDefault(reward)?.Metadata.Name ?? new SchrodingersString()) rewardNames.Add(new SemanticResultValue(name, (int)reward)); } @@ -147,27 +149,31 @@ public override void AddCommands() { AddCommand("Mark dungeon pendant/crystal", GetMarkDungeonRewardRule(), (result) => { - var dungeon = GetDungeonFromResult(TrackerBase, result); + var dungeon = (IHasReward)GetDungeonFromResult(TrackerBase, result); var reward = (RewardType)result.Semantics[RewardKey].Value; - TrackerBase.SetDungeonReward(dungeon, reward, result.Confidence); + TrackerBase.RewardTracker.SetAreaReward(dungeon, reward, result.Confidence); }); AddCommand("Mark remaining dungeons", GetMarkRemainingDungeonRewardsRule(), (result) => { - TrackerBase.SetUnmarkedDungeonReward(RewardType.CrystalBlue, result.Confidence); + TrackerBase.RewardTracker.SetUnmarkedRewards(RewardType.CrystalBlue, result.Confidence); }); AddCommand("Mark dungeon as cleared", GetClearDungeonRule(), (result) => { - var dungeon = GetDungeonFromResult(TrackerBase, result); - TrackerBase.MarkDungeonAsCleared(dungeon, result.Confidence); + if (GetDungeonFromResult(TrackerBase, result) is IHasBoss dungeon) + { + TrackerBase.BossTracker.MarkBossAsDefeated(dungeon, result.Confidence); + } }); AddCommand("Mark dungeon medallion", GetMarkDungeonRequirementRule(), (result) => { - var dungeon = GetDungeonFromResult(TrackerBase, result); - var medallion = GetItemFromResult(TrackerBase, result, out _); - TrackerBase.SetDungeonRequirement(dungeon, medallion.Type, result.Confidence); + if (GetDungeonFromResult(TrackerBase, result) is IHasPrerequisite dungeon) + { + var medallion = GetItemFromResult(TrackerBase, result, out _); + TrackerBase.PrerequisiteTracker.SetDungeonRequirement(dungeon, medallion.Type, result.Confidence); + } }); AddCommand("Clear dungeon treasure", GetTreasureTrackingRule(), (result) => @@ -176,9 +182,8 @@ public override void AddCommands() ? (int)result.Semantics[TreasureCountKey].Value : 1; var dungeon = GetDungeonFromResult(TrackerBase, result); - TrackerBase.TrackDungeonTreasure(dungeon, result.Confidence, amount: count); - - dungeon.DungeonState.HasManuallyClearedTreasure = true; + TrackerBase.TreasureTracker.TrackDungeonTreasure(dungeon, result.Confidence, amount: count); }); } + #endregion } diff --git a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/UndoModule.cs b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/UndoModule.cs index 88655648a..96be924a8 100644 --- a/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/UndoModule.cs +++ b/src/TrackerCouncil.Smz3.Tracking/VoiceCommands/UndoModule.cs @@ -14,11 +14,11 @@ public class UndoModule : TrackerModule /// Initializes a new instance of the class. /// /// The tracker instance. - /// Service to get item information - /// Service to get world information + /// Service to get item information + /// Service to get world information /// Used to log information. - public UndoModule(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger) - : base(tracker, itemService, worldService, logger) + public UndoModule(TrackerBase tracker, IPlayerProgressionService playerProgressionService, IWorldQueryService worldQueryService, ILogger logger) + : base(tracker, playerProgressionService, worldQueryService, logger) { } diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/App.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/App.xaml deleted file mode 100644 index 2db7d9ae2..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/App.xaml +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/App.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/App.xaml.cs deleted file mode 100644 index 4d8dbad41..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/App.xaml.cs +++ /dev/null @@ -1,295 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using BunLabs.IO; -using GitHubReleaseChecker; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Win32; -using MSURandomizerLibrary; -using MSURandomizerLibrary.Models; -using MSURandomizerLibrary.Services; -using MSURandomizerUI; -using NReco.Logging.File; -using SharpHook; -using TrackerCouncil.Smz3.Abstractions; -using TrackerCouncil.Smz3.Chat.Integration; -using TrackerCouncil.Smz3.Data.Configuration; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.Services; -using TrackerCouncil.Smz3.Multiplayer.Client; -using TrackerCouncil.Smz3.SeedGenerator; -using TrackerCouncil.Smz3.Tracking; -using TrackerCouncil.Smz3.Tracking.AutoTracking; -using TrackerCouncil.Smz3.Tracking.VoiceCommands; -using TrackerCouncil.Smz3.Chat.Twitch; -using TrackerCouncil.Smz3.UI.Legacy.Controls; -using TrackerCouncil.Smz3.UI.Legacy.Windows; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -/// -/// Interaction logic for App.xaml -/// -public partial class App -{ - private const string BaseRegistryKey = "Software\\SMZ3 Cas Randomizer"; - private const string WindowPositionKey = "Windows"; - - private IHost? _host; - private ILogger? _logger; - private SpriteDownloaderWindow? _spriteDownloaderWindow; - private IGlobalHook? _hook; - private Task? _hookRunner; - - public static void SaveWindowPositionAndSize(TWindow window) - where TWindow : Window - { - using var baseKey = Registry.CurrentUser.CreateSubKey(BaseRegistryKey); - using var windowKey = baseKey.CreateSubKey(WindowPositionKey); - using var key = windowKey.CreateSubKey(typeof(TWindow).Name); - key.SetValue("Width", window.Width, RegistryValueKind.DWord); - key.SetValue("Height", window.Height, RegistryValueKind.DWord); - key.SetValue("Left", window.Left, RegistryValueKind.DWord); - key.SetValue("Top", window.Top, RegistryValueKind.DWord); - } - - public static void RestoreWindowPositionAndSize(TWindow window) - where TWindow : Window - { - try - { - using var baseKey = Registry.CurrentUser.OpenSubKey(BaseRegistryKey); - if (baseKey == null) - return; - - using var windowKey = baseKey.OpenSubKey(WindowPositionKey); - if (windowKey == null) - return; - - using var key = windowKey.OpenSubKey(typeof(TWindow).Name); - if (key == null) - return; - - var vScreenWidth = SystemParameters.VirtualScreenWidth; - var vScreenHeight = SystemParameters.VirtualScreenHeight; - var vScreenTop = SystemParameters.VirtualScreenTop; - var vScreenLeft = SystemParameters.VirtualScreenLeft; - - window.Width = (int)key.GetValue("Width", window.Width); - window.Height = (int)key.GetValue("Height", window.Height); - window.Left = (int)key.GetValue("Left", window.Left); - window.Top = (int)key.GetValue("Top", window.Top); - - if (window.Left < vScreenLeft) - { - window.Left = vScreenLeft; - } - else if (window.Left > vScreenLeft + vScreenWidth - window.Width) - { - window.Left = vScreenLeft + vScreenWidth - window.Width; - } - - if (window.Top < vScreenTop) - { - window.Top = vScreenTop; - } - else if (window.Top > vScreenTop + vScreenHeight - window.Height) - { - window.Top = vScreenTop + vScreenHeight - window.Height; - } - } - catch (Exception) - { - // ¯\_(ツ)_/¯ - } - } - - protected static void ConfigureServices(IServiceCollection services) - { - // Randomizer + Tracker - services.AddConfigs(); - services.AddRandomizerServices(); - services.AddTracker() - .AddOptionalModule() - .AddOptionalModule() - .AddOptionalModule() - .AddOptionalModule(); - services.AddScoped(); - services.AddScoped(); - services.AddSingleton(); - services.AddMultiplayerServices(); - services.AddSingleton(); - - // Chat - services.AddSingleton(); - services.AddScoped(); - services.AddSingleton(); - - // MSU Randomizer - services.AddMsuRandomizerServices(); - - // Misc - services.AddGitHubReleaseCheckerServices(); - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - - // WPF - services.AddScoped(); - services.AddSingleton(); - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddWindows(); - services.AddTransient(); - services.AddMsuRandomizerUIServices(); - } - - private void Application_Startup(object sender, StartupEventArgs e) - { - _host = Host.CreateDefaultBuilder(e.Args) - .ConfigureLogging(logging => - { - logging.AddFile($"%LocalAppData%\\SMZ3CasRandomizer\\smz3-cas-{DateTime.UtcNow:yyyyMMdd}.log", options => - { - options.Append = true; - options.FileSizeLimitBytes = FileSize.MB(20); - options.MaxRollingFiles = 5; - }); - }) - .ConfigureServices((_, services) => ConfigureServices(services)) - .Start(); - - _logger = _host.Services.GetRequiredService>(); - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - - TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException; - - ToolTipService.ShowDurationProperty.OverrideMetadata(typeof(DependencyObject), new FrameworkPropertyMetadata(int.MaxValue)); - - _hook = _host.Services.GetRequiredService(); - _hookRunner = _hook.RunAsync(); - - InitializeMsuRandomizer(); - - _ = StartAsync(); - } - - private async Task StartAsync() - { - var options = _host!.Services.GetRequiredService().Create(); - await DownloadConfigsAsync(options); - await DownloadSpritesAsync(options); - var mainWindow = _host!.Services.GetRequiredService(); - _host!.Services.GetRequiredService().LookupMsus(); - mainWindow.Show(); - _spriteDownloaderWindow?.Close(); - } - - private async Task DownloadConfigsAsync(RandomizerOptions options) - { - if (string.IsNullOrEmpty(options.GeneralOptions.Z3RomPath) || - !options.GeneralOptions.DownloadConfigsOnStartup) - { - return; - } - - var configSource = options.GeneralOptions.ConfigSources.FirstOrDefault(); - if (configSource == null) - { - configSource = new ConfigSource() { Owner = "TheTrackerCouncil", Repo = "SMZ3CasConfigs" }; - options.GeneralOptions.ConfigSources.Add(configSource); - } - await _host!.Services.GetRequiredService().DownloadFromSourceAsync(configSource); - options.Save(); - } - - private async Task DownloadSpritesAsync(RandomizerOptions options) - { - if (string.IsNullOrEmpty(options.GeneralOptions.Z3RomPath) || - !options.GeneralOptions.DownloadSpritesOnStartup) - { - return; - } - - var spriteDownloader = _host!.Services.GetRequiredService(); - var toDownload = await spriteDownloader.GetSpritesToDownloadAsync("TheTrackerCouncil", "SMZ3CasSprites"); - - if (toDownload is not { Count: > 4 }) - { - await spriteDownloader.DownloadSpritesAsync("TheTrackerCouncil", "SMZ3CasSprites", toDownload); - await _host!.Services.GetRequiredService().LoadSpritesAsync(); - } - else - { - _spriteDownloaderWindow = new SpriteDownloaderWindow(); - _spriteDownloaderWindow.Show(); - await spriteDownloader.DownloadSpritesAsync("TheTrackerCouncil", "SMZ3CasSprites", toDownload); - await _host!.Services.GetRequiredService().LoadSpritesAsync(); - } - } - - private void TaskSchedulerOnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) - { - _logger?.LogCritical(e.Exception, "[CRASH] Uncaught {ExceptionType}: ", e.Exception.GetType().Name); - } - - private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - if (e.ExceptionObject is Exception ex) - _logger?.LogCritical(ex, "[CRASH] Uncaught {exceptionType}: ", ex.GetType().Name); - else - _logger?.LogCritical("Unhandled exception in current domain but exception object is not an exception ({obj})", e.ExceptionObject); - } - - private async void Application_Exit(object sender, ExitEventArgs e) - { - if (_hook != null) - { - _hook.Dispose(); - _hookRunner?.GetAwaiter().GetResult(); - } - if (_host != null) - { - await _host.StopAsync(); - _host.Dispose(); - } - } - - private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) - { - _logger?.LogCritical(e.Exception, "[CRASH] Uncaught {exceptionType} in Dispatcher: ", e.Exception.GetType().Name); - - var logFileLocation = Environment.ExpandEnvironmentVariables("%LocalAppData%\\SMZ3CasRandomizer"); - MessageBox.Show("An unexpected problem occurred and the SMZ3 Cas’ Randomizer needs to shut down.\n\n" + - $"For technical details, please see the log files in '{logFileLocation}' and " + - "post them in Discord or on GitHub at https://github.com/TheTrackerCouncil/SMZ3Randomizer/issues.", "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Stop); - - e.Handled = true; - Environment.FailFast("Uncaught exception in Dispatcher", e.Exception); - } - - private void InitializeMsuRandomizer() - { - var settingsStream = Assembly.GetExecutingAssembly() - .GetManifestResourceStream("TrackerCouncil.Smz3.UI.Legacy.msu-randomizer-settings.yml"); - var msuInitializationRequest = new MsuRandomizerInitializationRequest() - { - MsuAppSettingsStream = settingsStream, - LookupMsus = false - }; -#if DEBUG - msuInitializationRequest.UserOptionsPath = "%LocalAppData%\\SMZ3CasRandomizer\\msu-user-settings-debug.yml"; -#endif - _host!.Services.GetRequiredService().Initialize(msuInitializationRequest); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/AssemblyInfo.cs b/src/TrackerCouncil.Smz3.UI.Legacy/AssemblyInfo.cs deleted file mode 100644 index 8b5504ecf..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Windows; - -[assembly: ThemeInfo( - ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) - ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) -)] diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/BoolToVisibilityConverter.cs b/src/TrackerCouncil.Smz3.UI.Legacy/BoolToVisibilityConverter.cs deleted file mode 100644 index 21efc3ca6..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/BoolToVisibilityConverter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Data; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -[ValueConversion(typeof(bool), typeof(Visibility))] -public class BoolToVisibilityConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return (bool) value ? Visibility.Visible : Visibility.Collapsed; - } - - public object ConvertBack(object value, Type targetType, object parameter,CultureInfo culture) - { - throw new NotImplementedException(); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ColorBrushConverter.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ColorBrushConverter.cs deleted file mode 100644 index e2845b4db..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ColorBrushConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; -using System.Windows.Media; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -[ValueConversion(typeof(byte[]), typeof(SolidColorBrush))] -internal class ColorBrushConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - var rgb = (byte[])value; - return new SolidColorBrush(Color.FromArgb(rgb[0], rgb[1], rgb[2], rgb[3])); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - var color = ((SolidColorBrush)value).Color; - return new byte[] { color.A, color.R, color.G, color.B }; - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/FileSystemInput.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/FileSystemInput.xaml deleted file mode 100644 index 53c54c8da..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/FileSystemInput.xaml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/FileSystemInput.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/FileSystemInput.xaml.cs deleted file mode 100644 index 5d61bc7aa..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/FileSystemInput.xaml.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Windows; -using System.Windows.Controls; -using Microsoft.Win32; -using Microsoft.WindowsAPICodePack.Dialogs; - -namespace TrackerCouncil.Smz3.UI.Legacy.Controls; - -/// -/// Interaction logic for FileSystemInput.xaml -/// -public partial class FileSystemInput : UserControl -{ - public static readonly DependencyProperty PathProperty = - DependencyProperty.Register("Path", typeof(string), typeof(FileSystemInput), new PropertyMetadata(string.Empty)); - - public static readonly DependencyProperty FilterProperty = - DependencyProperty.Register("Filter", typeof(string), typeof(FileSystemInput), new PropertyMetadata("All files (*.*)|*.*")); - - public static readonly DependencyProperty DialogTitleProperty = - DependencyProperty.Register("DialogTitle", typeof(string), typeof(FileSystemInput), new PropertyMetadata(string.Empty)); - - public static readonly DependencyProperty IsFolderPickerProperty = - DependencyProperty.Register("IsFolderPicker", typeof(bool), typeof(FileSystemInput), new PropertyMetadata(false)); - - public static readonly DependencyProperty IsSavePickerProperty = - DependencyProperty.Register("IsSavePicker", typeof(bool), typeof(FileSystemInput), new PropertyMetadata(false)); - - public static readonly DependencyProperty FileValidationHashProperty = - DependencyProperty.Register(nameof(FileValidationHash), typeof(string), typeof(FileSystemInput), new PropertyMetadata(string.Empty)); - - public static readonly DependencyProperty FileValidationErrorMessageProperty = - DependencyProperty.Register(nameof(FileValidationErrorMessage), typeof(string), typeof(FileSystemInput), new PropertyMetadata(string.Empty)); - - public FileSystemInput() - { - InitializeComponent(); - } - - public string Path - { - get => (string)GetValue(PathProperty); - set => SetValue(PathProperty, value); - } - - public string Filter - { - get => (string)GetValue(FilterProperty); - set => SetValue(FilterProperty, value); - } - - public string DialogTitle - { - get => (string)GetValue(DialogTitleProperty); - set => SetValue(DialogTitleProperty, value); - } - - public bool IsFolderPicker - { - get => (bool)GetValue(IsFolderPickerProperty); - set => SetValue(IsFolderPickerProperty, value); - } - - public bool IsSavePicker - { - get => (bool)GetValue(IsSavePickerProperty); - set => SetValue(IsSavePickerProperty, value); - } - - public string FileValidationHash - { - get => (string)GetValue(FileValidationHashProperty); - set => SetValue(FileValidationHashProperty, value); - } - - public string FileValidationErrorMessage - { - get => (string)GetValue(FileValidationErrorMessageProperty); - set => SetValue(FileValidationErrorMessageProperty, value); - } - - private void BrowseButton_Click(object sender, RoutedEventArgs e) - { - if (IsFolderPicker) - BrowseFolder(); - else if (IsSavePicker) - BrowseSaveFile(); - else - BrowseFile(); - } - - private void BrowseFile() - { - var folderPath = System.IO.Path.GetDirectoryName(Path); - var dialog = new OpenFileDialog - { - CheckFileExists = true, - Filter = Filter, - Title = DialogTitle, - InitialDirectory = Directory.Exists(folderPath) ? folderPath : null, - FileName = Path - }; - - var owner = Window.GetWindow(this); - if (dialog.ShowDialog(owner) == true) - { - if (string.IsNullOrEmpty(FileValidationHash) || string.IsNullOrEmpty(FileValidationErrorMessage)) - { - Path = dialog.FileName; - OnPathUpdated?.Invoke(this, EventArgs.Empty); - } - else - { - using var md5 = MD5.Create(); - using var stream = File.OpenRead(dialog.FileName); - var hash = md5.ComputeHash(stream); - var hashString = BitConverter.ToString(hash).Replace("-", ""); - - if (!FileValidationHash.Equals(hashString, StringComparison.OrdinalIgnoreCase)) - { - var response = MessageBox.Show(Window.GetWindow(this)!, FileValidationErrorMessage, "SMZ3 Cas’ Randomizer", - MessageBoxButton.YesNo, MessageBoxImage.Error); - if (response == MessageBoxResult.No) return; - } - - Path = dialog.FileName; - OnPathUpdated?.Invoke(this, EventArgs.Empty); - } - } - } - - private void BrowseSaveFile() - { - var folderPath = System.IO.Path.GetDirectoryName(Path); - var dialog = new SaveFileDialog() - { - Filter = Filter, - Title = DialogTitle, - InitialDirectory = Directory.Exists(folderPath) ? folderPath : null, - FileName = Path - }; - - var owner = Window.GetWindow(this); - if (dialog.ShowDialog(owner) == true) - { - Path = dialog.FileName; - OnPathUpdated?.Invoke(this, EventArgs.Empty); - } - } - - private void BrowseFolder() - { - using var dialog = new CommonOpenFileDialog - { - EnsurePathExists = true, - Title = DialogTitle, - InitialDirectory = Directory.Exists(Path) ? Path : null, - IsFolderPicker = true - }; - - var owner = Window.GetWindow(this); - if (dialog.ShowDialog(owner) == CommonFileDialogResult.Ok) - { - Path = dialog.FileName; - OnPathUpdated?.Invoke(this, EventArgs.Empty); - } - } - - public event EventHandler? OnPathUpdated; - -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/GameSettingsBasicPanel.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/GameSettingsBasicPanel.xaml deleted file mode 100644 index 3a76093fa..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/GameSettingsBasicPanel.xaml +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - - Presets - - - - - - - - - - - - Import Settings - - - - - - - - - - - - Seed - - - - - - - - - - Summary - - - - - - - - - - - - - - - - - - - - - - - Link Sprite - - - - Samus Sprite - - - - Ship Sprite - - - - - - Custom Music Pack - - - - - - - - - - - - Shuffle style: - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/GameSettingsBasicPanel.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/GameSettingsBasicPanel.xaml.cs deleted file mode 100644 index 51321a993..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/GameSettingsBasicPanel.xaml.cs +++ /dev/null @@ -1,245 +0,0 @@ -using System; -using System.IO; -using System.Windows; -using System.Windows.Controls; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Win32; -using MSURandomizerLibrary; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.Services; -using TrackerCouncil.Smz3.Data.ViewModels; -using TrackerCouncil.Smz3.UI.Legacy.Windows; - -namespace TrackerCouncil.Smz3.UI.Legacy.Controls; - -public partial class GameSettingsBasicPanel : UserControl -{ - private IServiceProvider? _serviceProvider; - private GenerationSettingsWindowService? _service; - private MsuUiService? _msuUiService; - - public GameSettingsBasicPanel() - { - InitializeComponent(); - } - - public void SetServices(IServiceProvider serviceProvider, - GenerationSettingsWindowService generationSettingsWindowService) - { - _serviceProvider = serviceProvider; - _service = generationSettingsWindowService; - _service.ConfigError += ServiceOnConfigError; - _msuUiService = _serviceProvider.GetRequiredService(); - } - - public static readonly DependencyProperty DataProperty = - DependencyProperty.Register(nameof(Data), - propertyType: typeof(GenerationWindowViewModel), - ownerType: typeof(GameSettingsBasicPanel), - typeMetadata: new PropertyMetadata()); - - public GenerationWindowViewModel Data - { - get { return (GenerationWindowViewModel)GetValue(DataProperty); } - set { SetValue(DataProperty, value); } - } - - private void ServiceOnConfigError(object? sender, EventArgs e) - { - ShowErrorMessageBox("Unable to parse randomizer settings string"); - } - - private void LinkSpriteButton_OnClick(object sender, RoutedEventArgs e) - { - if (_serviceProvider == null || _service == null) - { - return; - } - - var spriteWindow = _serviceProvider.GetRequiredService(); - spriteWindow.SetSpriteType(SpriteType.Link); - var result = spriteWindow.ShowDialog(); - _service.SaveSpriteSettings(result == true, spriteWindow.SelectedSprite ?? Sprite.DefaultLink, - spriteWindow.SelectedSpriteOptions, spriteWindow.Model.SearchText, spriteWindow.Model.SpriteFilter); - } - - private void SamusSpriteButton_OnClick(object sender, RoutedEventArgs e) - { - if (_serviceProvider == null || _service == null) - { - return; - } - - var spriteWindow = _serviceProvider.GetRequiredService(); - spriteWindow.SetSpriteType(SpriteType.Samus); - var result = spriteWindow.ShowDialog(); - _service.SaveSpriteSettings(result == true, spriteWindow.SelectedSprite ?? Sprite.DefaultSamus, - spriteWindow.SelectedSpriteOptions, spriteWindow.Model.SearchText, spriteWindow.Model.SpriteFilter); - } - - private void ShipSpriteButton_OnClick(object sender, RoutedEventArgs e) - { - if (_serviceProvider == null || _service == null) - { - return; - } - - var spriteWindow = _serviceProvider.GetRequiredService(); - spriteWindow.SetSpriteType(SpriteType.Ship); - var result = spriteWindow.ShowDialog(); - _service.SaveSpriteSettings(result == true, spriteWindow.SelectedSprite ?? Sprite.DefaultShip, - spriteWindow.SelectedSpriteOptions, spriteWindow.Model.SearchText, spriteWindow.Model.SpriteFilter); - } - - private void SelectMsuButton_OnClick(object sender, RoutedEventArgs e) - { - if (_msuUiService == null || _service == null) - { - return; - } - - if (_msuUiService.OpenMsuWindow(Window.GetWindow(this)!, SelectionMode.Single, null)) - { - Data.Basic.MsuRandomizationStyle = null; - } - - _service.UpdateMsuText(); - } - - private void MsuOptionsButton_OnClick(object sender, RoutedEventArgs e) - { - if (_msuUiService == null || _service == null) - { - return; - } - - if (MsuOptionsButton.ContextMenu == null) return; - MsuOptionsButton.ContextMenu.IsOpen = true; - } - - private void RandomMsuMenuItem_OnClick(object sender, RoutedEventArgs e) - { - if (_msuUiService == null || _service == null) - { - return; - } - - if (_msuUiService.OpenMsuWindow(Window.GetWindow(this)!, SelectionMode.Multiple, MsuRandomizationStyle.Single)) - { - Data.Basic.MsuRandomizationStyle = MsuRandomizationStyle.Single; - } - _service.UpdateMsuText(); - } - - private void ShuffledMsuMenuItem_OnClick(object sender, RoutedEventArgs e) - { - if (_msuUiService == null || _service == null) - { - return; - } - - if (_msuUiService.OpenMsuWindow(Window.GetWindow(this)!, SelectionMode.Multiple, - MsuRandomizationStyle.Shuffled)) - { - Data.Basic.MsuRandomizationStyle = MsuRandomizationStyle.Shuffled; - } - _service.UpdateMsuText(); - } - - private void ContinuousShuffleMsuMenuItem_OnClick(object sender, RoutedEventArgs e) - { - if (_msuUiService == null || _service == null) - { - return; - } - - if (_msuUiService.OpenMsuWindow(Window.GetWindow(this)!, SelectionMode.Multiple, - MsuRandomizationStyle.Continuous)) - { - Data.Basic.MsuRandomizationStyle = MsuRandomizationStyle.Continuous; - } - _service.UpdateMsuText(); - } - - private void SelectMsuFileMenuItem_OnClick(object sender, RoutedEventArgs e) - { - if (_serviceProvider == null || _service == null) - { - return; - } - - var options = _serviceProvider.GetRequiredService().Create(); - - var dialog = new OpenFileDialog - { - CheckFileExists = true, - Filter = "MSU-1 files (*.msu)|*.msu|All files (*.*)|*.*", - Title = "Select MSU-1 file", - InitialDirectory = Directory.Exists(options.GeneralOptions.MsuPath) ? options.GeneralOptions.MsuPath : null - }; - - if (dialog.ShowDialog(Window.GetWindow(this)!) == true && File.Exists(dialog.FileName)) - { - _service.SetMsuPath(dialog.FileName); - Data.Basic.MsuRandomizationStyle = null; - } - } - - private void VanillaMusicMenuItem_OnClick(object sender, RoutedEventArgs e) - { - _service?.ClearMsu(); - } - - private void ApplyPresetButton_OnClick(object sender, RoutedEventArgs e) - { - if (_service?.ApplySelectedPreset() == false) - { - ShowErrorMessageBox("Unable to apply preset"); - } - } - - private void DeletePresetButton_OnClick(object sender, RoutedEventArgs e) - { - if (_service?.DeleteSelectedPreset(out var error) == false) - { - ShowErrorMessageBox(error ?? "Unable to delete preset"); - } - } - - private void ImportSeedButton_OnClick(object sender, RoutedEventArgs e) - { - _service?.ApplyConfig(Data.Basic.ImportString, true); - } - - private void ImportSettingsButton_OnClick(object sender, RoutedEventArgs e) - { - _service?.ApplyConfig(Data.Basic.ImportString, false); - } - - private void ClearSeedButton_OnClick(object sender, RoutedEventArgs e) - { - Data.Basic.Seed = ""; - } - - protected MessageBoxResult ShowErrorMessageBox(string message) - { - return MessageBox.Show(Window.GetWindow(this)!, message, "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Error); - } - - private void GameSettingsBasicPanel_OnLoaded(object sender, RoutedEventArgs e) - { - DataContext = Data; - _service?.UpdateSummaryText(); - } - - private void ImportStringTextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e) - { - Data.Basic.ImportString = ImportStringTextBox.Text; - } - - private void SeedTextBox_OnTextChanged(object sender, TextChangedEventArgs e) - { - Data.Basic.Seed = SeedTextBox.Text; - } -} - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/GameSettingsItemPanel.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/GameSettingsItemPanel.xaml deleted file mode 100644 index 22c24881a..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/GameSettingsItemPanel.xaml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/ItemSettingsPanel.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/ItemSettingsPanel.xaml.cs deleted file mode 100644 index 0dd5f7b47..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/ItemSettingsPanel.xaml.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using TrackerCouncil.Smz3.Data.Options; - -namespace TrackerCouncil.Smz3.UI.Legacy.Controls; - -/// -/// Interaction logic for ItemSettingsPanel.xaml -/// -public partial class ItemSettingsPanel : UserControl -{ - public ItemSettingsPanel() - { - InitializeComponent(); - } - - private SeedOptions _seedOptions => (SeedOptions)DataContext; - private List _comboBoxes = new(); - - public void InitDropdowns() - { - foreach (var itemOptions in ItemSettingOptions.GetOptions().OrderBy(x => x.IsMetroid).ThenBy(x => x.Item)) - { - AddItemComboBox(itemOptions); - } - } - - public void AddItemComboBox(ItemSettingOptions itemOptions) - { - var selectedIndex = _seedOptions.ItemOptions.ContainsKey(itemOptions.Item) ? _seedOptions.ItemOptions[itemOptions.Item] : 0; - if (selectedIndex < 0 || selectedIndex >= itemOptions.Options.Count) - selectedIndex = 0; - - var comboBox = new ComboBox - { - ItemsSource = itemOptions.Options, - SelectedIndex = selectedIndex, - Tag = itemOptions, - DisplayMemberPath = "Display" - }; - comboBox.SelectionChanged += ItemComboBox_Changed; - _comboBoxes.Add(comboBox); - - var labeledControl = new LabeledControl { Text = itemOptions.Item, Content = comboBox }; - if (itemOptions.IsMetroid) - { - MetroidItemsStackPanel.Children.Add(labeledControl); - } - else - { - ZeldaItemsStackPanel.Children.Add(labeledControl); - } - } - - private void ItemComboBox_Changed(object sender, SelectionChangedEventArgs e) - { - var comboBox = sender as ComboBox; - if (comboBox?.Tag is not ItemSettingOptions itemOptions) return; - var index = comboBox.SelectedIndex; - - if (index == 0) - { - _seedOptions.ItemOptions.Remove(itemOptions.Item); - } - else - { - _seedOptions.ItemOptions[itemOptions.Item] = index; - } - } - - private void ItemSettingsPanel_OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) - { - if (MetroidItemsStackPanel == null) return; - MetroidItemsStackPanel.Children.Clear(); - ZeldaItemsStackPanel.Children.Clear(); - InitDropdowns(); - } - - private void ResetButton_OnClick(object sender, RoutedEventArgs e) - { - _seedOptions.ItemOptions.Clear(); - foreach (var comboBox in _comboBoxes) - { - comboBox.SelectedIndex = 0; - } - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/LabeledControl.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/LabeledControl.cs deleted file mode 100644 index 3de5157e2..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/LabeledControl.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Windows; -using System.Windows.Controls; - -namespace TrackerCouncil.Smz3.UI.Legacy.Controls; - -public class LabeledControl : ContentControl -{ - public static readonly DependencyProperty TextProperty = - DependencyProperty.Register("Text", - propertyType: typeof(string), - ownerType: typeof(LabeledControl), - typeMetadata: new PropertyMetadata("Label")); - - public string Text - { - get { return (string)GetValue(TextProperty); } - set { SetValue(TextProperty, value); } - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MetroidControlsPanel.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MetroidControlsPanel.xaml deleted file mode 100644 index be29c3e73..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MetroidControlsPanel.xaml +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Button mappings are based on the default SNES controls and do not account for different emulator button mappings. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MetroidControlsPanel.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MetroidControlsPanel.xaml.cs deleted file mode 100644 index 815e864ad..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MetroidControlsPanel.xaml.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows.Controls; -using TrackerCouncil.Smz3.Data.Options; - -namespace TrackerCouncil.Smz3.UI.Legacy.Controls; - -/// -/// Interaction logic for SMControlsPanel.xaml -/// -public partial class MetroidControlsPanel : UserControl -{ - public MetroidControlsPanel() - { - InitializeComponent(); - } - - /// - /// Function to prevent mapping the same button to multiple commands - /// - /// - /// - private void ComboBox_ButtonMappingChanged(object sender, SelectionChangedEventArgs e) - { - if (sender is not ComboBox comboBox) return; - var updatedButton = (MetroidButton)comboBox.SelectedItem; - var dropdowns = new List() - { - ComboBox_Shoot, - ComboBox_Jump, - ComboBox_Dash, - ComboBox_ItemSelect, - ComboBox_ItemCancel, - ComboBox_AngleUp, - ComboBox_AngleDown, - }; - var duplicateDropdown = dropdowns.FirstOrDefault(x => x != null && (MetroidButton)x.SelectedItem == updatedButton && x != comboBox); - if (duplicateDropdown == null) return; - var selectedButtons = dropdowns.Select(x => (MetroidButton)x!.SelectedItem).Distinct().ToList(); - var missingButton = Enum.GetValues().First(x => !selectedButtons.Contains(x)); - duplicateDropdown.SelectedItem = missingButton; - } - - private void SetLabels() - { - var options = DataContext as MetroidControlOptions; - - if (RunButtonLabel == null) return; - - RunButtonLabel.Text = options?.RunButtonBehavior switch - { - RunButtonBehavior.AutoRun => "Walk button", - _ => "Run button" - }; - - ItemCancelLabel.Text = options?.ItemCancelBehavior switch - { - ItemCancelBehavior.HoldSupersOnly => "Hold to equip", - ItemCancelBehavior.Hold => "Hold to equip", - ItemCancelBehavior.Toggle => "Quick item toggle", - _ => "Item Cancel button" - }; - - AimUpLabel.Text = options?.AimButtonBehavior switch - { - AimButtonBehavior.UnifiedAim => "Aim button", - _ => "Angle up button" - }; - - AimDownLabel.Text = options?.AimButtonBehavior switch - { - AimButtonBehavior.UnifiedAim => "Quick Morph button", - _ => "Angle down button" - }; - - } - - private void ComboBox_OptionSelectionChanged(object sender, SelectionChangedEventArgs e) - { - SetLabels(); - } - - private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e) - { - SetLabels(); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MultiRomListPanel.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MultiRomListPanel.xaml deleted file mode 100644 index 85d732a95..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MultiRomListPanel.xaml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - It looks like you haven't joined or created any multiplayer games! Click Create game to host a new game for others to join, or click Join game to join a multiplayer game created by someone else. Multiplayer games require auto tracking, so verify that is fully setup. - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MultiRomListPanel.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MultiRomListPanel.xaml.cs deleted file mode 100644 index 240d1b991..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/MultiRomListPanel.xaml.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using TrackerCouncil.Smz3.Data.Interfaces; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.Services; -using TrackerCouncil.Smz3.SeedGenerator.Infrastructure; -using TrackerCouncil.Smz3.Shared.Models; -using TrackerCouncil.Smz3.UI.Legacy.ViewModels; -using TrackerCouncil.Smz3.UI.Legacy.Windows; - -namespace TrackerCouncil.Smz3.UI.Legacy.Controls; - -/// -/// Interaction logic for MultiRomListPanel.xaml -/// -public partial class MultiRomListPanel : RomListPanel -{ - public MultiRomListPanel(IServiceProvider serviceProvider, - OptionsFactory optionsFactory, - ILogger logger, - IRomGenerationService romGenerationService, - IGameDbService gameDbService, - RomLauncherService romLauncherService) : base(serviceProvider, optionsFactory, logger, romGenerationService, gameDbService, romLauncherService) - { - Model = new MultiplayerGamesViewModel(); - DataContext = Model; - InitializeComponent(); - UpdateList(); - } - - public MultiplayerGamesViewModel Model { get; } - - public sealed override void UpdateList() - { - var models = GameDbService.GetMultiplayerGamesList() - .OrderByDescending(x => x.Id) - .ToList(); - Model.UpdateList(models); - } - - private void CreateMultiGameButton_Click(object sender, RoutedEventArgs e) - { - using var scope = ServiceProvider.CreateScope(); - var multiWindow = scope.ServiceProvider.GetRequiredService(); - multiWindow.Owner = Window.GetWindow(this); - multiWindow.IsCreatingGame = true; - multiWindow.Options = Options; - if (multiWindow.ShowDialog() == true) OpenStatusWindow(null); - UpdateList(); - } - - private void JoinMultiGameButton_Click(object sender, RoutedEventArgs e) - { - using var scope = ServiceProvider.CreateScope(); - var multiWindow = scope.ServiceProvider.GetRequiredService(); - multiWindow.Owner = Window.GetWindow(this); - multiWindow.IsJoiningGame = true; - if (multiWindow.ShowDialog() == true) OpenStatusWindow(null); - UpdateList(); - } - - /// - /// Opens the multiplayer status window - /// - /// - private void OpenStatusWindow(MultiplayerGameDetails? game) - { - using var scope = ServiceProvider.CreateScope(); - var statusWindow = scope.ServiceProvider.GetRequiredService(); - statusWindow.Owner = Window.GetWindow(this); - statusWindow.ParentPanel = this; - statusWindow.MultiplayerGameDetails = game; - statusWindow.Show(); - } - - /// - /// Menu item for deleting a multiplayer game and rom - /// - /// - /// - /// - private void DeleteRomMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: MultiplayerGameDetails details }) - return; - - if (!GameDbService.DeleteMultiplayerGame(details, out var error)) - { - ShowErrorMessageBox(error); - } - - UpdateList(); - } - - /// - /// The user has clicked on a quick launch button for a rom - /// - /// - /// - private void LaunchButton_Click(object sender, RoutedEventArgs e) - { - if (sender is not Button { Tag: MultiplayerGameDetails details }) - return; - - OpenStatusWindow(details); - } - - /// - /// Right click menu to open the folder for a rom - /// - /// - /// - private void OpenFolderMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: MultiplayerGameDetails details } || details.GeneratedRom == null) - return; - - OpenFolder(details.GeneratedRom); - } - - /// - /// Menu item for viewing the spoiler log for a rom - /// - /// - /// - private void ViewSpoilerMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: MultiplayerGameDetails details } || details.GeneratedRom == null) - return; - - OpenSpoilerLog(details.GeneratedRom); - } - - private void ProgressionLogMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: MultiplayerGameDetails details } || details.GeneratedRom == null) - return; - - OpenProgressionLog(details.GeneratedRom); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/RomListPanel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/RomListPanel.cs deleted file mode 100644 index dd5f50815..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/RomListPanel.cs +++ /dev/null @@ -1,306 +0,0 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using TrackerCouncil.Smz3.Data.Interfaces; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.Services; -using TrackerCouncil.Smz3.SeedGenerator.Infrastructure; -using TrackerCouncil.Smz3.Shared.Models; -using TrackerCouncil.Smz3.Tracking.Services; -using TrackerCouncil.Smz3.UI.Legacy.Windows; - -namespace TrackerCouncil.Smz3.UI.Legacy.Controls; - -public abstract class RomListPanel : UserControl -{ - private TrackerWindow? _trackerWindow; - private RomLauncherService _romLauncherService; - - public RomListPanel(IServiceProvider serviceProvider, - OptionsFactory optionsFactory, - ILogger logger, - IRomGenerationService romGenerationService, - IGameDbService gameDbService, - RomLauncherService romLauncherService) - { - ServiceProvider = serviceProvider; - Logger = logger; - RomGenerationService = romGenerationService; - GameDbService = gameDbService; - Options = optionsFactory.Create(); - _romLauncherService = romLauncherService; - CheckSpeechRecognition(); - } - - protected IGameDbService GameDbService; - - /// - /// Verifies that speech recognition is working - /// - protected void CheckSpeechRecognition() - { - try - { - var installedRecognizers = System.Speech.Recognition.SpeechRecognitionEngine.InstalledRecognizers(); - Logger.LogInformation("{Count} installed recognizer(s): {Recognizers}", - installedRecognizers.Count, string.Join(", ", installedRecognizers.Select(x => x.Description))); - CanStartTracker = installedRecognizers.Count != 0; - } - catch (Exception ex) - { - Logger.LogError(ex, "Unexpected error occurred while checking speech recognition capabilities."); - CanStartTracker = false; - } - } - - protected virtual bool CanStartTracker { get; private set; } - - protected virtual IRomGenerationService RomGenerationService { get; private set; } - - public RandomizerOptions Options { get; private set; } - - protected readonly IServiceProvider ServiceProvider; - - protected readonly ILogger Logger; - - /// - /// Launches a rom with the set quick launch options - /// - /// The rom to launch - public void QuickLaunchRom(GeneratedRom rom) - { - var launchButtonOptions = Options.GeneralOptions.LaunchButtonOption; - - if (launchButtonOptions is LaunchButtonOptions.PlayAndTrack or LaunchButtonOptions.OpenFolderAndTrack or LaunchButtonOptions.TrackOnly) - { - LaunchTracker(rom); - } - - if (launchButtonOptions is LaunchButtonOptions.OpenFolderAndTrack or LaunchButtonOptions.OpenFolderOnly) - { - OpenFolder(rom); - } - - if (launchButtonOptions is LaunchButtonOptions.PlayAndTrack or LaunchButtonOptions.PlayOnly) - { - LaunchRom(rom); - } - } - - protected void TrackerWindowOnSavedState(object? sender, EventArgs e) - { - UpdateList(); - } - - /// - /// Launches the tracker window for the given rom - /// - /// The rom to open tracker for - public void LaunchTracker(GeneratedRom rom) - { - if (!CanStartTracker) - { - ShowWarningMessageBox($"No speech recognition capabilities detected. Please check Windows settings under Time & Language > Speech."); - return; - } - - if (_trackerWindow is { IsVisible: true }) - { - ShowWarningMessageBox($"An instance of tracker is already open."); - return; - } - - if (!GeneratedRom.IsValid(rom)) - { - ShowWarningMessageBox($"Selected rom is invalid. Please try generating a new rom."); - return; - } - - try - { - var scope = ServiceProvider.CreateScope(); - var trackerOptionsAccessor = scope.ServiceProvider.GetRequiredService(); - trackerOptionsAccessor.Options = Options.GeneralOptions.GetTrackerOptions(); - - _trackerWindow = scope.ServiceProvider.GetRequiredService(); - _trackerWindow.Closed += (_, _) => scope.Dispose(); - _trackerWindow.Rom = rom; - _trackerWindow.SavedState += TrackerWindowOnSavedState; - _trackerWindow.Show(); - } - catch (YamlDotNet.Core.SemanticErrorException ex) - { - MessageBox.Show(ex.Message + "\n\n" + ex.InnerException?.Message, - "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Error); - } - catch (Exception ex) - { - Logger.LogCritical(ex, "An unhandled exception occurred when starting the tracker."); - } - } - - /// - /// Opens the folder containing the rom - /// - /// The rom to open the folder for - public void OpenFolder(GeneratedRom rom) - { - var path = Path.Combine(Options.RomOutputPath, rom.RomPath); - if (File.Exists(path)) - { - Process.Start("explorer.exe", $"/select,\"{path}\""); - } - else - { - ShowWarningMessageBox($"Could not find rom file at {path}"); - } - } - - /// - /// Launches the current rom in the default program - /// - /// The rom to execute - public void LaunchRom(GeneratedRom rom) - { - var path = Path.Combine(Options.RomOutputPath, rom.RomPath); - if (File.Exists(path)) - { - try - { - _romLauncherService.LaunchRom(path, Options.GeneralOptions.LaunchApplication, - Options.GeneralOptions.LaunchArguments); - } - catch (Win32Exception e) - { - Logger.LogError(e, "Could not open rom file"); - ShowErrorMessageBox($"Could not open rom file.\nVerify you have a default application for sfc files."); - } - } - else - { - ShowWarningMessageBox($"Could not find rom file at {path}"); - } - } - - public void CopyTextToClipboard(string text) - { - try - { - Clipboard.SetText(text); - } - catch (System.Runtime.InteropServices.COMException) - { - try - { - Clipboard.Clear(); - Clipboard.SetDataObject(text); - } - catch (Exception e) - { - Logger.LogError(e, "Unable to copy to clipboard"); - } - } - } - - protected void OpenProgressionLog(GeneratedRom rom) - { - if (rom.TrackerState == null) - { - ShowWarningMessageBox("There is no history for the selected rom."); - return; - } - - var history = GameDbService.GetGameHistory(rom); - - if (rom.TrackerState?.History == null || rom.TrackerState.History.Count == 0) - { - ShowWarningMessageBox("There is no history for the selected rom."); - return; - } - - var path = Path.Combine(Options.RomOutputPath, rom.SpoilerPath).Replace("Spoiler_Log", "Progression_Log"); - var historyText = HistoryService.GenerateHistoryText(rom, history.ToList()); - File.WriteAllText(path, historyText); - Process.Start(new ProcessStartInfo - { - FileName = path, - UseShellExecute = true - }); - } - - protected bool DeleteGeneratedRom(GeneratedRom rom) - { - if (ShowWarningMessageBox("Are you sure you want to delete this rom and tracker information? This cannot be undone.") == MessageBoxResult.No) - { - return false; - } - - if (!GameDbService.DeleteGeneratedRom(rom, out var error)) - { - ShowErrorMessageBox(error); - } - return true; - } - - public void OpenSpoilerLog(GeneratedRom rom) - { - var path = Path.Combine(Options.RomOutputPath, rom.SpoilerPath); - if (File.Exists(path)) - { - Process.Start(new ProcessStartInfo - { - FileName = path, - UseShellExecute = true - }); - } - else - { - ShowWarningMessageBox($"Could not find spoiler file at {path}"); - } - } - - public void CloseTracker() - { - _trackerWindow?.Close(); - } - - public abstract void UpdateList(); - - public bool ShowGenerateRomWindow(PlandoConfig? plandoConfig, bool isMulti) - { - using var scope = ServiceProvider.CreateScope(); - - var generateWindow = scope.ServiceProvider.GetRequiredService(); - generateWindow.Owner = Window.GetWindow(this); - - if (plandoConfig != null) - { - generateWindow.SetPlandoConfig(plandoConfig); - } - - if (isMulti) - { - generateWindow.SetMultiplayerEnabled(); - } - - var successful = generateWindow.ShowDialog(); - return successful.HasValue && successful.Value; - } - - protected MessageBoxResult ShowWarningMessageBox(string message) - { - return MessageBox.Show(Window.GetWindow(this)!, message, "SMZ3 Cas’ Randomizer", MessageBoxButton.YesNo, MessageBoxImage.Warning); - } - - protected MessageBoxResult ShowErrorMessageBox(string message) - { - return MessageBox.Show(Window.GetWindow(this)!, message, "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Error); - } - -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/SoloRomListPanel.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/SoloRomListPanel.xaml deleted file mode 100644 index 7d0528dbd..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/SoloRomListPanel.xaml +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - It looks like you don't have any generated roms yet! Press quick play below to generate and launch a rom with the either the default or last used settings, or click generate custom game to change settings before playing. - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/SoloRomListPanel.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Controls/SoloRomListPanel.xaml.cs deleted file mode 100644 index 16267a82f..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Controls/SoloRomListPanel.xaml.cs +++ /dev/null @@ -1,311 +0,0 @@ -using System; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Win32; -using TrackerCouncil.Smz3.Data.Interfaces; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.Services; -using TrackerCouncil.Smz3.SeedGenerator.Contracts; -using TrackerCouncil.Smz3.SeedGenerator.Generation; -using TrackerCouncil.Smz3.SeedGenerator.Infrastructure; -using TrackerCouncil.Smz3.Shared.Models; -using TrackerCouncil.Smz3.UI.Legacy.ViewModels; -using YamlDotNet.Core; - -namespace TrackerCouncil.Smz3.UI.Legacy.Controls; - -/// -/// Interaction logic for SoloRomListPanel.xaml -/// -public partial class SoloRomListPanel : RomListPanel -{ - - public SoloRomListPanel(IServiceProvider serviceProvider, - OptionsFactory optionsFactory, - ILogger logger, - IRomGenerationService romGenerationService, - IGameDbService gameDbService, - RomLauncherService romLauncherService) : base(serviceProvider, optionsFactory, logger, romGenerationService, gameDbService, romLauncherService) - { - Model = new GeneratedRomsViewModel(); - DataContext = Model; - InitializeComponent(); - UpdateList(); - } - - public GeneratedRomsViewModel Model { get; } - - public sealed override void UpdateList() - { - var models = GameDbService.GetGeneratedRomsList() - .OrderByDescending(x => x.Id) - .ToList(); - Model.UpdateList(models); - RomsList.SelectedIndex = 0; - } - - /// - /// Opens the page to generate a custom rom with new settings - /// - /// - /// - private void GenerateRomButton_Click(object sender, RoutedEventArgs e) - { - if (ShowGenerateRomWindow(null, false)) UpdateList(); - } - - private async void StartPlandoButton_Click(object sender, RoutedEventArgs e) - { - using var scope = ServiceProvider.CreateScope(); - var window = Window.GetWindow(this); - - var plandoBrowser = new OpenFileDialog - { - Title = "Open plando configuration - SMZ3 Cas’ Randomizer", - Filter = "YAML files (*.yml; *.yaml)|*.yml;*.yaml|All files (*.*)|*.*" - }; - if (plandoBrowser.ShowDialog(window) != true) - return; - - try - { - var plandoConfigLoader = scope.ServiceProvider.GetRequiredService(); - var plandoConfig = await plandoConfigLoader.LoadAsync(plandoBrowser.FileName); - if (ShowGenerateRomWindow(plandoConfig, false)) UpdateList(); - } - catch (PlandoConfigurationException ex) - { - Logger.LogWarning(ex, "Plando config '{FileName}' contains errors.", plandoBrowser.FileName); - ShowWarningMessageBox("The selected plando configuration contains errors:\n\n" + ex.Message); - } - catch (YamlException ex) - { - Logger.LogWarning(ex, "Plando config '{FileName}' is malformed.", plandoBrowser.FileName); - ShowWarningMessageBox($"The selected plando configuration contains errors in line {ex.Start.Line}, col {ex.Start.Column}:\n\n{ex.InnerException?.Message ?? ex.Message}"); - } - catch (Exception ex) - { - Logger.LogCritical(ex, "An unknown exception occurred while trying to generate a plando."); - ShowErrorMessageBox("Oops. Something went wrong. Please try again."); - } - } - - /// - /// The user has clicked on a quick launch button for a rom - /// - /// - /// - private void LaunchButton_Click(object sender, RoutedEventArgs e) - { - if (sender is not Button { Tag: GeneratedRom rom }) - return; - - QuickLaunchRom(rom); - } - - /// - /// Right click menu to play a rom - /// - /// - /// - private void PlayMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: GeneratedRom rom }) - return; - - LaunchRom(rom); - } - - /// - /// Right click menu to open the folder for a rom - /// - /// - /// - private void OpenFolderMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: GeneratedRom rom }) - return; - - OpenFolder(rom); - } - - /// - /// Menu item for opening the tracker for a rom - /// - /// - /// - private void OpenTrackerMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: GeneratedRom rom }) - return; - - LaunchTracker(rom); - } - - /// - /// Menu item for viewing the spoiler log for a rom - /// - /// - /// - private void ViewSpoilerMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: GeneratedRom rom }) - return; - - OpenSpoilerLog(rom); - } - - /// - /// Menu item for editing the label for a rom - /// - /// - /// - private void EditLabelMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Parent: ContextMenu { PlacementTarget: Grid grid } }) - return; - - ShowEditTextBox(grid); - } - - /// - /// Toggles the edit textbox and hides the textblock for a rom for editing the label - /// - /// The grid that houses both the textbox and textblock for the rom - private void ShowEditTextBox(Grid grid) - { - if (grid.FindName("EditLabelTextBox") is not TextBox editLabelTextBox) - return; - - if (grid.FindName("LabelTextBlock") is not TextBlock labelTextBlock) - return; - - labelTextBlock.Visibility = Visibility.Collapsed; - editLabelTextBox.Visibility = Visibility.Visible; - editLabelTextBox.Focus(); - } - - /// - /// Updates the name for a rom and toggles the visibility for the textbox and textblock - /// - /// The grid that houses both the textbox and textblock for the rom - private void UpdateName(Grid grid) - { - if (grid.FindName("EditLabelTextBox") is not TextBox editLabelTextBox) - return; - - if (grid.FindName("LabelTextBlock") is not TextBlock labelTextBlock) - return; - - if (editLabelTextBox.Tag is not GeneratedRom rom) - return; - - labelTextBlock.Visibility = Visibility.Visible; - editLabelTextBox.Visibility = Visibility.Collapsed; - - if (GameDbService.UpdateGeneratedRom(rom, label: editLabelTextBox.Text)) - { - UpdateList(); - } - - } - - /// - /// Menu item for copying the seed for a rom to the clipboard - /// - /// - /// - private void CopySeedMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: GeneratedRom rom }) - return; - - CopyTextToClipboard(rom.Seed); - } - - /// - /// Menu item for copying the seed's config string for sending to someone else - /// - /// - /// - private void CopyConfigMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: GeneratedRom rom }) - return; - - CopyTextToClipboard(rom.Settings); - } - - /// - /// Menu item for deleting a rom from the db and filesystem - /// - /// - /// - /// - private void DeleteRomMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: GeneratedRom rom }) - return; - - DeleteGeneratedRom(rom); - UpdateList(); - } - - /// - /// Updates the label for a rom when clicking away from the text box - /// - /// - /// - private void EditLabelTextBox_LostFocus(object sender, RoutedEventArgs e) - { - if (sender is not TextBox { Parent: Grid grid }) - return; - - UpdateName(grid); - } - - /// - /// Updates the label for a rom when pressing enter/return - /// - /// - /// - private void EditLabelTextBox_KeyDown(object sender, KeyEventArgs e) - { - if (e.Key != Key.Return) - return; - - if (sender is not TextBox { Parent: Grid grid }) - return; - - UpdateName(grid); - } - - private void ProgressionLogMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem { Tag: GeneratedRom rom }) - return; - - OpenProgressionLog(rom); - } - - /// - /// Generates a rom based on te previous settings - /// - /// - /// - private async void QuickPlayButton_Click(object sender, RoutedEventArgs e) - { - var rom = await RomGenerationService.GenerateRandomRomAsync(Options); - if (!string.IsNullOrEmpty(rom.GenerationError)) - { - MessageBox.Show(rom.GenerationError, "SMZ3 Cas' Randomizer", MessageBoxButton.OK, MessageBoxImage.Error); - } - if (rom.Rom == null || !GeneratedRom.IsValid(rom.Rom)) return; - UpdateList(); - QuickLaunchRom(rom.Rom); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/DelegateCommand.cs b/src/TrackerCouncil.Smz3.UI.Legacy/DelegateCommand.cs deleted file mode 100644 index 67dda5ec2..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/DelegateCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Windows.Input; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -public class DelegateCommand : ICommand -{ - private readonly Action _execute; - private readonly Func _canExecute; - - public DelegateCommand(Action execute, Func canExecute) - { - _execute = execute; - _canExecute = canExecute; - } - -#pragma warning disable 67 - public event EventHandler? CanExecuteChanged; -#pragma warning restore 67 - - public bool CanExecute(object? parameter) - => _canExecute(); - - public void Execute(object? parameter) - => _execute(); -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/EmbeddedResource.cs b/src/TrackerCouncil.Smz3.UI.Legacy/EmbeddedResource.cs deleted file mode 100644 index 8b4d51ea8..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/EmbeddedResource.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.IO; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -public static class EmbeddedResource -{ - public static Stream? GetStream(string resourceName) - { - var assembly = typeof(T).Assembly; - return assembly.GetManifestResourceStream(typeof(T), resourceName); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/EnumBindingSourceExtension.cs b/src/TrackerCouncil.Smz3.UI.Legacy/EnumBindingSourceExtension.cs deleted file mode 100644 index 48b5cb9d3..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/EnumBindingSourceExtension.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Windows.Markup; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -public class EnumBindingSourceExtension : MarkupExtension -{ - private Type? _enumType; - - public EnumBindingSourceExtension() - { - } - - public EnumBindingSourceExtension(Type enumType) - { - EnumType = enumType; - } - - public Type? EnumType - { - get => _enumType; - set - { - if (value != _enumType) - { - if (null != value) - { - var enumType = Nullable.GetUnderlyingType(value) ?? value; - if (!enumType.IsEnum) - throw new ArgumentException("Type must be for an Enum."); - } - - _enumType = value; - } - } - } - - public override object ProvideValue(IServiceProvider serviceProvider) - { - if (null == _enumType) - throw new InvalidOperationException("The EnumType must be specified."); - - var actualEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType; - var enumValues = Enum.GetValues(actualEnumType); - - if (actualEnumType == _enumType) - return enumValues; - - var tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1); - enumValues.CopyTo(tempArray, 1); - return tempArray; - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/EnumDescriptionBindingSourceExtension.cs b/src/TrackerCouncil.Smz3.UI.Legacy/EnumDescriptionBindingSourceExtension.cs deleted file mode 100644 index e4b635c3a..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/EnumDescriptionBindingSourceExtension.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Linq; -using System.Windows.Markup; -using TrackerCouncil.Smz3.Shared; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -public class EnumDescriptionBindingSourceExtension : MarkupExtension -{ - private Type? _enumType; - - public EnumDescriptionBindingSourceExtension() - { - } - - public EnumDescriptionBindingSourceExtension(Type enumType) - { - EnumType = enumType; - } - - public Type? EnumType - { - get => _enumType; - set - { - if (value != _enumType) - { - if (null != value) - { - var enumType = Nullable.GetUnderlyingType(value) ?? value; - if (!enumType.IsEnum) - throw new ArgumentException("Type must be for an Enum."); - } - - _enumType = value; - } - } - } - - public override object ProvideValue(IServiceProvider serviceProvider) - { - if (null == _enumType) - throw new InvalidOperationException("The EnumType must be specified."); - - var actualEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType; - var enumValues = Enum.GetValues(actualEnumType); - var descriptions = enumValues.Cast().Select(x => x.GetDescription()); - - if (actualEnumType == _enumType) - return descriptions; - - var tempArray = new string[enumValues.Length + 1]; - enumValues.CopyTo(tempArray, 1); - return tempArray; - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/EnumDescriptionConverter.cs b/src/TrackerCouncil.Smz3.UI.Legacy/EnumDescriptionConverter.cs deleted file mode 100644 index 97d066f8b..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/EnumDescriptionConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Windows.Data; -using TrackerCouncil.Smz3.Shared; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -public class EnumDescriptionConverter : IValueConverter -{ - public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - var enumValue = (Enum)value!; - return enumValue.GetDescription(); - } - - public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - var enumValues = Enum.GetValues(targetType); - if (value == null) - { - return enumValues.GetValue(0)!; - } - var descriptions = enumValues.Cast().ToDictionary(x => x.GetDescription() as object, x => x); - return descriptions[value]; - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/EnumToBooleanConverter.cs b/src/TrackerCouncil.Smz3.UI.Legacy/EnumToBooleanConverter.cs deleted file mode 100644 index ec4f78903..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/EnumToBooleanConverter.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Data; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -public class EnumToBooleanConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - => value.Equals(parameter); - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - => (bool)value ? parameter : Binding.DoNothing; -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ItemSpriteImageSourceConverter.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ItemSpriteImageSourceConverter.cs deleted file mode 100644 index 1bd333caf..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ItemSpriteImageSourceConverter.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Reflection; -using System.Windows.Data; -using System.Windows.Media.Imaging; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -internal class ItemSpriteImageSourceConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return new BitmapImage(new Uri( - Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, - "Sprites", "Items", (string)value))); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/MsuUiService.cs b/src/TrackerCouncil.Smz3.UI.Legacy/MsuUiService.cs deleted file mode 100644 index d8aaab1eb..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/MsuUiService.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using Microsoft.WindowsAPICodePack.Dialogs; -using MSURandomizerLibrary; -using MSURandomizerLibrary.Services; -using MSURandomizerUI; -using TrackerCouncil.Smz3.Data.Options; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -public class MsuUiService -{ - private readonly IMsuLookupService _msuLookupService; - private readonly IMsuUiFactory _msuUiFactory; - private readonly RandomizerOptions _options; - - public MsuUiService(OptionsFactory optionsFactory, IMsuLookupService msuLookupService, IMsuUiFactory msuUiFactory) - { - _options = optionsFactory.Create(); - _msuLookupService = msuLookupService; - _msuUiFactory = msuUiFactory; - } - - public bool OpenMsuWindow(Window parentWindow, SelectionMode selectionMode, MsuRandomizationStyle? randomizationStyle) - { - if (!VerifyMsuDirectory(parentWindow)) return false; - if (!_msuUiFactory.OpenMsuWindow(selectionMode, true, out var options)) return false; - if (options.SelectedMsus?.Any() != true) return false; - _options.PatchOptions.MsuPaths = options.SelectedMsus.ToList(); - _options.PatchOptions.MsuRandomizationStyle = randomizationStyle; - return true; - } - - public bool LookupMsus() - { - if (!string.IsNullOrEmpty(_options.GeneralOptions.MsuPath) && Directory.Exists(_options.GeneralOptions.MsuPath) || _msuLookupService.Status != MsuLoadStatus.Loading) - { - Task.Run(() => - { - _msuLookupService.LookupMsus(_options.GeneralOptions.MsuPath); - }); - - return true; - } - - return false; - } - - public bool VerifyMsuDirectory(Window parentWindow) - { - if (!string.IsNullOrEmpty(_options.GeneralOptions.MsuPath) && Directory.Exists(_options.GeneralOptions.MsuPath)) - { - return true; - } - - MessageBox.Show(parentWindow, "Please select the parent folder than contains all of your MSUs. To preserve drive space, it is recommended that the Rom Output and MSU folders be on the same drive.", "MSU Path Needed", - MessageBoxButton.OK, MessageBoxImage.Exclamation); - - using var dialog = new CommonOpenFileDialog(); - dialog.EnsurePathExists = true; - dialog.Title = "Select MSU Path"; - dialog.IsFolderPicker = true; - - if (dialog.ShowDialog(parentWindow) == CommonFileDialogResult.Ok) - { - _options.GeneralOptions.MsuPath = dialog.FileName; - } - - if (string.IsNullOrEmpty(_options.GeneralOptions.MsuPath) || - !Directory.Exists(_options.GeneralOptions.MsuPath)) - { - return false; - } - - Task.Run(() => - { - _msuLookupService.LookupMsus(_options.GeneralOptions.MsuPath); - }); - - MessageBox.Show(parentWindow, "Updated MSU folder. If you want to change the MSU path in the future, you can do so in the Tools -> Options window", "MSU Path Updated", - MessageBoxButton.OK, MessageBoxImage.Information); - - LookupMsus(); - - return true; - } - -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/NativeMethods.cs b/src/TrackerCouncil.Smz3.UI.Legacy/NativeMethods.cs deleted file mode 100644 index 7d7fef20c..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/NativeMethods.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -internal static class NativeMethods -{ - [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] - public static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/NotAServiceAttribute.cs b/src/TrackerCouncil.Smz3.UI.Legacy/NotAServiceAttribute.cs deleted file mode 100644 index 8e37a1d42..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/NotAServiceAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -/// -/// Specifies that a window should not be added as a service. -/// -[AttributeUsage(AttributeTargets.Class)] -internal class NotAServiceAttribute : Attribute -{ - public NotAServiceAttribute() - { - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Properties/launchSettings.json b/src/TrackerCouncil.Smz3.UI.Legacy/Properties/launchSettings.json deleted file mode 100644 index ee161ac3f..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Properties/launchSettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "profiles": { - "TrackerCouncil.Smz3.UI.Legacy": { - "commandName": "Project", - "environmentVariables": { - "DOTNET_ENVIRONMENT": "Development" - } - } - } -} \ No newline at end of file diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Resources/DarkMode.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Resources/DarkMode.xaml deleted file mode 100644 index 9797bddae..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Resources/DarkMode.xaml +++ /dev/null @@ -1,730 +0,0 @@ - - - - - - - - - - - - - - - - - - M 0,0 L 3.5,4 L 7,0 Z - M 0,4 L 3.5,0 L 7,4 Z - M 0,0 L 4,3.5 L 0,7 Z - F1 M 10.0,1.2 L 4.7,9.1 L 4.5,9.1 L 0,5.2 L 1.3,3.5 L 4.3,6.1L 8.3,0 L 10.0,1.2 Z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Resources/Font Awesome 5 Free-Regular-400.otf b/src/TrackerCouncil.Smz3.UI.Legacy/Resources/Font Awesome 5 Free-Regular-400.otf deleted file mode 100644 index 0f558089e..000000000 Binary files a/src/TrackerCouncil.Smz3.UI.Legacy/Resources/Font Awesome 5 Free-Regular-400.otf and /dev/null differ diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Resources/Font Awesome 5 Free-Solid-900.otf b/src/TrackerCouncil.Smz3.UI.Legacy/Resources/Font Awesome 5 Free-Solid-900.otf deleted file mode 100644 index fb8c079bb..000000000 Binary files a/src/TrackerCouncil.Smz3.UI.Legacy/Resources/Font Awesome 5 Free-Solid-900.otf and /dev/null differ diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Resources/LICENSE.txt b/src/TrackerCouncil.Smz3.UI.Legacy/Resources/LICENSE.txt deleted file mode 100644 index f31bef92b..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Resources/LICENSE.txt +++ /dev/null @@ -1,34 +0,0 @@ -Font Awesome Free License -------------------------- - -Font Awesome Free is free, open source, and GPL friendly. You can use it for -commercial projects, open source projects, or really almost whatever you want. -Full Font Awesome Free license: https://fontawesome.com/license/free. - -# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) -In the Font Awesome Free download, the CC BY 4.0 license applies to all icons -packaged as SVG and JS file types. - -# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL) -In the Font Awesome Free download, the SIL OFL license applies to all icons -packaged as web and desktop font files. - -# Code: MIT License (https://opensource.org/licenses/MIT) -In the Font Awesome Free download, the MIT license applies to all non-font and -non-icon files. - -# Attribution -Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font -Awesome Free files already contain embedded comments with sufficient -attribution, so you shouldn't need to do anything additional when using these -files normally. - -We've kept attribution comments terse, so we ask that you do not actively work -to remove them from files, especially code. They're a great way for folks to -learn about Font Awesome. - -# Brand Icons -All brand icons are trademarks of their respective owners. The use of these -trademarks does not indicate endorsement of the trademark holder by Font -Awesome, nor vice versa. **Please do not use brand logos for any purpose except -to represent the company, product, or service to which they refer.** diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Resources/empty.pcm b/src/TrackerCouncil.Smz3.UI.Legacy/Resources/empty.pcm deleted file mode 100644 index c3bc6be3a..000000000 Binary files a/src/TrackerCouncil.Smz3.UI.Legacy/Resources/empty.pcm and /dev/null differ diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/StringColorConverter.cs b/src/TrackerCouncil.Smz3.UI.Legacy/StringColorConverter.cs deleted file mode 100644 index 69e9005ca..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/StringColorConverter.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using System.Globalization; -using System.Windows.Data; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -[ValueConversion(typeof(byte[]), typeof(string))] -internal class StringColorConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (targetType != typeof(string)) - throw new ArgumentException($"Invalid target type '{targetType}', expected string."); - - var color = (byte[])value; - return $"#{color[0]:X2}{color[1]:X2}{color[2]:X2}{color[3]:X2}"; - } - - public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - if (targetType != typeof(byte[])) - throw new ArgumentException($"Invalid target type '{targetType}', expected Color."); - - var hex = ((string)value).AsSpan(); - if (hex.Length < 1) - return null; - - if (hex[0] != '#') - return new ValidationResult("Expected color in the format #RGB, #RRGGBB or #AARRGGBB"); - - if (!TryParseColor(hex, out var a, out var r, out var g, out var b)) - return new ValidationResult("Expected color in the format #RGB, #RRGGBB or #AARRGGBB"); - - return new byte[] { a, r, g, b }; - } - - private static bool TryParseColor(ReadOnlySpan value, - out byte a, out byte r, out byte g, out byte b) - { - const NumberStyles style = NumberStyles.HexNumber; - var format = CultureInfo.InvariantCulture; - - a = 0; - r = 0; - g = 0; - b = 0; - - if (value.Length == 4) - { - a = 0xFF; - if (byte.TryParse(value.Slice(1, 1), style, format, out r) - && byte.TryParse(value.Slice(2, 1), style, format, out g) - && byte.TryParse(value.Slice(3, 1), style, format, out b)) - { - r *= 0x11; - g *= 0x11; - b *= 0x11; - return true; - } - } - else if (value.Length == 7) - { - a = 0xFF; - return byte.TryParse(value.Slice(1, 2), style, format, out r) - && byte.TryParse(value.Slice(3, 2), style, format, out g) - && byte.TryParse(value.Slice(5, 2), style, format, out b); - } - else if (value.Length == 9) - { - return byte.TryParse(value.Slice(1, 2), style, format, out a) - && byte.TryParse(value.Slice(3, 2), style, format, out r) - && byte.TryParse(value.Slice(5, 2), style, format, out g) - && byte.TryParse(value.Slice(7, 2), style, format, out b); - } - - return false; - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/StringExtensions.cs b/src/TrackerCouncil.Smz3.UI.Legacy/StringExtensions.cs deleted file mode 100644 index 0a959ae59..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/StringExtensions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace TrackerCouncil.Smz3.UI.Legacy; - -public static class StringExtensions -{ - public static string Or(this string value, string fallbackValue) - => string.IsNullOrEmpty(value) ? fallbackValue : value; -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/TrackerCouncil.Smz3.UI.Legacy.csproj b/src/TrackerCouncil.Smz3.UI.Legacy/TrackerCouncil.Smz3.UI.Legacy.csproj deleted file mode 100644 index a1004b582..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/TrackerCouncil.Smz3.UI.Legacy.csproj +++ /dev/null @@ -1,104 +0,0 @@ - - - - WinExe - net8.0-windows - true - chozo20.ico - 9.8.1 - SMZ3 Cas' Randomizer - SMZ3 Cas' Randomizer - Vivelin - SMZ3 Cas' Randomizer - Randomizer.App - en - app.manifest - enable - - - - 1701;1702;0067 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - - - - $(DefaultXamlRuntime) - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/TrackerLocationSyncer.cs b/src/TrackerCouncil.Smz3.UI.Legacy/TrackerLocationSyncer.cs deleted file mode 100644 index c8e7d1676..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/TrackerLocationSyncer.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.ComponentModel; -using Microsoft.Extensions.Logging; -using TrackerCouncil.Smz3.Abstractions; -using TrackerCouncil.Smz3.Data.WorldData; -using TrackerCouncil.Smz3.Tracking.Services; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -/// -/// This is a shared class that is meant to act as an intermediary between -/// the tracker and the location/map windows to keep everything in sync as -/// well as clean up some of the logic in view models -/// -public class TrackerLocationSyncer -{ - private readonly ILogger _logger; - private bool _showOutOfLogicLocations; - - /// - /// Creates a new instance of the TrackerLocationSyncer that will be - /// synced with a given tracker - /// - /// The tracker to keep things in sync with - /// Service for retrieving the item data - /// Service for retrieving world data - /// Logger - public TrackerLocationSyncer(TrackerBase tracker, IItemService itemService, IWorldService worldService, ILogger logger) - { - Tracker = tracker; - ItemService = itemService; - WorldService = worldService; - _logger = logger; - - // Set all events from the tracker to point to the two in this class - Tracker.MarkedLocationsUpdated += (_, _) => - { - TrackedLocationUpdated?.Invoke(this, new("")); - MarkedLocationUpdated?.Invoke(this, new("")); - }; - Tracker.LocationCleared += (_, e) => - { - TrackedLocationUpdated?.Invoke(this, new(e.Location.Name)); - MarkedLocationUpdated?.Invoke(this, new(e.Location.Name)); - }; - Tracker.DungeonUpdated += (_, _) => - { - TrackedLocationUpdated?.Invoke(this, new("")); - MarkedLocationUpdated?.Invoke(this, new("")); - }; - Tracker.ItemTracked += (_, _) => - { - TrackedLocationUpdated?.Invoke(this, new("")); - MarkedLocationUpdated?.Invoke(this, new("")); - }; - Tracker.ActionUndone += (_, _) => - { - TrackedLocationUpdated?.Invoke(this, new("")); - MarkedLocationUpdated?.Invoke(this, new("")); - }; - Tracker.StateLoaded += (_, _) => - { - TrackedLocationUpdated?.Invoke(this, new("")); - MarkedLocationUpdated?.Invoke(this, new("")); - }; - Tracker.BossUpdated += (_, _) => - { - TrackedLocationUpdated?.Invoke(this, new("")); - MarkedLocationUpdated?.Invoke(this, new("")); - }; - Tracker.HintTileUpdated += (_, _) => - { - TrackedLocationUpdated?.Invoke(this, new("")); - HintTileUpdated?.Invoke(this, new PropertyChangedEventArgs("")); - }; - } - - public event PropertyChangedEventHandler? TrackedLocationUpdated; - - public event PropertyChangedEventHandler? MarkedLocationUpdated; - - public event PropertyChangedEventHandler? HintTileUpdated; - - /// - /// If out of logic locations should be displayed on the tracker - /// - public bool ShowOutOfLogicLocations - { - get => _showOutOfLogicLocations; - set - { - _showOutOfLogicLocations = value; - OnLocationUpdated(); - } - } - - public TrackerBase Tracker { get; private set; } - - public IItemService ItemService { get; private set; } - - public IWorldService WorldService { get; private set; } - - public World World => Tracker.World; - - /// - /// Calls the event handlers when a location has been updated somehow - /// - /// - /// The name of the location that was updated - /// - /// - /// Whether a tracked location has been potentially updated - /// - /// - /// Whether a marked location has been potentially updated - /// - public void OnLocationUpdated(string? location = null, bool updateToTrackedLocation = true, bool updateToMarkedLocation = true) - { - if (updateToTrackedLocation) TrackedLocationUpdated?.Invoke(this, new(location)); - if (updateToMarkedLocation) MarkedLocationUpdated?.Invoke(this, new(location)); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/GeneratedRomsViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/GeneratedRomsViewModel.cs deleted file mode 100644 index 842e359ee..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/GeneratedRomsViewModel.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using System.Windows; -using TrackerCouncil.Smz3.Shared.Models; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -/// -/// Class for the generated roms list window -/// -public class GeneratedRomsViewModel : INotifyPropertyChanged -{ - public GeneratedRomsViewModel() - { - if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) - { - RomsList = new() - { - new(new GeneratedRom() { Label = "Test1", Date = DateTimeOffset.Now, Seed = "12345", TrackerState = new TrackerState() { SecondsElapsed = 342.54, PercentageCleared = 54 } }), - new(new GeneratedRom() { Date = DateTimeOffset.UtcNow, Seed = "45623" }), - new(new GeneratedRom() { Label = "Test2", Date = DateTimeOffset.UtcNow, Seed = "5634" }), - new(new GeneratedRom() { Date = DateTimeOffset.UtcNow, Seed = "234", TrackerState = new TrackerState() { SecondsElapsed = 4245.64, PercentageCleared = 20 } }), - new(new GeneratedRom() { Label = "Test3", Date = DateTimeOffset.UtcNow, Seed = "4564656423" }) - }; - } - else - { - RomsList = new(); - } - } - - public GeneratedRomsViewModel(List roms) - { - RomsList = UpdateList(roms); - } - - public List UpdateList(List roms) - { - RomsList = roms.Select(x => new GeneratedRomViewModel(x)).ToList(); - OnPropertyChanged(); - return RomsList; - } - - public Visibility RomsListVisibility => RomsList.Count == 0 ? Visibility.Collapsed : Visibility.Visible; - - public Visibility IntroVisibility => RomsList.Count == 0 ? Visibility.Visible : Visibility.Collapsed; - - public List RomsList { get; set; } - - public event PropertyChangedEventHandler? PropertyChanged; - - public void OnPropertyChanged() - { - PropertyChanged?.Invoke(this, new("")); - } -} - -/// -/// Class for an individual entry for a generated rom -/// -public class GeneratedRomViewModel -{ - public GeneratedRomViewModel(GeneratedRom rom) - { - Rom = rom; - } - - public GeneratedRom Rom { get; } - - public string TextBoxName => $"EditLabelTextBox{Rom.Id}"; - public string Name => Rom.Label; - - public string NameLabel => string.IsNullOrEmpty(Rom.Label) ? $"Seed: {Rom.Seed}" : Rom.Label; - - public string LocationsLabel - { - get - { - if (Rom.TrackerState == null) return ""; - return $"Cleared: {Rom.TrackerState.PercentageCleared}%"; - } - } - - public Visibility ProgressionLogVisibility => Rom.TrackerState?.History?.Count > 0 ? Visibility.Visible : Visibility.Collapsed; - - public string TimeLabel - { - get - { - return $"{Rom.Date.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern)} {Rom.Date.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern)}"; - } - } - - public string ElapsedLabel - { - get - { - if (Rom.TrackerState == null) return ""; - var timeSpan = TimeSpan.FromSeconds(Rom.TrackerState.SecondsElapsed); - var duration = timeSpan.Hours > 0 - ? timeSpan.ToString("h':'mm':'ss") - : timeSpan.ToString("mm':'ss"); - return $"Duration: {duration}"; - } - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/HintTileViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/HintTileViewModel.cs deleted file mode 100644 index 62b919eab..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/HintTileViewModel.cs +++ /dev/null @@ -1,16 +0,0 @@ -using TrackerCouncil.Smz3.Shared.Models; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -public class HintTileViewModel -{ - public HintTileViewModel(PlayerHintTile hintTile) - { - PlayerHintTile = hintTile; - } - - public PlayerHintTile PlayerHintTile { get; init; } - - public string Name => PlayerHintTile.LocationKey; - public string Quality => PlayerHintTile.Usefulness?.ToString() ?? ""; -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/LocationViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/LocationViewModel.cs deleted file mode 100644 index f7b9591bd..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/LocationViewModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Windows.Input; -using TrackerCouncil.Smz3.Data.WorldData; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -public class LocationViewModel -{ - private readonly Location _location; - private readonly TrackerLocationSyncer _syncer; - - public LocationViewModel(Location location, TrackerLocationSyncer syncer) - { - _location = location; - _syncer = syncer; - } - - public string Name => _location.Metadata.Name?[0] ?? _location.Name; - - public string Area => _location.Region.Metadata.Name?[0] ?? _location.Region.Name; - - public bool InLogic => _location.IsAvailable(_syncer.ItemService.GetProgression(false)); - - public bool InLogicWithKeys => !InLogic && _location.IsAvailable(_syncer.ItemService.GetProgression(true)); - - public double Opacity => (InLogic || InLogicWithKeys) ? 1.0 : 0.33; - - public ICommand Clear => new DelegateCommand( - execute: () => - { - _syncer.Tracker.Clear(_location); - }, - canExecute: () => !_location.State.Cleared); -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MarkedLocationViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MarkedLocationViewModel.cs deleted file mode 100644 index 68e481c48..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MarkedLocationViewModel.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using TrackerCouncil.Smz3.Data.WorldData; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -public class MarkedLocationViewModel -{ - private readonly Location _location; - private readonly Item _itemData; - private readonly TrackerLocationSyncer _syncer; - - public MarkedLocationViewModel(Location location, Item itemData, string? itemSprite, TrackerLocationSyncer syncer) - { - _location = location; - _itemData = itemData; - _syncer = syncer; - ItemSprite = itemSprite != null ? new BitmapImage(new Uri(itemSprite)) : null; - } - - public ImageSource? ItemSprite { get; } - - public double Opacity => _syncer.ShowOutOfLogicLocations || _syncer.WorldService.IsAvailable(_location) ? 1.0 : 0.33; - - public string Item => _itemData.Name; - - public string Location => _location.Metadata.Name?[0] ?? _location.Name; - - public string Area => _location.Region.Metadata.Name?[0] ?? _location.Region.Name; -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerGameViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerGameViewModel.cs deleted file mode 100644 index 919cfa620..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerGameViewModel.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using TrackerCouncil.Smz3.Shared.Models; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -/// -/// View model for an individual multiplayer game -/// -public class MultiplayerGameViewModel -{ - public MultiplayerGameViewModel(MultiplayerGameDetails details) - { - Details = details; - } - - public MultiplayerGameDetails Details { get; } - - public string TypeLabel => $"Type: {Details.Type}"; - - public string StatusLabel => $"Status: {Details.Status}"; - - public string TimeLabel - { - get - { - return $"{Details.JoinedDate.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern)} {Details.JoinedDate.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern)}"; - } - } - - public string ElapsedLabel - { - get - { - if (Details.GeneratedRom?.TrackerState == null) return ""; - var timeSpan = TimeSpan.FromSeconds(Details.GeneratedRom.TrackerState.SecondsElapsed); - var duration = timeSpan.Hours > 0 - ? timeSpan.ToString("h':'mm':'ss") - : timeSpan.ToString("mm':'ss"); - return $"Duration: {duration}"; - } - } - - public Visibility GeneratedRomMenuItemVisibility => Details.GeneratedRom != null ? Visibility.Visible : Visibility.Collapsed; -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerGamesViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerGamesViewModel.cs deleted file mode 100644 index 2d7f3237e..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerGamesViewModel.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Windows; -using TrackerCouncil.Smz3.Shared.Models; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -/// -/// View model for the list of multiplayer games -/// -public class MultiplayerGamesViewModel : INotifyPropertyChanged -{ - public List Games { get; set; } - - public MultiplayerGamesViewModel() - { - Games = new List(); - } - - public void UpdateList(ICollection details) - { - Games = details.Select(x => new MultiplayerGameViewModel(x)).ToList(); - OnPropertyChanged(); - } - - public Visibility GamesVisibility => Games.Count == 0 ? Visibility.Collapsed : Visibility.Visible; - - public Visibility IntroVisibility => Games.Count == 0 ? Visibility.Visible : Visibility.Collapsed; - - public event PropertyChangedEventHandler? PropertyChanged; - - public void OnPropertyChanged(string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } -} - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerPlayerStateViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerPlayerStateViewModel.cs deleted file mode 100644 index 5dfd7070b..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerPlayerStateViewModel.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.ComponentModel; -using System.Windows; -using TrackerCouncil.Smz3.Shared; -using TrackerCouncil.Smz3.Shared.Multiplayer; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -/// -/// View model for an individual player in the muliplayer status window -/// -public class MultiplayerPlayerStateViewModel : INotifyPropertyChanged -{ - private bool _isConnectedToServer; - - public MultiplayerPlayerStateViewModel(MultiplayerPlayerState state, bool isLocalPlayer, bool isLocalPlayerAdmin, bool isConnectedToServer, MultiplayerGameStatus gameStatus) - { - State = state; - PlayerGuid = state.Guid; - PlayerName = state.PlayerName; - IsLocalPlayer = isLocalPlayer; - IsLocalPlayerAdmin = isLocalPlayerAdmin; - _isConnectedToServer = isConnectedToServer; - GameStatus = gameStatus; - } - - public MultiplayerPlayerState State { get; private set; } - public string PlayerGuid { get; } - public string PlayerName { get; } - public bool IsLocalPlayer { get; } - public bool IsLocalPlayerAdmin { get; } - public string StatusLabel => "(" + Status + ")"; - public MultiplayerGameStatus GameStatus { get; set; } - - public bool IsConnectedToServer - { - get - { - return _isConnectedToServer; - } - set - { - _isConnectedToServer = value; - OnPropertyChanged(nameof(IsConnectedToServer)); - OnPropertyChanged(nameof(StatusLabel)); - } - } - - public string Status - { - get - { - if (!IsConnectedToServer) return IsLocalPlayer ? "Disconnected" : "Unknown"; - return State.IsConnected ? State.Status.GetDescription() : "Disconnected"; - } - } - - public Visibility EditConfigVisibility => GameStatus == MultiplayerGameStatus.Created && IsLocalPlayer - ? Visibility.Visible - : Visibility.Collapsed; - - public Visibility ForfeitVisiblity => - (IsLocalPlayer || IsLocalPlayerAdmin) && GameStatus != MultiplayerGameStatus.Generating && - !State.HasForfeited && !State.HasCompleted - ? Visibility.Visible - : Visibility.Collapsed; - - public void Update(MultiplayerPlayerState state) - { - State = state; - OnPropertyChanged(); - } - - public void Update(MultiplayerGameStatus status) - { - GameStatus = status; - OnPropertyChanged(); - } - - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged(string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerStatusViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerStatusViewModel.cs deleted file mode 100644 index 2af8a7524..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/MultiplayerStatusViewModel.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Windows; -using TrackerCouncil.Smz3.Shared.Models; -using TrackerCouncil.Smz3.Shared.Multiplayer; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -/// -/// View model for the multiplayer status window -/// -public class MultiplayerStatusViewModel : INotifyPropertyChanged -{ - private bool _isConnected; - private string _gameUrl = ""; - private MultiplayerGameStatus? _gameStatus; - private bool _allPlayersSubmittedConfigs; - private GeneratedRom? _generatedRom; - - public MultiplayerStatusViewModel() - { - if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) - { - Players = new List() - { - new MultiplayerPlayerStateViewModel(new MultiplayerPlayerState - { - Guid = "Player One", - PlayerName = "Player One", - }, true, false, true, MultiplayerGameStatus.Created), - new MultiplayerPlayerStateViewModel(new MultiplayerPlayerState - { - Guid = "Player Two", - PlayerName = "Player Two", - }, false, false, true, MultiplayerGameStatus.Created), - }; - } - else - { - Players = new(); - } - } - - public string ConnectionStatus => IsConnected ? "Connected" : "Not Connected"; - public Visibility GeneratingLabelVisibility => GameStatus == MultiplayerGameStatus.Generating || (GameStatus == MultiplayerGameStatus.Started && _generatedRom == null) ? Visibility.Visible : Visibility.Collapsed; - public Visibility ReconnectButtonVisibility => IsConnected ? Visibility.Collapsed : Visibility.Visible; - public Visibility StartButtonVisiblity => (LocalPlayer?.IsAdmin ?? false) && GameStatus == MultiplayerGameStatus.Created ? Visibility.Visible : Visibility.Collapsed; - public Visibility PlayButtonsVisibility => GameStatus == MultiplayerGameStatus.Started && _generatedRom != null - ? Visibility.Visible - : Visibility.Collapsed; - public bool PlayButtonsEnabled => GameStatus == MultiplayerGameStatus.Started && _generatedRom != null && LocalPlayer?.HasCompleted != true && LocalPlayer?.HasForfeited != true; - public bool CanStartGame => IsConnected && AllPlayersSubmittedConfigs; - public MultiplayerPlayerState? LocalPlayer { get; private set; } - public List Players { get; private set; } - - public bool IsConnected - { - get - { - return _isConnected; - } - set - { - _isConnected = value; - OnPropertyChanged(nameof(IsConnected)); - OnPropertyChanged(nameof(ConnectionStatus)); - OnPropertyChanged(nameof(ReconnectButtonVisibility)); - OnPropertyChanged(nameof(CanStartGame)); - Players.ForEach(x => x.IsConnectedToServer = value); - } - } - - public string GameUrl - { - get - { - return _gameUrl; - } - set - { - _gameUrl = value; - OnPropertyChanged(nameof(GameUrl)); - } - } - - public MultiplayerGameStatus? GameStatus - { - get - { - return _gameStatus; - } - set - { - _gameStatus = value; - OnPropertyChanged(nameof(GameStatus)); - OnPropertyChanged(nameof(ConnectionStatus)); - OnPropertyChanged(nameof(GeneratingLabelVisibility)); - OnPropertyChanged(nameof(StartButtonVisiblity)); - if (_gameStatus != null) - { - foreach (var player in Players) - { - player.Update(_gameStatus.Value); - } - } - } - } - - public bool AllPlayersSubmittedConfigs - { - get - { - return _allPlayersSubmittedConfigs; - } - set - { - _allPlayersSubmittedConfigs = value; - OnPropertyChanged(nameof(AllPlayersSubmittedConfigs)); - OnPropertyChanged(nameof(CanStartGame)); - } - } - - public GeneratedRom? GeneratedRom - { - get - { - return _generatedRom; - } - set - { - _generatedRom = value; - OnPropertyChanged(nameof(PlayButtonsEnabled)); - OnPropertyChanged(nameof(PlayButtonsVisibility)); - OnPropertyChanged(nameof(GeneratingLabelVisibility)); - } - } - - - - public void UpdateList(List players, MultiplayerPlayerState? localPlayer) - { - LocalPlayer = localPlayer; - Players = players.Select(x => new MultiplayerPlayerStateViewModel(x, x == localPlayer, localPlayer?.IsAdmin ?? false, IsConnected, GameStatus ?? MultiplayerGameStatus.Created)).ToList(); - OnPropertyChanged(); - } - - public void UpdatePlayer(MultiplayerPlayerState player, MultiplayerPlayerState? localPlayer) - { - LocalPlayer = localPlayer; - var playerViewModel = Players.FirstOrDefault(x => x.PlayerGuid == player.Guid); - if (playerViewModel != null) - { - playerViewModel.Update(player); - if (player == LocalPlayer) OnPropertyChanged(nameof(PlayButtonsEnabled)); - } - else - { - Players = Players.Concat(new List() - { - new (player, false, localPlayer?.IsAdmin ?? false, IsConnected, - GameStatus ?? MultiplayerGameStatus.Created) - }).ToList(); - OnPropertyChanged(); - } - } - - public void Refresh() - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); - } - - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged(string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/TrackerMapLocationViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/TrackerMapLocationViewModel.cs deleted file mode 100644 index 48b2cd902..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/TrackerMapLocationViewModel.cs +++ /dev/null @@ -1,417 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.WorldData; -using TrackerCouncil.Smz3.Data.WorldData.Regions; -using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -using TrackerCouncil.Smz3.Shared; -using TrackerCouncil.Smz3.Shared.Enums; -using TrackerCouncil.Smz3.Tracking.Services; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -/// -/// ViewModel for displaying a specific location on the map and its progress -/// -public class TrackerMapLocationViewModel -{ - private static readonly Style? s_contextMenuStyle = Application.Current.FindResource("DarkContextMenu") as Style; - - /// - /// Initializes a new instance of the class - /// with data to display a specific location on the map and its progress - /// - /// - /// The TrackerMapLocation with details of where to place this location - /// on the map - /// - /// - /// The LocationSyncer for keeping in sync with the data in the tracker - /// - /// - /// The ratio in which the canvas has been scaled to fit in the grid - /// - public TrackerMapLocationViewModel(TrackerMapLocation mapLocation, TrackerLocationSyncer syncer, double scaledRatio) - { - Size = 36; - X = (mapLocation.X * scaledRatio) - (Size / 2); - Y = (mapLocation.Y * scaledRatio) - (Size / 2); - Syncer = syncer ?? throw new ArgumentNullException(nameof(syncer)); - Region = WorldService.Region(mapLocation.RegionTypeName) ?? throw new InvalidOperationException($"Map Region {mapLocation.RegionTypeName} not found"); - Type = mapLocation.Type; - - // If no location was specified, it's a boss or dungeon - if (Type == TrackerMapLocation.MapLocationType.Boss) - { - if (Region.CanEnter(Syncer.ItemService.GetProgression(Region), true)) - { - if (Region is IHasReward rewardRegion) - { - RewardRegion = rewardRegion; - Name = RewardRegion.Reward.Type.GetDescription(); - Y -= 22; - } - else if (Region is IHasBoss bossRegion) - { - BossRegion = bossRegion; - Name = bossRegion.Boss.Metadata.ToString() ?? bossRegion.Boss.Name; - } - } - } - // Else figure out the current status of all of the locations - else if (Type == TrackerMapLocation.MapLocationType.Item) - { - Name = mapLocation.GetName(syncer.WorldService.World); - - Locations = Syncer.WorldService.AllLocations().Where(mapLocation.MatchesSMZ3Location).ToList(); - var progression = Syncer.ItemService.GetProgression(!(Region is HyruleCastle || Region.World.Config.KeysanityForRegion(Region))); - var locationStatuses = Locations.Select(x => (Location: x, Status: x.GetStatus(progression))).ToList(); - - ClearableLocationsCount = locationStatuses.Count(x => x.Status == LocationStatus.Available); - RelevantLocationsCount = locationStatuses.Count(x => x.Status == LocationStatus.Relevant); - OutOfLogicLocationsCount = Syncer.ShowOutOfLogicLocations ? locationStatuses.Count(x => x.Status == LocationStatus.OutOfLogic) : 0; - UnclearedLocationsCount = locationStatuses.Count(x => x.Status != LocationStatus.Cleared); - ClearedLocationsCount = locationStatuses.Count() - UnclearedLocationsCount; - - // If there are any valid locations, see if anything was marked - if (ClearableLocationsCount > 0 || RelevantLocationsCount > 0) - { - var hintTileLocations = Syncer.World.ActiveHintTileLocations.ToList(); - var markedLocations = locationStatuses - .Where(x => x.Status is LocationStatus.Available or LocationStatus.Relevant && - hintTileLocations.Contains(x.Location.Id)).ToList(); - - // For hint tile locations, try to find the actual hint tile to use or look at the actual locations (hopefully should never happen) - if (markedLocations.Any()) - { - var activeLocationIds = markedLocations.Select(x => x.Location.Id); - var hintTile = Syncer.World.HintTiles.FirstOrDefault(x => x.Locations?.Intersect(activeLocationIds).Any() == true); - if (hintTile?.Usefulness is LocationUsefulness.Mandatory or LocationUsefulness.Sword - or LocationUsefulness.NiceToHave) - { - IsMarkedGood = true; - IsMarkedUseless = false; - } - else if (hintTile?.Usefulness == LocationUsefulness.Useless) - { - IsMarkedGood = false; - IsMarkedUseless = true; - } - else if (markedLocations.Any(x => x.Location.Item.Type.IsPossibleProgression(x.Location.World.Config.ZeldaKeysanity, x.Location.World.Config.MetroidKeysanity))) - { - IsMarkedGood = true; - IsMarkedUseless = false; - } - else - { - IsMarkedGood = false; - IsMarkedUseless = true; - } - } - // For marked items, we compare the marked items at the locations - else - { - markedLocations = locationStatuses - .Where(x => x.Status is LocationStatus.Available or LocationStatus.Relevant && - x.Location.State.MarkedItem != null).ToList(); - - if (markedLocations.Any()) - { - if (markedLocations.Any(x => x.Location.State.MarkedItem!.Value.IsPossibleProgression(x.Location.World.Config.ZeldaKeysanity, x.Location.World.Config.MetroidKeysanity))) - { - IsMarkedGood = true; - IsMarkedUseless = false; - } - else - { - IsMarkedGood = false; - IsMarkedUseless = true; - } - } - else - { - IsMarkedGood = false; - IsMarkedUseless = false; - } - } - } - else - { - IsMarkedGood = false; - IsMarkedUseless = false; - } - } - else if (Type == TrackerMapLocation.MapLocationType.SMDoor) - { - Item = Syncer.Tracker.ItemService.FirstOrDefault(mapLocation.Name); - Name = "Need " + (Item?.Name ?? "Keycard"); - } - - } - - private IWorldService WorldService => Syncer.WorldService; - - /// - /// Initializes a new instance of the class - /// with data to display a specific map location in the sub menu - /// - /// - /// - public TrackerMapLocationViewModel(Location location, TrackerLocationSyncer syncer) - { - Locations = new List() { location }; - Region = location.Region; - Syncer = syncer; - Name = $"Clear {location.Metadata.Name?[0] ?? location.Name}"; - } - - /// - /// The list of SMZ3 randomizer locations to look at to determine - /// progress - /// - public List Locations { get; set; } = new(); - - - /// - /// The list of locations underneath this one for the right click menu - /// - public List SubLocationModels - => Locations.Where(x => Syncer.WorldService.IsAvailable(x)) - .Select(x => new TrackerMapLocationViewModel(x, Syncer)) - .ToList(); - - /// - /// The X value of where to display this location on the map - /// - public double X { get; set; } - - /// - /// The Y value of where to display this location on the map - /// - public double Y { get; set; } - - /// - /// The size of the square/circle to display - /// - public double Size { get; set; } - - /// - /// The name of the location - /// - public string Name { get; set; } = ""; - - /// - /// The rewards for if this is not an actual location - /// - private IHasBoss? BossRegion { get; set; } - private IHasReward? RewardRegion { get; set; } - private Item? Item { get; set; } - - /// - /// The number of available/accessible locations here that have not been - /// cleared - /// - public int ClearableLocationsCount { get; set; } - - /// - /// The total number of all locations here that have been cleared - /// - public int UnclearedLocationsCount { get; set; } - - public int OutOfLogicLocationsCount { get; set; } - - public int ClearedLocationsCount { get; set; } - - public int RelevantLocationsCount { get; set; } - - public bool IsMarkedGood { get; set; } - - public bool IsMarkedUseless { get; set; } - - /// - /// Display the icon if there are available uncleared locations that - /// match the number of total uncleared locations - /// - public Visibility IconVisibility { get; set; } - - public Visibility MarkedVisibility => - IsMarkedGood || IsMarkedUseless ? Visibility.Visible : Visibility.Collapsed; - - private TrackerMapLocation.MapLocationType Type { get; set; } - - /// - /// Get the icon to display for the location - /// - public ImageSource IconImage - { - get - { - var image = "blank.png"; - - if (Type == TrackerMapLocation.MapLocationType.Boss) - { - var progression = Syncer.ItemService.GetProgression(Region); - var actualProgression = Syncer.ItemService.GetProgression(false); - if (BossRegion != null && BossRegion.Boss.State.Defeated != true && BossRegion.CanBeatBoss(progression)) - { - image = "boss.png"; - } - else if (RewardRegion != null && RewardRegion.Reward.State.Cleared != true) - { - var regionLocations = (IHasLocations)Region; - - // If the player can complete the region with the current actual progression - // or if they can access all locations in the dungeon (unless this is Castle Tower - // in Keysanity because it doesn't have a location for Aga himself) - if (RewardRegion.CanComplete(actualProgression) - || (regionLocations.Locations.All(x => x.IsAvailable(progression, true)) - && !(Region.Config.ZeldaKeysanity && RewardRegion is CastleTower))) - { - var dungeon = (IDungeon)Region; - image = dungeon.MarkedReward.GetDescription().ToLowerInvariant() + ".png"; - } - } - } - else if (Type == TrackerMapLocation.MapLocationType.Item) - { - if (ClearableLocationsCount > 0 && ClearableLocationsCount == UnclearedLocationsCount) - { - image = "accessible.png"; - } - else if (RelevantLocationsCount > 0 && RelevantLocationsCount + ClearableLocationsCount == UnclearedLocationsCount) - { - image = "relevant.png"; - } - else if (RelevantLocationsCount > 0 && ClearableLocationsCount == 0) - { - image = "partial_relevance.png"; - } - else if (ClearableLocationsCount > 0 && ClearableLocationsCount < UnclearedLocationsCount) - { - image = "partial.png"; - } - else if (ClearableLocationsCount == 0 && OutOfLogicLocationsCount > 0) - { - image = "outoflogic.png"; - } - } - else if (Type == TrackerMapLocation.MapLocationType.SMDoor) - { - if (Item is { State.TrackingState: 0 }) - { - image = DoorImage; - } - } - - IconVisibility = image == "blank.png" ? Visibility.Collapsed : Visibility.Visible; - - return new BitmapImage(new Uri(System.IO.Path.Combine(Sprite.SpritePath, "Maps", image))); - } - } - - - /// - /// Get the icon to display for the number of locations - /// - public ImageSource NumberImage - { - get - { - if (ClearableLocationsCount > 1) - { - return new BitmapImage(new Uri(System.IO.Path.Combine( - Sprite.SpritePath, "Marks", $"{Math.Min(9, ClearableLocationsCount)}.png"))); - - } - else - { - return new BitmapImage(new Uri(System.IO.Path.Combine( - Sprite.SpritePath, "Maps", "blank.png"))); - } - } - } - - public ImageSource MarkedImage - { - get - { - if (IsMarkedGood) - { - return new BitmapImage(new Uri(System.IO.Path.Combine( - Sprite.SpritePath, "Maps", "marked_good.png"))); - } - else - { - return new BitmapImage(new Uri(System.IO.Path.Combine( - Sprite.SpritePath, "Maps", "marked_useless.png"))); - } - } - } - - - /// - /// The visual style for the right click menu - /// - public Style? ContextMenuStyle => s_contextMenuStyle; - - /// - /// The region for this location on the map - /// - public Region Region { get; } - - /// - /// Get the tag to use for the location. Use the region for dungeons - /// and the list of locations for all other places - /// - public object? Tag - { - get - { - if (Type == TrackerMapLocation.MapLocationType.Boss) - { - return BossRegion == null ? RewardRegion : BossRegion; - } - else if (Type == TrackerMapLocation.MapLocationType.SMDoor) - { - return Item; - } - else if (Type == TrackerMapLocation.MapLocationType.Item) - { - return Region.Name == Name ? Region : Locations.Where(x => Syncer.WorldService.IsAvailable(x)).ToList(); - } - return null; - } - } - - private string DoorImage => Item?.Type switch - { - ItemType.CardCrateriaL1 => "door1.png", - ItemType.CardCrateriaL2 => "door2.png", - ItemType.CardCrateriaBoss => "doorb.png", - ItemType.CardBrinstarL1 => "door1.png", - ItemType.CardBrinstarL2 => "door2.png", - ItemType.CardBrinstarBoss => "doorb.png", - ItemType.CardWreckedShipL1 => "door1.png", - ItemType.CardWreckedShipBoss => "doorb.png", - ItemType.CardMaridiaL1 => "door1.png", - ItemType.CardMaridiaL2 => "door2.png", - ItemType.CardMaridiaBoss => "doorb.png", - ItemType.CardNorfairL1 => "door1.png", - ItemType.CardNorfairL2 => "door2.png", - ItemType.CardNorfairBoss => "doorb.png", - ItemType.CardLowerNorfairL1 => "door1.png", - ItemType.CardLowerNorfairBoss => "doorb.png", - _ => "blank.png" - }; - - /// - /// LocationSyncer to use for keeping locations in sync between the - /// locations list and map - /// - protected TrackerLocationSyncer Syncer { get; } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/TrackerMapViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/TrackerMapViewModel.cs deleted file mode 100644 index 9d888c799..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/TrackerMapViewModel.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Windows; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; -using TrackerCouncil.Smz3.Data.Options; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -/// -/// ViewModel to use for displaying the Tracker Map Window -/// -public class TrackerMapViewModel : INotifyPropertyChanged -{ - private readonly bool _isDesign; - - /// - /// The current map the user has selected - /// - private TrackerMap? _currentMap; - - /// - /// The size of the parent grid of the canvas to use for resizing the - /// canvas - /// - private Size _gridSize; - - private TrackerLocationSyncer? _syncer; - - /// - /// Initializes a new instance of the class - /// with data to display a specific location on the map and its progress - /// - public TrackerMapViewModel() - { - _isDesign = DesignerProperties.GetIsInDesignMode(new DependencyObject()); - } - - /// - /// Event for when one of the values has been updated to notify the - /// window to refresh - /// - public event PropertyChangedEventHandler? PropertyChanged; - - /// - /// List of all map names for displaying in the dropdown - /// - public List MapNames { get; set; } = new(); - - /// - /// The current map the user has selected - /// - public TrackerMap? CurrentMap - { - get => _currentMap; - set - { - _currentMap = value; - OnPropertyChanged(); - } - } - - /// - /// The size of the parent grid of the canvas to use for resizing the - /// canvas - /// - public Size GridSize - { - get => _gridSize; - set - { - _gridSize = value; - OnPropertyChanged(); - } - } - - /// - /// Calculated size of the map canvas to maintain the image aspect ratio - /// - public Size MapSize - { - get - { - if (_isDesign) - return new Size(400, 400); - - if (CurrentMap == null) - return new Size(0, 0); - - double imageWidth = CurrentMap.Width; - double imageHeight = CurrentMap.Height; - double ratio = imageWidth / imageHeight; - - double gridWidth = GridSize.Width; - double gridHeight = GridSize.Height; - - if (gridWidth / ratio > gridHeight) - { - return new Size(gridHeight * ratio, gridHeight); - } - else - { - return new Size(gridWidth, gridWidth / ratio); - } - } - } - - /// - /// The background image to display in the canvas of the map - /// - public ImageSource CanvasImage - { - get - { - if (_isDesign || CurrentMap == null) - { - return new BitmapImage(new Uri(System.IO.Path.Combine( - Sprite.SpritePath, "Maps", "lttp_lightworld.png"))); - } - - return new BitmapImage(new Uri(System.IO.Path.Combine( - Sprite.SpritePath, "Maps", CurrentMap.Image))); - } - } - - /// - /// TrackerLocationSyncer used to keep items synced between both - /// locations and map windows - /// - public TrackerLocationSyncer Syncer - { - get => _syncer ?? throw new InvalidOperationException("Syncer not set for TrackerLocationSyncer"); - set - { - _syncer = value; - } - } - - /// - /// A list of all tracker map location view models to display on the map - /// - public List TrackerMapLocations - { - get - { - if (_currentMap == null) return new List(); - - // This is used to determine if there are invalid locations on - // the map - if (Debugger.IsAttached) - { - var test = _currentMap.FullLocations - .Where(mapLoc => Syncer.WorldService.AllLocations().Where(loc => mapLoc.MatchesSMZ3Location(loc)).Count() == 0) - .Select(mapLoc => mapLoc.Name) - .ToList(); - - if (test.Count > 0) - { - Console.WriteLine(test); - } - } - - return _currentMap.FullLocations - .Where(x => x.Type != TrackerMapLocation.MapLocationType.SMDoor || Syncer.World.Config.MetroidKeysanity) - .Select(mapLoc => new TrackerMapLocationViewModel(mapLoc, - Syncer, - MapSize.Width / _currentMap.Width)) - .ToList(); - } - } - - /// - /// Call to execute the PropertyChanged event to notify the window to - /// refresh - /// - public void OnPropertyChanged() - { - PropertyChanged?.Invoke(this, new("")); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/TrackerViewModel.cs b/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/TrackerViewModel.cs deleted file mode 100644 index baeed29c9..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/ViewModels/TrackerViewModel.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.ComponentModel; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Windows; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.WorldData; -using TrackerCouncil.Smz3.Shared.Enums; -using TrackerCouncil.Smz3.Tracking.Services; - -namespace TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -public class TrackerViewModel : INotifyPropertyChanged -{ - private readonly IUIService _uiService; - private readonly bool _isDesign; - private readonly TrackerLocationSyncer _syncer; - private RegionFilter _filter; - - public TrackerViewModel(TrackerLocationSyncer syncer, IUIService uiService) - { - _isDesign = DesignerProperties.GetIsInDesignMode(new DependencyObject()); - _syncer = syncer; - _syncer.TrackedLocationUpdated += (_, _) => OnPropertyChanged(nameof(TopLocations)); - _syncer.MarkedLocationUpdated += (_, _) => OnPropertyChanged(nameof(MarkedLocations)); - _syncer.HintTileUpdated += (_, _) => OnPropertyChanged(nameof(HintTiles)); - _uiService = uiService; - } - - public event PropertyChangedEventHandler? PropertyChanged; - - public RegionFilter Filter - { - get => _filter; - set - { - if (value != _filter) - { - _filter = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(TopLocations)); - } - } - } - - public bool ShowOutOfLogicLocations - { - get => _syncer.ShowOutOfLogicLocations; - set => _syncer.ShowOutOfLogicLocations = value; - } - - public IEnumerable HintTiles - { - get - { - if (_isDesign) - return new List(); - - return _syncer.WorldService.ViewedHintTiles - .Where(x => x.Locations?.Count() > 1) - .Select(x => new HintTileViewModel(x)); - } - } - - public IEnumerable MarkedLocations - { - get - { - if (_isDesign) - return GetDummyMarkedLocations(); - - var viewModels = new List(); - - foreach (var markedLocation in _syncer.WorldService.MarkedLocations()) - { - var markedItemType = markedLocation.State.MarkedItem ?? ItemType.Nothing; - if (markedItemType == ItemType.Nothing) continue; - var item = _syncer.Tracker.ItemService.FirstOrDefault(markedItemType); - if (item == null) continue; - viewModels.Add(new MarkedLocationViewModel(markedLocation, item, _uiService.GetSpritePath(item), _syncer)); - } - - return viewModels; - } - } - - public IEnumerable TopLocations - { - get - { - return _syncer.WorldService.Locations(unclearedOnly: true, outOfLogic: ShowOutOfLogicLocations, assumeKeys: true, sortByTopRegion: true, regionFilter: Filter) - .Select(x => new LocationViewModel(x, _syncer)) - .ToImmutableList(); - } - } - - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - => PropertyChanged?.Invoke(this, new(propertyName)); - - private IEnumerable GetDummyMarkedLocations() - { - var world = new World(new Config(), "", 0, ""); - var item = new Item(ItemType.XRay, world); - yield return new MarkedLocationViewModel( - _syncer.World.LightWorldSouth.Library, - item, - null, - _syncer); - - item = new Item(ItemType.XRay, world); - yield return new MarkedLocationViewModel( - _syncer.World.FindLocation(LocationId.KingZora), - item, - null, - _syncer); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AboutWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AboutWindow.xaml deleted file mode 100644 index de0f3fb7b..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AboutWindow.xaml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Created by Vivelin, MattEqualsCoder, and CPColin - - Based on the original - Super Metroid & A Link to the Past Crossover Randomizer - by RebelusQuo, tewtal and others. - - - Special thanks: - - • - Diabetus - and - PinkKittyRose - for streaming so much with the Cas’ Randomizer - - - • Fragger for the original sprite work and the original SMZ3 EmoTracker - customizations - - - • PaddyCo and others for their original sprite work - - - • Everyone from the BCU who helped test the Cas’ Randomizer and Tracker - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AboutWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AboutWindow.xaml.cs deleted file mode 100644 index 894bd46bf..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AboutWindow.xaml.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Diagnostics; -using System.Reflection; -using System.Windows; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -/// -/// Interaction logic for AboutWindow.xaml -/// -public partial class AboutWindow : Window -{ - public AboutWindow() - { - var version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location); - Version = version.ProductVersion ?? ""; - - if (Version.Contains('+')) - { - Version = Version[..Version.IndexOf('+')]; - } - - InitializeComponent(); - } - - public string Version { get; } - - private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) - { - Process.Start(new ProcessStartInfo - { - FileName = e.Uri.ToString(), - UseShellExecute = true - }); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AutotrackerWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AutotrackerWindow.xaml deleted file mode 100644 index cd2345b31..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AutotrackerWindow.xaml +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - Auto tracker will automatically track cleared locations and picked up items so that you don't have to! - - - - Supported Connection Modes - - - - • Lua Script - - - - Supported Emulators: - - - - • Snes9x-rr v1.60 - (Recommended) - • BizHawk 2.8 - (BSNES Core Only) - - - - • QUSB2SNES - - - - Older versions or other emulators may work, but they are currently untested. - - - - Enabling Auto Tracking - - - - After you have opened the Tracker window, right click on the link icon ( or ) in the status bar to enable auto tracker in the desired connection mode. In the settings you can also set auto tracking to automatically be enabled. - - - - Enabling Lua Script in Emulator - - - - 1. Locate the auto tracker scripts folder. You can find it by right clicking on the link icon and selecting "Open Auto Tracker Scripts Folder." - - - - 2. In your emulator, open the Lua scripting window. - - - Snes9x-rr: Go to File -> Lua Scripting -> New Lua Script Window. - BizHawk: Go to Tools -> Lua Console. - - - - 3. Open the autotracker.lua file under the appropriate folder for your emulator version. - - - - Auto Tracker Icons - - - - The link icon color changes based on the following: - - - - - - Enabled and waiting for a connection with the emulator - - - - - - - Connected successfully with the emulator - - - - - - - Disabled - - - - - Lua Script Errors - - - - If you have an error when running the lua script, then try one of the following: - - - - • Make sure you're using the correct autotracker.lua script in the folder matching your emulator. The 64bit version of snes9x-rr will need the auto tracker script within snex9xrr_64bit and 32bit will need snes9xrr_32bit. If you're not sure which version you are, try the one in the opposite folder. - - - - • If you're using BizHawk, make sure you are set to use luainterface instead nlua. - - - - • Try copying the socket.dll file within the appropriate folder for your emulator and place it within the base directory of your emulator. - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AutotrackerWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AutotrackerWindow.xaml.cs deleted file mode 100644 index 7135977e5..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/AutotrackerWindow.xaml.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Diagnostics; -using System.Windows; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -/// -/// Interaction logic for AutotrackerWindow.xaml -/// -public partial class AutoTrackerWindow : Window -{ - public AutoTrackerWindow() - { - InitializeComponent(); - } - - private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) - { - Process.Start(new ProcessStartInfo - { - FileName = e.Uri.ToString(), - UseShellExecute = true - }); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/GenerationSettingsWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/GenerationSettingsWindow.xaml deleted file mode 100644 index 99eaae4b9..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/GenerationSettingsWindow.xaml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/GenerationSettingsWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/GenerationSettingsWindow.xaml.cs deleted file mode 100644 index 550c2685a..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/GenerationSettingsWindow.xaml.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Windows; -using Microsoft.Extensions.DependencyInjection; -using TrackerCouncil.Smz3.Data.Interfaces; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.Services; -using TrackerCouncil.Smz3.Data.ViewModels; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -public partial class GenerationSettingsWindow : Window -{ - private GenerationSettingsWindowService _service; - private IServiceProvider _serviceProvider; - private MsuUiService _msuUiService; - private GenerationWindowViewModel _model = new(); - - public GenerationSettingsWindow(GenerationSettingsWindowService service, IServiceProvider serviceProvider, MsuUiService msuUiService, OptionsFactory optionsFactory) - { - _service = service; - _serviceProvider = serviceProvider; - _msuUiService = msuUiService; - optionsFactory.Create(); - InitializeComponent(); - DataContext = _model = _service.GetViewModel(); - BasicPanel.SetServices(serviceProvider, service); - BasicPanel.DataContext = DataContext; - ItemPanel.DataContext = DataContext; - } - - public void SetPlandoConfig(PlandoConfig config) - { - _service.LoadPlando(config); - } - - public void SetMultiplayerEnabled() - { - _model.IsMultiplayer = true; - } - - private void GenerationSettingsWindow_OnLoaded(object sender, RoutedEventArgs e) - { - _msuUiService.LookupMsus(); - } - - private void GenerateStatsMenuItem_OnClick(object sender, RoutedEventArgs e) - { - _service.SaveSettings(); - var statGenerator = _serviceProvider.GetRequiredService(); - var statWindow = new ProgressDialog(this, "Generating stats test"); - - statGenerator.StatProgressUpdated += (o, args) => - { - statWindow.Report(args.Current / (double)args.Total); - }; - - statGenerator.StatsCompleted += (o, args) => - { - statWindow.Close(); - MessageBox.Show(Window.GetWindow(this)!, args.Message, "SMZ3 Cas’ Randomizer", MessageBoxButton.OK); - }; - - statGenerator.GenerateStatsAsync(5, _service.GetConfig(), - statWindow.CancellationToken); - statWindow.StartTimer(); - statWindow.ShowDialog(); - } - - private void SavePresetMenuItem_OnClick(object sender, RoutedEventArgs e) - { - var window = new RandomizerPresetWindow(); - var result = window.ShowDialog(); - if (result == true) - { - if (!_service.CreatePreset(window.PresetName, out var error)) - { - ShowErrorMessageBox(error ?? "Could not create preset"); - } - } - } - - private void GenerateMenuButton_OnClick(object sender, RoutedEventArgs e) - { - if (GenerateMenuButton.ContextMenu == null) return; - GenerateMenuButton.ContextMenu.IsOpen = true; - } - - private async void GenerateGameButton_OnClick(object sender, RoutedEventArgs e) - { - await GenerateRom(); - } - - private async Task GenerateRom() - { - _service.SaveSettings(); - - if (_model.IsMultiplayer) - { - DialogResult = true; - Close(); - return; - } - - var generatedRom = await _service.GenerateRom(); - - if (!string.IsNullOrEmpty(generatedRom.GenerationError)) - { - MessageBox.Show(this, generatedRom.GenerationError, "SMZ3 Cas' Randomizer", MessageBoxButton.OK, - MessageBoxImage.Error); - } - - if (generatedRom.Rom != null) - { - DialogResult = true; - Close(); - } - } - - private void CancelButton_OnClick(object sender, RoutedEventArgs e) - { - DialogResult = false; - Close(); - } - - private MessageBoxResult ShowErrorMessageBox(string message) - { - return MessageBox.Show(Window.GetWindow(this)!, message, "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Error); - } -} - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MsuTrackWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MsuTrackWindow.xaml deleted file mode 100644 index b1df3c36e..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MsuTrackWindow.xaml +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MsuTrackWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MsuTrackWindow.xaml.cs deleted file mode 100644 index 1d409ff0b..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MsuTrackWindow.xaml.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Windows; -using MSURandomizerUI.Controls; -using TrackerCouncil.Smz3.Data.Options; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -public partial class MsuTrackWindow : Window, IDisposable -{ - private MsuCurrentPlayingTrackControl? _panel; - private RandomizerOptions? _options; - - public MsuTrackWindow() - { - InitializeComponent(); - Smz3.UI.Legacy.App.RestoreWindowPositionAndSize(this); - } - - public void Init(MsuCurrentPlayingTrackControl panel, RandomizerOptions options) - { - _panel = panel; - panel.IsEmbedded = true; - panel.TrackDisplayFormat = options.GeneralOptions.TrackDisplayFormat; - MainDockPanel.Children.Add(panel); - _options = options; - _options.GeneralOptions.DisplayMsuTrackWindow = true; - _options.Save(); - } - - public void Close(bool isShuttingDown) - { - if (!isShuttingDown && _options != null) - { - _options.GeneralOptions.DisplayMsuTrackWindow = false; - _options.Save(); - } - Close(); - } - - public void Dispose() - { - _panel?.Dispose(); - } -} - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerConnectWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerConnectWindow.xaml deleted file mode 100644 index de47b36a7..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerConnectWindow.xaml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerConnectWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerConnectWindow.xaml.cs deleted file mode 100644 index 52ef07d3b..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerConnectWindow.xaml.cs +++ /dev/null @@ -1,268 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using System.Windows; -using System.Windows.Controls; -using Microsoft.Extensions.Logging; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Multiplayer.Client; -using TrackerCouncil.Smz3.Shared; -using TrackerCouncil.Smz3.Shared.Multiplayer; -using TrackerCouncil.Smz3.Tracking.Services; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -/// -/// Interaction logic for MultiplayerConnectWindow.xaml -/// Window for creating a new multiplayer game or connecting to a previously started one -/// -public sealed partial class MultiplayerConnectWindow : Window, INotifyPropertyChanged -{ - private static readonly Regex s_illegalCharacters = new(@"[^A-Z0-9\-]", RegexOptions.IgnoreCase); - - private static readonly List s_defaultServers = new() - { - "https://smz3.celestialrealm.net", -#if DEBUG - "http://192.168.50.100:5000", - "http://localhost:5000" -#endif - }; - - private readonly MultiplayerClientService _multiplayerClientService; - private readonly ILogger _logger; - private readonly string _version; - private readonly ICommunicator _communicator; - private string _previousError = ""; - - public MultiplayerConnectWindow(MultiplayerClientService multiplayerClientService, ILogger logger, ICommunicator communicator) - { - _logger = logger; - _multiplayerClientService = multiplayerClientService; - InitializeComponent(); - DataContext = this; - - _version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion ?? ""; - if (_version.Contains('+')) - { - _version = _version[.._version.IndexOf('+')]; - } - - _multiplayerClientService.Connected += MultiplayerClientServiceConnected; - _multiplayerClientService.Error += MultiplayerClientServiceError; - _multiplayerClientService.GameCreated += MultiplayerClientServiceGameJoined; - _multiplayerClientService.GameJoined += MultiplayerClientServiceGameJoined; - - _communicator = communicator; - } - - private void MultiplayerClientServiceError(string error, Exception? exception) - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(() => MultiplayerClientServiceError(error, exception)); - return; - } - DisplayError(error); - } - - private async void MultiplayerClientServiceConnected() - { - if (IsCreatingGame) - { - _logger.LogInformation("Connected to server successfully. Creating new game."); - if (Options != null) Options.MultiplayerUrl = Url; - await _multiplayerClientService.CreateGame(DisplayName, PhoneticName, MultiplayerGameType, _version, AsyncGame, SendItemsOnComplete, DeathLink); - } - else - { - _logger.LogInformation("Connected to Server successfully. Joining game."); - await _multiplayerClientService.JoinGame(GameGuid, DisplayName, PhoneticName, _version); - } - } - - private void MultiplayerClientServiceGameJoined() - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(MultiplayerClientServiceGameJoined); - return; - } - DialogResult = true; - Close(); - } - - protected override void OnClosing(CancelEventArgs e) - { - base.OnClosing(e); - _multiplayerClientService.Connected -= MultiplayerClientServiceConnected; - _multiplayerClientService.Error -= MultiplayerClientServiceError; - _multiplayerClientService.GameJoined -= MultiplayerClientServiceGameJoined; - _multiplayerClientService.GameCreated -= MultiplayerClientServiceGameJoined; - } - - public bool IsCreatingGame { get; set; } - public bool IsJoiningGame { get; set; } - public bool IsConnecting { get; set; } - public string UrlLabelText => IsCreatingGame ? "Server Url:" : "Game Url:"; - public bool CanEnterInput => !IsConnecting; - public bool CanEnterGameMode => !IsConnecting && IsCreatingGame; - public bool CanPressButton => PlayerNameTextBox.Text.Length > 0 && Url.Length > 0; - public string StatusText => IsConnecting ? "Connecting..." : ""; - public RandomizerOptions? Options { get; set; } - public Visibility ServerListVisibility => IsCreatingGame ? Visibility.Visible : Visibility.Collapsed; - public bool AsyncGame { get; set; } - public bool SendItemsOnComplete { get; set; } - public bool DeathLink { get; set; } - - private string _url = ""; - public string Url - { - get - { - return _url; - } - set - { - _url = value; - OnPropertyChanged(nameof(Url)); - OnPropertyChanged(nameof(CanPressButton)); - } - } - public string DisplayName => PlayerNameTextBox.Text; - public string PhoneticName { get; set; } = ""; - public MultiplayerGameType MultiplayerGameType { get; set; } - - public string ServerUrl => Url.SubstringBeforeCharacter('?') ?? ServerUrlTextBox.Text; - public string GameGuid => Url.SubstringAfterCharacter('=') ?? ""; - - public string GameButtonText - { - get - { - if (IsConnecting) return "Cancel Connecting"; - return IsJoiningGame? "Join Game" : "Create Game"; - } - } - - private async void CancelButton_Click(object sender, RoutedEventArgs e) - { - _multiplayerClientService.Error -= MultiplayerClientServiceError; - await _multiplayerClientService.Disconnect(); - IsConnecting = false; - Close(); - } - - private async void NewGameButton_Click(object sender, RoutedEventArgs e) - { - if (s_illegalCharacters.IsMatch(PlayerNameTextBox.Text)) - { - DisplayError("Player names can only contains letters, numbers, hyphens, and underscores."); - return; - } - - if (IsConnecting) - { - await _multiplayerClientService.Disconnect(); - IsConnecting = false; - OnPropertyChanged(); - } - else - { - IsConnecting = true; - OnPropertyChanged(); - await _multiplayerClientService.Connect(ServerUrl); - } - } - - public event PropertyChangedEventHandler? PropertyChanged; - - private void OnPropertyChanged(string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - private void PlayerNameTextBox_OnTextChanged(object sender, TextChangedEventArgs e) - { - OnPropertyChanged(); - } - - private void DisplayError(string message) - { - if (message == _previousError) - { - return; - } - - _previousError = message; - if (Dispatcher.CheckAccess()) - { - MessageBox.Show(this, message, "SMZ3 Cas' Randomizer", MessageBoxButton.OK, MessageBoxImage.Error); - if (IsConnecting) - { - IsConnecting = false; - OnPropertyChanged(); - } - } - else - { - Dispatcher.Invoke(() => - { - DisplayError(message); - }); - } - } - - private void MultiplayerConnectWindow_OnLoaded(object sender, RoutedEventArgs e) - { - if (IsCreatingGame) - { - if (!string.IsNullOrEmpty(Options?.MultiplayerUrl)) - Url = Options.MultiplayerUrl; - else - Url = s_defaultServers.First(); - - if (ServerListButton.ContextMenu != null) - { - foreach (var url in s_defaultServers) - { - var menuItem = new MenuItem { Header = url }; - menuItem.Click += ServerMenuItemClick; - ServerListButton.ContextMenu.Items.Add(menuItem); - } - - } - OnPropertyChanged(nameof(Url)); - OnPropertyChanged(nameof(CanPressButton)); - } - -#if DEBUG - // Added this so that we can use this box for easily testing how tracker messages sound - PhoneticNameTextBox.MaxLength = 1000; -#endif - } - - private void ServerMenuItemClick(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem menuItem) return; - Url = menuItem.Header as string ?? s_defaultServers.First(); - OnPropertyChanged(nameof(Url)); - } - - private void PhoneticNameTestButton_OnClick(object sender, RoutedEventArgs e) - { - var nameToSay = string.IsNullOrEmpty(PhoneticName) ? DisplayName : PhoneticName; - _communicator.Say(new SpeechRequest(nameToSay)); - } - - private void ServerListButton_OnClick(object sender, RoutedEventArgs e) - { - if (ServerListButton.ContextMenu == null) return; - ServerListButton.ContextMenu.DataContext = ServerListButton.DataContext; - ServerListButton.ContextMenu.IsOpen = true; - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerStatusWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerStatusWindow.xaml deleted file mode 100644 index 3cf30cd08..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerStatusWindow.xaml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - Game Url: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerStatusWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerStatusWindow.xaml.cs deleted file mode 100644 index 1649b50f4..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/MultiplayerStatusWindow.xaml.cs +++ /dev/null @@ -1,361 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using TrackerCouncil.Smz3.Data.Interfaces; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.Services; -using TrackerCouncil.Smz3.Multiplayer.Client; -using TrackerCouncil.Smz3.Shared.Enums; -using TrackerCouncil.Smz3.Shared.Models; -using TrackerCouncil.Smz3.Shared.Multiplayer; -using TrackerCouncil.Smz3.UI.Legacy.Controls; -using TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -/// -/// Interaction logic for MultiplayerStatusWindow.xaml -/// This window lists all of the players and allows players to update their configs and forfeit -/// -public partial class MultiplayerStatusWindow : Window -{ - private readonly MultiplayerClientService _multiplayerClientService; - private readonly MultiplayerGameService _multiplayerGameService; - private readonly IRomGenerationService _romGenerationService; - private readonly SpriteService _spriteService; - private string _previousError = ""; - - public MultiplayerStatusWindow(MultiplayerClientService multiplayerClientService, - MultiplayerGameService multiplayerGameService, IRomGenerationService romGenerationService, SpriteService spriteService) - { - _multiplayerClientService = multiplayerClientService; - _multiplayerGameService = multiplayerGameService; - _romGenerationService = romGenerationService; - _spriteService = spriteService; - DataContext = Model; - InitializeComponent(); - - _multiplayerClientService.Error += MultiplayerClientServiceError; - _multiplayerClientService.GameRejoined += MultiplayerClientServiceOnGameRejoined; - _multiplayerClientService.PlayerForfeited += MultiplayerClientServiceOnPlayerForfeit; - _multiplayerClientService.PlayerListUpdated += MultiplayerClientServiceOnPlayerListUpdated; - _multiplayerClientService.PlayerUpdated += MultiplayerClientServiceOnPlayerSync; - _multiplayerClientService.Connected += MultiplayerClientServiceOnConnected; - _multiplayerClientService.ConnectionClosed += MultiplayerClientServiceOnConnectionClosed; - _multiplayerClientService.GameStateUpdated += MultiplayerClientServiceOnGameStateUpdated; - _multiplayerClientService.GameStarted += MultiplayerClientServiceOnGameStarted; - } - - public MultiplayerStatusViewModel Model { get; set; } = new(); - public MultiRomListPanel ParentPanel { get; set; } = null!; - public MultiplayerGameDetails? MultiplayerGameDetails { get; set; } - - private async void MultiplayerClientServiceOnGameStarted(List playerGenerationData) - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(() => MultiplayerClientServiceOnGameStarted(playerGenerationData)); - return; - } - - // Regenerate the seed using all of the data for the players that came from the server - var seedData = _multiplayerGameService.RegenerateSeed(playerGenerationData, out var error); - if (!string.IsNullOrEmpty(error)) - { - DisplayError(error); - return; - } - - var rom = await _romGenerationService.GeneratePreSeededRomAsync(ParentPanel.Options, seedData!, _multiplayerClientService.DatabaseGameDetails!); - if (rom.Rom != null) - { - Model.GeneratedRom = rom.Rom; - DisplayMessage("Rom successfully generated.\nTo begin, launch tracker and the rom, then start auto tracking."); - } - } - - private void MultiplayerClientServiceOnGameStateUpdated() - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(MultiplayerClientServiceOnGameStateUpdated); - return; - } - Model.GameStatus = _multiplayerClientService.GameStatus; - } - - private async void MultiplayerClientServiceOnPlayerForfeit(MultiplayerPlayerState playerState, bool isLocalPlayer) - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(() => MultiplayerClientServiceOnPlayerForfeit(playerState, isLocalPlayer)); - return; - } - if (!isLocalPlayer || _multiplayerClientService.GameStatus != MultiplayerGameStatus.Created) return; - await _multiplayerClientService.Disconnect(); - Close(); - } - - private void MultiplayerClientServiceOnGameRejoined() - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(MultiplayerClientServiceOnGameRejoined); - return; - } - Model.IsConnected = true; - Model.GameUrl = _multiplayerClientService.GameUrl ?? ""; - Model.GameStatus = _multiplayerClientService.GameStatus ?? MultiplayerGameStatus.Created; - UpdatePlayerList(); - Model.Refresh(); - } - - private async void MultiplayerClientServiceOnConnected() - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(MultiplayerClientServiceOnConnected); - return; - } - await _multiplayerClientService.RejoinGame(); - } - - private void MultiplayerClientServiceOnConnectionClosed(string message, Exception? exception) - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(() => MultiplayerClientServiceOnConnectionClosed(message, exception)); - return; - } - Model.IsConnected = false; - } - - protected override void OnClosing(CancelEventArgs e) - { - base.OnClosing(e); - _multiplayerClientService.Error -= MultiplayerClientServiceError; - _multiplayerClientService.GameRejoined -= MultiplayerClientServiceOnGameRejoined; - _multiplayerClientService.PlayerListUpdated -= MultiplayerClientServiceOnPlayerListUpdated; - _multiplayerClientService.PlayerUpdated -= MultiplayerClientServiceOnPlayerSync; - _multiplayerClientService.Connected -= MultiplayerClientServiceOnConnected; - _multiplayerClientService.ConnectionClosed -= MultiplayerClientServiceOnConnectionClosed; - _multiplayerClientService.GameStateUpdated -= MultiplayerClientServiceOnGameStateUpdated; - _multiplayerClientService.GameStarted -= MultiplayerClientServiceOnGameStarted; - _multiplayerClientService.PlayerForfeited -= MultiplayerClientServiceOnPlayerForfeit; - Task.Run(async () => await _multiplayerClientService.Disconnect()); - ParentPanel.CloseTracker(); - ParentPanel.UpdateList(); - } - - private void MultiplayerClientServiceError(string error, Exception? exception) - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(() => MultiplayerClientServiceError(error, exception)); - return; - } - DisplayError(error); - } - - private void MultiplayerClientServiceOnPlayerListUpdated() - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(MultiplayerClientServiceOnPlayerListUpdated); - return; - } - UpdatePlayerList(); - } - - private void MultiplayerClientServiceOnPlayerSync(MultiplayerPlayerState state, MultiplayerPlayerState? previousState, bool isLocalPlayer) - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(() => MultiplayerClientServiceOnPlayerSync(state, previousState, isLocalPlayer)); - return; - } - Model.UpdatePlayer(state, _multiplayerClientService.LocalPlayer); - CheckPlayerConfigs(); - } - - protected void UpdatePlayerList() - { - if (!Dispatcher.CheckAccess()) - { - Dispatcher.Invoke(UpdatePlayerList); - return; - } - Model.UpdateList(_multiplayerClientService.Players ?? new List(), _multiplayerClientService.LocalPlayer); - CheckPlayerConfigs(); - } - - protected void CheckPlayerConfigs() - { - if (_multiplayerClientService.GameStatus == MultiplayerGameStatus.Created && _multiplayerClientService.LocalPlayer?.IsAdmin == true) - { - Model.AllPlayersSubmittedConfigs = - _multiplayerClientService.Players?.All(x => x.Config != null) ?? false; - } - } - - protected async Task ShowGenerateRomWindow() - { - if (ParentPanel.ShowGenerateRomWindow(null, true) != true) return; - var config = ParentPanel.Options.ToConfig(); - config.Seed = ""; // Not currently supported in multiplayer - config.SettingsString = ""; // Not currently supported in multiplayer - config.PlayerGuid = _multiplayerClientService.CurrentPlayerGuid!; - config.PlayerName = _multiplayerClientService.LocalPlayer!.PlayerName; - config.PhoneticName = _multiplayerClientService.LocalPlayer!.PhoneticName; - config.Race = false; // Not currently supported in multiplayer - config.ItemPlacementRule = ItemPlacementRule.Anywhere; // Not currently supported in multiplayer - await _multiplayerClientService.SubmitConfig(Config.ToConfigString(config)); - } - - private async void UpdateConfigButton_Click(object sender, RoutedEventArgs e) - { - await ShowGenerateRomWindow(); - } - - private async void StartGameButton_Click(object sender, RoutedEventArgs e) - { - if (_multiplayerClientService.Players == null) - { - DisplayError("No players found to start the game."); - } - - await _multiplayerClientService.UpdateGameStatus(MultiplayerGameStatus.Generating); - - var error = await _multiplayerGameService.GenerateSeed(); - - // If an error happened, set it back to being to the initial state - if (error != null) - { - DisplayError(error); - await _multiplayerClientService.UpdateGameStatus(MultiplayerGameStatus.Created); - } - } - - private void OpenTrackerButton_Click(object sender, RoutedEventArgs e) - { - if (Model.GeneratedRom != null) ParentPanel.QuickLaunchRom(Model.GeneratedRom!); - } - - private async void ReconnectButton_Click(object sender, RoutedEventArgs e) - { - await _multiplayerClientService.Reconnect(); - } - - private async void ForfeitButton_OnClick(object sender, RoutedEventArgs e) - { - if (sender is not Button { Tag: MultiplayerPlayerState state }) - return; - await _multiplayerClientService.ForfeitPlayerGame(state.Guid); - } - - private void CopyUrlButton_OnClick(object sender, RoutedEventArgs e) - { - ParentPanel.CopyTextToClipboard(_multiplayerClientService.GameUrl ?? ""); - } - - private MessageBoxResult? DisplayError(string message) - { - if (message == _previousError) - { - return null; - } - - _previousError = message; - return DisplayMessage(message, MessageBoxButton.OK, MessageBoxImage.Error); - } - - private MessageBoxResult DisplayMessage(string message, MessageBoxButton buttons = MessageBoxButton.OK, MessageBoxImage image = MessageBoxImage.None) - { - if (Dispatcher.CheckAccess()) - { - return MessageBox.Show(this, message , "SMZ3 Cas' Randomizer", buttons, image); - } - else - { - var output = MessageBoxResult.OK; - Dispatcher.Invoke(() => - { - output = DisplayMessage(message, buttons, image); - }); - return output; - } - } - - private async void MultiplayerStatusWindow_OnLoaded(object sender, RoutedEventArgs e) - { - if (!_multiplayerClientService.IsConnected && MultiplayerGameDetails != null) - { - Model.GeneratedRom = MultiplayerGameDetails.GeneratedRom; - await _multiplayerClientService.Reconnect(MultiplayerGameDetails); - } - else if (_multiplayerClientService.IsConnected) - { - MultiplayerClientServiceOnGameRejoined(); - } - } - - /// - /// Right click menu to play a rom - /// - /// - /// - private void PlayMenuItem_Click(object sender, RoutedEventArgs e) - { - if (Model.GeneratedRom != null) - ParentPanel.LaunchRom(Model.GeneratedRom); - } - - /// - /// Right click menu to open the folder for a rom - /// - /// - /// - private void OpenFolderMenuItem_Click(object sender, RoutedEventArgs e) - { - if (Model.GeneratedRom != null) - ParentPanel.OpenFolder(Model.GeneratedRom); - } - - /// - /// Menu item for opening the tracker for a rom - /// - /// - /// - private void OpenTrackerMenuItem_Click(object sender, RoutedEventArgs e) - { - if (Model.GeneratedRom != null) - ParentPanel.LaunchTracker(Model.GeneratedRom); - } - - /// - /// Menu item for viewing the spoiler log for a rom - /// - /// - /// - private void ViewSpoilerMenuItem_Click(object sender, RoutedEventArgs e) - { - if (Model.GeneratedRom != null) - ParentPanel.OpenSpoilerLog(Model.GeneratedRom); - } - - /// - /// Opens the launch options drop down - /// - /// - /// - private void LaunchOptions_OnClick(object sender, RoutedEventArgs e) - { - if (LaunchOptions.ContextMenu == null) return; - LaunchOptions.ContextMenu.DataContext = LaunchOptions.DataContext; - LaunchOptions.ContextMenu.IsOpen = true; - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/OptionsWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/OptionsWindow.xaml deleted file mode 100644 index fa9ad52f1..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/OptionsWindow.xaml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/OptionsWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/OptionsWindow.xaml.cs deleted file mode 100644 index a2209adce..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/OptionsWindow.xaml.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Windows; -using TrackerCouncil.Smz3.Data.Services; -using TrackerCouncil.Smz3.Data.ViewModels; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -public partial class OptionsWindow : Window -{ - private OptionsWindowService _service; - private OptionsWindowViewModel _model; - private SpriteDownloaderWindow? _spriteDownloaderWindow; - - public OptionsWindow(OptionsWindowService service) - { - _service = service; - InitializeComponent(); - DataContext = _model = _service.GetViewModel(); - - service.TwitchError += (sender, handler) => - { - MessageBox.Show(this, handler.Error, "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Error); - }; - - service.SpriteDownloadStarted += (sender, args) => - { - _spriteDownloaderWindow = new SpriteDownloaderWindow(); - _spriteDownloaderWindow.Show(); - }; - - service.SpriteDownloadEnded += (sender, args) => - { - if (_spriteDownloaderWindow?.IsVisible == true) - { - _spriteDownloaderWindow?.Close(); - } - }; - } - - private void OkButton_OnClick(object sender, RoutedEventArgs e) - { - _service.SaveViewModel(); - Close(); - } - -} - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/ProgressDialog.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/ProgressDialog.xaml deleted file mode 100644 index 6db1f5b52..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/ProgressDialog.xaml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - Casualizing logic... - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/ProgressDialog.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/ProgressDialog.xaml.cs deleted file mode 100644 index 6f0cdead1..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/ProgressDialog.xaml.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Threading; -using System.Windows; -using System.Windows.Shell; -using System.Windows.Threading; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -/// -/// Interaction logic for ProgressDialog.xaml -/// -[NotAService] -public partial class ProgressDialog : Window, IProgress -{ - private readonly CancellationTokenSource _cancellationTokenSource = new(); - private readonly DispatcherTimer _timer; - private DateTimeOffset? _shown; - - public ProgressDialog(Window owner, string title) - { - InitializeComponent(); - - _timer = new(DispatcherPriority.Render); - _timer.Tick += Timer_Tick; - _timer.Interval = TimeSpan.FromSeconds(1); - - Owner = owner; - Title = owner.Title; - - MainInstructionText.Text = title; - MainProgressBar.Minimum = 0d; - MainProgressBar.Maximum = 1d; - - TaskbarItemInfo = new TaskbarItemInfo() - { - - }; - } - - private void Timer_Tick(object? sender, EventArgs e) - { - var elapsed = DateTimeOffset.Now - _shown; - TimeElapsedText.Text = $"{elapsed:m\\:ss}"; - } - - public CancellationToken CancellationToken => _cancellationTokenSource.Token; - - public void StartTimer() - { - _shown = DateTimeOffset.Now; - _timer.Start(); - Timer_Tick(this, new EventArgs()); - } - - public void Report(double value) - { - Dispatcher.Invoke(() => - { - if (double.IsNaN(value)) - { - MainProgressBar.IsIndeterminate = true; - TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Indeterminate; - } - else - { - MainProgressBar.IsIndeterminate = false; - TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Normal; - MainProgressBar.Value = value; - TaskbarItemInfo.ProgressValue = value; - } - }, DispatcherPriority.Render, CancellationToken); - } - - private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) - { - _cancellationTokenSource.Cancel(); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RandomizerPresetWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RandomizerPresetWindow.xaml deleted file mode 100644 index c2d6a7b9c..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RandomizerPresetWindow.xaml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - Enter a name for the preset - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RandomizerPresetWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RandomizerPresetWindow.xaml.cs deleted file mode 100644 index 2ae4288f9..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RandomizerPresetWindow.xaml.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Windows; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -public partial class RandomizerPresetWindow : Window -{ - public RandomizerPresetWindow() - { - InitializeComponent(); - } - - public string PresetName = ""; - - private void SavePresetButton_OnClick(object sender, RoutedEventArgs e) - { - PresetName = PresetNameTextBox.Text; - DialogResult = true; - Close(); - } -} - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RomListWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RomListWindow.xaml deleted file mode 100644 index 2b407c719..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RomListWindow.xaml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - A new version of SMZ3 is now available! - - Click here to go to the download page. - - - - - - - - Ignore this Version - - - - - Don't Check for Updates - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RomListWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RomListWindow.xaml.cs deleted file mode 100644 index 9d301518d..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/RomListWindow.xaml.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Navigation; -using GitHubReleaseChecker; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using TrackerCouncil.Smz3.Chat.Integration; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.UI.Legacy.Controls; -using TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -/// -/// Interaction logic for RomListWindow.xaml -/// -public partial class RomListWindow : Window -{ - private readonly IServiceProvider _serviceProvider; - private readonly ILogger _logger; - private readonly IChatAuthenticationService _chatAuthenticationService; - private readonly IGitHubReleaseCheckerService _gitHubReleaseCheckerService; - private string _gitHubReleaseUrl = ""; - - public RomListWindow(IServiceProvider serviceProvider, - OptionsFactory optionsFactory, - ILogger logger, - IChatAuthenticationService chatAuthenticationService, - IGitHubReleaseCheckerService gitHubReleaseCheckerService) - { - _serviceProvider = serviceProvider; - _logger = logger; - InitializeComponent(); - Options = optionsFactory.Create(); - _chatAuthenticationService = chatAuthenticationService; - _gitHubReleaseCheckerService = gitHubReleaseCheckerService; - - Smz3.UI.Legacy.App.RestoreWindowPositionAndSize(this); - } - - public GeneratedRomsViewModel Model { get; } = new(); - public RandomizerOptions Options { get; } - - private async void Window_Loaded(object sender, RoutedEventArgs e) - { - if (!Options.GeneralOptions.Validate()) - { - MessageBox.Show(this, "If this is your first time using the randomizer," + - " there are some required options you need to configure before you " + - "can start playing randomized SMZ3 games. Please do so now.", - "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Information); - OptionsMenuItem_Click(this, new RoutedEventArgs()); - } - - if (!string.IsNullOrEmpty(Options.GeneralOptions.TwitchOAuthToken)) - { - var isTokenValid = await _chatAuthenticationService.ValidateTokenAsync(Options.GeneralOptions.TwitchOAuthToken, default); - if (!isTokenValid) - { - Options.GeneralOptions.TwitchOAuthToken = string.Empty; - MessageBox.Show(this, "Your Twitch login has expired. Please" + - " go to Options and log in with Twitch again to re-enable" + - " chat integration features.", "SMZ3 Cas’ Randomizer", - MessageBoxButton.OK, MessageBoxImage.Warning); - } - } - - var soloPanel = _serviceProvider.GetService(); - if (soloPanel != null) SoloTab.Children.Add(soloPanel); - var multiPanel = _serviceProvider.GetService(); - if (multiPanel != null) MultiTab.Children.Add(multiPanel); - - _ = CheckForUpdates(); - } - - private async Task CheckForUpdates() - { - if (!Options.GeneralOptions.CheckForUpdatesOnStartup) return; - - var version = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion; - - try - { - var gitHubRelease = await _gitHubReleaseCheckerService - .GetGitHubReleaseToUpdateToAsync("TheTrackerCouncil", "SMZ3Randomizer", version ?? "", false); - - if (gitHubRelease != null) - { - if (gitHubRelease.Url != Options.GeneralOptions.IgnoredUpdateUrl) - { - Dispatcher.Invoke(() => - { - UpdateNotificationBorder.Visibility = Visibility.Visible; - _gitHubReleaseUrl = gitHubRelease.Url; - }); - } - } - else - { - gitHubRelease = await _gitHubReleaseCheckerService - .GetGitHubReleaseToUpdateToAsync("TheTrackerCouncil", "SMZ3Randomizer", version ?? "", false); - - if (gitHubRelease != null && gitHubRelease.Url != Options.GeneralOptions.IgnoredUpdateUrl) - { - Dispatcher.Invoke(() => - { - UpdateNotificationBorder.Visibility = Visibility.Visible; - _gitHubReleaseUrl = gitHubRelease.Url; - }); - } - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting GitHub release"); - } - } - - private void Window_Closing(object sender, CancelEventArgs e) - { - try - { - Options.Save(OptionsFactory.GetFilePath()); - Smz3.UI.Legacy.App.SaveWindowPositionAndSize(this); - } - catch - { - // Oh well - } - } - - private void OptionsMenuItem_Click(object sender, RoutedEventArgs e) - { - using var scope = _serviceProvider.CreateScope(); - var optionsDialog = scope.ServiceProvider.GetRequiredService(); - optionsDialog.ShowDialog(); - - if (!string.IsNullOrEmpty(Options.GeneralOptions.RomOutputPath) && !Directory.Exists(Options.GeneralOptions.RomOutputPath)) - { - try - { - Directory.CreateDirectory(Options.GeneralOptions.RomOutputPath); - } - catch - { - // Oh well, next check will warn the user - } - } - - if (!Options.GeneralOptions.Validate()) - { - MessageBox.Show(this, "Missing required settings. Verify that you have entered valid " + - "roms and a valid output folder.", - "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Error); - } - - - try - { - - Options.Save(OptionsFactory.GetFilePath()); - } - catch - { - // Oh well - } - - } - - /// - /// Launches the about menu - /// - /// - /// - private void AboutMenuItem_Click(object sender, RoutedEventArgs e) - { - using var scope = _serviceProvider.CreateScope(); - var aboutWindow = scope.ServiceProvider.GetRequiredService(); - aboutWindow.Owner = this; - aboutWindow.ShowDialog(); - } - - private void UpdateNotificationHyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) - { - Process.Start(new ProcessStartInfo - { - FileName = _gitHubReleaseUrl, - UseShellExecute = true - }); - } - - private void CloseUpdateNotificationButton_OnClick(object sender, RoutedEventArgs e) - { - UpdateNotificationBorder.Visibility = Visibility.Collapsed; - } - - private void IgnoreVersionHyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) - { - Options.GeneralOptions.IgnoredUpdateUrl = _gitHubReleaseUrl; - Options.Save(); - UpdateNotificationBorder.Visibility = Visibility.Collapsed; - } - - private void StopUpdateCheckHyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) - { - Options.GeneralOptions.CheckForUpdatesOnStartup = false; - Options.Save(); - UpdateNotificationBorder.Visibility = Visibility.Collapsed; - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteDownloaderWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteDownloaderWindow.xaml deleted file mode 100644 index 02116a332..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteDownloaderWindow.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - Downloading Sprites - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteDownloaderWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteDownloaderWindow.xaml.cs deleted file mode 100644 index 066e7956e..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteDownloaderWindow.xaml.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Windows; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -public partial class SpriteDownloaderWindow : Window -{ - public SpriteDownloaderWindow() - { - InitializeComponent(); - } -} - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteWindow.xaml deleted file mode 100644 index 0ca151466..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteWindow.xaml +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - - - - - - - - - - Search - - - - - Filter - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteWindow.xaml.cs deleted file mode 100644 index ca5095098..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/SpriteWindow.xaml.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Threading; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.Services; -using TrackerCouncil.Smz3.Data.ViewModels; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -[NotAService] -public partial class SpriteWindow : Window -{ - private readonly SpriteService _spriteService; - - public SpriteWindow(SpriteService spriteService, OptionsFactory optionsFactory) - { - _spriteService = spriteService; - Options = optionsFactory.Create(); - InitializeComponent(); - - DataContext = Model = new SpriteWindowViewModel(); - - } - - public void SetSpriteType(SpriteType type) - { - SpriteType = type; - - if (type == SpriteType.Link) - { - SelectedSprite = Options.PatchOptions.SelectedLinkSprite; - Model.SearchText = Options.GeneralOptions.LinkSpriteSearchText; - Model.SpriteFilter = Options.GeneralOptions.LinkSpriteFilter; - } - else if (type == SpriteType.Samus) - { - SelectedSprite = Options.PatchOptions.SelectedSamusSprite; - Model.SearchText = Options.GeneralOptions.SamusSpriteSearchText; - Model.SpriteFilter = Options.GeneralOptions.SamusSpriteFilter; - } - else - { - SelectedSprite = Options.PatchOptions.SelectedShipSprite; - Model.SearchText = Options.GeneralOptions.ShipSpriteSearchText; - Model.SpriteFilter = Options.GeneralOptions.ShipSpriteFilter; - } - } - - public SpriteWindowViewModel Model { get; } = new(); - - private RandomizerOptions Options { get; set; } = new(); - - private SpriteType SpriteType { get; set; } - - private IEnumerable? _allSprites = new List(); - - private Dictionary _spriteOptions = new(); - - public Sprite? SelectedSprite { get; private set; } - public Dictionary SelectedSpriteOptions { get; private set; } = new(); - - private void SpriteWindow_OnLoaded(object sender, RoutedEventArgs e) - { - _spriteOptions = SpriteType switch - { - SpriteType.Link => Options.GeneralOptions.LinkSpriteOptions, - SpriteType.Samus => Options.GeneralOptions.SamusSpriteOptions, - SpriteType.Ship => Options.GeneralOptions.ShipSpriteOptions, - _ => new Dictionary() - }; - - _allSprites = _spriteService.Sprites.Where(x => x.SpriteType == SpriteType).OrderByDescending(x => x.IsRandomSprite).ThenByDescending(x => _spriteOptions.ContainsKey(x.FilePath)); - - Task.Factory.StartNew(() => - { - foreach (var sprite in _allSprites) - { - Application.Current.Dispatcher.BeginInvoke(new Action(() => - { - var model = new SpriteViewModel(sprite); - model.CheckFilterOption(Model.SearchText, Model.SpriteFilter); - Model.Sprites.Add(model); - }), DispatcherPriority.Background); - } - }); - } - - private void SearchTextBox_OnKeyUp(object sender, KeyEventArgs e) - { - if (SearchTextBox.Text == Model.SearchText) return; - Model.SearchText = SearchTextBox.Text; - Search(); - } - - private void Search() - { - foreach (var sprite in Model.Sprites) - { - sprite.CheckFilterOption(Model.SearchText, Model.SpriteFilter); - } - } - - private void HideButton_OnClick(object sender, RoutedEventArgs e) - { - if (sender is not Button { Tag: SpriteViewModel sprite }) return; - sprite.SpriteOption = SpriteOptions.Hide; - sprite.CheckFilterOption(Model.SearchText, Model.SpriteFilter); - } - - private void SpriteFilterComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) - { - Model.SpriteFilter = (SpriteFilter)SpriteFilterComboBox.SelectedItem; - Search(); - } - - private void ToggleFavoriteButton_OnClick(object sender, RoutedEventArgs e) - { - if (sender is not Button { Tag: SpriteViewModel sprite }) return; - sprite.SpriteOption = sprite.SpriteOption == SpriteOptions.Favorite - ? SpriteOptions.Default - : SpriteOptions.Favorite; - sprite.CheckFilterOption(Model.SearchText, Model.SpriteFilter); - } - - private void SelectButton_OnClick(object sender, RoutedEventArgs e) - { - if (sender is not Button { Tag: SpriteViewModel sprite }) return; - - if (sprite.Sprite.IsRandomSprite && !Model.Sprites.Any(x => x.Display && !x.Sprite.IsRandomSprite)) - { - MessageBox.Show( - "No available sprites to pick from. Change your search and filter options before selecting the random sprite option.", - "SMZ3 Cas' Randomizer", MessageBoxButton.OK, MessageBoxImage.Error); - return; - } - - SelectedSprite = sprite.Sprite; - SelectedSpriteOptions = Model.Sprites.Where(x => x.SpriteOption != SpriteOptions.Default) - .ToDictionary(x => x.Sprite?.FilePath ?? "", x => x.SpriteOption); - DialogResult = true; - Close(); - } - - private void SpriteWindow_OnClosing(object? sender, CancelEventArgs e) - { - SelectedSpriteOptions = _allSprites!.Where(x => x.SpriteOption != SpriteOptions.Default) - .ToDictionary(x => x.FilePath ?? "", x => x.SpriteOption); - } -} - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerHelpWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerHelpWindow.xaml deleted file mode 100644 index 708f631a8..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerHelpWindow.xaml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerHelpWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerHelpWindow.xaml.cs deleted file mode 100644 index 84b383645..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerHelpWindow.xaml.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using System.Windows; -using TrackerCouncil.Smz3.Abstractions; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -/// -/// Interaction logic for TrackerHelpWindow.xaml -/// -public partial class TrackerHelpWindow : Window -{ - public TrackerHelpWindow(TrackerBase tracker) - { - SpeechRecognitionSyntax = tracker.Syntax; - - InitializeComponent(); - } - - public IReadOnlyDictionary> SpeechRecognitionSyntax { get; } - - private void OkButton_Click(object sender, RoutedEventArgs e) - { - Close(); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerLocationsWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerLocationsWindow.xaml deleted file mode 100644 index 86cee62c5..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerLocationsWindow.xaml +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - : - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerLocationsWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerLocationsWindow.xaml.cs deleted file mode 100644 index 000bc647c..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerLocationsWindow.xaml.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Windows; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using TrackerCouncil.Smz3.Tracking.Services; -using TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -/// -/// Interaction logic for TrackerLocationsWindow.xaml -/// -public partial class TrackerLocationsWindow : Window -{ - public TrackerLocationsWindow(TrackerLocationSyncer syncer, IUIService uiService) - { - DataContext = new TrackerViewModel(syncer, uiService); - - InitializeComponent(); - - var sprite = uiService.GetSpritePath("Items", "chest.png", out _); - if (string.IsNullOrEmpty(sprite)) throw new InvalidOperationException("Could not load chest sprite"); - ChestSprite = new BitmapImage(new Uri(sprite)); - - sprite = uiService.GetSpritePath("Items", "key.png", out _); - if (string.IsNullOrEmpty(sprite)) throw new InvalidOperationException("Could not load key sprite"); - KeySprite = new BitmapImage(new Uri(sprite)); - - Smz3.UI.Legacy.App.RestoreWindowPositionAndSize(this); - } - - public ImageSource ChestSprite { get; } - - public ImageSource KeySprite { get; } - - private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) - { - Smz3.UI.Legacy.App.SaveWindowPositionAndSize(this); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerMapWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerMapWindow.xaml deleted file mode 100644 index 1144bda52..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerMapWindow.xaml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerMapWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerMapWindow.xaml.cs deleted file mode 100644 index 3bcf5de95..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerMapWindow.xaml.cs +++ /dev/null @@ -1,249 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Shapes; -using Microsoft.Extensions.Logging; -using TrackerCouncil.Smz3.Abstractions; -using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; -using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; -using TrackerCouncil.Smz3.Data.WorldData; -using TrackerCouncil.Smz3.Data.WorldData.Regions; -using TrackerCouncil.Smz3.UI.Legacy.ViewModels; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -/// -/// Interaction logic for TrackerMapWindow.xaml -/// -public partial class TrackerMapWindow : Window -{ - private readonly ILogger _logger; - private readonly TrackerBase _trackerBase; - private TrackerLocationSyncer _syncer; - - /// - /// Initializes a new instance of the - /// class to display the map of accessible locations that the player can - /// go to - /// - /// The tracker for tracking locations and items - /// Syncer to help communicate between the UI and tracker - /// - /// The config for the map json file with all the location details - /// - /// Logger for logging - public TrackerMapWindow(TrackerBase tracker, TrackerLocationSyncer syncer, TrackerMapConfig config, ILogger logger) - { - InitializeComponent(); - - _logger = logger; - _trackerBase = tracker; - _syncer = syncer; - - Maps = config.Maps; - var regions = config.Regions; - - // To make querying easier for the viewmodel, compile a list of all - // locations on each map by combining all of their regions, making - // any location adjusments needed - foreach (var map in Maps) - { - map.FullLocations = new List(); - foreach (var mapRegion in map.Regions) - { - var region = regions.First(region => mapRegion.Name == region.Name); - if (region.Rooms != null) - { - var mapLocations = region.Rooms - .Select(room => new TrackerMapLocation(TrackerMapLocation.MapLocationType.Item, mapRegion.Name, region.TypeName, room.Name, - x: (int)Math.Floor(room.X * mapRegion.Scale) + mapRegion.X, - y: (int)Math.Floor(room.Y * mapRegion.Scale) + mapRegion.Y)) - .ToList(); - map.FullLocations.AddRange(mapLocations); - } - - // Add the boss for this region if one is specified - if (region.BossX != null && region.BossY != null) - { - map.FullLocations.Add(new TrackerMapLocation(TrackerMapLocation.MapLocationType.Boss, mapRegion.Name, region.TypeName, "", - x: (int)Math.Floor((region.BossX ?? 0) * mapRegion.Scale) + mapRegion.X, - y: (int)Math.Floor((region.BossY ?? 0) * mapRegion.Scale) + mapRegion.Y)); - } - - // Add all SM keysanity doors - if (region.Doors != null) - { - foreach (var door in region.Doors) - { - map.FullLocations.Add(new TrackerMapLocation(TrackerMapLocation.MapLocationType.SMDoor, mapRegion.Name, - region.TypeName, door.Item, - x: (int)Math.Floor((door.X) * mapRegion.Scale) + mapRegion.X, - y: (int)Math.Floor((door.Y) * mapRegion.Scale) + mapRegion.Y)); - } - } - } - } - - TrackerMapViewModel = new TrackerMapViewModel(); - TrackerMapViewModel.MapNames = Maps.Select(x => x.ToString()).ToList(); - DataContext = TrackerMapViewModel; - - Smz3.UI.Legacy.App.RestoreWindowPositionAndSize(this); - } - - /// - /// A collection of all of the maps that the player can choose from - /// - public IReadOnlyCollection Maps { get; } - - /// - /// The current selected map - /// - public TrackerMap? CurrentMap { get; private set; } - - /// - /// The view model for the tracker map window - /// - public TrackerMapViewModel TrackerMapViewModel { get; set; } - - public TrackerLocationSyncer Syncer - { - get - { - return _syncer; - } - set - { - TrackerMapViewModel.Syncer = value; - value.TrackedLocationUpdated += PropertyChanged; - _syncer = value; - } - } - - /// - /// Updates the displayed map - /// - /// The name of the map to display - public void UpdateMap(string mapName) - { - if (string.IsNullOrEmpty(mapName)) - return; - - UpdateMap(Maps.First(x => x.ToString() == mapName)); - } - - /// - /// Updates the displayed map - /// - /// The map to display - public void UpdateMap(TrackerMap map) - { - CurrentMap = map; - TrackerMapViewModel.CurrentMap = CurrentMap; - } - - /// - /// When the canvas's parent grid size changes. Used for updating the - /// canvas size to the correct proportions. - /// - /// The original propagator of the change - /// The event with the resize information - private void Grid_SizeChanged(object sender, SizeChangedEventArgs e) - { - TrackerMapViewModel.GridSize = e.NewSize; - } - - /// - /// When the window has fully loaded. - /// - /// - /// - private void Window_Loaded(object sender, RoutedEventArgs e) - { - if (Maps.Count == 0) - { - _logger.LogError("Map json was not loaded successfully"); - return; - } - UpdateMap(Maps.Last()); - } - - /// - /// Updates the current map to the option in the combo box when the - /// combo box is updated - /// - /// - /// - private void MapComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - _trackerBase.UpdateMap(Maps.ElementAt(MapComboBox.SelectedIndex).ToString()); - } - - private void PropertyChanged(object?sender, PropertyChangedEventArgs e) - { - TrackerMapViewModel.OnPropertyChanged(); - } - - private void Window_Closing(object sender, CancelEventArgs e) - { - Smz3.UI.Legacy.App.SaveWindowPositionAndSize(this); - } - - /// - /// Called when a location is clicked on the map to clear out all - /// affiliated SMZ3 locations - /// - /// The rectangle/ellipse that was clicked - /// - private void Location_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) - { - // Because we have to do this a gross way, let's at least be sure - // this is originating from the correct object with Location data - if (sender is not Rectangle and not Ellipse) - return; - - var shape = (Shape)sender; - - // Determine what type of location this is - if (shape.Tag is List locations) - { - locations.Where(x => x.State.Cleared == false) - .ToList() - .ForEach(x => _trackerBase.Clear(x)); - } - else if (shape.Tag is Region region) - { - _trackerBase.ClearArea(region, false, false, null, region.Name != "Hyrule Castle"); - } - else if (shape.Tag is Boss boss) - { - _trackerBase.MarkBossAsDefeated(boss); - } - else if(shape.Tag is IDungeon dungeon) - { - _trackerBase.MarkDungeonAsCleared(dungeon); - } - - } - - /// - /// Clicked on the right click menu for clearing an individual location - /// inside of a room or region - /// - /// The menu item that was clicked - /// - private void LocationContextMenu_Click(object sender, RoutedEventArgs e) - { - if (sender is not MenuItem menuItem) - return; - - if (menuItem.Tag is not Location location) - return; - - _trackerBase.Clear(location); - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerWindow.xaml b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerWindow.xaml deleted file mode 100644 index 3d03f1c51..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerWindow.xaml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerWindow.xaml.cs b/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerWindow.xaml.cs deleted file mode 100644 index 2f3ea6422..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/Windows/TrackerWindow.xaml.cs +++ /dev/null @@ -1,1274 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Effects; -using System.Windows.Media.Imaging; -using System.Windows.Threading; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using MSURandomizerLibrary.Services; -using MSURandomizerUI.Controls; -using SnesConnectorLibrary; -using TrackerCouncil.Smz3.Abstractions; -using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; -using TrackerCouncil.Smz3.Data.Options; -using TrackerCouncil.Smz3.Data.Tracking; -using TrackerCouncil.Smz3.Data.WorldData; -using TrackerCouncil.Smz3.Data.WorldData.Regions; -using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; -using TrackerCouncil.Smz3.SeedGenerator.Contracts; -using TrackerCouncil.Smz3.SeedGenerator.Generation; -using TrackerCouncil.Smz3.Shared; -using TrackerCouncil.Smz3.Shared.Enums; -using TrackerCouncil.Smz3.Shared.Models; -using TrackerCouncil.Smz3.Tracking.Services; -using TrackerCouncil.Smz3.Tracking.VoiceCommands; -using WpfAnimatedGif; - -namespace TrackerCouncil.Smz3.UI.Legacy.Windows; - -/// -/// Interaction logic for TrackerWindow.xaml -/// -public partial class TrackerWindow : Window -{ - private const int GridItemPx = 32; - private const int GridItemMargin = 3; - private readonly DispatcherTimer _dispatcherTimer; - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; - private readonly IItemService _itemService; - private readonly IUIService _uiService; - private readonly List _mouseDownSenders = new(); - private readonly IWorldAccessor _world; - private readonly RandomizerOptions _options; - private readonly Smz3GeneratedRomLoader _romLoader; - private readonly IMsuLookupService _msuLookupService; - private bool _pegWorldMode; - private bool _shaktoolMode; - private TrackerLocationsWindow? _locationsWindow; - private TrackerHelpWindow? _trackerHelpWindow; - private TrackerMapWindow? _trackerMapWindow; - private AutoTrackerWindow? _autoTrackerHelpWindow; - private TrackerLocationSyncer? _locationSyncer; - private MsuTrackWindow? _msuTrackWindow; - private MenuItem? _autoTrackerDisableMenuItem; - private MenuItem? _autoTrackerLuaMenuItem; - private MenuItem? _autoTrackerUSB2SNESMenuItem; - private MenuItem? _autoTrackerSniMenuItem; - private MenuItem? _autoTrackerLuaCrowdControlMenuItem; - private MenuItem? _autoTrackerLuaEmoTrackerMenuItem; - private UILayout _layout; - private readonly UILayout _defaultLayout; - private UILayout? _previousLayout; - private TrackerBase? _tracker; - - public TrackerWindow(IServiceProvider serviceProvider, - IItemService itemService, - ILogger logger, - Smz3GeneratedRomLoader romLoader, - IUIService uiService, - OptionsFactory optionsFactory, - IWorldAccessor world, - ITrackerTimerService timerService, - IMsuLookupService msuLookupService) - { - InitializeComponent(); - - _serviceProvider = serviceProvider; - _itemService = itemService; - _logger = logger; - _romLoader = romLoader; - _uiService = uiService; - _options = optionsFactory.Create(); - _layout = uiService.GetLayout(_options.GeneralOptions.SelectedLayout); - _defaultLayout = _layout; - _world = world; - _msuLookupService = msuLookupService; - - foreach (var layout in uiService.SelectableLayouts) - { - var layoutMenuItem = new MenuItem - { - Header = layout.Name, - Tag = layout - }; - layoutMenuItem.Click += LayoutMenuItem_Click; - layoutMenuItem.IsCheckable = true; - layoutMenuItem.IsChecked = layout == _layout; - LayoutMenu.Items.Add(layoutMenuItem); - } - - _dispatcherTimer = new(TimeSpan.FromMilliseconds(1000), DispatcherPriority.Render, (sender, _) => - { - StatusBarTimer.Content = timerService.TimeString; - }, Dispatcher); - - Smz3.UI.Legacy.App.RestoreWindowPositionAndSize(this); - } - - protected enum Origin - { - TopLeft = 0, - TopRight = 1, - BottomLeft = 2, - BottomRight = 3 - } - - public TrackerBase TrackerBase => _tracker ?? throw new InvalidOperationException("Tracker not created"); - - public GeneratedRom? Rom { get; set; } - - /// - /// Occurs when the the tracker's state has been saved - /// - public event EventHandler? SavedState; - - protected Image GetGridItemControl(string? imageFileName, int column, int row, out ImageSource imageSource, string overlayFileName) - => GetGridItemControl(imageFileName, column, row, out imageSource, new Overlay(overlayFileName, 0, 0)); - - protected Image GetGridItemControl(string? imageFileName, int column, int row, out ImageSource imageSource, int counter, string? overlayFileName, int minCounter = 2) - { - var overlays = new List(); - if (overlayFileName != null) - overlays.Add(new(overlayFileName, 0, 0)); - - if (counter >= minCounter) - { - var offset = 0; - foreach (var digit in GetDigits(counter)) - { - var sprite = _uiService.GetSpritePath(digit); - - if (sprite == null) continue; - - overlays.Add(new(sprite, offset, 0) - { - OriginPoint = Origin.BottomLeft - }); - offset += 8; - } - } - - return GetGridItemControl(imageFileName, column, row, out imageSource, overlays.ToArray()); - } - - internal static IEnumerable GetDigits(int value) - { - var numDigits = value.ToString("0", CultureInfo.InvariantCulture).Length; - for (var i = numDigits; i > 0; i--) - { - yield return value / (int)Math.Pow(10, i - 1) % 10; - } - } - - protected Image GetGridItemControl(string? imageFileName, int column, int row, out ImageSource bitmapImage, - params Overlay[] overlays) - { - imageFileName ??= _uiService.GetSpritePath("Items", "blank.png", out _); - - bitmapImage = new BitmapImage(new Uri(imageFileName ?? "")); - if (overlays.Length == 0) - { - return GetGridItemControl(bitmapImage, column, row); - } - - var drawingGroup = new DrawingGroup(); - drawingGroup.Children.Add(new ImageDrawing(bitmapImage, - new Rect(0, 0, GridItemPx, GridItemPx))); - - foreach (var overlay in overlays) - { - var overlayImage = new BitmapImage(new Uri(overlay.FileName)); - var x = OffsetX(overlay.X, overlayImage.PixelWidth, overlay.OriginPoint); - var y = OffsetY(overlay.Y, overlayImage.PixelHeight, overlay.OriginPoint); - drawingGroup.Children.Add(new ImageDrawing(overlayImage, - new Rect(x, y, overlayImage.PixelWidth, overlayImage.PixelHeight))); - } - - return GetGridItemControl(new DrawingImage(drawingGroup), column, row); - - int OffsetX(int x, int width, Origin origin) => origin switch - { - Origin.TopLeft or Origin.BottomLeft => x, - Origin.TopRight or Origin.BottomRight => GridItemPx - width - x, - _ => throw new InvalidEnumArgumentException(nameof(origin), (int)origin, typeof(Origin)) - }; - - int OffsetY(int y, int height, Origin origin) => origin switch - { - Origin.TopLeft or Origin.TopRight => y, - Origin.BottomLeft or Origin.BottomRight => GridItemPx - height - y, - _ => throw new InvalidEnumArgumentException(nameof(origin), (int)origin, typeof(Origin)) - }; - } - - protected Image GetGridItemControl(ImageSource imageSource, int column, int row) - { - var image = new Image - { - Source = imageSource, - MaxWidth = GridItemPx, - MaxHeight = GridItemPx, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top - }; - - if (_options.GeneralOptions.TrackerShadows) - { - image.Effect = new DropShadowEffect - { - Color = Colors.Black, - Direction = 315, - BlurRadius = 5, - Opacity = 0.8, - ShadowDepth = 2 - }; - } - - RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.NearestNeighbor); - Grid.SetColumn(image, column - 1); - Grid.SetRow(image, row - 1); - return image; - } - - protected virtual void RefreshGridItems() - { - TrackerGrid.Children.Clear(); - - foreach (var gridLocation in _layout.GridLocations) - { - var labelImage = (Image?)null; - if (gridLocation.Image != null) - { - labelImage = GetGridItemControl(_uiService.GetSpritePath("Items", gridLocation.Image, out _), gridLocation.Column, gridLocation.Row, out _); - TrackerGrid.Children.Add(labelImage); - } - - // A group of items stacked on top of each other - if (gridLocation.Type == UIGridLocationType.Items) - { - var items = new List(); - var latestImage = (Image?)null; - foreach (var itemName in gridLocation.Identifiers) - { - var item = _itemService.FirstOrDefault(itemName); - if (item == null) - { - _logger.LogError("Item {ItemName} could not be found", itemName); - continue; - } - - items.Add(item); - var fileName = _uiService.GetSpritePath(item); - var overlay = GetOverlayImageFileName(item); - if (fileName == null) - { - _logger.LogError("Image for {ItemName} could not be found", item.Name); - continue; - } - - var counter = item.Counter; - - // Group bottle counts together - if (item.Type == ItemType.Bottle) - { - var countedBottleTypes = _itemService.LocalPlayersItems() - .Where(x => x.Type != ItemType.Bottle && x.Type.IsInCategory(ItemCategory.Bottle)) - .Select(x => x.Type).Distinct().ToList(); - foreach (var type in countedBottleTypes) - { - counter += _itemService.FirstOrDefault(type)?.Counter ?? 0; - } - } - - latestImage = GetGridItemControl(fileName, - gridLocation.Column, gridLocation.Row, - out _, counter, overlay, minCounter: 2); - latestImage.Opacity = item.State.TrackingState > 0 || counter > 0 ? 1.0d : 0.2d; - TrackerGrid.Children.Add(latestImage); - } - - if (latestImage == null) continue; - - // If only one item, left clicking should track it - if (items.Count == 1) - { - latestImage.MouseLeftButtonDown += Image_MouseDown; - latestImage.MouseLeftButtonUp += Image_LeftClick; - } - - if (labelImage != null) - { - labelImage.Opacity = items.Any(x => x.State.TrackingState > 0) ? 1.0d : 0.2d; - } - - latestImage.Tag = gridLocation; - latestImage.ContextMenu = CreateContextMenu(items); - } - // If it's a Zelda dungeon - else if (gridLocation.Type == UIGridLocationType.Dungeon) - { - var dungeon = _world.World.Dungeons.FirstOrDefault(x => x.DungeonName == gridLocation.Identifiers.First()); - if (dungeon == null) - { - _logger.LogError("Dungeon {DungeonName} could not be found", gridLocation.Identifiers.First()); - continue; - } - - var overlayPath = _uiService.GetSpritePath(dungeon); - var rewardPath = dungeon.MarkedReward != RewardType.None ? _uiService.GetSpritePath(dungeon.MarkedReward) : null; - var image = GetGridItemControl(rewardPath, - gridLocation.Column, gridLocation.Row, - out _, dungeon.DungeonState.RemainingTreasure, overlayPath, minCounter: 1); - image.Tag = gridLocation; - image.MouseLeftButtonDown += Image_MouseDown; - image.MouseLeftButtonUp += Image_LeftClick; - - if (dungeon.HasReward) - { - image.ContextMenu = CreateContextMenu(dungeon); - } - - image.Opacity = dungeon.DungeonState.Cleared ? 1.0d : 0.2d; - - TrackerGrid.Children.Add(image); - } - // If it's a Super Metroid boss - else if (gridLocation.Type == UIGridLocationType.SMBoss) - { - var boss = _world.World.AllBosses.FirstOrDefault(x => x.Name == gridLocation.Identifiers.First()); - if (boss == null) - continue; - - var fileName = _uiService.GetSpritePath(boss.Metadata); - var overlay = GetOverlayImageFileName(boss.Metadata); - if (fileName == null) - { - _logger.LogError("Image for {BossName} could not be found", boss.Name); - continue; - } - - var image = GetGridItemControl(fileName, - gridLocation.Column, gridLocation.Row, out _); - image.Tag = gridLocation; - image.ContextMenu = CreateContextMenu(boss); - image.MouseLeftButtonDown += Image_MouseDown; - image.MouseLeftButtonUp += Image_LeftClick; - image.Opacity = boss.State.Defeated ? 1.0d : 0.2d; - TrackerGrid.Children.Add(image); - } - // If it's a hammer peg - else if (gridLocation.Type == UIGridLocationType.Peg) - { - if (!int.TryParse(gridLocation.Identifiers.First(), out var pegNumber)) - { - _logger.LogError("Could not determine peg number"); - continue; - } - - var fileName = _uiService.GetSpritePath("Items", - TrackerBase?.PegsPegged >= pegNumber ? "pegged.png" : "peg.png", out _); - - var image = GetGridItemControl(fileName, gridLocation.Column, gridLocation.Row, out _); - image.Tag = gridLocation; - image.MouseLeftButtonDown += Image_MouseDown; - image.MouseLeftButtonUp += Image_LeftClick; - TrackerGrid.Children.Add(image); - } - // If it's a shaktool - else if (gridLocation.Type == UIGridLocationType.Shak) - { - var fileName = _uiService.GetSpritePath("Items", - "shakspin.gif", out _); - - var image = GetGridItemControl(fileName, gridLocation.Column, gridLocation.Row, out var imageSource); - image.Tag = gridLocation; - image.MouseLeftButtonDown += Image_MouseDown; - image.MouseLeftButtonUp += Image_LeftClick; - ImageBehavior.SetAnimatedSource(image, imageSource); - - TrackerGrid.Children.Add(image); - } - } - } - - private string? GetOverlayImageFileName(BossInfo boss) - { - return null; - } - - private string? GetOverlayImageFileName(Item item) - { - return item switch - { - { Type: ItemType.Bombos } => GetMatchingDungeonNameImages(item.Type), - { Type: ItemType.Ether } => GetMatchingDungeonNameImages(item.Type), - { Type: ItemType.Quake } => GetMatchingDungeonNameImages(item.Type), - _ => null - }; - - string? GetMatchingDungeonNameImages(ItemType requirement) - { - var names = TrackerBase.World.Dungeons.Where(x => x.DungeonState.MarkedMedallion == requirement) - .Select(x => x.DungeonName) - .ToList(); - - if (names.Count == 1) - { - return _uiService.GetSpritePath("Dungeons", $"{names[0]}.png", out _); - } - else if (names.Count > 1) - { - return _uiService.GetSpritePath("Dungeons", "both.png", out _); - } - - return null; - } - } - - private void Image_MouseDown(object sender, MouseButtonEventArgs e) - { - _mouseDownSenders.Add(sender); - } - - private void Image_LeftClick(object sender, MouseButtonEventArgs e) - { - if (!_mouseDownSenders.Remove(sender)) - return; - - if (sender is Image image) - { - if (image.Tag is UIGridLocation gridLocation) - { - if (gridLocation.Type == UIGridLocationType.Items) - { - var item = _itemService.FirstOrDefault(gridLocation.Identifiers.First()); - if (item != null) TrackerBase.TrackItem(item); - } - else if (gridLocation.Type == UIGridLocationType.Dungeon) - { - var dungeon = _world.World.Dungeons.First(x => x.DungeonName == gridLocation.Identifiers.First()); - TrackerBase.MarkDungeonAsCleared(dungeon); - } - else if (gridLocation.Type == UIGridLocationType.Peg) - { - TrackerBase.Peg(); - } - else if (gridLocation.Type == UIGridLocationType.SMBoss) - { - var boss = _world.World.AllBosses.First(x => x.Name == gridLocation.Identifiers.First()); - TrackerBase.MarkBossAsDefeated(boss); - } - else - { - _logger.LogError("Unrecognized UIGridLocationType tag type {TagType}", gridLocation.Type); - } - } - else - { - _logger.LogError("Unrecognized image tag type {TagType}", image.Tag.GetType()); - } - } - } - - private ContextMenu? CreateContextMenu(ICollection items) - { - var menu = new ContextMenu - { - Style = Application.Current.FindResource("DarkContextMenu") as Style - }; - - foreach (var item in items) - { - var sprite = _uiService.GetSpritePath(item); - if (sprite == null) continue; - - if (item.State.TrackingState == 0 || item.Metadata.Multiple) - { - var menuItem = new MenuItem - { - Header = "Track " + item.Name, - Icon = new Image - { - Source = new BitmapImage(new Uri(sprite)) - } - }; - menuItem.Click += (sender, e) => - { - TrackerBase.TrackItem(item); - RefreshGridItems(); - }; - menu.Items.Add(menuItem); - } - - if (item.State.TrackingState > 0) - { - var menuItem = new MenuItem - { - Header = "Untrack " + item.Name, - Icon = new Image - { - Source = new BitmapImage(new Uri(sprite)) - } - }; - menuItem.Click += (sender, e) => - { - TrackerBase.UntrackItem(item); - RefreshGridItems(); - }; - menu.Items.Add(menuItem); - } - - if (item.Type is ItemType.Bombos or ItemType.Ether or ItemType.Quake) - { - var medallion = item.Type; - var turtleRock = TrackerBase.World.TurtleRock as IDungeon; - var miseryMire = TrackerBase.World.MiseryMire as IDungeon; - - var requiredByNone = new MenuItem - { - Header = "Not required for any dungeon", - IsChecked = turtleRock.MarkedMedallion != medallion && miseryMire.MarkedMedallion != medallion - }; - requiredByNone.Click += (sender, e) => - { - if (turtleRock.MarkedMedallion == medallion) - turtleRock.MarkedMedallion = ItemType.Nothing; - if (miseryMire.MarkedMedallion == medallion) - miseryMire.MarkedMedallion = ItemType.Nothing; - _locationSyncer?.OnLocationUpdated(""); - RefreshGridItems(); - }; - - var requiredByTR = new MenuItem - { - Header = "Required for Turtle Rock", - IsChecked = turtleRock.Medallion == medallion && miseryMire.Medallion != medallion - }; - requiredByTR.Click += (sender, e) => - { - turtleRock.MarkedMedallion = medallion; - if (miseryMire.MarkedMedallion == medallion) - miseryMire.MarkedMedallion = ItemType.Nothing; - _locationSyncer?.OnLocationUpdated(""); - RefreshGridItems(); - }; - - var requiredByMM = new MenuItem - { - Header = "Required for Misery Mire", - IsChecked = turtleRock.MarkedMedallion != medallion && miseryMire.MarkedMedallion == medallion - }; - requiredByMM.Click += (sender, e) => - { - if (turtleRock.MarkedMedallion == medallion) - turtleRock.MarkedMedallion = ItemType.Nothing; - miseryMire.MarkedMedallion = medallion; - _locationSyncer?.OnLocationUpdated(""); - RefreshGridItems(); - }; - - var requiredByBoth = new MenuItem - { - Header = "Required by both", - IsChecked = turtleRock.MarkedMedallion == medallion && miseryMire.MarkedMedallion == medallion - }; - requiredByBoth.Click += (sender, e) => - { - turtleRock.MarkedMedallion = medallion; - miseryMire.MarkedMedallion = medallion; - _locationSyncer?.OnLocationUpdated(""); - RefreshGridItems(); - }; - - menu.Items.Add(requiredByNone); - menu.Items.Add(requiredByTR); - menu.Items.Add(requiredByMM); - menu.Items.Add(requiredByBoth); - } - } - - return menu.Items.Count > 0 ? menu : null; - } - - private ContextMenu? CreateContextMenu(Boss boss) - { - var menu = new ContextMenu - { - Style = Application.Current.FindResource("DarkContextMenu") as Style - }; - - if (boss.State.Defeated) - { - var unclear = new MenuItem - { - Header = $"Revive {boss.Name}", - }; - unclear.Click += (sender, e) => - { - TrackerBase.MarkBossAsNotDefeated(boss); - }; - menu.Items.Add(unclear); - } - - return menu.Items.Count > 0 ? menu : null; - } - - private ContextMenu? CreateContextMenu(IDungeon dungeon) - { - var menu = new ContextMenu - { - Style = Application.Current.FindResource("DarkContextMenu") as Style - }; - - if (dungeon.DungeonState.Cleared) - { - var unclear = new MenuItem - { - Header = "Reset cleared status", - }; - unclear.Click += (sender, e) => - { - TrackerBase.MarkDungeonAsIncomplete(dungeon); - }; - menu.Items.Add(unclear); - } - - if (dungeon.HasReward && dungeon.GetType() != typeof(CastleTower)) - { - foreach (var reward in Enum.GetValues().Where(x => x != RewardType.Agahnim)) - { - var sprite = _uiService.GetSpritePath(reward); - if (string.IsNullOrEmpty(sprite) || dungeon.DungeonState == null) continue; - - var item = new MenuItem - { - Header = $"Mark as {reward.GetDescription()}", - IsChecked = dungeon.DungeonState.MarkedReward == reward, - Icon = new Image - { - Source = new BitmapImage(new Uri(sprite)) - } - }; - - item.Click += (sender, e) => - { - dungeon.DungeonState.MarkedReward = reward; - RefreshGridItems(); - }; - menu.Items.Add(item); - } - } - - return menu.Items.Count > 0 ? menu : null; - } - - private void Window_Loaded(object sender, RoutedEventArgs e) - { - Background = new SolidColorBrush(Color.FromArgb(_options.GeneralOptions.TrackerBGColor[0], _options.GeneralOptions.TrackerBGColor[1], _options.GeneralOptions.TrackerBGColor[2], _options.GeneralOptions.TrackerBGColor[3])); - - // If a rom was passed in, generate its seed to populate all locations and items - if (GeneratedRom.IsValid(Rom)) - { - _romLoader.LoadGeneratedRom(Rom); - } - - InitializeTracker(); - ResetGridSize(); - RefreshGridItems(); - - if (!TrackerBase.TryStartTracking()) - { - ShowModuleWarning(); - } - - TrackerBase.ConnectToChat(_options.GeneralOptions.TwitchUserName, _options.GeneralOptions.TwitchOAuthToken, - _options.GeneralOptions.TwitchChannel, _options.GeneralOptions.TwitchId); - _dispatcherTimer.Start(); - - // Show proper voice status bar icon and warn the user if no mic is available - StatusBarConfidence.Visibility = TrackerBase.VoiceRecognitionEnabled ? Visibility.Visible : Visibility.Collapsed; - StatusBarVoiceDisabled.Visibility = TrackerBase.VoiceRecognitionEnabled ? Visibility.Collapsed : Visibility.Visible; - if (!TrackerBase.MicrophoneInitialized && TrackerBase.Options.SpeechRecognitionMode != SpeechRecognitionMode.Disabled) - { - ShowNoMicrophoneWarning(); - } - else if (TrackerBase.MicrophoneInitialized && !TrackerBase.MicrophoneInitializedAsDesiredDevice) - { - ShowFallbackMicrophoneWarning(); - } - - _locationSyncer = _serviceProvider.GetRequiredService(); - _locationsWindow = _serviceProvider.GetRequiredService(); - _locationsWindow.Show(); - _trackerMapWindow = _serviceProvider.GetRequiredService(); - _trackerMapWindow.Syncer = _locationSyncer; - _trackerMapWindow.Show(); - - if (_options.GeneralOptions.DisplayMsuTrackWindow) - { - _msuTrackWindow = new MsuTrackWindow(); - _msuTrackWindow.Init(_serviceProvider.GetRequiredService(), _options); - _msuTrackWindow.Show(); - } - - InitializeAutoTracker(); - } - - private void InitializeTracker() - { - if (_options == null) - throw new InvalidOperationException("Cannot initialize Tracker before assigning " + nameof(_options)); - - Task.Run(() => - { - _msuLookupService.LookupMsus(_options.GeneralOptions.MsuPath); - }); - - _tracker = _serviceProvider.GetRequiredService(); - - // If a rom was passed in with a valid tracker state, reload the state from the database - if (GeneratedRom.IsValid(Rom)) - { - var romPath = Path.Combine(_options.RomOutputPath, Rom.RomPath); - TrackerBase.Load(Rom, romPath); - } - - TrackerBase.SpeechRecognized += (sender, e) => Dispatcher.Invoke(() => - { - UpdateStats(e); - }); - TrackerBase.ItemTracked += (sender, e) => Dispatcher.Invoke(() => - { - TogglePegWorld(false); - ToggleShaktoolMode(false); - RefreshGridItems(); - }); - TrackerBase.ToggledPegWorldModeOn += (sender, e) => Dispatcher.Invoke(() => - { - TogglePegWorld(TrackerBase.PegWorldMode); - RefreshGridItems(); - }); - TrackerBase.ToggledShaktoolMode += (sender, e) => Dispatcher.Invoke(() => - { - ToggleShaktoolMode(TrackerBase.ShaktoolMode); - RefreshGridItems(); - }); - TrackerBase.PegPegged += (sender, e) => Dispatcher.Invoke(() => - { - TogglePegWorld(e.AutoTracked || TrackerBase.PegsPegged < PegWorldModeModule.TotalPegs); - RefreshGridItems(); - }); - TrackerBase.DungeonUpdated += (sender, e) => Dispatcher.Invoke(() => - { - TogglePegWorld(false); - RefreshGridItems(); - }); - TrackerBase.BossUpdated += (sender, e) => Dispatcher.Invoke(() => - { - TogglePegWorld(false); - ToggleShaktoolMode(false); - RefreshGridItems(); - }); - TrackerBase.GoModeToggledOn += (sender, e) => Dispatcher.Invoke(() => - { - TrackerStatusBar.Background = Brushes.Green; - StatusBarGoMode.Visibility = Visibility.Visible; - }); - TrackerBase.ActionUndone += (sender, e) => Dispatcher.Invoke(() => - { - if (!TrackerBase.GoMode) - { - TrackerStatusBar.Background = null; - StatusBarGoMode.Visibility = Visibility.Collapsed; - } - - RefreshGridItems(); - }); - TrackerBase.StateLoaded += (sender, e) => Dispatcher.Invoke(() => - { - RefreshGridItems(); - ResetGridSize(); - TrackerStatusBar.Background = TrackerBase.GoMode ? Brushes.Green : null; - StatusBarGoMode.Visibility = TrackerBase.GoMode ? Visibility.Visible : Visibility.Collapsed; - }); - TrackerBase.MapUpdated += (sender, e) => Dispatcher.Invoke(() => - { - _trackerMapWindow?.UpdateMap(TrackerBase.CurrentMap); - }); - } - - private void TogglePegWorld(bool enable) - { - if (_pegWorldMode == enable) return; - _pegWorldMode = enable; - if (_pegWorldMode) - { - _previousLayout = _layout; - _layout = _uiService.GetLayout("Peg World"); - } - else - { - _layout = _previousLayout ?? _defaultLayout; - _previousLayout = null; - } - } - - private void ToggleShaktoolMode(bool enable) - { - if (_shaktoolMode == enable) return; - _shaktoolMode = enable; - if (_shaktoolMode) - { - _previousLayout = _layout; - _layout = _uiService.GetLayout("Shak"); - } - else - { - _layout = _previousLayout ?? _defaultLayout; - _previousLayout = null; - } - } - - private ContextMenu CreateAutoTrackerMenu(bool enableAutoTracker) - { - var menu = new ContextMenu - { - Style = Application.Current.FindResource("DarkContextMenu") as Style - }; - - _autoTrackerDisableMenuItem = new MenuItem - { - Header = "Disable Auto Tracker", - IsCheckable = true - }; - _autoTrackerDisableMenuItem.Click += (sender, e) => - { - TrackerBase.AutoTracker?.SetConnector(new SnesConnectorSettings()); - }; - menu.Items.Add(_autoTrackerDisableMenuItem); - - _autoTrackerSniMenuItem = new MenuItem - { - Header = "SNI Auto Tracker", - IsCheckable = true - }; - _autoTrackerSniMenuItem.Click += (sender, e) => - { - TrackerBase.AutoTracker?.SetConnector(_options.GeneralOptions.SnesConnectorSettings, SnesConnectorType.Sni); - }; - menu.Items.Add(_autoTrackerSniMenuItem); - - _autoTrackerUSB2SNESMenuItem = new MenuItem - { - Header = "USB2SNES Auto Tracker", - IsCheckable = true - }; - _autoTrackerUSB2SNESMenuItem.Click += (sender, e) => - { - TrackerBase.AutoTracker?.SetConnector(_options.GeneralOptions.SnesConnectorSettings, SnesConnectorType.Usb2Snes); - }; - menu.Items.Add(_autoTrackerUSB2SNESMenuItem); - - _autoTrackerLuaMenuItem = new MenuItem - { - Header = "Lua Auto Tracker", - IsCheckable = true - }; - _autoTrackerLuaMenuItem.Click += (sender, e) => - { - TrackerBase.AutoTracker?.SetConnector(_options.GeneralOptions.SnesConnectorSettings, SnesConnectorType.Lua); - }; - menu.Items.Add(_autoTrackerLuaMenuItem); - - _autoTrackerLuaCrowdControlMenuItem = new MenuItem - { - Header = "Lua Auto Tracker (Crowd Control Script)", - IsCheckable = true - }; - _autoTrackerLuaCrowdControlMenuItem.Click += (sender, e) => - { - TrackerBase.AutoTracker?.SetConnector(_options.GeneralOptions.SnesConnectorSettings, SnesConnectorType.LuaCrowdControl); - }; - menu.Items.Add(_autoTrackerLuaCrowdControlMenuItem); - - _autoTrackerLuaEmoTrackerMenuItem = new MenuItem - { - Header = "Lua Auto Tracker (EmoTracker Script)", - IsCheckable = true - }; - _autoTrackerLuaEmoTrackerMenuItem.Click += (sender, e) => - { - TrackerBase.AutoTracker?.SetConnector(_options.GeneralOptions.SnesConnectorSettings, SnesConnectorType.LuaEmoTracker); - }; - menu.Items.Add(_autoTrackerLuaEmoTrackerMenuItem); - - var folder = new MenuItem - { - Header = "Show Auto Tracker Scripts Folder", - }; - folder.Click += (sender, e) => - { - var path = _options.AutoTrackerScriptsOutputPath; - if (string.IsNullOrEmpty(path)) - { - path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SMZ3CasRandomizer", "AutoTrackerScripts"); - } - Process.Start("explorer.exe", $"/select,\"{path}\""); - }; - menu.Items.Add(folder); - - var help = new MenuItem - { - Header = "Open Auto Tracker Help", - }; - help.Click += (sender, e) => - { - OpenAutoTrackerHelp(); - }; - menu.Items.Add(help); - - return menu; - } - - public void InitializeAutoTracker() - { - var menu = CreateAutoTrackerMenu(true); - StatusBarAutoTrackerEnabled.ContextMenu = menu; - StatusBarAutoTrackerConnected.ContextMenu = menu; - StatusBarAutoTrackerDisabled.ContextMenu = menu; - - if (TrackerBase.AutoTracker == null) - { - _logger.LogError("Auto tracker not found"); - return; - } - - TrackerBase.AutoTracker.AutoTrackerEnabled += (sender, e) => Dispatcher.Invoke(UpdateAutoTrackerMenu); - TrackerBase.AutoTracker.AutoTrackerDisabled += (sender, e) => Dispatcher.Invoke(UpdateAutoTrackerMenu); - TrackerBase.AutoTracker.AutoTrackerConnected += (sender, e) => Dispatcher.Invoke(UpdateAutoTrackerMenu); - TrackerBase.AutoTracker.AutoTrackerDisconnected += (sender, e) => Dispatcher.Invoke(UpdateAutoTrackerMenu); - - TrackerBase.AutoTracker.SetConnector(_options.GeneralOptions.SnesConnectorSettings); - } - - private void UpdateAutoTrackerMenu() - { - if (TrackerBase.AutoTracker == null) return; - StatusBarAutoTrackerDisabled.Visibility = !TrackerBase.AutoTracker.IsEnabled ? Visibility.Visible : Visibility.Collapsed; - StatusBarAutoTrackerEnabled.Visibility = TrackerBase.AutoTracker.IsEnabled && !TrackerBase.AutoTracker.IsConnected ? Visibility.Visible : Visibility.Collapsed; - StatusBarAutoTrackerConnected.Visibility = TrackerBase.AutoTracker.IsEnabled && TrackerBase.AutoTracker.IsConnected ? Visibility.Visible : Visibility.Collapsed; - if (_autoTrackerDisableMenuItem != null) - _autoTrackerDisableMenuItem.IsChecked = - TrackerBase.AutoTracker?.ConnectorType == SnesConnectorType.None; - if (_autoTrackerLuaMenuItem != null) - _autoTrackerLuaMenuItem.IsChecked = - TrackerBase.AutoTracker?.ConnectorType == SnesConnectorType.Lua; - if (_autoTrackerUSB2SNESMenuItem != null) - _autoTrackerUSB2SNESMenuItem.IsChecked = - TrackerBase.AutoTracker?.ConnectorType == SnesConnectorType.Usb2Snes; - if (_autoTrackerLuaCrowdControlMenuItem != null) - _autoTrackerLuaCrowdControlMenuItem.IsChecked = - TrackerBase.AutoTracker?.ConnectorType == SnesConnectorType.LuaCrowdControl; - if (_autoTrackerLuaEmoTrackerMenuItem != null) - _autoTrackerLuaEmoTrackerMenuItem.IsChecked = - TrackerBase.AutoTracker?.ConnectorType == SnesConnectorType.LuaEmoTracker; - } - - private void UpdateStats(TrackerEventArgs e) - { - if (e.Confidence != null) - StatusBarConfidence.Content = $"{e.Confidence:P2}"; - StatusBarRecognizedPhrase.ToolTip = $"“{e.Phrase}”"; - RecognizedPhraseText.Text = $"“{e.Phrase}”"; - } - - private void ResetGridSize() - { - var columns = _layout.GridLocations.Max(x => x.Column); - - TrackerGrid.ColumnDefinitions.Clear(); - for (var i = 0; i <= columns; i++) - TrackerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(GridItemPx + GridItemMargin) }); - - var rows = _layout.GridLocations.Max(x => x.Row); - - TrackerGrid.RowDefinitions.Clear(); - for (var i = 0; i <= rows; i++) - TrackerGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(GridItemPx + GridItemMargin) }); - } - - private async void Window_Closing(object sender, CancelEventArgs e) - { - if (TrackerBase.IsDirty) - { - if (TrackerBase.World.Config.MultiWorld) - { - await SaveStateAsync(); - } - else if (MessageBox.Show("You have unsaved changes in your tracker. Do you want to save?", "SMZ3 Cas’ Randomizer", - MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes) - { - await SaveStateAsync(); - } - } - TrackerBase.StopTracking(); - _dispatcherTimer.Stop(); - Smz3.UI.Legacy.App.SaveWindowPositionAndSize(this); - } - - private void Window_Closed(object sender, EventArgs e) - { - _locationsWindow?.Close(); - _locationsWindow = null; - _trackerMapWindow?.Close(); - _trackerMapWindow = null; - _msuTrackWindow?.Close(true); - _msuTrackWindow = null; - _msuTrackWindow?.Dispose(); - - if (_tracker is IDisposable disposable) - { - disposable.Dispose(); - } - } - - private void LocationsMenuItem_Click(object sender, RoutedEventArgs e) - { - if (_locationsWindow != null && Application.Current.Windows.OfType().Any()) - { - _locationsWindow.Activate(); - } - else if (_locationSyncer != null) - { - _locationsWindow = new TrackerLocationsWindow(_locationSyncer, _uiService); - _locationsWindow.Show(); - } - else - { - ShowErrorWindow("Unable to open locations window."); - } - } - - private void HelpMenuItem_Click(object sender, RoutedEventArgs e) - { - if (_trackerHelpWindow != null && Application.Current.Windows.OfType().Any()) - { - _trackerHelpWindow.Activate(); - } - else - { - _trackerHelpWindow = new TrackerHelpWindow(TrackerBase); - _trackerHelpWindow.Show(); - } - } - - private void AutoTrackerMenuItem_Click(object sender, RoutedEventArgs e) - { - OpenAutoTrackerHelp(); - } - - private async void LoadSavedStateMenuItem_Click(object sender, RoutedEventArgs e) - { - // If there is a valid rom, then load the state from the db - if (GeneratedRom.IsValid(Rom)) - { - var romPath = Path.Combine(_options.RomOutputPath, Rom.RomPath); - await Task.Run(() => TrackerBase.Load(Rom, romPath)); - - TrackerBase.StartTimer(true); - if (_dispatcherTimer.IsEnabled) - { - _dispatcherTimer.Start(); - } - } - else - { - ShowErrorWindow("Could not save tracker state."); - } - } - - private async Task SaveStateAsync() - { - // If there is a rom, save it to the database - if (GeneratedRom.IsValid(TrackerBase.Rom)) - { - await TrackerBase.SaveAsync(); - } - - SavedState?.Invoke(this, EventArgs.Empty); - } - - private async void SaveStateMenuItem_Click(object sender, RoutedEventArgs e) - { - await SaveStateAsync(); - } - - private void StatusBarTimer_MouseDoubleClick(object sender, MouseButtonEventArgs e) - { - // Reset timer on double click - TrackerBase.ResetTimer(); - } - - private void StatusBarTimer_MouseRightButtonUp(object sender, MouseButtonEventArgs e) - { - // Pause/resume timer on right click - TrackerBase.ToggleTimer(); - } - - /// - /// Double clicking on the status bar icon to disable voice recognition - /// - /// - /// - private void StatusBarStatusBarConfidence_MouseDoubleClick(object sender, MouseButtonEventArgs e) - { - TrackerBase.DisableVoiceRecognition(); - StatusBarConfidence.Visibility = TrackerBase.VoiceRecognitionEnabled ? Visibility.Visible : Visibility.Collapsed; - StatusBarVoiceDisabled.Visibility = TrackerBase.VoiceRecognitionEnabled ? Visibility.Collapsed : Visibility.Visible; - } - - /// - /// Double clicking on the status bar icon to attempt to enable voice recognition - /// - /// - /// - private void StatusBarVoiceDisabled_MouseDoubleClick(object sender, MouseButtonEventArgs e) - { - if (TrackerBase.Options.SpeechRecognitionMode == SpeechRecognitionMode.Disabled) - { - return; - } - - if (!TrackerBase.MicrophoneInitialized) - { - if (!TrackerBase.InitializeMicrophone()) - { - ShowNoMicrophoneWarning(); - return; - } - else if (!TrackerBase.MicrophoneInitializedAsDesiredDevice) - { - ShowFallbackMicrophoneWarning(); - } - } - - try - { - TrackerBase.EnableVoiceRecognition(); - } - catch (InvalidOperationException ex) - { - _logger.LogError(ex, "Error enabling voice recognition"); - ShowModuleWarning(); - } - - StatusBarConfidence.Visibility = TrackerBase.VoiceRecognitionEnabled ? Visibility.Visible : Visibility.Collapsed; - StatusBarVoiceDisabled.Visibility = TrackerBase.VoiceRecognitionEnabled ? Visibility.Collapsed : Visibility.Visible; - } - - /// - /// Displays a warning to the user that we could not detect a microphone - /// - private void ShowNoMicrophoneWarning() - { - MessageBox.Show(this, "There is a problem with your microphone. Please check your sound settings to ensure you have a microphone enabled.\n\n" + - "Voice recognition has been disabled. You can attempt to re-enable it by double clicking on the Voice Disabled text.", "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Warning); - } - - private void ShowFallbackMicrophoneWarning() - { - MessageBox.Show(this, "Could not locate requested audio input device. Falling back to default windows microphone.", "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Warning); - } - - private void ShowModuleWarning() - { - MessageBox.Show(this, "There was a problem with loading one or more of the tracker modules.\n" + - "Some tracking functionality may be limited.", "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Warning); - } - - private void ShowErrorWindow(string baseErrorMessage) - { - var logFileLocation = Environment.ExpandEnvironmentVariables("%LocalAppData%\\SMZ3CasRandomizer"); - MessageBox.Show($"{baseErrorMessage}\n\n" + - $"Please try again. If the problem persists, please see the log files in '{logFileLocation}' and " + - "post them in Discord or on GitHub at https://github.com/TheTrackerCouncil/SMZ3Randomizer/issues.", "SMZ3 Cas’ Randomizer", MessageBoxButton.OK, MessageBoxImage.Error); - } - - private void MapMenuItem_Click(object sender, RoutedEventArgs e) - { - if (_trackerMapWindow != null && Application.Current.Windows.OfType().Any()) - { - _trackerMapWindow.Activate(); - } - else if (_locationSyncer != null) - { - var scope = _serviceProvider.CreateScope(); - _trackerMapWindow = scope.ServiceProvider.GetRequiredService(); - _trackerMapWindow.Syncer = _locationSyncer; - _trackerMapWindow.Show(); - } - else - { - ShowErrorWindow("Unable to open map window."); - } - } - - protected record Overlay(string FileName, int X, int Y) - { - public Origin OriginPoint { get; init; } - } - - protected void OpenAutoTrackerHelp() - { - if (_autoTrackerHelpWindow != null && Application.Current.Windows.OfType().Any()) - { - _autoTrackerHelpWindow.Activate(); - } - else - { - _autoTrackerHelpWindow = new AutoTrackerWindow(); - _autoTrackerHelpWindow.Show(); - } - } - - private void LayoutMenuItem_Click(object sender, RoutedEventArgs e) - { - if (sender is MenuItem { Tag: UILayout layout }) - { - _layout = layout; - _options.GeneralOptions.SelectedLayout = layout.Name; - _options.Save(); - ResetGridSize(); - RefreshGridItems(); - foreach (var layoutMenuItem in LayoutMenu.Items.OfType()) - { - layoutMenuItem.IsChecked = layoutMenuItem.Tag == _layout; - } - } - } - - private void CurrentSongMenuItem_OnClick(object sender, RoutedEventArgs e) - { - if (_msuTrackWindow != null && Application.Current.Windows.OfType().Any()) - { - _msuTrackWindow.Activate(); - } - else - { - _msuTrackWindow = new MsuTrackWindow(); - _msuTrackWindow.Init(_serviceProvider.GetRequiredService(), _options); - _msuTrackWindow.Show(); - } - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/WpfServiceCollectionExtensions.cs b/src/TrackerCouncil.Smz3.UI.Legacy/WpfServiceCollectionExtensions.cs deleted file mode 100644 index 29dc569aa..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/WpfServiceCollectionExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; -using System.Reflection; -using System.Windows; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -namespace TrackerCouncil.Smz3.UI.Legacy; - -internal static class WpfServiceCollectionExtensions -{ - public static IServiceCollection AddWindows(this IServiceCollection services) - => services.AddWindows(typeof(TAssembly).Assembly); - - public static IServiceCollection AddWindows(this IServiceCollection services, Assembly assembly) - { - var windows = assembly.GetTypes() - .Where(x => x.IsAssignableTo(typeof(Window))); - foreach (var window in windows) - { - if (window.GetCustomAttribute() != null) - continue; - - services.TryAddScoped(window); - } - - return services; - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/app.manifest b/src/TrackerCouncil.Smz3.UI.Legacy/app.manifest deleted file mode 100644 index d0b6c562c..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/app.manifest +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PerMonitorV2,PerMonitor - true - true - - - - - - - - - - - - - - diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/appSettings.Development.json b/src/TrackerCouncil.Smz3.UI.Legacy/appSettings.Development.json deleted file mode 100644 index 5cbcccd2f..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/appSettings.Development.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Randomizer": "Trace", - "Microsoft": "Information", - "Randomizer.SMZ3.Tracking.AutoTracking.LuaConnector": "Debug", - "Randomizer.SMZ3.Tracking.AutoTracking.USB2SNESConnector": "Debug", - "Randomizer.SMZ3.Generation": "Debug" - } - } -} diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/chozo20.ico b/src/TrackerCouncil.Smz3.UI.Legacy/chozo20.ico deleted file mode 100644 index c147e0b43..000000000 Binary files a/src/TrackerCouncil.Smz3.UI.Legacy/chozo20.ico and /dev/null differ diff --git a/src/TrackerCouncil.Smz3.UI.Legacy/msu-randomizer-settings.yml b/src/TrackerCouncil.Smz3.UI.Legacy/msu-randomizer-settings.yml deleted file mode 100644 index 79db3ee12..000000000 --- a/src/TrackerCouncil.Smz3.UI.Legacy/msu-randomizer-settings.yml +++ /dev/null @@ -1,11 +0,0 @@ -UserOptionsFilePath: "%LocalAppData%\\SMZ3CasRandomizer\\msu-user-settings.yml" -SaveDataDirectory: "%LocalAppData%" -MsuCachePath: "%LocalAppData%\\SMZ3CasRandomizer" -ContinuousReshuffleSeconds: 60 -MsuWindowDisplayRandomButton: false -MsuWindowDisplayShuffleButton: false -MsuWindowDisplayContinuousButton: false -MsuWindowDisplayOptionsButton: false -MsuWindowDisplaySelectButton: true -ForcedMsuType: "Super Metroid / A Link to the Past Combination Randomizer" -MsuWindowTitle: SMZ3 Cas' Randomizer diff --git a/src/TrackerCouncil.Smz3.UI/Services/MultiplayerStatusWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/MultiplayerStatusWindowService.cs index be025dbfc..f3631e76c 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/MultiplayerStatusWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/MultiplayerStatusWindowService.cs @@ -131,15 +131,28 @@ public void OpenSpoilerLog() public void LaunchTracker() { _trackerWindow = sharedCrossplatformService.LaunchTracker(_model.GeneratedRom); - if (_trackerWindow != null) - { - _trackerWindow.Closed += (_, _) => _trackerWindow = null; - } + FinalizeLaunch(); } public void LaunchRom() { - sharedCrossplatformService.LaunchRom(_model.GeneratedRom); + _trackerWindow = sharedCrossplatformService.LaunchRom(_model.GeneratedRom); + FinalizeLaunch(); + } + + private void FinalizeLaunch() + { + if (_trackerWindow != null) + { + _trackerWindow.Closed += (_, _) => + { + _trackerWindow = null; + if (!_window.IsVisible) + { + _window.Show(MessageWindow.GlobalParentWindow!); + } + }; + } } private async void MultiplayerClientServiceOnGameStarted(List playerGenerationData) diff --git a/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs b/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs index b57092f13..f28f7e8b6 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/SharedCrossplatformService.cs @@ -191,19 +191,20 @@ public void OpenFolder(GeneratedRom? rom) } } - public void LaunchRom(GeneratedRom? rom) + public TrackerWindow? LaunchRom(GeneratedRom? rom) { if (rom == null) { DisplayError("Invalid rom"); - return; + return null; } var launchButtonOptions = Options.GeneralOptions.LaunchButtonOption; + TrackerWindow? trackerWindow = null; if (launchButtonOptions is LaunchButtonOptions.PlayAndTrack or LaunchButtonOptions.OpenFolderAndTrack or LaunchButtonOptions.TrackOnly) { - LaunchTracker(rom); + trackerWindow = LaunchTracker(rom); } if (launchButtonOptions is LaunchButtonOptions.OpenFolderAndTrack or LaunchButtonOptions.OpenFolderOnly) @@ -215,6 +216,8 @@ public void LaunchRom(GeneratedRom? rom) { PlayRom(rom); } + + return trackerWindow; } public void CopyRomSeed(GeneratedRom? rom) diff --git a/src/TrackerCouncil.Smz3.UI/Services/SoloRomListService.cs b/src/TrackerCouncil.Smz3.UI/Services/SoloRomListService.cs index 43560d1fa..bcf27b86e 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/SoloRomListService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/SoloRomListService.cs @@ -10,6 +10,7 @@ using TrackerCouncil.Smz3.Data.Interfaces; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.Services; +using TrackerCouncil.Smz3.SeedGenerator.Generation; using TrackerCouncil.Smz3.UI.ViewModels; using TrackerCouncil.Smz3.UI.Views; @@ -18,7 +19,8 @@ namespace TrackerCouncil.Smz3.UI.Services; public class SoloRomListService(IRomGenerationService romGenerationService, IGameDbService gameDbService, OptionsFactory optionsFactory, - SharedCrossplatformService sharedCrossplatformService) : ControlService + SharedCrossplatformService sharedCrossplatformService, + RomParserService romParserService) : ControlService { private SoloRomListViewModel _model = new(); private SoloRomListPanel _panel = null!; @@ -143,6 +145,62 @@ public void LaunchTracker(GeneratedRomViewModel rom) sharedCrossplatformService.LaunchTracker(rom.Rom); } + public async Task OpenArchipelagoModeAsync() + { + var storageItem = await CrossPlatformTools.OpenFileDialogAsync(ParentWindow, FileInputControlType.OpenFile, + "Rom file (*.sfc)|*.sfc|All files (*.*)|*.*", "/home/matt/Games/Randomizers/Archipelago"); + + var pathString = HttpUtility.UrlDecode(storageItem?.Path.AbsolutePath); + + if (pathString == null) + { + return; + } + + romParserService.ParseRomFile(pathString); + + // archipelagoScannerService.ScanArchipelagoRom(rom); + + /*var rom = await File.ReadAllBytesAsync(pathString); + romGenerationService.ApplyCasPatches(rom, new PatchOptions() + { + CasPatches = new CasPatches() + { + AimAnyButton = true, + DisableFlashing = true, + DisableScreenShake = true, + EasierWallJumps = true, + FastDoors = true, + FastElevators = true, + InfiniteSpaceJump = true, + MetroidAutoSave = true, + NerfedCharge = true, + SnapMorph = true, + Respin = true, + RefillAtSaveStation = true, + SandPitPlatforms = true, + }, + MetroidControls = new MetroidControlOptions() + { + RunButtonBehavior = RunButtonBehavior.AutoRun, + ItemCancelBehavior = ItemCancelBehavior.HoldSupersOnly, + AimButtonBehavior = AimButtonBehavior.UnifiedAim, + Shoot = MetroidButton.Y, + Jump = MetroidButton.B, + Dash = MetroidButton.X, + ItemSelect = MetroidButton.Select, + ItemCancel = MetroidButton.R, + AimUp = MetroidButton.L, + AimDown = MetroidButton.R + } + }); + + var folder = Path.GetDirectoryName(pathString)!; + var fileName = Path.GetFileNameWithoutExtension(pathString)+"_updated"; + var extension = Path.GetExtension(pathString); + await File.WriteAllBytesAsync(Path.Combine(folder, fileName + extension), rom);*/ + } + private void OpenMessageWindow(string message, MessageWindowIcon icon = MessageWindowIcon.Error, MessageWindowButtons buttons = MessageWindowButtons.OK) { var window = new MessageWindow(new MessageWindowRequest() diff --git a/src/TrackerCouncil.Smz3.UI/Services/TrackerLocationsWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/TrackerLocationsWindowService.cs index e4e4abcef..af7ccad03 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/TrackerLocationsWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/TrackerLocationsWindowService.cs @@ -1,68 +1,72 @@ using System.Collections.Generic; using System.Linq; +using Avalonia.Threading; using AvaloniaControls.ControlServices; +using AvaloniaControls.Services; +using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Abstractions; +using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Tracking.Services; using TrackerCouncil.Smz3.UI.ViewModels; namespace TrackerCouncil.Smz3.UI.Services; -public class TrackerLocationsWindowService(TrackerBase trackerBase, IWorldService worldService, IUIService uiService, IItemService itemService) : ControlService +public class TrackerLocationsWindowService(TrackerBase trackerBase, IWorldQueryService worldQueryService, IUIService uiService) : ControlService { - private TrackerLocationsViewModel _model = new(); + private readonly TrackerLocationsViewModel _model = new(); + private RegionViewModel? _lastRegion; + private List _allRegions = []; public TrackerLocationsViewModel GetViewModel() { - UpdateModel(); + InitMarkedLocations(); + InitHintTiles(); + InitRegions(); - trackerBase.MarkedLocationsUpdated += (_, _) => UpdateModel(); - trackerBase.LocationCleared += (_, _) => UpdateModel(); - trackerBase.DungeonUpdated += (_, _) => UpdateModel(); - trackerBase.ItemTracked += (_, _) => UpdateModel(); - trackerBase.ActionUndone += (_, _) => UpdateModel(); - trackerBase.StateLoaded += (_, _) => UpdateModel(); - trackerBase.BossUpdated += (_, _) => UpdateModel(); - trackerBase.HintTileUpdated += (_, _) => UpdateModel(); + trackerBase.LocationTracker.LocationMarked += (_, args) => + { + AddUpdateMarkedLocation(args.Location); + }; + + _model.FinishedLoading = true; return _model; } - public void UpdateModel() + public void UpdateShowOutOfLogic(bool showOutOfLogic) { - var markedLocations = new List(); + // Because the map update is snappy while this is slow, run this in a separate + // thread to avoid locking up the map + ITaskService.Run(() => + { + _model.ShowOutOfLogic = showOutOfLogic; - var progressionWithoutKeys = itemService.GetProgression(false); - var progressionWithKeys = itemService.GetProgression(true); + foreach (var region in _allRegions) + { + foreach (var location in region.Locations) + { + location.ShowOutOfLogic = showOutOfLogic; + } - foreach (var markedLocation in worldService.MarkedLocations()) - { - var markedItemType = markedLocation.State.MarkedItem ?? ItemType.Nothing; - if (markedItemType == ItemType.Nothing) continue; - var item = itemService.FirstOrDefault(markedItemType); - if (item == null) continue; - markedLocations.Add(new MarkedLocationViewModel(markedLocation, item, uiService.GetSpritePath(item), - markedLocation.IsAvailable(progressionWithoutKeys))); - } + region.ShowOutOfLogic = showOutOfLogic; + region.UpdateLocationCount(); + region.SortLocations(); + } - _model.MarkedLocations = markedLocations; + ShowSortedRegions(); + }); - _model.HintTiles = worldService.ViewedHintTiles - .Where(x => x.Locations?.Count() > 1) - .Select(x => new HintTileViewModel(x)) - .ToList(); + } - var locations = worldService.Locations(unclearedOnly: true, outOfLogic: _model.ShowOutOfLogic, assumeKeys: true, - sortByTopRegion: true, regionFilter: _model.Filter).ToList(); + public void UpdateFilter(RegionFilter filter) + { + _model.Filter = filter; - _model.Regions = locations.Select(x => x.Region).Distinct().Select(region => new RegionViewModel() + foreach (var region in _allRegions) { - RegionName = region.ToString(), - Locations = locations.Where(loc => loc.Region == region) - .Select(loc => new LocationViewModel(loc, loc.IsAvailable(progressionWithoutKeys), - loc.IsAvailable(progressionWithKeys))) - .ToList() - }).ToList(); + region.MatchesFilter = region.Region?.MatchesFilter(filter) == true; + } } public void ClearLocation(LocationViewModel model) @@ -71,6 +75,96 @@ public void ClearLocation(LocationViewModel model) { return; } - trackerBase.Clear(model.Location); + trackerBase.LocationTracker.Clear(model.Location); + } + + private void InitMarkedLocations() + { + foreach (var markedLocation in worldQueryService.MarkedLocations()) + { + AddUpdateMarkedLocation(markedLocation); + } + } + + private void AddUpdateMarkedLocation(Location location) + { + if (location.MarkedItem == null) + { + var previousModel = _model.MarkedLocations.FirstOrDefault(x => x.Location == location); + if (previousModel != null) + { + location.AccessibilityUpdated -= previousModel.LocationOnAccessibilityUpdated; + _model.MarkedLocations.Remove(previousModel); + } + } + else + { + if (location.MarkedItem == ItemType.Nothing) return; + var item = worldQueryService.FirstOrDefault(location.MarkedItem.Value); + if (item == null) return; + + var previousModel = _model.MarkedLocations.FirstOrDefault(x => x.Location == location); + if (previousModel != null) + { + location.AccessibilityUpdated -= previousModel.LocationOnAccessibilityUpdated; + _model.MarkedLocations.Remove(previousModel); + } + + var newModel = new MarkedLocationViewModel(location, item, uiService.GetSpritePath(item)); + location.AccessibilityUpdated += newModel.LocationOnAccessibilityUpdated; + _model.MarkedLocations.Add(newModel); + } + } + + private void InitHintTiles() + { + var world = worldQueryService.World; + var hintTiles = new List(); + + foreach (var worldHintTile in world.HintTiles.Where(x => x.Locations?.Count() > 1)) + { + var locationIds = worldHintTile.Locations!.ToHashSet(); + var locations = world.Locations.Where(x => locationIds.Contains(x.Id)); + hintTiles.Add(new HintTileViewModel(worldHintTile, locations)); + } + + _model.HintTiles = hintTiles; + } + + private void InitRegions() + { + var regions = worldQueryService.World.Regions; + var regionModels = new List(); + + foreach (var region in regions) + { + var regionModel = new RegionViewModel(region); + regionModels.Add(regionModel); + regionModel.RegionUpdated += (_, _) => + { + if (_lastRegion == regionModel) return; + + regionModel.SortOrder = 1; + if (_lastRegion != null) + { + _lastRegion.SortOrder = 0; + } + + _lastRegion = regionModel; + ShowSortedRegions(); + }; + } + + _allRegions = regionModels; + ShowSortedRegions(); + _lastRegion = _model.Regions.First(); + _lastRegion.SortOrder = 1; + } + + private void ShowSortedRegions() + { + _model.Regions = _allRegions.Where(x => x.VisibleLocations > 0).OrderByDescending(x => x.SortOrder) + .ThenByDescending(x => x.InLogicLocationCount) + .ToList(); } } diff --git a/src/TrackerCouncil.Smz3.UI/Services/TrackerMapWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/TrackerMapWindowService.cs index 1039a2499..b6476fbdf 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/TrackerMapWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/TrackerMapWindowService.cs @@ -5,13 +5,13 @@ using Avalonia; using AvaloniaControls; using AvaloniaControls.ControlServices; +using AvaloniaControls.Services; using TrackerCouncil.Smz3.Abstractions; using TrackerCouncil.Smz3.Data.Configuration.ConfigFiles; using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.WorldData; using TrackerCouncil.Smz3.Data.WorldData.Regions; -using TrackerCouncil.Smz3.Data.WorldData.Regions.Zelda; using TrackerCouncil.Smz3.SeedGenerator.Contracts; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Tracking.Services; @@ -23,11 +23,10 @@ public class TrackerMapWindowService( TrackerBase tracker, TrackerMapConfig trackerMapConfig, IWorldAccessor worldAccessor, - IWorldService worldService, - IItemService itemService) : ControlService + IWorldQueryService worldQueryService) : ControlService { - private TrackerMapWindowViewModel _model = new(); - private Dictionary> _mapLocations = new(); + private readonly TrackerMapWindowViewModel _model = new(); + private readonly Dictionary> _mapLocations = new(); private string _markedImageGoodPath = ""; private string _markedImageUselessPath = ""; @@ -35,255 +34,251 @@ public TrackerMapWindowViewModel GetViewModel() { _model.Maps = trackerMapConfig.Maps.ToList(); + // The map initializes really slowly, so actually initialize the data in a separate thread + ITaskService.Run(InitViewModelData); + + return _model; + } + + private void InitViewModelData() + { _markedImageGoodPath = Path.Combine(Sprite.SpritePath, "Maps", "marked_good.png"); _markedImageUselessPath = Path.Combine(Sprite.SpritePath, "Maps", "marked_useless.png"); - var locations = worldAccessor.World.Locations.ToList(); + var allLocations = worldAccessor.World.Locations.ToList(); // To make querying easier for the viewmodel, compile a list of all // locations on each map by combining all of their regions, making - // any location adjusments needed + // any location adjustments needed foreach (var map in _model.Maps) { var locationModels = new List(); - foreach (var mapRegion in map.Regions) + foreach (var mapLocation in map.Regions) { - var configRegion = trackerMapConfig.Regions.First(region => mapRegion.Name == region.Name); - var worldRegion = worldService.Region(configRegion.TypeName) ?? throw new InvalidOperationException(); + var mapRegion = trackerMapConfig.Regions.First(region => mapLocation.Name == region.Name); - var regionLocations = locations.Where(loc => configRegion.TypeName == loc.Region.GetType().FullName).ToList(); - - if (configRegion.Rooms != null) + if (mapLocation.Type == TrackerMapLocation.MapLocationType.Item) { - locationModels.AddRange(configRegion.Rooms.Select(room => - new TrackerMapLocationViewModel(configRegion, - mapRegion, room, worldRegion, - regionLocations.Where(loc => room.Name == loc.Name || - room.Name == loc.Room?.Name || - configRegion.Name == room.Name).ToList()))); + locationModels.AddRange(GetItemLocationModels(mapRegion, mapLocation, allLocations)); } - if (configRegion is { BossX: not null, BossY: not null }) + if (mapRegion is { BossX: not null, BossY: not null }) { - locationModels.Add(new TrackerMapLocationViewModel(configRegion, mapRegion, worldRegion)); + locationModels.Add(GetBossLocationModel(mapRegion, mapLocation)); } - if (configRegion.Doors != null) + if (mapRegion.Doors != null && worldAccessor.World.Config.MetroidKeysanity) { - locationModels.AddRange(configRegion.Doors.Select(door => - new TrackerMapLocationViewModel(configRegion, mapRegion, worldRegion, door)).ToList()); + locationModels.AddRange(GetDoorLocationModels(mapRegion, mapLocation)); } } _mapLocations[map] = locationModels; } - tracker.LocationCleared += (_, _) => UpdateLocations(); - tracker.DungeonUpdated += (_, _) => UpdateLocations(); - tracker.ItemTracked += (_, _) => UpdateLocations(); - tracker.ActionUndone += (_, _) => UpdateLocations(); - tracker.StateLoaded += (_, _) => UpdateLocations(); - tracker.BossUpdated += (_, _) => UpdateLocations(); - tracker.MapUpdated += TrackerOnMapUpdated; - tracker.MarkedLocationsUpdated += (_, _) => UpdateLocations(); + tracker.GameStateTracker.MapUpdated += TrackerOnMapUpdated; _model.SelectedMap = _model.Maps.Last(); UpdateMap(); - return _model; + + _model.FinishedLoading = true; } - private void TrackerOnMapUpdated(object? sender, EventArgs e) + private List GetItemLocationModels(TrackerMapRegion mapRegion, TrackerMapLocation mapLocation, List allLocations) { - if (_model.SelectedMap?.ToString() == tracker.CurrentMap) - { - return; - } + var worldRegion = worldQueryService.Region(mapRegion.TypeName) ?? throw new InvalidOperationException(); + var regionLocations = allLocations.Where(loc => mapRegion.TypeName == loc.Region.GetType().FullName).ToList(); + var toReturn = new List(); - _model.Locations = []; - var newMap = _model.Maps.FirstOrDefault(x => x.ToString() == tracker.CurrentMap); - if (newMap != null) + foreach (var room in mapRegion.Rooms!) { - _model.SelectedMap = newMap; - UpdateMap(); + var roomLocations = regionLocations.Where(loc => + room.Name == loc.Name || room.Name == loc.Room?.Name || mapRegion.Name == room.Name).ToList(); + + var roomModel = new TrackerMapLocationViewModel(mapRegion, mapLocation, room, worldRegion, roomLocations); + + foreach (var location in roomLocations) + { + location.AccessibilityUpdated += (_, _) => UpdateItemLocationModel(roomModel, roomLocations); + } + + UpdateItemLocationModel(roomModel, roomLocations); + + toReturn.Add(roomModel); } + + return toReturn; } - public void UpdateLocations(List? locations = null) + private void UpdateItemLocationModel(TrackerMapLocationViewModel model, List? locations) { - if (_model.SelectedMap == null) + var region = model.Region; + var image = ""; + var displayNumber = 0; + + locations ??= region.Locations.Where(loc => + model.Name == loc.Name || model.Name == loc.Room?.Name || region.Name == model.Name).ToList(); + + var locationStatuses = locations + .Select(x => (Location: x, Status: x.GetKeysanityAdjustedAccessibility())).ToList(); + + var clearableLocationsCount = displayNumber = locationStatuses.Count(x => x.Status == Accessibility.Available); + var relevantLocationsCount = locationStatuses.Count(x => x.Status == Accessibility.Relevant); + var outOfLogicLocationsCount = locationStatuses.Count(x => x.Status == Accessibility.OutOfLogic); + var unclearedLocationsCount = locationStatuses.Count(x => x.Status != Accessibility.Cleared); + + if (clearableLocationsCount > 0 && clearableLocationsCount == unclearedLocationsCount) { - return; + model.IsInLogic = true; + image = "accessible.png"; + } + else if (relevantLocationsCount > 0 && relevantLocationsCount + clearableLocationsCount == unclearedLocationsCount) + { + model.IsInLogic = true; + image = "relevant.png"; + } + else if (relevantLocationsCount > 0 && clearableLocationsCount == 0) + { + model.IsInLogic = true; + image = "partial_relevance.png"; + } + else if (clearableLocationsCount > 0 && clearableLocationsCount < unclearedLocationsCount) + { + model.IsInLogic = true; + image = "partial.png"; + } + else if (clearableLocationsCount == 0 && outOfLogicLocationsCount > 0) + { + model.IsInLogic = false; + image = "outoflogic.png"; } - locations ??= _model.Locations; + // If there are any valid locations, see if anything was marked + if (clearableLocationsCount > 0 || relevantLocationsCount > 0) + { + var usefulness = locationStatuses + .Where(x => x.Status is Accessibility.Available or Accessibility.Relevant) + .Max(x => x.Location.MarkedUsefulness); - if (locations.Count == 0) + if (usefulness is null) + { + model.MarkedVisibility = false; + } + else if (usefulness == LocationUsefulness.Useless) + { + model.MarkedImagePath = _markedImageUselessPath; + model.MarkedVisibility = true; + } + else + { + model.MarkedImagePath = _markedImageGoodPath; + model.MarkedVisibility = true; + } + } + else { - return; + model.MarkedVisibility = false; } - var world = locations.First().Region.World; - var hintTileLocations = world.ActiveHintTileLocations.ToList(); + UpdateLocationModel(model, image, displayNumber); + } - foreach (var location in locations) + private TrackerMapLocationViewModel GetBossLocationModel(TrackerMapRegion mapRegion, TrackerMapLocation mapLocation) + { + var worldRegion = worldQueryService.Region(mapRegion.TypeName) ?? throw new InvalidOperationException(); + var model = new TrackerMapLocationViewModel(mapRegion, mapLocation, worldRegion); + + if (worldRegion is IHasBoss bossRegion) + { + bossRegion.Boss.UpdatedAccessibility += (_, _) => UpdateBossLocationModel(model); + bossRegion.Boss.UpdatedBossState += (_, _) => UpdateBossLocationModel(model); + } + + if (worldRegion is IHasReward rewardRegion) { - UpdateLocationModel(location, world, hintTileLocations); + rewardRegion.Reward.UpdatedAccessibility += (_, _) => UpdateBossLocationModel(model); + rewardRegion.Reward.UpdatedRewardState += (_, _) => UpdateBossLocationModel(model); } + + UpdateBossLocationModel(model); + return model; } - private void UpdateLocationModel(TrackerMapLocationViewModel location, World world, List hintTileLocations) + private void UpdateBossLocationModel(TrackerMapLocationViewModel location) { - var region = location.Region; var image = ""; - var displayNumber = 0; - if (location.Type == TrackerMapLocation.MapLocationType.Item) + + if (location.RewardRegion is { HasReceivedReward: false } rewardRegion && rewardRegion.GetKeysanityAdjustedBossAccessibility() == Accessibility.Available) + { + image = rewardRegion.MarkedReward.GetDescription().ToLowerInvariant() + ".png"; + location.IsInLogic = true; + } + else if (location.BossRegion is { BossDefeated: false } && location.BossRegion.GetKeysanityAdjustedBossAccessibility() == Accessibility.Available) + { + image = "boss.png"; + location.IsInLogic = true; + } + else { - var progression = itemService.GetProgression(!(region is HyruleCastle || region.World.Config.KeysanityForRegion(region))); - var locationStatuses = location.Locations!.Select(x => (Location: x.Location, Status: x.Location.GetStatus(progression))).ToList(); + location.IsInLogic = false; + } - var clearableLocationsCount = displayNumber = locationStatuses.Count(x => x.Status == LocationStatus.Available); - var relevantLocationsCount = locationStatuses.Count(x => x.Status == LocationStatus.Relevant); - var outOfLogicLocationsCount = _model.ShowOutOfLogicLocations ? locationStatuses.Count(x => x.Status == LocationStatus.OutOfLogic) : 0; - var unclearedLocationsCount = locationStatuses.Count(x => x.Status != LocationStatus.Cleared); + UpdateLocationModel(location, image); + } - if (clearableLocationsCount > 0 && clearableLocationsCount == unclearedLocationsCount) - { - image = "accessible.png"; - } - else if (relevantLocationsCount > 0 && relevantLocationsCount + clearableLocationsCount == unclearedLocationsCount) - { - image = "relevant.png"; - } - else if (relevantLocationsCount > 0 && clearableLocationsCount == 0) - { - image = "partial_relevance.png"; - } - else if (clearableLocationsCount > 0 && clearableLocationsCount < unclearedLocationsCount) - { - image = "partial.png"; - } - else if (clearableLocationsCount == 0 && outOfLogicLocationsCount > 0) - { - image = "outoflogic.png"; - } + private List GetDoorLocationModels(TrackerMapRegion mapRegion, TrackerMapLocation mapLocation) + { + var doors = mapRegion.Doors ?? throw new InvalidOperationException(); + var worldRegion = worldQueryService.Region(mapRegion.TypeName) ?? throw new InvalidOperationException(); + var toReturn = new List(); - // If there are any valid locations, see if anything was marked - if (clearableLocationsCount > 0 || relevantLocationsCount > 0) - { - var markedLocations = locationStatuses - .Where(x => x.Status is LocationStatus.Available or LocationStatus.Relevant && - hintTileLocations.Contains(x.Location.Id)).ToList(); + foreach (var door in doors) + { + var model = new TrackerMapLocationViewModel(mapRegion, mapLocation, worldRegion, door); + var item = worldQueryService.FirstOrDefault(door.Item) ?? throw new InvalidOperationException(); + item.UpdatedItemState += (_, _) => UpdateDoorLocationModel(model, item); + toReturn.Add(model); + UpdateDoorLocationModel(model, item); + } - if (markedLocations.Any()) - { - var activeLocationIds = markedLocations.Select(x => x.Location.Id); - var hintTile = world.HintTiles.FirstOrDefault(x => x.Locations?.Intersect(activeLocationIds).Any() == true); - if (hintTile?.Usefulness is LocationUsefulness.Mandatory or LocationUsefulness.Sword - or LocationUsefulness.NiceToHave) - { - location.MarkedImagePath = _markedImageGoodPath; - } - else if (hintTile?.Usefulness == LocationUsefulness.Useless) - { - location.MarkedImagePath = _markedImageUselessPath; - } - else if (markedLocations.Any(x => x.Location.Item.Type.IsPossibleProgression(x.Location.World.Config.ZeldaKeysanity, x.Location.World.Config.MetroidKeysanity))) - { - location.MarkedImagePath = _markedImageGoodPath; - } - else - { - location.MarkedImagePath = _markedImageUselessPath; - } - - location.MarkedVisibility = true; - } - // For marked items, we compare the marked items at the locations - else - { - markedLocations = locationStatuses - .Where(x => x.Status is LocationStatus.Available or LocationStatus.Relevant && - x.Location.State.MarkedItem != null).ToList(); - - if (markedLocations.Any()) - { - if (markedLocations.Any(x => x.Location.State.MarkedItem!.Value.IsPossibleProgression(x.Location.World.Config.ZeldaKeysanity, x.Location.World.Config.MetroidKeysanity))) - { - location.MarkedImagePath = _markedImageGoodPath; - } - else - { - location.MarkedImagePath = _markedImageUselessPath; - } - - location.MarkedVisibility = true; - } - else - { - location.MarkedVisibility = false; - } - } - } - else - { - location.MarkedVisibility = false; - } + return toReturn; + } + private void UpdateDoorLocationModel(TrackerMapLocationViewModel location, Item? item) + { + var image = ""; + item ??= worldQueryService.FirstOrDefault(location.Name); + if (item?.TrackingState > 0) + { + image = ""; } - else if (location.Type == TrackerMapLocation.MapLocationType.Boss) + else if (item?.Type.IsInCategory(ItemCategory.KeycardL1) == true) { - var progression = itemService.GetProgression(region); - var actualProgression = itemService.GetProgression(false); - if (location.BossRegion != null && location.BossRegion.Boss.State.Defeated != true && location.BossRegion.CanBeatBoss(progression)) - { - image = "boss.png"; - } - else if (location.RewardRegion != null && location.RewardRegion.Reward.State.Cleared != true) - { - var regionLocations = (IHasLocations)location.Region; - - // If the player can complete the region with the current actual progression - // or if they can access all locations in the dungeon (unless this is Castle Tower - // in Keysanity because it doesn't have a location for Aga himself) - if (location.RewardRegion.CanComplete(actualProgression) - || (regionLocations.Locations.All(x => x.IsAvailable(progression, true)) - && !(location.Region.Config.ZeldaKeysanity && location.RewardRegion is CastleTower))) - { - var dungeon = (IDungeon)location.Region; - image = dungeon.MarkedReward.GetDescription().ToLowerInvariant() + ".png"; - } - } + image = "door1.png"; } - else if (location.Type == TrackerMapLocation.MapLocationType.SMDoor && world.Config.MetroidKeysanity) + else if (item?.Type.IsInCategory(ItemCategory.KeycardL2) == true) { - var item = itemService.FirstOrDefault(location.Name); - if (item?.State.TrackingState > 0) - { - image = ""; - } - else if (item?.Type.IsInCategory(ItemCategory.KeycardL1) == true) - { - image = "door1.png"; - } - else if (item?.Type.IsInCategory(ItemCategory.KeycardL2) == true) - { - image = "door2.png"; - } - else if (item?.Type.IsInCategory(ItemCategory.KeycardBoss) == true) - { - image = "doorb.png"; - } + image = "door2.png"; } + else if (item?.Type.IsInCategory(ItemCategory.KeycardBoss) == true) + { + image = "doorb.png"; + } + + UpdateLocationModel(location, image); + } + private void UpdateLocationModel(TrackerMapLocationViewModel location, string image, int displayNumber = 0) + { if (!string.IsNullOrEmpty(image)) { location.ImagePath = Path.Combine(Sprite.SpritePath, "Maps", image); - location.IconVisibility = true; + location.HasImage = true; } else { - location.IconVisibility = false; + location.HasImage = false; } if (displayNumber > 1) @@ -298,6 +293,30 @@ private void UpdateLocationModel(TrackerMapLocationViewModel location, World wor } } + private void TrackerOnMapUpdated(object? sender, EventArgs e) + { + if (_model.SelectedMap?.ToString() == tracker.GameStateTracker.CurrentMap) + { + return; + } + + _model.Locations = []; + var newMap = _model.Maps.FirstOrDefault(x => x.ToString() == tracker.GameStateTracker.CurrentMap); + if (newMap != null) + { + _model.SelectedMap = newMap; + UpdateMap(); + } + } + + public void UpdateOutOfLogic() + { + foreach (var location in _mapLocations.Values.SelectMany(x => x)) + { + location.ShowOutOfLogic = _model.ShowOutOfLogicLocations; + } + } + public void UpdateMap() { var selectedMap = _model.SelectedMap; @@ -307,7 +326,6 @@ public void UpdateMap() } var locations = _mapLocations[selectedMap]; - UpdateLocations(locations); UpdateSize(_model.GridSize, locations); _model.Locations = locations; } @@ -350,25 +368,25 @@ public void Clear(TrackerMapLocationViewModel model) { if (model.Type == TrackerMapLocation.MapLocationType.Item) { - model.Locations?.Where(x => x.Location.State.Cleared == false) + model.Locations?.Where(x => x.Location.Cleared == false) .ToList() - .ForEach(x => tracker.Clear(x.Location)); + .ForEach(x => tracker.LocationTracker.Clear(x.Location)); } else if (model.Type == TrackerMapLocation.MapLocationType.Boss) { if (model.BossRegion != null) { - tracker.MarkBossAsDefeated(model.BossRegion.Boss); + tracker.BossTracker.MarkBossAsDefeated(model.BossRegion, admittedGuilt: true); } - else if(model.RewardRegion is IDungeon dungeon) + else if(model.RewardRegion != null) { - tracker.MarkDungeonAsCleared(dungeon); + tracker.RewardTracker.GiveAreaReward(model.RewardRegion, false, true); } } } public void Clear(TrackerMapSubLocationViewModel model) { - tracker.Clear(model.Location); + tracker.LocationTracker.Clear(model.Location); } } diff --git a/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs index 75840df61..155605ae8 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs @@ -17,6 +17,7 @@ using TrackerCouncil.Smz3.Data.Configuration.ConfigTypes; using TrackerCouncil.Smz3.Data.Options; using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; using TrackerCouncil.Smz3.SeedGenerator.Contracts; using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Shared.Models; @@ -30,21 +31,17 @@ namespace TrackerCouncil.Smz3.UI.Services; public class TrackerWindowService( TrackerBase tracker, IUIService uiService, - IItemService itemService, OptionsFactory optionsFactory, IWorldAccessor world, ITrackerTimerService trackerTimerService, IServiceProvider serviceProvider, + IWorldQueryService worldQueryService, ILogger logger) : ControlService { private RandomizerOptions? _options; private TrackerWindow _window = null!; private readonly DispatcherTimer _dispatcherTimer = new(); private readonly TrackerWindowViewModel _model = new(); - private readonly Dictionary _bossModels = new(); - private readonly Dictionary _dungeonModels = new(); - private readonly Dictionary _itemModels = new(); - private readonly List _medallions = new(); private TrackerMapWindow? _trackerMapWindow; private TrackerLocationsWindow? _trackerLocationsWindow; private TrackerHelpWindow? _trackerHelpWindow; @@ -71,112 +68,12 @@ public TrackerWindowViewModel GetViewModel(TrackerWindow parent) _dispatcherTimer.Tick += (_, _) => _model.TimeString = trackerTimerService.TimeString; _dispatcherTimer.Start(); - tracker.BossUpdated += (_, args) => - { - if (args.Boss == null) - { - return; - } - - if (_bossModels.TryGetValue(args.Boss.Name, out var bossPanel)) - { - bossPanel.BossDefeated = args.Boss.State.Defeated; - } - - if (_model.ShaktoolMode) - { - ToggleShaktoolMode(false); - } - }; - - tracker.DungeonUpdated += (_, args) => - { - if (args.Dungeon == null) - { - foreach (var dungeonPanel in _dungeonModels.Values.Where(x => x.Dungeon != null)) - { - var rewardImage = dungeonPanel.Dungeon!.MarkedReward != RewardType.None - ? uiService.GetSpritePath(dungeonPanel.Dungeon.MarkedReward) - : null; - dungeonPanel.RewardImage = rewardImage; - dungeonPanel.DungeonCleared = dungeonPanel.Dungeon.DungeonState.Cleared; - dungeonPanel.DungeonTreasure = dungeonPanel.Dungeon.DungeonState.RemainingTreasure; - - if (dungeonPanel.Dungeon.NeedsMedallion) - { - foreach (var medallion in _medallions) - { - var item = medallion.Items?.Keys.FirstOrDefault(); - medallion.IsMMRequirement = world.World.MiseryMire.DungeonState.MarkedMedallion == item?.Type; - medallion.IsTRRequirement = world.World.TurtleRock.DungeonState.MarkedMedallion == item?.Type; - } - } - } - } - else - { - if (_dungeonModels.TryGetValue(args.Dungeon.DungeonName, out var dungeonPanel)) - { - var rewardImage = args.Dungeon.MarkedReward != RewardType.None - ? uiService.GetSpritePath(args.Dungeon.MarkedReward) - : null; - dungeonPanel.RewardImage = rewardImage; - dungeonPanel.DungeonCleared = args.Dungeon.DungeonState.Cleared; - dungeonPanel.DungeonTreasure = args.Dungeon.DungeonState.RemainingTreasure; - } - - if (args.Dungeon.NeedsMedallion) - { - foreach (var medallion in _medallions) - { - var item = medallion.Items?.Keys.FirstOrDefault(); - medallion.IsMMRequirement = world.World.MiseryMire.DungeonState.MarkedMedallion == item?.Type; - medallion.IsTRRequirement = world.World.TurtleRock.DungeonState.MarkedMedallion == item?.Type; - } - } - } - - }; - - tracker.ItemTracked += (_, args) => - { - if (args.Item == null) - { - foreach (var item in itemService.LocalPlayersItems()) - { - if (_itemModels.TryGetValue(item.Name, out var itemsPanel)) - { - var itemPath = uiService.GetSpritePath(item); - itemsPanel.UpdateItem(item, itemPath); - } - } - } - else - { - if (_itemModels.TryGetValue(args.Item.Name, out var itemsPanel)) - { - var itemPath = uiService.GetSpritePath(args.Item); - itemsPanel.UpdateItem(args.Item, itemPath); - } - } - - if (_model.PegWorldMode) - { - TogglePegWorld(false); - } - - if (_model.ShaktoolMode) - { - ToggleShaktoolMode(false); - } - }; - - tracker.GoModeToggledOn += (sender, args) => + tracker.ModeTracker.GoModeToggledOn += (sender, args) => { _model.IsInGoMode = true; }; - tracker.GoModeToggledOff += (sender, args) => + tracker.ModeTracker.GoModeToggledOff += (sender, args) => { _model.IsInGoMode = false; }; @@ -202,19 +99,19 @@ public TrackerWindowViewModel GetViewModel(TrackerWindow parent) } }; - tracker.ToggledPegWorldModeOn += (sender, args) => + tracker.ModeTracker.ToggledPegWorldModeOn += (sender, args) => { - TogglePegWorld(tracker.PegWorldMode); + TogglePegWorld(tracker.ModeTracker.PegWorldMode); }; - tracker.PegPegged += (sender, args) => + tracker.ModeTracker.PegPegged += (sender, args) => { UpdatePegs(); }; - tracker.ToggledShaktoolMode += (sender, args) => + tracker.ModeTracker.ToggledShaktoolMode += (sender, args) => { - ToggleShaktoolMode(tracker.ShaktoolMode); + ToggleShaktoolMode(tracker.ModeTracker.ShaktoolMode); }; tracker.DisableVoiceRecognition(); @@ -363,7 +260,7 @@ private void UpdatePegs() [ new TrackerWindowPanelImage() { - ImagePath = i <= tracker.PegsPegged ? peggedFileName! : unpeggedFileName!, + ImagePath = i <= tracker.ModeTracker.PegsPegged ? peggedFileName! : unpeggedFileName!, IsActive = true } ]; @@ -444,11 +341,6 @@ public async Task Shutdown() public void SetLayout(UILayout layout, bool automatic = false) { - _bossModels.Clear(); - _dungeonModels.Clear(); - _itemModels.Clear(); - _medallions.Clear(); - _model.LayoutName = layout.Name; _model.CurrentLayout = layout; @@ -544,13 +436,16 @@ public TrackerSpeechWindow OpenTrackerSpeechWindow() private TrackerWindowPanelViewModel GetItemPanelViewModel(UIGridLocation gridLocation) { var items = new Dictionary(); + var allItems = new List(); List? connectedItems = null; var labelImage = uiService.GetSpritePath("Items", gridLocation.Image ?? "", out _); foreach (var itemName in gridLocation.Identifiers) { - var item = itemService.FirstOrDefault(itemName); + var currentItems = worldQueryService.LocalPlayersItems().Where(x => x.Is(itemName)).ToList(); + allItems.AddRange(currentItems); + var item = currentItems.FirstOrDefault(); if (item == null) { logger.LogError("Item {ItemName} could not be found", itemName); @@ -559,8 +454,9 @@ private TrackerWindowPanelViewModel GetItemPanelViewModel(UIGridLocation gridLoc if (item.Type == ItemType.Bottle) { - connectedItems = itemService.LocalPlayersItems() + connectedItems = worldQueryService.LocalPlayersItems() .Where(x => x.Type.IsInCategory(ItemCategory.Bottle)).ToList(); + allItems.AddRange(connectedItems); } var fileName = uiService.GetSpritePath(item); @@ -583,54 +479,68 @@ private TrackerWindowPanelViewModel GetItemPanelViewModel(UIGridLocation gridLoc ItemReplacementImages = replacementImages, ConnectedItems = connectedItems, LabelImage = labelImage, - IsLabelActive = items.Keys.Any(x => x.State.TrackingState > 0), + IsLabelActive = items.Keys.Any(x => x.TrackingState > 0), Row = gridLocation.Row, Column = gridLocation.Column, AddShadows = _model.AddShadows, - IsMedallion = items.Keys.First().Type is ItemType.Bombos or ItemType.Ether or ItemType.Quake + IsMedallion = items.Keys.First().Type.IsInCategory(ItemCategory.Medallion) }; - foreach (var item in items.Keys.Concat(connectedItems ?? [])) + foreach (var item in allItems) { - _itemModels[item.Name] = model; + item.UpdatedItemState += (sender, args) => + { + var sprite = uiService.GetSpritePath(item); + model.UpdateItem(item, sprite); + }; } if (model.IsMedallion) { - model.IsMMRequirement = world.World.MiseryMire.DungeonState.MarkedMedallion == items.Keys.First().Type; - model.IsTRRequirement = world.World.TurtleRock.DungeonState.MarkedMedallion == items.Keys.First().Type; - _medallions.Add(model); + var miseryMire = world.World.MiseryMire; + model.IsMMRequirement = miseryMire.PrerequisiteState.MarkedItem == items.Keys.First().Type; + miseryMire.UpdatedPrerequisite += (_, _) => + { + model.IsMMRequirement = miseryMire.PrerequisiteState.MarkedItem == items.Keys.First().Type; + }; + + var turtleRock = world.World.TurtleRock; + model.IsTRRequirement = turtleRock.PrerequisiteState.MarkedItem == items.Keys.First().Type; + turtleRock.UpdatedPrerequisite += (_, _) => + { + model.IsTRRequirement = turtleRock.PrerequisiteState.MarkedItem == items.Keys.First().Type; + }; } model.UpdateItem(null, null); if (items.Count == 1) { - model.Clicked += (_, _) => tracker.TrackItem(items.Keys.First()); + model.Clicked += (_, _) => tracker.ItemTracker.TrackItem(items.Keys.First()); } - model.ItemGiven += (_, args) => tracker.TrackItem(args.Item); - model.ItemRemoved += (_, args) => tracker.UntrackItem(args.Item); + model.ItemGiven += (_, args) => tracker.ItemTracker.TrackItem(args.Item); + model.ItemRemoved += (_, args) => tracker.ItemTracker.UntrackItem(args.Item); model.ItemSetAsDungeonRequirement += (_, args) => { var item = items.Keys.First(); - if (args.IsMMRequirement && world.World.MiseryMire.DungeonState.MarkedMedallion != item.Type) + if (args.IsMMRequirement && world.World.MiseryMire.PrerequisiteState.MarkedItem != item.Type) { - tracker.SetDungeonRequirement(world.World.MiseryMire, item.Type); + tracker.PrerequisiteTracker.SetDungeonRequirement(world.World.MiseryMire, item.Type); } - else if (!args.IsMMRequirement && world.World.MiseryMire.DungeonState.MarkedMedallion == item.Type) + else if (!args.IsMMRequirement && world.World.MiseryMire.PrerequisiteState.MarkedItem == item.Type) { - tracker.SetDungeonRequirement(world.World.MiseryMire); + tracker.PrerequisiteTracker.SetDungeonRequirement(world.World.MiseryMire); } - if (args.IsTRRequirement && world.World.TurtleRock.DungeonState.MarkedMedallion != item.Type) + if (args.IsTRRequirement && world.World.TurtleRock.PrerequisiteState.MarkedItem != item.Type) { - tracker.SetDungeonRequirement(world.World.TurtleRock, item.Type); + tracker.PrerequisiteTracker.SetDungeonRequirement(world.World.TurtleRock, item.Type); } - else if (!args.IsTRRequirement && world.World.TurtleRock.DungeonState.MarkedMedallion == item.Type) + else if (!args.IsTRRequirement && world.World.TurtleRock.PrerequisiteState.MarkedItem == item.Type) { - tracker.SetDungeonRequirement(world.World.TurtleRock); + tracker.PrerequisiteTracker.SetDungeonRequirement(world.World.TurtleRock); } }; @@ -639,34 +549,53 @@ private TrackerWindowPanelViewModel GetItemPanelViewModel(UIGridLocation gridLoc private TrackerWindowPanelViewModel? GetDungeonPanelViewModel(UIGridLocation gridLocation) { - var dungeon = world.World.Dungeons.FirstOrDefault(x => x.DungeonName == gridLocation.Identifiers.First()); + var dungeon = world.World.TreasureRegions.FirstOrDefault(x => x.Name == gridLocation.Identifiers.First()); if (dungeon == null) { logger.LogError("Dungeon {DungeonName} could not be found", gridLocation.Identifiers.First()); return null; } + var rewardRegion = dungeon as IHasReward; + var bossRegion = dungeon as IHasBoss; + var dungeonImage = uiService.GetSpritePath(dungeon); - var rewardImage = dungeon.MarkedReward != RewardType.None ? uiService.GetSpritePath(dungeon.MarkedReward) : null; + var rewardImage = rewardRegion?.RewardType.GetCategories().Length > 0 ? uiService.GetSpritePath(rewardRegion.MarkedReward) : null; var model = new TrackerWindowDungeonPanelViewModel() + { + Region = dungeon as Region, + DungeonImage = dungeonImage, + RewardImage = rewardImage, + Row = gridLocation.Row, + Column = gridLocation.Column, + AddShadows = _model.AddShadows, + DungeonCleared = bossRegion?.BossDefeated == true, + DungeonTreasure = dungeon.RemainingTreasure, + }; + + if (bossRegion != null) { - Dungeon = dungeon, - DungeonImage = dungeonImage, - RewardImage = rewardImage, - Row = gridLocation.Row, - Column = gridLocation.Column, - AddShadows = _model.AddShadows, - DungeonCleared = dungeon.DungeonState.Cleared, - DungeonTreasure = dungeon.DungeonState.RemainingTreasure, - }; + bossRegion.Boss.UpdatedBossState += (_, _) => model.DungeonCleared = bossRegion.BossDefeated; + model.Clicked += (_, _) => tracker.BossTracker.MarkBossAsDefeated(bossRegion); + model.ResetCleared += (_, _) => tracker.BossTracker.MarkBossAsNotDefeated(bossRegion); + } - model.Clicked += (_, _) => tracker.MarkDungeonAsCleared(dungeon); - model.ResetCleared += (_, _) => tracker.MarkDungeonAsIncomplete(dungeon); - model.TreasureCleared += (_, _) => tracker.ClearDungeon(dungeon); - model.RewardSet += (_, args) => tracker.SetDungeonReward(dungeon, args.RewardType); + dungeon.UpdatedTreasure += (_, _) => model.DungeonTreasure = dungeon.RemainingTreasure; + model.TreasureCleared += (_, _) => tracker.TreasureTracker.ClearDungeon(dungeon); + + if (rewardRegion != null) + { + rewardRegion.Reward.UpdatedRewardState += (_, _) => + { + var newImage = rewardRegion?.MarkedReward.GetCategories().Length > 0 + ? uiService.GetSpritePath(rewardRegion.MarkedReward) + : null; + model.RewardImage = newImage; + }; - _dungeonModels.Add(dungeon.DungeonName, model); + model.RewardSet += (_, args) => tracker.RewardTracker.SetAreaReward(rewardRegion, args.RewardType); + } return model; } @@ -691,16 +620,19 @@ private TrackerWindowPanelViewModel GetItemPanelViewModel(UIGridLocation gridLoc { Boss = boss, BossImage = fileName, - BossDefeated = boss.State.Defeated, + BossDefeated = boss.Defeated, Row = gridLocation.Row, Column = gridLocation.Column, AddShadows = _model.AddShadows }; - _bossModels.Add(boss.Name, model); + boss.UpdatedBossState += (_, _) => + { + model.BossDefeated = boss.Defeated; + }; - model.Clicked += (_, _) => tracker.MarkBossAsDefeated(boss); - model.BossRevived += (_, _) => tracker.MarkBossAsNotDefeated(boss); + model.Clicked += (_, _) => tracker.BossTracker.MarkBossAsDefeated(boss); + model.BossRevived += (_, _) => tracker.BossTracker.MarkBossAsNotDefeated(boss); return model; } @@ -714,7 +646,7 @@ private TrackerWindowPanelViewModel GetItemPanelViewModel(UIGridLocation gridLoc } var fileName = uiService.GetSpritePath("Items", - tracker.PegsPegged >= pegNumber ? "pegged.png" : "peg.png", out _); + tracker.ModeTracker.PegsPegged >= pegNumber ? "pegged.png" : "peg.png", out _); if (string.IsNullOrEmpty(fileName)) { @@ -734,7 +666,7 @@ private TrackerWindowPanelViewModel GetItemPanelViewModel(UIGridLocation gridLoc _pegWorldImages[pegNumber] = model; - model.Clicked += (_, _) => tracker.Peg(); + model.Clicked += (_, _) => tracker.ModeTracker.Peg(); return model; } @@ -765,7 +697,7 @@ private TrackerWindowPanelViewModel GetItemPanelViewModel(UIGridLocation gridLoc ] }; - model.Clicked += (_, _) => tracker.StopShaktoolMode(); + model.Clicked += (_, _) => tracker.ModeTracker.StopShaktoolMode(); return model; } diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/HintTileViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/HintTileViewModel.cs index 4562f79d1..0fc8bc5a2 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/HintTileViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/HintTileViewModel.cs @@ -1,10 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using ReactiveUI.Fody.Helpers; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Shared.Enums; using TrackerCouncil.Smz3.Shared.Models; namespace TrackerCouncil.Smz3.UI.ViewModels; -public class HintTileViewModel(PlayerHintTile? hintTile) +public class HintTileViewModel : ViewModelBase { - public PlayerHintTile? PlayerHintTile { get; init; } = hintTile; - public string Name { get; init; } = hintTile?.LocationKey ?? ""; - public string Quality { get; init; } = hintTile?.Usefulness?.ToString() ?? ""; + private List _locations; + + public HintTileViewModel(PlayerHintTile hintTile, IEnumerable locations) + { + PlayerHintTile = hintTile; + IsVisible = hintTile.HintState == HintState.Viewed; + + PlayerHintTile.HintStateUpdated += (_, _) => + { + IsVisible = hintTile.HintState == HintState.Viewed; + }; + + _locations = locations.ToList(); + + foreach (var location in _locations) + { + location.AccessibilityUpdated += (_, _) => + { + UpdateOpacity(); + }; + } + + UpdateOpacity(); + } + + private void UpdateOpacity() + { + Opacity = _locations.Any(x => x.Accessibility is Accessibility.Available or Accessibility.AvailableWithKeys) + ? 1.0 + : 0.33; + } + + public PlayerHintTile PlayerHintTile { get; } + public string Name => PlayerHintTile.LocationKey; + public string Quality => PlayerHintTile.Usefulness.ToString() ?? ""; + [Reactive] public bool IsVisible { get; set; } + [Reactive] public double Opacity { get; set; } } diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/LocationViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/LocationViewModel.cs index 1a6f452f8..e868bc120 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/LocationViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/LocationViewModel.cs @@ -1,15 +1,48 @@ +using AvaloniaControls.Models; +using ReactiveUI.Fody.Helpers; using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Shared.Enums; namespace TrackerCouncil.Smz3.UI.ViewModels; -public class LocationViewModel(Location? location, bool isInLogic, bool isInLogicWithKeys) +public class LocationViewModel : ViewModelBase { + public LocationViewModel(string name, string area) + { + Name = name; + Area = area; + } + + public LocationViewModel(Location location) + { + Name = location.Metadata.Name?[0] ?? location.Name; + Area = location.Region.Metadata.Name?[0] ?? location.Region.Name; + InLogic = location.Accessibility == Accessibility.Available; + InLogicWithKeys = location.Accessibility == Accessibility.AvailableWithKeys; + Location = location; + } + public static string? KeyImage { get; set; } - public string Name { get; init; } = location?.Metadata.Name?[0] ?? location?.Name ?? ""; - public string Area { get; init; } = location?.Region.Metadata.Name?[0] ?? location?.Region.Name ?? ""; + public string Name { get; init; } + public string Area { get; init; } public double Opacity => InLogic || InLogicWithKeys ? 1.0 : 0.33; - public bool InLogic => isInLogic; - public bool InLogicWithKeys => isInLogicWithKeys; - public bool ShowKeyIcon => isInLogicWithKeys && !isInLogic; - public Location? Location => location; + public bool ShowKeyIcon => InLogicWithKeys && !InLogic; + + [Reactive] + [ReactiveLinkedProperties(nameof(IsVisible))] + public bool ShowOutOfLogic { get; set; } + + [Reactive] + [ReactiveLinkedProperties(nameof(Opacity), nameof(ShowKeyIcon))] + public bool InLogic { get; set; } + + [Reactive] + [ReactiveLinkedProperties(nameof(Opacity), nameof(ShowKeyIcon), nameof(IsVisible))] + public bool InLogicWithKeys { get; set; } + + public bool Cleared { get; set; } + + public bool IsVisible => !Cleared && (InLogic || InLogicWithKeys || ShowOutOfLogic); + + public Location? Location { get; } } diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/MarkedLocationViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/MarkedLocationViewModel.cs index 2f279930e..82dabe312 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/MarkedLocationViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/MarkedLocationViewModel.cs @@ -1,14 +1,23 @@ +using System; +using ReactiveUI.Fody.Helpers; using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Shared.Enums; namespace TrackerCouncil.Smz3.UI.ViewModels; -public class MarkedLocationViewModel(Location? location, Item? itemData, string? itemSprite, bool isAvailable) +public class MarkedLocationViewModel(Location location, Item? itemData, string? itemSprite) : ViewModelBase { + public Location Location => location; public string? ItemSprite { get; init; } = itemSprite; - public bool IsAvailable { get; set; } = isAvailable; + [Reactive] public bool IsAvailable { get; set; } = location.Accessibility is Accessibility.Available or Accessibility.AvailableWithKeys; public bool ShowOutOfLogic { get; set; } public double Opacity => ShowOutOfLogic || IsAvailable ? 1.0 : 0.33; public string Item { get; init; }= itemData?.Name ?? ""; - public string Location { get; init; }= location?.Metadata.Name?[0] ?? location?.Name ?? ""; - public string Area { get; init; } = location?.Region.Metadata.Name?[0] ?? location?.Region.Name ?? ""; + public string LocationName { get; init; }= location.Metadata.Name?[0] ?? location.Name ?? ""; + public string Area { get; init; } = location.Region.Metadata.Name?[0] ?? location.Region.Name ?? ""; + + public void LocationOnAccessibilityUpdated(object? sender, EventArgs e) + { + IsAvailable = location.Accessibility is Accessibility.Available or Accessibility.AvailableWithKeys; + } } diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/RegionViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/RegionViewModel.cs index 7ab42e9e5..4c00e86e5 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/RegionViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/RegionViewModel.cs @@ -1,11 +1,85 @@ +using System; using System.Collections.Generic; +using System.Linq; +using AvaloniaControls.Models; +using ReactiveUI.Fody.Helpers; +using TrackerCouncil.Smz3.Data.WorldData; +using TrackerCouncil.Smz3.Data.WorldData.Regions; +using TrackerCouncil.Smz3.Shared.Enums; namespace TrackerCouncil.Smz3.UI.ViewModels; -public class RegionViewModel +public class RegionViewModel : ViewModelBase { + public RegionViewModel(string regionName, List locationNames) + { + RegionName = regionName; + Locations = locationNames.Select(x => new LocationViewModel(x, regionName)).ToList(); + } + + public RegionViewModel(Region region) + { + Region = region; + RegionName = region.Name; + AllLocations = region.Locations.ToDictionary(x => x.Id, x => new LocationViewModel(x)); + + var locations = new List(); + + foreach (var location in AllLocations.Values.Where(x => x.Location != null)) + { + locations.Add(location); + location.Location!.AccessibilityUpdated += LocationOnAccessibilityUpdated; + } + + Locations = locations; + UpdateLocationCount(); + } + + private void LocationOnAccessibilityUpdated(object? sender, EventArgs e) + { + if (sender is not Location location) return; + var model = AllLocations[location.Id]; + model.InLogic = location.Accessibility == Accessibility.Available; + model.InLogicWithKeys = location.Accessibility == Accessibility.AvailableWithKeys; + model.Cleared = location.Cleared; + SortLocations(); + UpdateLocationCount(); + + if (location.Accessibility == Accessibility.Cleared) + { + RegionUpdated?.Invoke(this, EventArgs.Empty); + } + } + + public Region? Region { get; set; } public static string? ChestImage { get; set; } - public string RegionName { get; set; } = ""; - public string LocationCount => Locations.Count.ToString(); - public List Locations { get; set; } = []; + public string RegionName { get; set; } + [Reactive] public string LocationCount { get; set; } = ""; + public int InLogicLocationCount => Locations.Count(x => x.InLogic || x.InLogicWithKeys); + public int SortOrder { get; set; } + public bool ShowOutOfLogic { get; set; } + public bool IsVisible => VisibleLocations > 0 && MatchesFilter; + [Reactive] public List Locations { get; set; } + public Dictionary AllLocations { get; set; } = []; + + [Reactive] + [ReactiveLinkedProperties(nameof(IsVisible))] + public bool MatchesFilter { get; set; } + + [Reactive] + [ReactiveLinkedProperties(nameof(IsVisible))] + public int VisibleLocations { get; set; } + + public event EventHandler? RegionUpdated; + + public void UpdateLocationCount() + { + VisibleLocations = Locations.Count(x => x.IsVisible); + LocationCount = VisibleLocations.ToString(); + } + + public void SortLocations() + { + Locations = AllLocations.Values.OrderBy(x => !x.InLogic).ToList(); + } } diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerLocationsViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerLocationsViewModel.cs index d49b1a8ec..1eebc0e95 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerLocationsViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerLocationsViewModel.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; +using AvaloniaControls.Models; using ReactiveUI.Fody.Helpers; using TrackerCouncil.Smz3.Shared.Enums; @@ -7,8 +9,9 @@ namespace TrackerCouncil.Smz3.UI.ViewModels; public class TrackerLocationsViewModel : ViewModelBase { [Reactive] public List HintTiles { get; set; } = []; - [Reactive] public List MarkedLocations { get; set; } = []; + [Reactive] public ObservableCollection MarkedLocations { get; set; } = []; [Reactive] public List Regions { get; set; } = []; [Reactive] public RegionFilter Filter { get; set; } [Reactive] public bool ShowOutOfLogic { get; set; } + [Reactive] public bool FinishedLoading { get; set; } } diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerMapLocationViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerMapLocationViewModel.cs index 4b72c9874..0946427d8 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerMapLocationViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerMapLocationViewModel.cs @@ -39,7 +39,7 @@ public TrackerMapLocationViewModel(TrackerMapRegion mapRegion, TrackerMapLocatio RegionName = mapRegion.Name; Region = region; - if (RewardRegion != null) + if (region is IHasTreasure) { _yOffset = -22; } @@ -65,7 +65,7 @@ public TrackerMapLocationViewModel(TrackerMapRegion mapRegion, TrackerMapLocatio public IHasBoss? BossRegion { get; set; } public string Name { get; set; } public List? Locations { get; set; } - [Reactive] public bool IconVisibility { get; set; } = true; + public bool IconVisibility => HasImage && (IsInLogic || ShowOutOfLogic); [Reactive] public bool MarkedVisibility { get; set; } = true; [Reactive] public bool NumberVisibility { get; set; } = true; public int Size { get; set; } = 36; @@ -80,4 +80,16 @@ public TrackerMapLocationViewModel(TrackerMapRegion mapRegion, TrackerMapLocatio [Reactive] [ReactiveLinkedProperties(nameof(X), nameof(Y))] public Size Offset { get; set; } + + [Reactive] + [ReactiveLinkedProperties(nameof(IconVisibility))] + public bool HasImage { get; set; } + + [Reactive] + [ReactiveLinkedProperties(nameof(IconVisibility))] + public bool IsInLogic { get; set; } = true; + + [Reactive] + [ReactiveLinkedProperties(nameof(IconVisibility))] + public bool ShowOutOfLogic { get; set; } } diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerMapWindowViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerMapWindowViewModel.cs index 370c4f6da..2ea8e4302 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerMapWindowViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerMapWindowViewModel.cs @@ -21,6 +21,7 @@ public class TrackerMapWindowViewModel : ViewModelBase : Path.Combine(Sprite.SpritePath, "Maps", SelectedMap.Image); [Reactive] public List Locations { get; set; } = []; + [Reactive] public bool FinishedLoading { get; set; } public Size GridSize { get; set; } diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowDungeonPanelViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowDungeonPanelViewModel.cs index cc9486f66..c77960608 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowDungeonPanelViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowDungeonPanelViewModel.cs @@ -16,7 +16,11 @@ public class DungeonSetRewardEventArgs : EventArgs public class TrackerWindowDungeonPanelViewModel : TrackerWindowPanelViewModel { - public IDungeon? Dungeon { get; set; } + public Region? Region { get; set; } + public IHasTreasure? TreasureRegion => Region as IHasTreasure; + public IHasReward? RewardRegion => Region as IHasReward; + public IHasPrerequisite? PrereqRegion => Region as IHasPrerequisite; + public IHasBoss? BossRegion => Region as IHasBoss; public string? DungeonImage { get; set; } = ""; [Reactive] public string? RewardImage { get; set; } [Reactive] public bool DungeonCleared { get; set; } @@ -69,7 +73,7 @@ public override List GetMenuItems() menuItems.Add(menuItem); } - if (Dungeon?.HasReward == true) + if (RewardRegion != null) { AddRewardMenuItem(menuItems, RewardType.PendantGreen); AddRewardMenuItem(menuItems, RewardType.PendantRed); @@ -83,7 +87,7 @@ public override List GetMenuItems() private void AddRewardMenuItem(List menuItems, RewardType rewardType) { - if (rewardType == Dungeon?.MarkedReward) + if (RewardRegion == null || RewardRegion.MarkedReward == rewardType) { return; } diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowItemPanelViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowItemPanelViewModel.cs index a287184af..0497ee07b 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowItemPanelViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowItemPanelViewModel.cs @@ -67,16 +67,16 @@ public void UpdateItem(Item? item, string? path) if (ConnectedItems != null) { ItemCount = ConnectedItems.Select(x => x.State).Distinct().Sum(x => x.TrackingState); - IsLabelActive = ConnectedItems.Any(x => x.State.TrackingState > 0); - RetrievedItemCount = ConnectedItems.Count(x => x.State.TrackingState > 0); - Stage = ConnectedItems.Sum(x => x.State.TrackingState); + IsLabelActive = ConnectedItems.Any(x => x.TrackingState > 0); + RetrievedItemCount = ConnectedItems.Count(x => x.TrackingState > 0); + Stage = ConnectedItems.Sum(x => x.TrackingState); } else if (Items != null) { ItemCount = Items.Keys.Sum(x => x.Counter); - IsLabelActive = Items?.Keys.Any(x => x.State.TrackingState > 0) == true; - RetrievedItemCount = Items?.Keys.Count(x => x.State.TrackingState > 0) ?? 0; - Stage = Items?.Keys.Sum(x => x.State.TrackingState) ?? 0; + IsLabelActive = Items?.Keys.Any(x => x.TrackingState > 0) == true; + RetrievedItemCount = Items?.Keys.Count(x => x.TrackingState > 0) ?? 0; + Stage = Items?.Keys.Sum(x => x.TrackingState) ?? 0; } } @@ -92,8 +92,8 @@ public override List GetMainImages() foreach (var item in Items) { var isActive = ConnectedItems?.Count > 1 - ? ConnectedItems.Any(x => x.State.TrackingState > 0) - : item.Key.State.TrackingState > 0; + ? ConnectedItems.Any(x => x.TrackingState > 0) + : item.Key.TrackingState > 0; images.Add(new TrackerWindowPanelImage { @@ -112,7 +112,7 @@ public override List GetMainImages() images.Add(new TrackerWindowPanelImage { ImagePath = RequirementImages[ItemDungeonRequirement.Both], - IsActive = Items?.Keys.First().State.TrackingState > 0, + IsActive = Items?.Keys.First().TrackingState > 0, Height = 16, OffsetY = 16 }); @@ -122,7 +122,7 @@ public override List GetMainImages() images.Add(new TrackerWindowPanelImage { ImagePath = RequirementImages[ItemDungeonRequirement.MM], - IsActive = Items?.Keys.First().State.TrackingState > 0, + IsActive = Items?.Keys.First().TrackingState > 0, Height = 16, OffsetY = 16 }); @@ -132,7 +132,7 @@ public override List GetMainImages() images.Add(new TrackerWindowPanelImage { ImagePath = RequirementImages[ItemDungeonRequirement.TR], - IsActive = Items?.Keys.First().State.TrackingState > 0, + IsActive = Items?.Keys.First().TrackingState > 0, Height = 16, OffsetY = 16 }); @@ -150,7 +150,7 @@ public override List GetMenuItems() { foreach (var item in Items.Keys) { - if (item.State.TrackingState == 0) + if (item.TrackingState == 0) { var menuItem = new MenuItem { Header = $"Track {item.Name}" }; menuItem.Click += (_, _) => ItemGiven?.Invoke(this, new ItemChangedEventArgs { Item = item }); @@ -167,7 +167,7 @@ public override List GetMenuItems() else if (Items?.Count == 1) { var item = Items.Keys.First(); - if (item.State.TrackingState > 0) + if (item.TrackingState > 0) { var menuItem = new MenuItem { Header = $"Untrack {item.Name}" }; menuItem.Click += (_, _) => ItemRemoved?.Invoke(this, new ItemChangedEventArgs { Item = item }); diff --git a/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml b/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml index 3811df3af..1c6694fad 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml +++ b/src/TrackerCouncil.Smz3.UI/Views/SoloRomListPanel.axaml @@ -56,6 +56,8 @@ +